We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 20 of 28
partition and unzip: two collections from one pass
Take a list of (name, score) rows and produce three things.
pub fn split_scores(
entries: Vec<(String, i64)>,
cutoff: i64,
) -> (Vec<String>, Vec<String>, Vec<i64>)
-
.0— the names whose score is at leastcutoff; -
.1— the names whose score is belowcutoff; -
.2— all the scores, in the original input order.
entries = [("a", 5), ("b", 1), ("c", 9)], cutoff = 5
-> (["a", "c"], ["b"], [5, 1, 9])
Both partitions preserve input relative order. Say that back to yourself,
because most people assume the opposite: partition is not a sort, does not
reorder, and does not group. It walks once and pushes each element onto one
of two piles, so within each pile the original order is intact. The hidden
cases check it.
Two consumers that build more than one thing
Almost every consumer you have met so far returns one value. These two return a pair, and that changes what inference can do for you.
fn unzip<A, B, FromA, FromB>(self) -> (FromA, FromB)
where Self: Iterator<Item = (A, B)>,
FromA: Default + Extend<A>,
FromB: Default + Extend<B>;
fn partition<B, F>(self, f: F) -> (B, B)
where B: Default + Extend<Self::Item>,
F: FnMut(&Self::Item) -> bool;
unzip takes an iterator of pairs and gives you two collections. partition
takes a predicate and gives you two collections of the same type — true
first, false second. Both make exactly one pass.
Why the starter does not compile
Remove the annotations and you get:
error[E0282]: type annotations needed for `(_, _)`
Look at the bounds again. Neither function’s return type is determined by its
input: FromA only has to be Default + Extend<A>, and Vec<A>,
VecDeque<A>, BTreeSet<A> and String (for A = char) all qualify.
Nothing in the expression picks one. This is return-type-driven
inference, the same mechanism as collect and parse, and it means you
have to say what you want:
let (names, scores): (Vec<String>, Vec<i64>) = entries.into_iter().unzip();
unzip needs both halves inferable. partition needs its single
collection type known — a turbofish works too:
.partition::<Vec<_>, _>(..), though the binding annotation is usually
easier to read.
Extend is the thing underneath
Both bounds mention it, and it is worth a minute:
pub trait Extend<A> {
fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T);
}
FromIterator (what collect uses) builds a new collection. Extend
appends to an existing one. That distinction is the whole reason
partition and unzip can be written generically at all: they create two
empty collections with Default and then push into them with Extend.
It is also directly useful. v.extend(other_iter) avoids the intermediate
Vec that v.append(&mut other.collect()) would build, and it accepts any
IntoIterator, so v.extend(a.iter().copied()) and v.extend([1, 2, 3])
both work. When you catch yourself writing a for loop whose whole body is
v.push(x), that is extend.
type_complexity will bite you
Partitioning pairs means annotating a pair-of-vectors-of-pairs, and clippy has a default-on opinion about that:
error: very complex type used. Consider factoring parts into `type` definitions
[clippy::type_complexity]
The fix is a type alias, and it is a genuine readability win rather than lint-appeasement:
type Scored = Vec<(String, i64)>;
let (pass, fail): (Scored, Scored) = ...;
You will meet this lint again in item 9.23, where chained adapter types get much worse than this.
Order of operations
You need the scores twice — once to decide the partition, once to return them
whole and in order. Unzip first, then zip the names back against the scores
by value (scores.iter().copied()), partition that, and strip the scores off
each half afterwards. The original scores vector is untouched and is your
third return value.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.