ExamplesBy LevelBy TopicLearning Paths
091 Intermediate

091 — Zip and Unzip

Functional Programming

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

  • • Use .zip(other) to combine two iterators into an iterator of pairs
  • • Understand that zip stops when the shorter iterator is exhausted
  • • Use .unzip() to split Vec<(A, B)> into (Vec<A>, Vec<B>) with type annotation
  • • Implement zip_with as zip + map for element-wise operations
  • • Map Rust's zip to OCaml's List.combine / recursive zip
  • • Recognise unzip as the categorical dual of zip
  • Code 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

    AspectRustOCaml
    Zip.zip(other) adapterList.combine / recursive
    Unzip.unzip() consumerList.split
    Zip-withzip + mapList.map2 f l1 l2
    Shorter listSilently truncatesSame — stops at shorter
    Type of resultVec<(&A, &B)> (references)('a * 'b) list (values)
    Unzip annotation(Vec<A>, Vec<B>) requiredInferred 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]);
        }
    }
    ✓ Tests Rust test suite
    #[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

  • • See example.ml for implementation
  • Rust Approach

  • • See example.rs for implementation
  • Comparison Table

    FeatureOCamlRust
    Seeexample.mlexample.rs

    Exercises

  • Implement zip3<A, B, C>(a: &[A], b: &[B], c: &[C]) -> Vec<(&A, &B, &C)> using two .zip calls.
  • Use enumerate() (which is zip with a counter) to add indices to a vector of strings.
  • Write dot_product(a: &[f64], b: &[f64]) -> f64 using zip + map + sum.
  • Implement transpose(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> using repeated zip operations.
  • In OCaml, implement zip_longest that pads the shorter list with None values instead of truncating.
  • Open Source Repos