261: Lookahead with Peekable
Functional Programming
Tutorial
The Problem
Many parsing and grouping algorithms need to examine the next element before deciding whether to consume it. A lexer must decide if the current < starts <, <=, or << by looking ahead. A run-length encoder must check if the next element continues the current run. Without lookahead, you must buffer elements manually or restructure the algorithm. Peekable wraps any iterator to add a one-element lookahead without consuming the peeked element.
🎯 Learning Outcomes
peek() inspects the next element without advancing the iteratorPeekable to implement run-length grouping and lexer-style tokenizationpeek() returns Option<&Item> — a reference to the next elementpeek() with while let to consume elements conditionallyCode Example
#![allow(clippy::all)]
//! 261. Lookahead with Peekable
//!
//! `Peekable` adds `peek()` to inspect the next element without consuming it.
#[cfg(test)]
mod tests {
#[test]
fn test_peek_no_consume() {
let mut iter = [1i32, 2, 3].iter().peekable();
let p = iter.peek().copied().copied();
let n = iter.next().copied();
assert_eq!(p, Some(1));
assert_eq!(n, Some(1));
assert_eq!(iter.next().copied(), Some(2));
}
#[test]
fn test_peek_groups() {
let data = [1i32, 1, 2, 3, 3];
let mut iter = data.iter().peekable();
let mut groups: Vec<Vec<i32>> = Vec::new();
while let Some(&val) = iter.peek() {
let mut g = Vec::new();
while iter.peek() == Some(&val) {
g.push(*iter.next().unwrap());
}
groups.push(g);
}
assert_eq!(groups, vec![vec![1, 1], vec![2], vec![3, 3]]);
}
#[test]
fn test_peek_empty() {
let data: Vec<i32> = vec![];
let mut p = data.iter().peekable();
assert_eq!(p.peek(), None);
}
}Key Differences
Peekable<I> as a standard adapter; OCaml requires manual buffering.peek() returns Option<&Item> — a reference — so the item remains available for next(); no copying occurs.Peekable buffers exactly one element; for deeper lookahead you need a different approach.nom and pest build on this pattern for full recursive-descent parsing.OCaml Approach
OCaml's standard library does not provide a built-in peekable abstraction. The idiomatic approach is to use a ref holding an optional buffered element, or to restructure the algorithm to pass the "next" element as an argument to recursive calls:
(* Manual lookahead with a buffer ref *)
let peekable seq =
let buf = ref None in
let peek () = match !buf with
| Some v -> Some v
| None -> let v = Seq.uncons seq in buf := Option.map fst v; Option.map fst v
in ...
Full Source
#![allow(clippy::all)]
//! 261. Lookahead with Peekable
//!
//! `Peekable` adds `peek()` to inspect the next element without consuming it.
#[cfg(test)]
mod tests {
#[test]
fn test_peek_no_consume() {
let mut iter = [1i32, 2, 3].iter().peekable();
let p = iter.peek().copied().copied();
let n = iter.next().copied();
assert_eq!(p, Some(1));
assert_eq!(n, Some(1));
assert_eq!(iter.next().copied(), Some(2));
}
#[test]
fn test_peek_groups() {
let data = [1i32, 1, 2, 3, 3];
let mut iter = data.iter().peekable();
let mut groups: Vec<Vec<i32>> = Vec::new();
while let Some(&val) = iter.peek() {
let mut g = Vec::new();
while iter.peek() == Some(&val) {
g.push(*iter.next().unwrap());
}
groups.push(g);
}
assert_eq!(groups, vec![vec![1, 1], vec![2], vec![3, 3]]);
}
#[test]
fn test_peek_empty() {
let data: Vec<i32> = vec![];
let mut p = data.iter().peekable();
assert_eq!(p.peek(), None);
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_peek_no_consume() {
let mut iter = [1i32, 2, 3].iter().peekable();
let p = iter.peek().copied().copied();
let n = iter.next().copied();
assert_eq!(p, Some(1));
assert_eq!(n, Some(1));
assert_eq!(iter.next().copied(), Some(2));
}
#[test]
fn test_peek_groups() {
let data = [1i32, 1, 2, 3, 3];
let mut iter = data.iter().peekable();
let mut groups: Vec<Vec<i32>> = Vec::new();
while let Some(&val) = iter.peek() {
let mut g = Vec::new();
while iter.peek() == Some(&val) {
g.push(*iter.next().unwrap());
}
groups.push(g);
}
assert_eq!(groups, vec![vec![1, 1], vec![2], vec![3, 3]]);
}
#[test]
fn test_peek_empty() {
let data: Vec<i32> = vec![];
let mut p = data.iter().peekable();
assert_eq!(p.peek(), None);
}
}
Exercises
Peekable to implement a simple tokenizer that merges consecutive digit characters into a single number token.group_consecutive function that groups adjacent equal elements without allocating a combined collection first.peek() to implement a merge_sorted function that merges two sorted iterators into one sorted output without a temporary buffer.