ExamplesBy LevelBy TopicLearning Paths
257 Intermediate

257: Pairing Elements with zip()

Functional Programming

Tutorial

The Problem

Many algorithms operate on two parallel sequences simultaneously: pairing keys with values, computing dot products, correlating time-series data, or combining two streams element-by-element. The naive approach uses index-based access (a[i], b[i]) which is error-prone and requires bounds checking. The zip() combinator solves this by producing pairs (a_i, b_i) lazily, stopping when the shorter source is exhausted — eliminating off-by-one errors entirely.

🎯 Learning Outcomes

  • • Understand how zip() pairs elements from two iterators, stopping at the shorter one
  • • Use zip() to build HashMap from key and value iterators
  • • Recognize the truncation behavior as a feature for handling mismatched-length sources
  • • Combine zip() with map() to compute pairwise operations like dot products
  • Code Example

    #![allow(clippy::all)]
    //! 257. Pairing elements with zip()
    //!
    //! `zip()` pairs elements from two iterators, stopping at the shorter one.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_zip_basic() {
            let a = [1i32, 2, 3];
            let b = ["x", "y", "z"];
            let result: Vec<_> = a.iter().zip(b.iter()).collect();
            assert_eq!(result.len(), 3);
            assert_eq!(result[0], (&1, &"x"));
        }
    
        #[test]
        fn test_zip_truncates() {
            let a = [1i32, 2, 3, 4, 5];
            let b = [10i32, 20];
            let result: Vec<_> = a.iter().zip(b.iter()).collect();
            assert_eq!(result.len(), 2);
        }
    
        #[test]
        fn test_zip_into_hashmap() {
            let keys = vec!["a", "b"];
            let vals = vec![1i32, 2];
            let map: std::collections::HashMap<_, _> = keys.into_iter().zip(vals).collect();
            assert_eq!(map["a"], 1);
            assert_eq!(map["b"], 2);
        }
    }

    Key Differences

  • Length mismatch: Rust's zip() silently truncates to the shorter iterator; OCaml's List.map2 raises Invalid_argument.
  • Laziness: Rust's zip() is lazy; OCaml's List.combine immediately creates a new list.
  • Pair type: Rust yields Rust tuples (A, B) which are unnamed; OCaml produces named-field or anonymous tuples identically.
  • Unzip: Rust has Iterator::unzip() as the inverse; OCaml uses List.split.
  • OCaml Approach

    OCaml provides List.combine for zipping two lists into a list of pairs, and List.map2 for applying a binary function pairwise. These are strict and raise Invalid_argument on length mismatch, unlike Rust's truncation:

    let pairs = List.combine [1;2;3] ["a";"b";"c"]
    (* [(1,"a"); (2,"b"); (3,"c")] — strict, raises on unequal lengths *)
    let dot = List.fold_left (+) 0 (List.map2 ( * ) a b)
    

    Full Source

    #![allow(clippy::all)]
    //! 257. Pairing elements with zip()
    //!
    //! `zip()` pairs elements from two iterators, stopping at the shorter one.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_zip_basic() {
            let a = [1i32, 2, 3];
            let b = ["x", "y", "z"];
            let result: Vec<_> = a.iter().zip(b.iter()).collect();
            assert_eq!(result.len(), 3);
            assert_eq!(result[0], (&1, &"x"));
        }
    
        #[test]
        fn test_zip_truncates() {
            let a = [1i32, 2, 3, 4, 5];
            let b = [10i32, 20];
            let result: Vec<_> = a.iter().zip(b.iter()).collect();
            assert_eq!(result.len(), 2);
        }
    
        #[test]
        fn test_zip_into_hashmap() {
            let keys = vec!["a", "b"];
            let vals = vec![1i32, 2];
            let map: std::collections::HashMap<_, _> = keys.into_iter().zip(vals).collect();
            assert_eq!(map["a"], 1);
            assert_eq!(map["b"], 2);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_zip_basic() {
            let a = [1i32, 2, 3];
            let b = ["x", "y", "z"];
            let result: Vec<_> = a.iter().zip(b.iter()).collect();
            assert_eq!(result.len(), 3);
            assert_eq!(result[0], (&1, &"x"));
        }
    
        #[test]
        fn test_zip_truncates() {
            let a = [1i32, 2, 3, 4, 5];
            let b = [10i32, 20];
            let result: Vec<_> = a.iter().zip(b.iter()).collect();
            assert_eq!(result.len(), 2);
        }
    
        #[test]
        fn test_zip_into_hashmap() {
            let keys = vec!["a", "b"];
            let vals = vec![1i32, 2];
            let map: std::collections::HashMap<_, _> = keys.into_iter().zip(vals).collect();
            assert_eq!(map["a"], 1);
            assert_eq!(map["b"], 2);
        }
    }

    Exercises

  • Zip a list of student names with their grades and compute the average grade using zip() and map().
  • Implement a dot product function dot(a: &[f64], b: &[f64]) -> f64 using zip() and sum().
  • Use zip() with enumerate() to pair every element with both its index and a label from a separate labels slice.
  • Open Source Repos