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
- `step_by(n)`: skip `n-1` elements between each yield. `iter.step_by(2)` โ every other element. `(0..10).step_by(3)` โ `0, 3, 6, 9`. Python's `range(0, 10, 3)` works the same way.
- `enumerate()`: wraps each element with its 0-based index โ `(usize, &T)`. Python's `enumerate(data)` is identical.
- `rev()`: iterate backwards. Requires `DoubleEndedIterator`. Python's `reversed()` is similar but creates a new iterator object; Rust's is zero-cost.
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
- Sampling: take every nth measurement from a sensor stream with `.step_by(n)`.
- Validation with context: `.enumerate()` lets you report which element failed in an error message.
- Reversed display: show items newest-first without reversing the underlying storage.
Key Differences
| Concept | OCaml | Rust | |
|---|---|---|---|
| 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 step | Manual recursion | `(start..stop).step_by(n)` | |
| Composability | Pipe `\ | >` with intermediate lists | Method 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<_>>()