ExamplesBy LevelBy TopicLearning Paths
269 Intermediate

269: Splitting by Predicate with partition()

Functional Programming

Tutorial

The Problem

Classifying elements into exactly two groups — evens and odds, valid and invalid, passing and failing — is extremely common. The naive approach iterates twice: once to collect matches, once to collect non-matches. The partition() adapter solves this by splitting an iterator into two collections in a single pass, reducing both computation time and code verbosity. This appears constantly in data validation, input parsing, and batch processing pipelines.

🎯 Learning Outcomes

  • • Understand partition(pred) as a single-pass split into two collections based on a predicate
  • • Recognize when partition() is more efficient than two separate filter() calls
  • • Use partition() to separate Result::is_ok from Result::is_err in a collection of results
  • • Combine partition() with type annotations to clarify output collection types
  • Code Example

    #![allow(clippy::all)]
    //! 269. Splitting by predicate with partition()
    //!
    //! `partition(pred)` splits an iterator into two collections in a single pass.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_partition_even_odd() {
            let (evens, odds): (Vec<i32>, Vec<i32>) = (1..=6).partition(|&x| x % 2 == 0);
            assert_eq!(evens, vec![2, 4, 6]);
            assert_eq!(odds, vec![1, 3, 5]);
        }
    
        #[test]
        fn test_partition_results() {
            let v: Vec<Result<i32, i32>> = vec![Ok(1), Err(2), Ok(3)];
            let (oks, errs): (Vec<_>, Vec<_>) = v.into_iter().partition(Result::is_ok);
            assert_eq!(oks.len(), 2);
            assert_eq!(errs.len(), 1);
        }
    
        #[test]
        fn test_partition_all_true() {
            let (yes, no): (Vec<i32>, Vec<i32>) = [2i32, 4, 6].iter().copied().partition(|&x| x > 0);
            assert_eq!(yes.len(), 3);
            assert!(no.is_empty());
        }
    }

    Key Differences

  • Exact equivalent: List.partition in OCaml and Iterator::partition() in Rust are semantically identical — same name, same behavior.
  • Single pass: Both implementations split in one traversal; using two filter() calls would require two passes.
  • Result splitting: A common Rust pattern uses partition(Result::is_ok) then unwrap() — but partition_map from the itertools crate offers a cleaner API.
  • Stable ordering: Both languages preserve the relative order of elements in each partition.
  • OCaml Approach

    OCaml provides List.partition which is exactly equivalent:

    let (evens, odds) = List.partition (fun x -> x mod 2 = 0) [1;2;3;4;5;6]
    (* evens = [2;4;6], odds = [1;3;5] *)
    

    This is a standard library function in OCaml, making it one of the cleaner parallels between the two languages.

    Full Source

    #![allow(clippy::all)]
    //! 269. Splitting by predicate with partition()
    //!
    //! `partition(pred)` splits an iterator into two collections in a single pass.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_partition_even_odd() {
            let (evens, odds): (Vec<i32>, Vec<i32>) = (1..=6).partition(|&x| x % 2 == 0);
            assert_eq!(evens, vec![2, 4, 6]);
            assert_eq!(odds, vec![1, 3, 5]);
        }
    
        #[test]
        fn test_partition_results() {
            let v: Vec<Result<i32, i32>> = vec![Ok(1), Err(2), Ok(3)];
            let (oks, errs): (Vec<_>, Vec<_>) = v.into_iter().partition(Result::is_ok);
            assert_eq!(oks.len(), 2);
            assert_eq!(errs.len(), 1);
        }
    
        #[test]
        fn test_partition_all_true() {
            let (yes, no): (Vec<i32>, Vec<i32>) = [2i32, 4, 6].iter().copied().partition(|&x| x > 0);
            assert_eq!(yes.len(), 3);
            assert!(no.is_empty());
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_partition_even_odd() {
            let (evens, odds): (Vec<i32>, Vec<i32>) = (1..=6).partition(|&x| x % 2 == 0);
            assert_eq!(evens, vec![2, 4, 6]);
            assert_eq!(odds, vec![1, 3, 5]);
        }
    
        #[test]
        fn test_partition_results() {
            let v: Vec<Result<i32, i32>> = vec![Ok(1), Err(2), Ok(3)];
            let (oks, errs): (Vec<_>, Vec<_>) = v.into_iter().partition(Result::is_ok);
            assert_eq!(oks.len(), 2);
            assert_eq!(errs.len(), 1);
        }
    
        #[test]
        fn test_partition_all_true() {
            let (yes, no): (Vec<i32>, Vec<i32>) = [2i32, 4, 6].iter().copied().partition(|&x| x > 0);
            assert_eq!(yes.len(), 3);
            assert!(no.is_empty());
        }
    }

    Exercises

  • Partition a list of file paths into existing and non-existing files using Path::exists() as the predicate.
  • Parse a Vec<&str> of numbers, using partition() to simultaneously collect successfully parsed integers and the strings that failed to parse.
  • Partition a vector of transactions into credits (positive amounts) and debits (negative amounts), computing the sum of each group.
  • Open Source Repos