HashMap & HashSet
Prerequisites: Error handling with Result
HashMap<K, V> stores values looked up by key; HashSet<T> stores unique
values. Both live in std::collections and must be imported with use. Keys
(and set members) must implement Eq and Hash, which strings, integers,
char, bool and tuples of them already do.
Iteration order is unspecified. Rust's default hasher is randomly seeded,
so looping over the same map can print entries in a different order on every
run. If you need a stable order, sort the entries first, or use BTreeMap /
BTreeSet, which keep keys sorted.
Syntax
use std::collections::{HashMap, HashSet};
fn main() {
let mut ages: HashMap<String, u32> = HashMap::new();
ages.insert(String::from("Ada"), 36); // insert or overwrite; returns the old value
let _ = ages.get("Ada"); // Option<&u32>
let _ = ages.contains_key("Bob"); // false
let _ = ages.remove("Ada"); // Option<u32>
*ages.entry(String::from("Bob")).or_insert(0) += 1; // insert a default if missing, then modify
let mut seen: HashSet<i32> = HashSet::new();
let _ = seen.insert(3); // true: newly added
let _ = seen.insert(3); // false: already present
}
The entry API
map.entry(key) looks the key up once and returns an Entry that is
either occupied or vacant. or_insert(default) returns a mutable reference to
the value, inserting default first if the key was missing. It replaces the
clumsy "check, then insert, then look up again" pattern. or_default() does the
same using the value type's Default (0 for numbers, empty for Vec/String).
Example
use std::collections::{HashMap, HashSet};
fn main() {
let text = "the cat sat on the mat the end";
// Count words.
let mut counts: HashMap<&str, u32> = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
// Sort for a predictable order: highest count first, then alphabetically.
let mut pairs: Vec<(&str, u32)> = counts.into_iter().collect();
pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
for (word, n) in &pairs {
println!("{word}: {n}");
}
// Group words by their first letter.
let mut by_letter: HashMap<char, Vec<&str>> = HashMap::new();
for word in ["apple", "avocado", "banana", "blueberry", "cherry"] {
let first = word.chars().next().unwrap();
by_letter.entry(first).or_default().push(word);
}
println!("b -> {:?}", by_letter[&'b']);
// Set operations.
let rust: HashSet<&str> = ["alice", "bob", "carol"].into_iter().collect();
let go: HashSet<&str> = ["bob", "dave", "carol"].into_iter().collect();
let mut both: Vec<_> = rust.intersection(&go).collect();
both.sort();
println!("both: {both:?}");
let mut only_rust: Vec<_> = rust.difference(&go).collect();
only_rust.sort();
println!("only rust: {only_rust:?}");
}
Expected output
the: 3
cat: 1
end: 1
mat: 1
on: 1
sat: 1
b -> ["banana", "blueberry"]
both: ["bob", "carol"]
only rust: ["alice"]
Without the sort calls these lines would come out in a different order from
run to run.
Common mistakes
- Printing a map with
{:?}or looping over it and expecting insertion order (or the same order each run). Sort, or useBTreeMap. - Indexing with
map["key"]when the key might be missing. It panics; usemap.get("key")and handle theNone. - Inserting an owned
Stringkey and then being surprised it was moved: aftermap.insert(name, 1)you can no longer usename(borrow of moved value: `name`). Clone it if you still need it. - Writing
if !map.contains_key(k) { map.insert(k, 0); } *map.get_mut(k).unwrap() += 1;. It hashes the key three times;*map.entry(k).or_insert(0) += 1does it once. - Using
f64as a key. Floats don't implementEqorHash, soerror[E0599]: the method `insert` exists for struct `HashMap<f64, i32>`, but its trait bounds were not satisfied.
Practice
Given let votes = ["red", "blue", "red", "green", "blue", "red"];, count the
votes with a HashMap and print each color and its count in alphabetical
order. Then print how many distinct colors there were using a HashSet.
Show solution
use std::collections::{HashMap, HashSet};
fn main() {
let votes = ["red", "blue", "red", "green", "blue", "red"];
let mut counts: HashMap<&str, u32> = HashMap::new();
for color in votes {
*counts.entry(color).or_insert(0) += 1;
}
let mut colors: Vec<_> = counts.keys().collect();
colors.sort();
for color in colors {
println!("{color}: {}", counts[color]);
}
let distinct: HashSet<&str> = votes.into_iter().collect();
println!("distinct colors: {}", distinct.len());
}
blue: 2
green: 1
red: 3
distinct colors: 3