ExamplesBy LevelBy TopicLearning Paths
268 Intermediate

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

  • • Understand unzip() as the inverse of zip() — splitting Iterator<(A, B)> into (Vec<A>, Vec<B>)
  • • Recognize that unzip() operates in a single pass without intermediate storage
  • • Use unzip() to separate keys from values, x-coordinates from y-coordinates
  • • Apply unzip() after map() to transform and simultaneously split data
  • Code 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

  • Name: Rust calls it unzip(); OCaml calls it List.split; Haskell calls it unzip — all are identical in semantics.
  • Generic output: Rust's unzip() works into any FromIterator-implementing collection; OCaml's List.split is list-specific.
  • Single pass: Both Rust and OCaml implement unzip in a single pass over the pairs, building both outputs simultaneously.
  • Transform then split: The common pattern is 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

  • Parse a list of "key=value" strings, splitting on =, and use unzip() to collect keys and values into separate Vec<String> collections.
  • Given a list of 2D points as (f64, f64) pairs, use unzip() to get separate x and y coordinate vectors for plotting.
  • Use enumerate() followed by unzip() to simultaneously extract indices and values from a slice.
  • Open Source Repos