091 — Zip and Unzip
Tutorial
The Problem
Use Rust's iterator zip to pair elements from two sequences, .unzip() to split a sequence of pairs back into two vectors, and zip + map to implement a pairwise zip_with. Compare with OCaml's recursive zip, unzip, and zip_with on lists.
🎯 Learning Outcomes
.zip(other) to combine two iterators into an iterator of pairszip stops when the shorter iterator is exhausted.unzip() to split Vec<(A, B)> into (Vec<A>, Vec<B>) with type annotationzip_with as zip + map for element-wise operationszip to OCaml's List.combine / recursive zipunzip as the categorical dual of zipCode Example
#![allow(clippy::all)]
// 091: Zip and Unzip
#[cfg(test)]
mod tests {
#[test]
fn test_zip() {
let v: Vec<_> = [1, 2, 3].iter().zip(["a", "b", "c"].iter()).collect();
assert_eq!(v, vec![(&1, &"a"), (&2, &"b"), (&3, &"c")]);
}
#[test]
fn test_zip_unequal() {
let v: Vec<_> = [1, 2].iter().zip([10, 20, 30].iter()).collect();
assert_eq!(v.len(), 2);
}
#[test]
fn test_unzip() {
let (a, b): (Vec<i32>, Vec<&str>) = vec![(1, "a"), (2, "b")].into_iter().unzip();
assert_eq!(a, vec![1, 2]);
assert_eq!(b, vec!["a", "b"]);
}
#[test]
fn test_zip_with() {
let v: Vec<i32> = [1, 2, 3]
.iter()
.zip([10, 20, 30].iter())
.map(|(a, b)| a + b)
.collect();
assert_eq!(v, vec![11, 22, 33]);
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Zip | .zip(other) adapter | List.combine / recursive |
| Unzip | .unzip() consumer | List.split |
| Zip-with | zip + map | List.map2 f l1 l2 |
| Shorter list | Silently truncates | Same — stops at shorter |
| Type of result | Vec<(&A, &B)> (references) | ('a * 'b) list (values) |
| Unzip annotation | (Vec<A>, Vec<B>) required | Inferred from usage |
zip and unzip are fundamental pairing and un-pairing operations. In Rust, they integrate naturally into the iterator chain. OCaml provides List.combine/List.split in the standard library for the same purpose. Both languages make zip_with trivially expressible.
OCaml Approach
OCaml's zip is implemented with pattern matching: match l1, l2 with | [], _ | _, [] -> [] | x::xs, y::ys -> (x,y) :: zip xs ys. unzip uses fold_right to reconstruct two lists simultaneously. zip_with f l1 l2 applies f element-wise. OCaml's standard library provides List.combine (equivalent to zip) and List.split (equivalent to unzip).
Full Source
#![allow(clippy::all)]
// 091: Zip and Unzip
#[cfg(test)]
mod tests {
#[test]
fn test_zip() {
let v: Vec<_> = [1, 2, 3].iter().zip(["a", "b", "c"].iter()).collect();
assert_eq!(v, vec![(&1, &"a"), (&2, &"b"), (&3, &"c")]);
}
#[test]
fn test_zip_unequal() {
let v: Vec<_> = [1, 2].iter().zip([10, 20, 30].iter()).collect();
assert_eq!(v.len(), 2);
}
#[test]
fn test_unzip() {
let (a, b): (Vec<i32>, Vec<&str>) = vec![(1, "a"), (2, "b")].into_iter().unzip();
assert_eq!(a, vec![1, 2]);
assert_eq!(b, vec!["a", "b"]);
}
#[test]
fn test_zip_with() {
let v: Vec<i32> = [1, 2, 3]
.iter()
.zip([10, 20, 30].iter())
.map(|(a, b)| a + b)
.collect();
assert_eq!(v, vec![11, 22, 33]);
}
}#[cfg(test)]
mod tests {
#[test]
fn test_zip() {
let v: Vec<_> = [1, 2, 3].iter().zip(["a", "b", "c"].iter()).collect();
assert_eq!(v, vec![(&1, &"a"), (&2, &"b"), (&3, &"c")]);
}
#[test]
fn test_zip_unequal() {
let v: Vec<_> = [1, 2].iter().zip([10, 20, 30].iter()).collect();
assert_eq!(v.len(), 2);
}
#[test]
fn test_unzip() {
let (a, b): (Vec<i32>, Vec<&str>) = vec![(1, "a"), (2, "b")].into_iter().unzip();
assert_eq!(a, vec![1, 2]);
assert_eq!(b, vec!["a", "b"]);
}
#[test]
fn test_zip_with() {
let v: Vec<i32> = [1, 2, 3]
.iter()
.zip([10, 20, 30].iter())
.map(|(a, b)| a + b)
.collect();
assert_eq!(v, vec![11, 22, 33]);
}
}
Deep Comparison
Core Insight
Zip interleaves two iterators element-wise; unzip is the inverse — separates pairs into two collections
OCaml Approach
Rust Approach
Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| See | example.ml | example.rs |
Exercises
zip3<A, B, C>(a: &[A], b: &[B], c: &[C]) -> Vec<(&A, &B, &C)> using two .zip calls.enumerate() (which is zip with a counter) to add indices to a vector of strings.dot_product(a: &[f64], b: &[f64]) -> f64 using zip + map + sum.transpose(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> using repeated zip operations.zip_longest that pads the shorter list with None values instead of truncating.