๐Ÿฆ€ Functional Rust

115: Vec Operations Functionally

Difficulty: 2 Level: Intermediate Rust's iterator adapters (`map`, `filter`, `fold`, `flat_map`) translate OCaml's list operations directly โ€” with lazy evaluation and zero intermediate allocation.

The Problem This Solves

Imperative loops work, but they obscure intent. A `for` loop that filters some items, transforms others, and accumulates a result puts the how (loop, counter, conditional append) in the way of the what (select items matching X, transform by Y, reduce to Z). Bugs hide in the bookkeeping. Python has list comprehensions and `map`/`filter`, but they're eager โ€” every intermediate result is a full list in memory. Chain three operations and you have three lists allocated. For large datasets, this matters. Java's streams are lazy but verbose. And languages without an ownership model have to decide: does `map` copy the collection or share it? Does the caller own the transformed result? Rust's iterator system is lazy and zero-cost. `vec.iter().map(f).filter(g)` allocates nothing until you call `.collect()` or consume the iterator. Each element flows through the entire pipeline in one pass. The resulting code reads like a data pipeline description โ€” what you're doing, not how you're doing it โ€” and compiles to code as efficient as a hand-written loop.

The Intuition

Rust's iterator adapters are lazy transformations that chain without allocating intermediate collections โ€” call `.collect()` at the end to materialize the result exactly once.

How It Works in Rust

let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// OCaml: List.map (fun x -> x * x) lst
let squares: Vec<i32> = numbers.iter()
 .map(|&x| x * x)
 .collect();

// OCaml: List.filter (fun x -> x mod 2 = 0) lst
let evens: Vec<i32> = numbers.iter()
 .filter(|&&x| x % 2 == 0)
 .copied()
 .collect();

// OCaml: List.fold_left (+) 0 lst
let sum: i32 = numbers.iter().sum();
let sum2: i32 = numbers.iter().fold(0, |acc, &x| acc + x);

// OCaml: List.filter_map
let parsed: Vec<i32> = vec!["1", "two", "3", "four", "5"]
 .iter()
 .filter_map(|s| s.parse().ok())
 .collect();
// [1, 3, 5]

// OCaml: List.flatten (List.map f lst)
let flat: Vec<i32> = vec![vec![1, 2], vec![3, 4], vec![5]]
 .into_iter()
 .flatten()
 .collect();

// flat_map = map + flatten
let words = vec!["hello world", "foo bar"];
let all_words: Vec<&str> = words.iter()
 .flat_map(|s| s.split_whitespace())
 .collect();

// Chaining multiple operations โ€” one pass, zero intermediate allocations
let result: Vec<String> = numbers.iter()
 .filter(|&&x| x % 2 == 0)   // keep evens
 .map(|&x| x * x)             // square them
 .filter(|&x| x > 10)         // keep > 10
 .map(|x| format!("{}!", x))  // format as strings
 .collect();
// ["16!", "36!", "64!", "100!"]

// zip, enumerate, take, skip
let paired: Vec<(i32, i32)> = numbers.iter()
 .copied()
 .zip(numbers.iter().copied().rev())
 .take(3)
 .collect();
// [(1, 10), (2, 9), (3, 8)]

// into_iter() consumes the Vec; iter() borrows
let owned_result: Vec<i32> = numbers.into_iter()
 .map(|x| x * 2)
 .collect();
// numbers is moved โ€” can't use it again

What This Unlocks

Key Differences

ConceptOCamlRust
Map`List.map f lst``vec.iter().map(f).collect()`
Filter`List.filter pred lst``vec.iter().filter(pred).collect()`
Fold`List.fold_left f init lst``vec.iter().fold(init, f)`
Flat map`List.concat_map f lst``vec.iter().flat_map(f).collect()`
LazinessEager (lists) / Lazy (Seq)Always lazy โ€” evaluated on `.collect()` or consumption
Intermediate allocationsOne per operationZero โ€” single pass through the pipeline
// Example 115: Vec Operations Functionally
// OCaml list functions โ†’ Rust Vec/iterator methods

// Approach 1: map, filter, fold via iterators
fn approach1() {
    let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let sum: i32 = data.iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * 2)
        .sum();
    assert_eq!(sum, 60);
    println!("Sum of doubled evens: {}", sum);
}

// Approach 2: zip, unzip, partition
fn approach2() {
    let names = vec!["Alice", "Bob", "Charlie"];
    let ages = vec![30, 25, 35];
    let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();
    
    let (young, old): (Vec<_>, Vec<_>) = pairs.iter()
        .partition(|(_, &&age)| age < 30);
    
    assert_eq!(young.len(), 1);
    assert_eq!(old.len(), 2);
    println!("Young: {}, Old: {}", young.len(), old.len());
}

// Approach 3: flat_map, scan, windows
fn approach3() {
    // flat_map
    let nested = vec![vec![1, 2], vec![3], vec![4, 5, 6]];
    let flat: Vec<i32> = nested.into_iter().flatten().collect();
    assert_eq!(flat, vec![1, 2, 3, 4, 5, 6]);
    
    // scan (running accumulation)
    let running: Vec<i32> = [1, 2, 3, 4, 5].iter()
        .scan(0, |state, &x| { *state += x; Some(*state) })
        .collect();
    assert_eq!(running, vec![1, 3, 6, 10, 15]);
    
    // windows
    let data = vec![1, 2, 3, 4, 5];
    let pairs: Vec<_> = data.windows(2).map(|w| (w[0], w[1])).collect();
    assert_eq!(pairs, vec![(1,2), (2,3), (3,4), (4,5)]);
    
    println!("Flat: {:?}", flat);
    println!("Running: {:?}", running);
    println!("Windows: {:?}", pairs);
}

fn main() {
    println!("=== Approach 1: Map/Filter/Fold ===");
    approach1();
    println!("\n=== Approach 2: Zip/Partition ===");
    approach2();
    println!("\n=== Approach 3: FlatMap/Scan/Windows ===");
    approach3();
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_filter_map() {
        let v: Vec<i32> = (1..=5).filter(|x| x % 2 == 1).collect();
        assert_eq!(v, vec![1, 3, 5]);
    }

    #[test]
    fn test_fold() {
        let product: i32 = vec![1, 2, 3, 4].iter().product();
        assert_eq!(product, 24);
    }

    #[test]
    fn test_zip_unzip() {
        let (a, b): (Vec<_>, Vec<_>) = vec![(1, "a"), (2, "b")].into_iter().unzip();
        assert_eq!(a, vec![1, 2]);
        assert_eq!(b, vec!["a", "b"]);
    }

    #[test]
    fn test_flat_map() {
        let v: Vec<i32> = vec![1, 2, 3].into_iter()
            .flat_map(|x| vec![x, x * 10])
            .collect();
        assert_eq!(v, vec![1, 10, 2, 20, 3, 30]);
    }

    #[test]
    fn test_chunks() {
        let v = vec![1, 2, 3, 4, 5];
        let chunks: Vec<&[i32]> = v.chunks(2).collect();
        assert_eq!(chunks, vec![&[1, 2][..], &[3, 4][..], &[5][..]]);
    }
}
(* Example 115: Vec Operations Functionally โ€” OCaml List Functions โ†’ Rust *)

(* Approach 1: map, filter, fold *)
let approach1 () =
  let data = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] in
  let evens = List.filter (fun x -> x mod 2 = 0) data in
  let doubled = List.map (fun x -> x * 2) evens in
  let sum = List.fold_left ( + ) 0 doubled in
  assert (sum = 60);
  Printf.printf "Sum of doubled evens: %d\n" sum

(* Approach 2: zip, unzip, partition *)
let zip a b = List.map2 (fun x y -> (x, y)) a b
let unzip lst = (List.map fst lst, List.map snd lst)

let approach2 () =
  let names = ["Alice"; "Bob"; "Charlie"] in
  let ages = [30; 25; 35] in
  let pairs = zip names ages in
  let (young, old) = List.partition (fun (_, age) -> age < 30) pairs in
  assert (List.length young = 1);
  assert (List.length old = 2);
  Printf.printf "Young: %d, Old: %d\n" (List.length young) (List.length old)

(* Approach 3: flat_map, scan, windows *)
let flat_map f lst = List.concat (List.map f lst)
let scan f init lst =
  let rec go acc state = function
    | [] -> List.rev acc
    | x :: rest ->
      let next = f state x in
      go (next :: acc) next rest
  in go [init] init lst

let approach3 () =
  let nested = [[1;2]; [3]; [4;5;6]] in
  let flat = flat_map (fun x -> x) nested in
  assert (flat = [1;2;3;4;5;6]);
  let running = scan ( + ) 0 [1;2;3;4;5] in
  assert (running = [0;1;3;6;10;15]);
  Printf.printf "Flat: %s\n" (String.concat "," (List.map string_of_int flat))

let () =
  approach1 ();
  approach2 ();
  approach3 ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Vec Operations

Map/Filter/Fold

OCaml:

๐Ÿช Show OCaml equivalent
let result = List.fold_left ( + ) 0
(List.map (fun x -> x * 2)
 (List.filter (fun x -> x mod 2 = 0) data))

Rust:

let result: i32 = data.iter()
 .filter(|&&x| x % 2 == 0)
 .map(|&x| x * 2)
 .sum();

Zip

OCaml:

๐Ÿช Show OCaml equivalent
let pairs = List.map2 (fun a b -> (a, b)) names ages

Rust:

let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();

Flat Map

OCaml:

๐Ÿช Show OCaml equivalent
let flat = List.concat (List.map f nested)

Rust:

let flat: Vec<_> = nested.into_iter().flatten().collect();
// or: nested.into_iter().flat_map(|x| x).collect()