RustArena

Step 3 of 6

Counting with entry

The most common thing to do with a map is count: words in a text, hits per page, errors per server. Each time you see a key you want "add one, or start at one if it's new". With what you know so far, that's a get_mut and an insert: two lookups and a match. The entry API does it in one:

use std::collections::HashMap;

fn main() {
    let text = "the crab and the gull and the crab";
    let mut counts: HashMap<&str, u32> = HashMap::new();
    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }

    let mut pairs: Vec<(&str, u32)> = Vec::new();
    for (word, n) in &counts {
        pairs.push((*word, *n));
    }
    pairs.sort();
    for (word, n) in pairs {
        println!("{word}: {n}");
    }
}
and: 2
crab: 2
gull: 1
the: 3

Read *counts.entry(word).or_insert(0) += 1; from the inside out:

  1. counts.entry(word) finds the slot for word. That slot, an Entry, is either occupied (the word was seen before) or vacant.
  2. .or_insert(0) puts 0 in the slot if it was vacant. Either way it returns a &mut u32 pointing at the count inside the map.
  3. *... += 1 adds one through that reference.

One lookup, no match, and it reads like the sentence "add one to this word's count, starting from zero".

Other ways to fill an entry

use std::collections::BTreeMap;

fn main() {
    let visits = ["home", "about", "home", "shop", "home"];

    let mut counts: BTreeMap<&str, u32> = BTreeMap::new();
    for page in visits {
        counts.entry(page).and_modify(|n| *n += 1).or_insert(1);
    }
    println!("{:?}", counts);

    let mut totals: BTreeMap<&str, u32> = BTreeMap::new();
    for page in visits {
        *totals.entry(page).or_default() += 1;
    }
    println!("{:?}", totals);
}
{"about": 1, "home": 3, "shop": 1}
{"about": 1, "home": 3, "shop": 1}

Common mistakes

Your task

Write fn word_counts(text: &str) -> HashMap<String, u32> that counts each word (split on whitespace), ignoring case: "Rust" and "rust" are the same word, stored in lowercase.

Challenge 2

Fill in the blanks to count how often each letter appears, using a BTreeMap so the letters print in order.

Challenge 3

Write a sales report. Each input line is region amount (whole numbers). Total the amounts per region with the entry API, then print every region as region: total, highest total first, with equal totals in alphabetical order.

Previous: Iteration order and BTreeMap Next: HashSet: have I seen this before?