Rust HashMap
A vector finds items by position: the first, the second, the tenth. Often you want to find things by a name instead. What is Asha's phone number? How many times does the word "the" appear? What is the price of a mango? A HashMap stores pairs of a key and a value, and finds the value for a key very quickly, like a dictionary where you look up a word to find its meaning.
HashMap is not loaded automatically, so you bring it in with use:
use std::collections::HashMap;
fn main() {
let mut prices = HashMap::new();
prices.insert("mango", 60);
prices.insert("banana", 40);
prices.insert("apple", 120);
println!("A mango costs {}", prices["mango"]);
println!("We sell {} fruits", prices.len());
}A mango costs 60 We sell 3 fruits
Every key appears only once. Inserting a key that is already there replaces its old value.
Looking up safely with get
prices["kiwi"] would panic if there is no kiwi. get returns an Option instead, so you can deal with a missing key calmly:
use std::collections::HashMap;
fn main() {
let mut capitals = HashMap::new();
capitals.insert("India", "New Delhi");
capitals.insert("Japan", "Tokyo");
for country in ["Japan", "Peru"] {
match capitals.get(country) {
Some(city) => println!("The capital of {} is {}", country, city),
None => println!("I do not know the capital of {} yet", country),
}
}
println!("Knows India? {}", capitals.contains_key("India"));
}The capital of Japan is Tokyo I do not know the capital of Peru yet Knows India? true
Counting with entry
The classic HashMap job is counting. entry(key).or_insert(0) finds the counter for a key, creating it at 0 if it is new, and gives you a mutable reference to it. The * reaches through the reference to add one:
use std::collections::HashMap;
fn main() {
let text = "red blue red green blue red";
let mut counts: HashMap<&str, u32> = HashMap::new();
for colour in text.split_whitespace() {
*counts.entry(colour).or_insert(0) += 1;
}
let mut colours: Vec<_> = counts.iter().collect();
colours.sort();
for (colour, n) in colours {
println!("{}: {}", colour, n);
}
}blue: 2 green: 1 red: 3
Why sort before printing?
A HashMap does not keep its items in any particular order. Looping over it directly gives an order that can change from one run to the next. That is fine for looking things up, but confusing when you print. So here we collect the pairs into a vector and sort it first. If you always want keys in order, BTreeMap from the same module works just like HashMap but keeps its keys sorted.
Removing and updating
remove(key) takes a pair out and gives back the value as an Option. To change a value in place, use get_mut or entry:
use std::collections::HashMap;
fn main() {
let mut stock = HashMap::new();
stock.insert("pens", 10);
stock.insert("books", 4);
if let Some(n) = stock.get_mut("pens") {
*n -= 3;
}
let gone = stock.remove("books");
println!("pens left: {}, removed books: {:?}", stock["pens"], gone);
}pens left: 7, removed books: Some(4)
Practice exercises
- Make a HashMap of three friends and their birthdays, and look one of them up.
- Count how many times each letter appears in the word "banana", and print the counts in alphabetical order.
- Build a tiny phone book: ask for a name and print the number, or a friendly message if it is missing.