๐Ÿฆ€ Functional Rust

100: Step By, Enumerate, Rev

Difficulty: 3 Level: Intermediate Three zero-cost iterator adapters that change how you traverse โ€” every nth element, indexed pairs, or reverse order.

The Problem This Solves

Common traversal patterns need small modifications: take every other sample from sensor data, process items with their position in the list, walk a slice backwards without copying. Without dedicated adapters, you'd mix in index arithmetic and manual counters that obscure the real logic. These three adapters โ€” `step_by`, `enumerate`, `rev` โ€” are the building blocks of structured traversal. They're all zero-cost: no allocation, no extra passes, just a modified view of the underlying iterator.

The Intuition

All three compose freely with `.map()`, `.filter()`, `.zip()`, and each other.

How It Works in Rust

// step_by: every nth element
fn every_nth(data: &[i32], n: usize) -> Vec<i32> {
 data.iter().step_by(n).copied().collect()
}

// Stepped range โ€” equivalent to Python's range(start, stop, step)
fn range_step(start: i32, stop: i32, step: usize) -> Vec<i32> {
 (start..stop).step_by(step).collect()
}

// enumerate: index alongside value
fn find_with_index(data: &[i32], pred: impl Fn(&i32) -> bool) -> Option<(usize, i32)> {
 data.iter()
     .enumerate()
     .find(|(_, x)| pred(x))
     .map(|(i, &x)| (i, x))
}

// Numbered list: "1. item", "2. item", ...
fn format_numbered(items: &[&str]) -> Vec<String> {
 items.iter()
     .enumerate()
     .map(|(i, s)| format!("{}. {}", i + 1, s))
     .collect()
}

// rev: backward traversal without copy
fn reverse_words(sentence: &str) -> String {
 sentence.split_whitespace().rev().collect::<Vec<_>>().join(" ")
}

// Combine all three: every other element, numbered, reversed
fn combined(data: &[i32]) -> Vec<String> {
 data.iter()
     .step_by(2)
     .enumerate()
     .rev()
     .map(|(i, &x)| format!("{}: {}", i, x))
     .collect()
}
Note: `step_by` must be called before `enumerate` if you want the index to count after stepping. Swap the order for different index semantics.

What This Unlocks

Key Differences

ConceptOCamlRust
step_by`List.filteri (fun i _ -> i mod n = 0)``.step_by(n)`
Enumerate`List.mapi (fun i x -> (i, x))``.enumerate()`
Reverse`List.rev` (allocates new list)`.rev()` (zero-cost adapter)
Range with stepManual recursion`(start..stop).step_by(n)`
ComposabilityPipe `\>` with intermediate listsMethod chaining, no intermediates
// Example 100: Step By, Enumerate, Rev
// Iterator modifiers

// === Approach 1: step_by ===
fn every_nth(data: &[i32], n: usize) -> Vec<i32> {
    data.iter().step_by(n).copied().collect()
}

fn range_step(start: i32, stop: i32, step: usize) -> Vec<i32> {
    (start..stop).step_by(step).collect()
}

// === Approach 2: enumerate ===
fn find_with_index(data: &[i32], pred: impl Fn(&i32) -> bool) -> Option<(usize, i32)> {
    data.iter().enumerate().find(|(_, x)| pred(x)).map(|(i, &x)| (i, x))
}

fn indexed_filter(data: &[i32], pred: impl Fn(&i32) -> bool) -> Vec<(usize, i32)> {
    data.iter().enumerate()
        .filter(|(_, x)| pred(x))
        .map(|(i, &x)| (i, x))
        .collect()
}

fn format_numbered(items: &[&str]) -> Vec<String> {
    items.iter().enumerate()
        .map(|(i, s)| format!("{}. {}", i + 1, s))
        .collect()
}

// === Approach 3: rev ===
fn reverse_words(sentence: &str) -> String {
    sentence.split_whitespace().rev().collect::<Vec<_>>().join(" ")
}

fn last_n<T: Clone>(data: &[T], n: usize) -> Vec<T> {
    data.iter().rev().take(n).cloned().collect::<Vec<_>>().into_iter().rev().collect()
}

// Combined: enumerate + rev + step_by
fn every_other_reversed(data: &[i32]) -> Vec<(usize, i32)> {
    data.iter().enumerate().rev().step_by(2).map(|(i, &x)| (i, x)).collect()
}

// Practical: matrix diagonal via enumerate + step_by
fn diagonal(matrix: &[Vec<i32>]) -> Vec<i32> {
    matrix.iter().enumerate()
        .filter_map(|(i, row)| row.get(i).copied())
        .collect()
}

// Enumerate with custom start
fn enumerate_from<T>(data: &[T], start: usize) -> Vec<(usize, &T)> {
    data.iter().enumerate().map(|(i, x)| (i + start, x)).collect()
}

// Rev + fold for right-fold behavior
fn foldr<T, A: Clone>(data: &[T], init: A, f: impl Fn(A, &T) -> A) -> A {
    data.iter().rev().fold(init, |acc, x| f(acc, x))
}

fn main() {
    println!("Every 2nd: {:?}", every_nth(&[0,1,2,3,4,5,6,7,8,9], 2));
    println!("Every 3rd: {:?}", every_nth(&[0,1,2,3,4,5,6,7,8,9], 3));
    println!("Range step: {:?}", range_step(0, 10, 2));
    println!("Range step 3: {:?}", range_step(1, 10, 3));

    println!("Find >3: {:?}", find_with_index(&[1,2,3,4,5], |x| *x > 3));
    println!("Indexed evens: {:?}", indexed_filter(&[10,11,12,13,14], |x| x % 2 == 0));
    println!("Numbered: {:?}", format_numbered(&["apple","banana","cherry"]));

    println!("Reverse words: {}", reverse_words("hello world foo"));
    println!("Last 3: {:?}", last_n(&[1,2,3,4,5], 3));
    println!("Every other rev: {:?}", every_other_reversed(&[10,20,30,40,50]));

    let matrix = vec![vec![1,2,3], vec![4,5,6], vec![7,8,9]];
    println!("Diagonal: {:?}", diagonal(&matrix));

    // Right fold via rev
    let result = foldr(&["a","b","c"], String::new(), |acc, &s| {
        if acc.is_empty() { s.to_string() } else { format!("{},{}", acc, s) }
    });
    println!("Foldr: {}", result);
}

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

    #[test]
    fn test_step_by() {
        assert_eq!(every_nth(&[0,1,2,3,4,5,6,7,8,9], 2), vec![0,2,4,6,8]);
        assert_eq!(every_nth(&[0,1,2,3,4,5,6,7,8,9], 3), vec![0,3,6,9]);
    }

    #[test]
    fn test_range_step() {
        assert_eq!(range_step(0, 10, 2), vec![0,2,4,6,8]);
        assert_eq!(range_step(1, 10, 3), vec![1,4,7]);
    }

    #[test]
    fn test_find_with_index() {
        assert_eq!(find_with_index(&[1,2,3,4,5], |x| *x > 3), Some((3, 4)));
        assert_eq!(find_with_index(&[1,2,3], |x| *x > 10), None);
    }

    #[test]
    fn test_indexed_filter() {
        assert_eq!(indexed_filter(&[10,11,12,13,14], |x| x % 2 == 0),
            vec![(0,10), (2,12), (4,14)]);
    }

    #[test]
    fn test_format_numbered() {
        assert_eq!(format_numbered(&["a","b","c"]),
            vec!["1. a", "2. b", "3. c"]);
    }

    #[test]
    fn test_reverse_words() {
        assert_eq!(reverse_words("hello world foo"), "foo world hello");
    }

    #[test]
    fn test_last_n() {
        assert_eq!(last_n(&[1,2,3,4,5], 3), vec![3,4,5]);
        assert_eq!(last_n(&[1,2], 5), vec![1,2]);
    }

    #[test]
    fn test_diagonal() {
        let m = vec![vec![1,2,3], vec![4,5,6], vec![7,8,9]];
        assert_eq!(diagonal(&m), vec![1,5,9]);
    }

    #[test]
    fn test_enumerate_from() {
        let result = enumerate_from(&["a","b","c"], 10);
        assert_eq!(result[0].0, 10);
        assert_eq!(result[2].0, 12);
    }

    #[test]
    fn test_foldr() {
        let result = foldr(&[1,2,3], Vec::new(), |mut acc, &x| { acc.push(x); acc });
        assert_eq!(result, vec![3, 2, 1]);
    }

    #[test]
    fn test_every_other_reversed() {
        let result = every_other_reversed(&[10,20,30,40,50]);
        assert_eq!(result, vec![(4,50), (2,30), (0,10)]);
    }
}
(* Example 100: Step By, Enumerate, Rev *)
(* Iterator modifiers *)

(* Approach 1: Step by โ€” take every nth element *)
let step_by n lst =
  List.filteri (fun i _ -> i mod n = 0) lst

let range_step start stop step =
  let rec aux acc i =
    if i >= stop then List.rev acc
    else aux (i :: acc) (i + step)
  in
  aux [] start

(* Approach 2: Enumerate โ€” pair with index *)
let enumerate lst = List.mapi (fun i x -> (i, x)) lst

let find_with_index pred lst =
  let rec aux i = function
    | [] -> None
    | x :: _ when pred x -> Some (i, x)
    | _ :: rest -> aux (i + 1) rest
  in
  aux 0 lst

let indexed_filter pred lst =
  enumerate lst
  |> List.filter (fun (_, x) -> pred x)

(* Approach 3: Rev โ€” reverse iteration *)
let rev_map f lst = List.rev_map f lst |> List.rev
(* Note: List.rev_map reverses order, so we reverse back *)

let last_n n lst =
  let len = List.length lst in
  List.filteri (fun i _ -> i >= len - n) lst

let pairs_reversed lst =
  let rev = List.rev lst in
  List.combine lst rev

(* Practical combinations *)
let format_numbered lst =
  enumerate lst
  |> List.map (fun (i, x) -> Printf.sprintf "%d. %s" (i + 1) x)

let every_other lst = step_by 2 lst
let every_third lst = step_by 3 lst

let reverse_words sentence =
  String.split_on_char ' ' sentence
  |> List.rev
  |> String.concat " "

(* Tests *)
let () =
  assert (step_by 2 [0;1;2;3;4;5;6;7;8;9] = [0;2;4;6;8]);
  assert (step_by 3 [0;1;2;3;4;5;6;7;8;9] = [0;3;6;9]);

  assert (range_step 0 10 2 = [0;2;4;6;8]);
  assert (range_step 1 10 3 = [1;4;7]);

  assert (enumerate ["a";"b";"c"] = [(0,"a"); (1,"b"); (2,"c")]);

  assert (find_with_index (fun x -> x > 3) [1;2;3;4;5] = Some (3, 4));
  assert (find_with_index (fun x -> x > 10) [1;2;3] = None);

  let filtered = indexed_filter (fun x -> x mod 2 = 0) [10;11;12;13;14] in
  assert (filtered = [(0, 10); (2, 12); (4, 14)]);

  assert (last_n 3 [1;2;3;4;5] = [3;4;5]);

  assert (format_numbered ["apple"; "banana"; "cherry"] =
          ["1. apple"; "2. banana"; "3. cherry"]);

  assert (every_other [1;2;3;4;5;6] = [1;3;5]);

  assert (reverse_words "hello world foo" = "foo world hello");

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Step By, Enumerate, Rev

Step By

OCaml:

๐Ÿช Show OCaml equivalent
let step_by n lst =
List.filteri (fun i _ -> i mod n = 0) lst

let range_step start stop step =
let rec aux acc i =
 if i >= stop then List.rev acc
 else aux (i :: acc) (i + step)
in aux [] start

Rust:

data.iter().step_by(2).collect::<Vec<_>>()

(0..10).step_by(2).collect::<Vec<_>>()  // [0, 2, 4, 6, 8]

Enumerate

OCaml:

๐Ÿช Show OCaml equivalent
let enumerate lst = List.mapi (fun i x -> (i, x)) lst

let find_with_index pred lst =
let rec aux i = function
 | [] -> None
 | x :: _ when pred x -> Some (i, x)
 | _ :: rest -> aux (i + 1) rest
in aux 0 lst

Rust:

data.iter().enumerate().collect::<Vec<_>>()

data.iter().enumerate().find(|(_, x)| pred(x))

Rev

OCaml:

๐Ÿช Show OCaml equivalent
let reverse_words sentence =
String.split_on_char ' ' sentence
|> List.rev
|> String.concat " "

Rust:

fn reverse_words(s: &str) -> String {
 s.split_whitespace().rev().collect::<Vec<_>>().join(" ")
}

Combined Modifiers

OCaml (manual):

๐Ÿช Show OCaml equivalent
(* No easy composition โ€” manual index math *)

Rust:

data.iter().enumerate().rev().step_by(2).collect::<Vec<_>>()