Skip to content

← Closures and Iterators step 20 of 28

Medium Primitives

partition and unzip: split rows and group names

Separate names by score while keeping a complete list of the original scores.

pub fn split_scores(
    entries: Vec<(String, i64)>,
    cutoff: i64,
) -> (Vec<String>, Vec<String>, Vec<i64>)

Return three vectors in this order:

  1. Names with scores greater than or equal to cutoff.
  2. Names with scores below cutoff.
  3. Every score in the original input order, including scores from both groups.

Preserve input order within each name vector. Keep duplicate entries and preserve names exactly, including empty names, whitespace and Unicode. Empty input produces three empty vectors. Scores and the cutoff may be any i64 value; compare them directly rather than subtracting the cutoff.

entries = [("a", 5), ("b", 1), ("c", 9)]
cutoff  = 5
result  = (["a", "c"], ["b"], [5, 1, 9])

The last vector is not the scores from the first group followed by those from the second. It retains the original sequence.

Separate fields with unzip, group rows with partition

unzip consumes pairs and puts their first and second components into separate collections. partition sends whole items into one of two collections according to a predicate: matching items first, others second.

Here is a complete example using both operations independently:

fn main() {
    let (letters, values): (Vec<char>, Vec<i32>) =
        [('a', 2), ('b', 1), ('c', 4)].into_iter().unzip();
    assert_eq!(letters, ['a', 'b', 'c']);
    assert_eq!(values, [2, 1, 4]);

    let (even, odd): (Vec<i32>, Vec<i32>) =
        [3, 2, 4, 1].into_iter().partition(|n| n % 2 == 0);
    assert_eq!(even, [2, 4]);
    assert_eq!(odd, [3, 1]);
}

With vector outputs, items retain their encounter order within each result. Do not generalize that to every collection: a sorted set has its own ordering and removes duplicates. The result collection is part of the behavior.

Each operation consumes its input iterator. Using both operations, as in the starter, involves several traversal stages overall, not one traversal of all the data.

Make the output collection types clear

The starter leaves the result types of unzip and partition ambiguous. Iterator item types do not uniquely determine the collection types to build. Type inference can use later code, but it cannot resolve the starter's bindings from the uses shown.

Annotate the first binding:

let (names, scores): (Vec<String>, Vec<i64>) = entries.into_iter().unzip();

For the partition, name the row-vector type to keep the annotation readable:

type Scored = Vec<(String, i64)>;

Then annotate its binding as (Scored, Scored). This also avoids Clippy's type_complexity warning for spelling out that nested pair of vectors. An alias names an existing type; it does not add a wrapper or change the data.

unzip permits different collection types for its two outputs. partition uses the same collection type for both. Both rely on Default to create their outputs and Extend to add items.

Preserve the original score vector

Repair the starter's approach:

  1. Unzip the input into names and scores.
  2. Zip the owned names with scores.iter().copied().
  3. Partition those pairs using score >= cutoff.
  4. Extract the names from each group and return them with the original scores.

scores.iter() borrows the vector, and copied() yields i64 values. It does not clone the vector. The names move through the collections without requiring String clones. The intermediate collections can still allocate.

The partition predicate receives a reference to each item. In the starter's |(_, s)| *s >= cutoff, s is a reference to the score, so *s reads its value.

Use Extend when adding to an existing collection

collect uses FromIterator to construct a result from an iterator. Extend adds iterator items to a collection that already exists. For a vector it appends; for a set it inserts according to that set's rules.

fn main() {
    let mut values = vec![1];
    values.extend([2, 3]);
    assert_eq!(values, [1, 2, 3]);
}

extend avoids needing to collect an iterator into a temporary vector just to append its elements. It can still grow the destination's allocation.

A passing submission must compile, pass the tests, and pass clippy -D warnings. This exercise asks you to repair the supplied unzip/partition approach; output checks alone do not enforce that approach.