We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Collections, Text and First Iterators step 8 of 21
Group and count words with BTreeMap entries
Implement grouping and counting with BTreeMap's entry API. Keep both public signatures; you may remove or rewrite the starter's private bucket_for helper.
pub fn group_words(lines: Vec<String>) -> BTreeMap<String, Vec<String>>
pub fn word_counts(words: Vec<String>) -> BTreeMap<String, u32>
Group words from lines
Visit lines in input order and split each with str::split_whitespace. This recognizes Unicode whitespace, including tabs, newlines and non-breaking spaces. Runs of whitespace produce no empty words; empty or whitespace-only lines contribute nothing.
For each word, use its first Rust char, a Unicode scalar value, converted to String as the group key. Preserve the full word and append it in encounter order, including duplicates. Do not lowercase, strip punctuation or normalize text. A char is not necessarily a whole displayed character: a combining mark or the first scalar of a multi-scalar emoji can be the key.
For lines ["apple ant", "bee apple"], return groups a containing
["apple", "ant", "apple"] and b containing ["bee"].
The words "élan" and "étoile" share key "é"; decomposed "élan"
starts with "e" and belongs to a different group.
Count the supplied strings
word_counts counts each entire input string exactly. Unlike group_words,
it does not split strings on whitespace. Empty strings and strings containing
spaces are valid keys. Counts are case-sensitive and include duplicates.
For ["a b", "", "a b"], return "a b" with count 2 and "" with count 1.
Inputs for this exercise have no more than u32::MAX occurrences of any key.
Both functions return empty maps for empty input. BTreeMap iterates keys in ascending order. For String keys this is lexicographic UTF-8 byte order, not locale-specific dictionary order. Group vectors retain encounter order; do not sort their contents. The wrapper reports the maps as groups and counts.
Two separate starter defects
bucket_for uses get_mut and returns its borrow on the Some branch, then tries to mutate the map again on the None branch. The checked Rust 1.98.1 toolchain rejects that pattern with E0499. Use an entry handle to represent the occupied and vacant cases through one mutable borrow.
The counting function has a separate logic error: it inserts zero and never increments. Fixing bucket_for alone would leave every counter at zero. Moving an owned word into the map is allowed; the starter does not actually use that moved word afterwards.
Work through an entry handle
BTreeMap is an ordered tree. It compares keys; it does not hash them. entry returns an occupied-or-vacant handle for a key. In this complete example, or_default supplies an empty vector only for a missing entry:
use std::collections::BTreeMap;
fn main() {
let mut groups: BTreeMap<String, Vec<String>> = BTreeMap::new();
groups.entry("b".to_string()).or_default().push("bee".to_string());
groups.entry("a".to_string()).or_default().push("ant".to_string());
groups.entry("b".to_string()).or_default().push("bat".to_string());
assert_eq!(groups["b"], ["bee", "bat"]);
assert_eq!(groups.keys().map(String::as_str).collect::<Vec<_>>(), ["a", "b"]);
}
For counting, or_insert(0) returns a mutable reference to the stored u32. Dereference it to increment the counter. The caller owns each String from the input vector and can move it directly into entry; no key clone is needed.
Related choices are useful for other defaults:
- or_insert evaluates its argument before the call, even for an occupied key.
- or_insert_with runs its closure body only when a vacant entry needs a value.
- or_default uses the value type's Default when vacant.
- and_modify updates an occupied value and returns an entry handle for chaining.
See the BTreeMap entry API.
What the API and lints do not promise
Entry avoids expressing a separate lookup, conditional insertion and second lookup in your code. Exact comparison counts and speed depend on the data and implementation; a lint recommendation is not a benchmark result.
An entry call takes an owned key. If you only have a borrowed String in other code, cloning it before entry happens even when the key already exists. That tradeoff is different from this counting function, which consumes its keys.
Clippy's map_entry lint warns about some contains_key/insert patterns. With warnings treated as errors, those warnings fail the style check. Its pattern matching does not detect every inefficient implementation or verify your output. Check both grouping order and counts.
Passing requires both maps to match the specification, successful compilation and Clippy with warnings treated as errors.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.