Skip to content
Select theme

How to count word frequency with a HashMap

Short answer: split the text with split_whitespace(), then do one *counts.entry(word).or_insert(0) += 1 per word. Sort the keys before printing — a HashMap hands its keys back in no particular order.

From a fresh project (cargo new word-count — the name is mine, and nothing below prints it), replace the contents of src/main.rs with each block and run it with cargo run. Everything here is Rust 1.99.0, edition 2024.

brown: 2
dog: 1
fox: 2
jumps: 1
lazy: 1
over: 1
quick: 2
the: 3

One pass, one lookup per word. or_insert(0) puts a zero in when the word is new and hands back the slot either way, so the same line creates the entry and bumps it. This is the entry API from How to handle missing keys with the entry API applied to a real problem; for a usize value, or_default() is the same call with the zero implied.

Printing straight out of the map would give the keys back in whatever order the hasher left them, so the keys are collected and sorted first. That is the lesson from Why is a HashMap’s order different every run?: the map is fine, the print order is the part you control.

barks: 1
brown: 1
dog: 2
fox: 2
jumps: 1
lazy: 1
over: 1
quick: 1
runs: 1
the: 4

Three things make that block longer than the first. to_lowercase() folds The into the, which makes the map HashMap<String, usize> — every token allocates one String, hit or miss, and that allocation is to_lowercase, not entry. split_whitespace() splits on runs of any whitespace, newlines and tabs included; the standard library’s own words are “separated by any amount of whitespace”. And trim_matches(|c: char| !c.is_alphanumeric()) strips punctuation off both ends — “all prefixes and suffixes that match a pattern repeatedly removed” — so dog. counts as dog while the apostrophe in don't and the hyphen in well-known survive, because only the ends are trimmed.

A token that is nothing but punctuation — ..., an em dash — trims down to the empty string, and the is_empty check keeps it out of the map. Leave the check out and the output grows a : 1 line of its own.

the: 3
brown: 2
fox: 2

sort_by sorts on the count first, and then on the word breaks ties alphabetically. Without the tie-break, two words with the same count could swap places between runs — the counts are stable, but the order they arrive in from the map is not.

Next in this chapter: how to use a Vec as a queue.