259: Flattening with flat_map()
Functional Programming
Tutorial
The Problem
Many real-world transformations are one-to-many: splitting a sentence into words, expanding a range for each element, or parsing optional values from strings. A plain map() yields Iterator<Item = Iterator<...>> — a nested structure that requires an additional flatten() call to linearize. The flat_map() combinator (also known as bind or >>= in monadic contexts) fuses these two operations, mapping each element to an iterable and immediately flattening the result into a single stream.
🎯 Learning Outcomes
flat_map(f) as equivalent to map(f).flatten() — the monadic bind for iteratorsflat_map() to expand one element into multiple valuesflat_map() as the same as Haskell's concatMap and OCaml's List.concat_mapCode Example
#![allow(clippy::all)]
//! 259. Flattening with flat_map()
//!
//! `flat_map(f)` = `map(f).flatten()` — the iterator monad's bind operation.
#[cfg(test)]
mod tests {
#[test]
fn test_flat_map_expand() {
let result: Vec<i32> = [1i32, 2, 3].iter().flat_map(|&n| 0..n).collect();
assert_eq!(result, vec![0, 0, 1, 0, 1, 2]);
}
#[test]
fn test_flat_map_filter_parse() {
let strings = ["1", "x", "2", "y", "3"];
let result: Vec<i32> = strings.iter().flat_map(|s| s.parse::<i32>()).collect();
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_flat_map_words() {
let sentences = ["hello world", "foo bar"];
let words: Vec<&str> = sentences
.iter()
.flat_map(|s| s.split_whitespace())
.collect();
assert_eq!(words.len(), 4);
}
}Key Differences
flat_map; Haskell calls it concatMap; OCaml uses List.concat_map; all express the same monadic bind operation.flat_map accepts any IntoIterator, including Option and Result which iterate to 0 or 1 elements — enabling inline filtering.List.concat_map builds a new list eagerly.Result::ok() or Option from flat_map naturally filters failures without a separate filter() call.OCaml Approach
OCaml provides List.concat_map f xs (or List.map f xs |> List.concat for older versions), which is exactly flat_map. The monadic bind >>= for the list monad is defined as fun xs f -> List.concat_map f xs:
let words = List.concat_map String.split_on_char [' '] ["hello world"; "foo bar"]
(* ["hello"; "world"; "foo"; "bar"] *)
Full Source
#![allow(clippy::all)]
//! 259. Flattening with flat_map()
//!
//! `flat_map(f)` = `map(f).flatten()` — the iterator monad's bind operation.
#[cfg(test)]
mod tests {
#[test]
fn test_flat_map_expand() {
let result: Vec<i32> = [1i32, 2, 3].iter().flat_map(|&n| 0..n).collect();
assert_eq!(result, vec![0, 0, 1, 0, 1, 2]);
}
#[test]
fn test_flat_map_filter_parse() {
let strings = ["1", "x", "2", "y", "3"];
let result: Vec<i32> = strings.iter().flat_map(|s| s.parse::<i32>()).collect();
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_flat_map_words() {
let sentences = ["hello world", "foo bar"];
let words: Vec<&str> = sentences
.iter()
.flat_map(|s| s.split_whitespace())
.collect();
assert_eq!(words.len(), 4);
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_flat_map_expand() {
let result: Vec<i32> = [1i32, 2, 3].iter().flat_map(|&n| 0..n).collect();
assert_eq!(result, vec![0, 0, 1, 0, 1, 2]);
}
#[test]
fn test_flat_map_filter_parse() {
let strings = ["1", "x", "2", "y", "3"];
let result: Vec<i32> = strings.iter().flat_map(|s| s.parse::<i32>()).collect();
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_flat_map_words() {
let sentences = ["hello world", "foo bar"];
let words: Vec<&str> = sentences
.iter()
.flat_map(|s| s.split_whitespace())
.collect();
assert_eq!(words.len(), 4);
}
}
Exercises
Vec<String> of sentences into individual words using flat_map() and split_whitespace().flat_map() with Option to look up each key in a map and collect only the found values into a Vec.flat_map from scratch using only map() and flatten() and verify it produces identical results.