268: Splitting Pairs with unzip()
Functional Programming
Tutorial
The Problem
Data often arrives paired — key-value entries, coordinate pairs, or associated data — but needs to be split into separate collections for independent processing. The unzip() adapter is the exact inverse of zip(): it consumes an iterator of pairs and distributes them into two separate collections simultaneously, in a single pass. This is more efficient than collecting all pairs first and then splitting.
🎯 Learning Outcomes
unzip() as the inverse of zip() — splitting Iterator<(A, B)> into (Vec<A>, Vec<B>)unzip() operates in a single pass without intermediate storageunzip() to separate keys from values, x-coordinates from y-coordinatesunzip() after map() to transform and simultaneously split dataCode Example
#![allow(clippy::all)]
//! 268. Splitting pairs with unzip()
//!
//! `unzip()` splits an iterator of `(A, B)` tuples into two separate collections.
#[cfg(test)]
mod tests {
#[test]
fn test_unzip_basic() {
let pairs = vec![(1i32, 'a'), (2, 'b'), (3, 'c')];
let (nums, chars): (Vec<i32>, Vec<char>) = pairs.into_iter().unzip();
assert_eq!(nums, vec![1, 2, 3]);
assert_eq!(chars, vec!['a', 'b', 'c']);
}
#[test]
fn test_unzip_roundtrip() {
let a = vec![1i32, 2, 3];
let b = vec![4i32, 5, 6];
let (a2, b2): (Vec<i32>, Vec<i32>) = a.iter().copied().zip(b.iter().copied()).unzip();
assert_eq!(a, a2);
assert_eq!(b, b2);
}
#[test]
fn test_unzip_empty() {
let empty: Vec<(i32, i32)> = vec![];
let (a, b): (Vec<i32>, Vec<i32>) = empty.into_iter().unzip();
assert!(a.is_empty() && b.is_empty());
}
}Key Differences
unzip(); OCaml calls it List.split; Haskell calls it unzip — all are identical in semantics.unzip() works into any FromIterator-implementing collection; OCaml's List.split is list-specific.map(...).unzip() — transform pairs and split in one composed operation.OCaml Approach
OCaml provides List.split which is exactly unzip for lists of pairs:
let (firsts, seconds) = List.split [(1,'a'); (2,'b'); (3,'c')]
(* firsts = [1;2;3], seconds = ['a';'b';'c'] *)
This is strict (builds both lists in one pass) and is a standard library function, unlike zip / combine in OCaml.
Full Source
#![allow(clippy::all)]
//! 268. Splitting pairs with unzip()
//!
//! `unzip()` splits an iterator of `(A, B)` tuples into two separate collections.
#[cfg(test)]
mod tests {
#[test]
fn test_unzip_basic() {
let pairs = vec![(1i32, 'a'), (2, 'b'), (3, 'c')];
let (nums, chars): (Vec<i32>, Vec<char>) = pairs.into_iter().unzip();
assert_eq!(nums, vec![1, 2, 3]);
assert_eq!(chars, vec!['a', 'b', 'c']);
}
#[test]
fn test_unzip_roundtrip() {
let a = vec![1i32, 2, 3];
let b = vec![4i32, 5, 6];
let (a2, b2): (Vec<i32>, Vec<i32>) = a.iter().copied().zip(b.iter().copied()).unzip();
assert_eq!(a, a2);
assert_eq!(b, b2);
}
#[test]
fn test_unzip_empty() {
let empty: Vec<(i32, i32)> = vec![];
let (a, b): (Vec<i32>, Vec<i32>) = empty.into_iter().unzip();
assert!(a.is_empty() && b.is_empty());
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_unzip_basic() {
let pairs = vec![(1i32, 'a'), (2, 'b'), (3, 'c')];
let (nums, chars): (Vec<i32>, Vec<char>) = pairs.into_iter().unzip();
assert_eq!(nums, vec![1, 2, 3]);
assert_eq!(chars, vec!['a', 'b', 'c']);
}
#[test]
fn test_unzip_roundtrip() {
let a = vec![1i32, 2, 3];
let b = vec![4i32, 5, 6];
let (a2, b2): (Vec<i32>, Vec<i32>) = a.iter().copied().zip(b.iter().copied()).unzip();
assert_eq!(a, a2);
assert_eq!(b, b2);
}
#[test]
fn test_unzip_empty() {
let empty: Vec<(i32, i32)> = vec![];
let (a, b): (Vec<i32>, Vec<i32>) = empty.into_iter().unzip();
assert!(a.is_empty() && b.is_empty());
}
}
Exercises
"key=value" strings, splitting on =, and use unzip() to collect keys and values into separate Vec<String> collections.(f64, f64) pairs, use unzip() to get separate x and y coordinate vectors for plotting.enumerate() followed by unzip() to simultaneously extract indices and values from a slice.