Step 2 of 6
Iteration order and BTreeMap
To visit every pair, loop over a reference to the map. Each item is a (&key, &value) pair; .keys() and .values() give just one side.
for (city, temp) in &temps {
println!("{city}: {temp}");
}
Run that twice and you can get two different orders:
$ cargo run $ cargo run
oslo: 3 lima: 19
lima: 19 cairo: 31
cairo: 31 oslo: 3
A HashMap places each entry according to its hash, so the order is unrelated to the order you inserted things. On top of that, Rust's default hash function is seeded with random numbers each time the program starts. That's deliberate: if attackers could predict where keys land, they could send a web server thousands of keys that all collide in one slot and slow it to a crawl (a HashDoS attack). Random seeds make that impractical.
So: never rely on HashMap order. Not in output, and especially not in tests, where it causes "flaky" tests that pass or fail at random. You have two fixes.
Fix 1: copy the pairs into a Vec and sort it
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("zoe", 31);
ages.insert("ana", 27);
ages.insert("max", 45);
let mut pairs: Vec<(&str, u32)> = Vec::new();
for (name, age) in &ages {
pairs.push((*name, *age));
}
pairs.sort();
for (name, age) in &pairs {
println!("{name}: {age}");
}
}
ana: 27
max: 45
zoe: 31
Tuples sort by their first part, then by the second. To order by value instead, use pairs.sort_by(...) from the vectors module.
Fix 2: use a BTreeMap
BTreeMap has almost the same methods as HashMap (insert, get, get_mut, remove, contains_key, entry), but it keeps its keys sorted, so iterating and printing always go in key order:
use std::collections::BTreeMap;
fn main() {
let mut ages = BTreeMap::new();
ages.insert("zoe", 31);
ages.insert("ana", 27);
ages.insert("max", 45);
for (name, age) in &ages {
println!("{name}: {age}");
}
println!("{:?}", ages);
println!("{:?}", ages.first_key_value());
}
ana: 27
max: 45
zoe: 31
{"ana": 27, "max": 45, "zoe": 31}
Some(("ana", 27))
| HashMap | BTreeMap | |
|---|---|---|
| Lookup and insert | very fast (about constant time) | fast (grows slowly with size, O(log n)) |
| Iteration order | unspecified, changes between runs | sorted by key |
| Keys need | Hash + Eq |
Ord (a total order) |
| Use it for | lookups and counting | sorted output, smallest/largest key, ranges |
At work, HashMap is the default. Teams switch to BTreeMap (or sort before printing) wherever order is visible: reports, logs, JSON output, test expectations.
Common mistakes
- Trying to sort
keys().ages.keys()is an iterator, not a vector; there's nothing to sort yet. Push the keys (or pairs) into aVecfirst. - Comparing
format!("{:?}", map)in a test. With a HashMap, that test passes on some runs and fails on others.
Your task
Weather stations report lines like oslo 3. Read them all into a HashMap<String, i32> (a later report for the same city replaces the earlier one), then print every city with its latest temperature, in alphabetical order, as city: temp. Use a HashMap here and sort the pairs yourself; the next exercise uses BTreeMap.
Challenge 2
A warehouse logs lines like bolts 40. Add up the quantity for each product with a BTreeMap<String, u32>, then print every product and its total as product: total. Because a BTreeMap is sorted, a plain for loop prints them alphabetically.
Challenge 3
Write fn sorted_by_value(scores: &HashMap<String, u32>) -> Vec<(String, u32)> that returns all pairs with the highest score first and equal scores in alphabetical order by name. The tests compare the exact vector, so the order must be fully predictable.
Previous: Looking things up with HashMap Next: Counting with entry