Hash Maps and Hash Sets
Frequency Counting
Counting the occurrences of items in a sequence (a histogram or frequency table) is one of the most common applications of HashMap<K, usize> and the Entry API.
Idiomatic Single-Lookup Frequency Counter
The canonical one-line pattern for incrementing a key's count in a HashMap is:
*counts.entry(item).or_insert(0) += 1;
counts.entry(item)hashesitemand locates its bucket in 1 probe..or_insert(0)initializes the bucket to0if this is the first timeitemhas been seen, and returns&mut usize.*... += 1dereferences the&mut usizeand increments the counter in place.
use std::collections::HashMap;
fn main() {
let text = "to be or not to be that is the question";
let mut word_counts: HashMap<&str, usize> = HashMap::new();
for word in text.split_whitespace() {
*word_counts.entry(word).or_insert(0) += 1;
}
// Sort by (descending count, ascending word) for deterministic display:
let mut sorted: Vec<(&str, usize)> = word_counts.into_iter().collect();
sorted.sort_unstable_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0)));
for (word, count) in &sorted[..4] {
println!("{word:<8} -> {count}");
}
}
be -> 2
to -> 2
is -> 1
not -> 1
Functional Frequency Counting in an Iterator Pipeline (fold)
You can also express frequency counting inside a functional iterator chain using .fold():
use std::collections::HashMap;
fn char_frequencies(s: &str) -> HashMap<char, usize> {
s.chars()
.filter(|c| !c.is_whitespace())
.fold(HashMap::new(), |mut acc, ch| {
*acc.entry(ch).or_insert(0) += 1;
acc
})
}
fn main() {
let freqs = char_frequencies("abracadabra");
println!("'a' count: {}, 'b' count: {}, 'r' count: {}", freqs[&'a'], freqs[&'b'], freqs[&'r']);
}
'a' count: 5, 'b' count: 2, 'r' count: 2
Avoiding Clones When Counting Owned Keys (String)
When your map owns String keys (HashMap<String, usize>) and you are counting borrowed &str tokens from an input stream, calling map.entry(token.to_owned()) allocates a new String on every token, even when that word is already in the map!
To allocate a String only the first time a distinct word is encountered, check get_mut(token) first and fall back to insert(token.to_owned(), 1):
use std::collections::HashMap;
fn count_words_minimal_allocs(tokens: &[&str]) -> HashMap<String, usize> {
let mut counts: HashMap<String, usize> = HashMap::new();
for &token in tokens {
if let Some(count) = counts.get_mut(token) {
// Fast path on existing word: zero String allocation!
*count += 1;
} else {
// Cold path on new word: allocates String once
counts.insert(token.to_owned(), 1);
}
}
counts
}
fn main() {
let stream = ["rust", "fast", "rust", "safe", "rust"];
let counts = count_words_minimal_allocs(&stream);
println!("'rust' count: {}", counts["rust"]);
}
'rust' count: 3