๐Ÿฆ€ Functional Rust
๐ŸŽฌ How Rust Iterators Work Lazy evaluation, chaining, collect(), and zero-cost abstractions.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Iterators are lazy โ€” .map(), .filter(), .take() build a chain but do no work until consumed

โ€ข .collect() triggers evaluation, transforming the chain into a Vec, HashMap, or other collection

โ€ข Zero-cost abstraction: iterator chains compile to the same machine code as hand-written loops

โ€ข .iter() borrows, .into_iter() consumes, .iter_mut() borrows mutably

โ€ข Chaining replaces nested loops with a readable, composable pipeline

290: Advanced Splitting Patterns

Difficulty: 3 Level: Advanced Split iterators into multiple collections in a single pass โ€” unzip, partition, and multi-way categorization without multiple traversals.

The Problem This Solves

Data processing often requires splitting a collection based on a predicate or structure. The naive approach iterates multiple times โ€” once to find the positives, once for the negatives, once per category. For large datasets or non-replayable iterators (network streams, lazy generators), this is wrong or impossible. Rust's `partition` and `unzip` consume an iterator in a single pass and produce two collections simultaneously. `partition(|x| predicate)` splits by boolean โ€” true elements go left, false go right, exactly like `List.partition` in OCaml. `unzip()` on an iterator of pairs separates the pairs into two collections โ€” the iterator analogue of transposing a list of tuples. Combining these with `fold` gives you n-way splits, categorization by enum variant, and simultaneous accumulation into different container types โ€” all in one traversal. This is the functional pattern for what imperative code does with multiple loops and mutable accumulators.

The Intuition

`partition` and `unzip` are single-pass transformations that split one iterator into two collections โ€” fold lets you extend this to any number of buckets.

How It Works in Rust

// partition โ€” split by predicate
let nums = vec![-3i32, -1, 0, 1, 2, 5];
let (negatives, non_neg): (Vec<i32>, Vec<i32>) =
 nums.into_iter().partition(|&x| x < 0);
// negatives: [-3, -1], non_neg: [0, 1, 2, 5]

// unzip โ€” split pairs into two collections
let pairs = vec![(1, 'a'), (2, 'b'), (3, 'c')];
let (numbers, letters): (Vec<i32>, Vec<char>) = pairs.into_iter().unzip();
// numbers: [1, 2, 3], letters: ['a', 'b', 'c']

// Nested unzip โ€” unzip pairs-of-pairs
let nested: Vec<((i32, i32), &str)> = vec![((1, 2), "a"), ((3, 4), "b")];
let (inner_pairs, labels): (Vec<(i32,i32)>, Vec<&str>) = nested.into_iter().unzip();
let (lefts, rights): (Vec<i32>, Vec<i32>) = inner_pairs.into_iter().unzip();

// partition_map pattern: split by parse success
let data = vec!["1", "two", "3", "four"];
let (nums, words): (Vec<i32>, Vec<&&str>) = data.iter().fold(
 (Vec::new(), Vec::new()),
 |(mut ns, mut ws), s| {
     match s.parse::<i32>() {
         Ok(n) => ns.push(n),
         Err(_) => ws.push(s),
     }
     (ns, ws)
 }
);

// Three-way split using fold
let values = [1u32, 15, 100, 8, 50, 3, 200];
let (small, medium, large) = values.iter().fold(
 (vec![], vec![], vec![]),
 |(mut s, mut m, mut l), &v| {
     match v {
         0..=10   => s.push(v),
         11..=99  => m.push(v),
         _        => l.push(v),
     }
     (s, m, l)
 }
);
1. `partition(|x| bool)` โ†’ `(Vec<T>, Vec<T>)` โ€” single pass, two buckets. 2. `unzip()` on `Iterator<Item=(A,B)>` โ†’ `(Vec<A>, Vec<B>)` โ€” separate the pairs. 3. `fold((vec![], vec![], ...), |acc, item| { ... acc })` โ€” n-way split in one pass. 4. Combine with `flat_map`, `filter_map`, or `scan` for transform-then-split pipelines.

What This Unlocks

Key Differences

ConceptOCamlRust
Two-way split`List.partition pred lst``iter.partition(\x\pred(x))`
Pair separation`List.split [(a,b);...]``iter.unzip()`
N-way split`List.fold_left` with tuple accumulator`fold((vec![], ...), \acc, x\...)`
Result split`List.partition_map` (>=4.12)`fold` with `match Ok/Err` pattern
Single-pass guaranteeDepends on strictnessGuaranteed โ€” `Iterator` consumed once
//! 290. Advanced splitting patterns
//!
//! Advanced unzip and partition patterns for multi-group splitting.

fn main() {
    // partition_map pattern: split by transformation type
    let data = vec!["1", "two", "3", "four", "5"];
    let (nums, words): (Vec<i32>, Vec<&&str>) = data.iter().fold(
        (Vec::new(), Vec::new()),
        |(mut ns, mut ws), s| {
            match s.parse::<i32>() {
                Ok(n) => ns.push(n),
                Err(_) => ws.push(s),
            }
            (ns, ws)
        }
    );
    println!("Numbers: {:?}", nums);
    println!("Words: {:?}", words);

    // Trisect: negative, zero, positive
    let nums = [-3i32, 0, 1, -1, 0, 5, -2, 3];
    let (neg, non_neg): (Vec<i32>, Vec<i32>) = nums.iter().copied().partition(|&x| x < 0);
    let (zero, pos): (Vec<i32>, Vec<i32>) = non_neg.into_iter().partition(|&x| x == 0);
    println!("Neg: {:?}, Zero: {:?}, Pos: {:?}", neg, zero, pos);

    // Unzip nested pairs
    let nested: Vec<((i32, i32), &str)> = vec![((1, 2), "a"), ((3, 4), "b")];
    let (pairs, labels): (Vec<(i32,i32)>, Vec<&str>) = nested.into_iter().unzip();
    let (lefts, rights): (Vec<i32>, Vec<i32>) = pairs.into_iter().unzip();
    println!("Lefts: {:?}, Rights: {:?}, Labels: {:?}", lefts, rights, labels);

    // Multi-partition: categorize by multiple criteria
    #[derive(Debug)]
    enum Category { Small, Medium, Large }
    let values = [1u32, 15, 100, 8, 50, 3, 200];
    let mut small = Vec::new();
    let mut medium = Vec::new();
    let mut large = Vec::new();
    for &v in &values {
        match v {
            0..=10 => small.push(v),
            11..=100 => medium.push(v),
            _ => large.push(v),
        }
    }
    println!("Small: {:?}", small);
    println!("Medium: {:?}", medium);
    println!("Large: {:?}", large);
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_partition_map_fold() {
        let data = vec!["1", "x", "2"];
        let (nums, _words): (Vec<i32>, Vec<&&str>) = data.iter().fold(
            (Vec::new(), Vec::new()),
            |(mut ns, mut ws), s| {
                match s.parse::<i32>() {
                    Ok(n) => ns.push(n),
                    Err(_) => ws.push(s),
                }
                (ns, ws)
            }
        );
        assert_eq!(nums, vec![1, 2]);
    }

    #[test]
    fn test_trisect() {
        let data = [-1i32, 0, 1, -2, 0, 2];
        let (neg, non_neg): (Vec<i32>, Vec<i32>) = data.iter().copied().partition(|&x| x < 0);
        let (zero, pos): (Vec<i32>, Vec<i32>) = non_neg.into_iter().partition(|&x| x == 0);
        assert_eq!(neg.len(), 2);
        assert_eq!(zero.len(), 2);
        assert_eq!(pos.len(), 2);
    }

    #[test]
    fn test_nested_unzip() {
        let pairs: Vec<(i32, i32)> = vec![(1, 10), (2, 20), (3, 30)];
        let (a, b): (Vec<i32>, Vec<i32>) = pairs.into_iter().unzip();
        assert_eq!(a, vec![1, 2, 3]);
        assert_eq!(b, vec![10, 20, 30]);
    }
}
(* 290. Advanced splitting patterns - OCaml *)

type ('a, 'b) either = Left of 'a | Right of 'b

let partition_map f lst =
  let rec aux lefts rights = function
    | [] -> (List.rev lefts, List.rev rights)
    | x :: xs ->
      match f x with
      | Left l -> aux (l :: lefts) rights xs
      | Right r -> aux lefts (r :: rights) xs
  in
  aux [] [] lst

let () =
  let data = ["1"; "two"; "3"; "four"; "5"] in
  let (nums, words) = partition_map (fun s ->
    match int_of_string_opt s with
    | Some n -> Left n
    | None -> Right s
  ) data in
  Printf.printf "Numbers: %s\n" (String.concat ", " (List.map string_of_int nums));
  Printf.printf "Words: %s\n" (String.concat ", " words);

  (* Trisect: neg, zero, pos *)
  let nums = [-3; 0; 1; -1; 0; 5; -2; 3] in
  let neg = List.filter (fun x -> x < 0) nums in
  let zero = List.filter (fun x -> x = 0) nums in
  let pos = List.filter (fun x -> x > 0) nums in
  Printf.printf "Neg: %s, Zero: %d, Pos: %s\n"
    (String.concat "," (List.map string_of_int neg))
    (List.length zero)
    (String.concat "," (List.map string_of_int pos))