๐Ÿฆ€ Functional Rust

Example 013: Direct Run-Length Encoding

Difficulty: โญโญ Category: Lists, Stateful Recursion Concept: Unlike the two-step RLE (pack then count), direct RLE counts consecutive duplicates in a single pass. This tests your ability to carry state through recursion or folds. OCaml โ†’ Rust key insight: OCaml's recursive accumulator pattern (`aux count acc = function ...`) translates to Rust's `fold` with mutable accumulator state, or imperative `while` loops โ€” both are idiomatic.
// Direct Run-Length Encoding โ€” 99 Problems #13
// Encode without creating intermediate sublists

#[derive(Debug, PartialEq, Clone)]
enum Rle<T> {
    One(T),
    Many(usize, T),
}

/// Direct single-pass encoding using a fold-like approach.
fn encode<T: PartialEq + Clone>(lst: &[T]) -> Vec<Rle<T>> {
    let mut result: Vec<Rle<T>> = Vec::new();
    for item in lst {
        match result.last_mut() {
            Some(Rle::Many(n, x)) if x == item => *n += 1,
            Some(Rle::One(x)) if x == item => {
                let val = x.clone();
                *result.last_mut().unwrap() = Rle::Many(2, val);
            }
            _ => result.push(Rle::One(item.clone())),
        }
    }
    result
}

/// Fold-based version โ€” explicitly uses fold semantics.
fn encode_fold<T: PartialEq + Clone>(lst: &[T]) -> Vec<Rle<T>> {
    lst.iter().fold(Vec::new(), |mut acc, x| {
        match acc.last_mut() {
            Some(Rle::Many(n, y)) if y == x => *n += 1,
            Some(Rle::One(y)) if y == x => {
                let val = y.clone();
                *acc.last_mut().unwrap() = Rle::Many(2, val);
            }
            _ => acc.push(Rle::One(x.clone())),
        }
        acc
    })
}

fn main() {
    let input = vec!['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e'];
    println!("Input:      {:?}", input);
    println!("Encoded:    {:?}", encode(&input));
    println!("Fold enc:   {:?}", encode_fold(&input));

    println!("Singles:    {:?}", encode(&['a', 'b', 'c']));
    println!("All-same:   {:?}", encode(&['x', 'x', 'x']));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_encode_empty() {
        let empty: Vec<char> = vec![];
        assert_eq!(encode(&empty), vec![]);
    }

    #[test]
    fn test_encode_singles() {
        assert_eq!(
            encode(&['a', 'b', 'c']),
            vec![Rle::One('a'), Rle::One('b'), Rle::One('c')]
        );
    }

    #[test]
    fn test_encode_long() {
        let input = vec!['a', 'a', 'a', 'a', 'b', 'c', 'c', 'a', 'a', 'd', 'e', 'e', 'e', 'e'];
        assert_eq!(
            encode(&input),
            vec![
                Rle::Many(4, 'a'),
                Rle::One('b'),
                Rle::Many(2, 'c'),
                Rle::Many(2, 'a'),
                Rle::One('d'),
                Rle::Many(4, 'e'),
            ]
        );
    }

    #[test]
    fn test_encode_fold_matches() {
        let input = vec!['a', 'a', 'b'];
        assert_eq!(encode(&input), encode_fold(&input));
    }
}
(* Direct Run-Length Encoding โ€” 99 Problems #13 *)
(* Encode directly without creating intermediate sublists *)

type 'a rle =
  | One of 'a
  | Many of int * 'a

(* โ”€โ”€ Direct recursive with counting โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

let encode lst =
  let rec aux count acc = function
    | [] -> List.rev acc
    | [x] ->
      let item = if count = 0 then One x else Many (count + 1, x) in
      List.rev (item :: acc)
    | x :: (y :: _ as rest) ->
      if x = y then aux (count + 1) acc rest
      else
        let item = if count = 0 then One x else Many (count + 1, x) in
        aux 0 (item :: acc) rest
  in aux 0 [] lst

(* โ”€โ”€ Fold-based approach โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

let encode_fold lst =
  let update acc x =
    match acc with
    | Many (n, y) :: rest when y = x -> Many (n + 1, y) :: rest
    | One y :: rest when y = x -> Many (2, y) :: rest
    | _ -> One x :: acc
  in
  List.fold_left update [] lst |> List.rev

(* โ”€โ”€ Tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)
let () =
  assert (encode [] = []);
  assert (encode ['a';'b';'c'] = [One 'a'; One 'b'; One 'c']);
  assert (encode ['a';'a';'a';'a';'b';'c';'c';'a';'a';'d';'e';'e';'e';'e']
    = [Many (4,'a'); One 'b'; Many (2,'c'); Many (2,'a'); One 'd'; Many (4,'e')]);
  assert (encode_fold ['a';'a';'b'] = [Many (2,'a'); One 'b']);
  print_endline "โœ“ Direct RLE tests passed"

๐Ÿ“Š Detailed Comparison

Direct Run-Length Encoding: OCaml vs Rust

The Core Insight

Direct RLE eliminates the intermediate step of grouping โ€” it counts runs on the fly. This requires carrying mutable state (the current count) through traversal. OCaml does this with recursive accumulators; Rust offers both fold-based and imperative approaches, with the fold version being particularly interesting because it mutates the accumulator in place.

OCaml Approach

The recursive version carries a `count` alongside the accumulator:
๐Ÿช Show OCaml equivalent
let rec aux count acc = function
| [] -> List.rev acc
| [x] -> List.rev ((if count = 0 then One x else Many (count+1, x)) :: acc)
| x :: (y :: _ as rest) ->
 if x = y then aux (count+1) acc rest
 else let item = if count = 0 then One x else Many (count+1, x) in
      aux 0 (item :: acc) rest
The fold version builds the result in reverse, checking the head of the accumulator to extend the current run.

Rust Approach

Rust's fold approach modifies the last element of the accumulator in place:
list.iter().fold(Vec::new(), |mut acc, x| {
 match acc.last_mut() {
     Some(RleItem::Many(n, ref y)) if y == x => { *n += 1; }
     Some(RleItem::One(ref y)) if y == x => {
         *acc.last_mut().unwrap() = RleItem::Many(2, y.clone());
     }
     _ => acc.push(RleItem::One(x.clone())),
 }
 acc
})
The `last_mut()` method gives a mutable reference to the last element โ€” a pattern impossible in pure functional OCaml but natural in Rust.

Key Differences

AspectOCamlRust
State threadingExplicit parameters (`count`, `acc`)`fold` with mutable accumulator
MutationNever (new list nodes)In-place via `last_mut()`
Peeking ahead`x :: (y :: _ as rest)` pattern`tail.first()` or index comparison
Reverse at end`List.rev acc` (common pattern)Not needed (push appends)
Two-element look-aheadNative with pattern matchingRequires explicit index logic

What Rust Learners Should Notice

  • `last_mut()` enables accumulator mutation: Rust's ownership system lets you mutate the last element of a Vec through a mutable reference โ€” something OCaml can't do with immutable lists.
  • OCaml's two-element pattern is cleaner: `x :: (y :: _ as rest)` naturally looks at two elements. Rust requires comparing `list[i]` with `list[i-1]` or using `windows(2)`.
  • Both approaches are O(n): The algorithmic complexity is identical. The difference is stylistic โ€” OCaml's recursive version is more declarative, Rust's fold version is more imperative.
  • Direct encoding avoids allocation: Both languages benefit from not creating intermediate sublists, making this more memory-efficient than the two-step approach.

Further Reading

  • [99 OCaml Problems #13](https://ocaml.org/problems)
  • [Rust โ€” Iterator::fold](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.fold)
  • [Rust โ€” slice::last_mut](https://doc.rust-lang.org/std/primitive.slice.html#method.last_mut)