BBuzzard

HashMap<K, V> maps keys to values with average O(1) lookups. Keys need Eq + Hash. insert overwrites and returns the old value; get returns Option<&V>; remove returns the old value. Iteration order is unspecified and can change between runs.

The entry API looks a key up once and lets you act on the slot: or_insert writes a default when it is missing, or_insert_with builds the default lazily, or_default uses Default, and and_modify updates an existing value. It replaces the get-then-insert double lookup.

BTreeMap keeps keys sorted. It needs Ord instead of Hash, iterates in key order, and can walk a key range. Reach for it when you need ordering or deterministic output.

Inserting, reading, removing

fn demo_hashmap_basics() {
    println!("-- HashMap basics --");

    let mut scores = HashMap::new();
    scores.insert("ann", 10);
    scores.insert("ben", 8);
    println!("get {:?} {:?}", scores.get("ann"), scores.get("cy"));
    println!("insert returns old {:?}", scores.insert("ann", 11));
    println!("contains ben? {} len {}", scores.contains_key("ben"), scores.len());

    if let Some(s) = scores.get_mut("ben") {
        *s *= 2;
    }

    println!("remove {:?} then {:?}", scores.remove("ben"), scores.remove("ben"));

    // scores["cy"] panics on a missing key; get is the safe form.
    println!("indexing an existing key {}", scores["ann"]);
}

Iteration (sorted here so the output is stable)

fn demo_iteration() {
    println!("-- iteration --");

    let m: HashMap<&str, i32> = [("b", 2), ("a", 1), ("c", 3)].into();
    let mut pairs: Vec<_> = m.iter().collect();
    pairs.sort();
    println!("{pairs:?}");

    let mut keys: Vec<_> = m.keys().copied().collect();
    keys.sort();

    let total: i32 = m.values().sum();
    println!("keys {keys:?} total {total}");

    let mut m = m;
    for v in m.values_mut() {
        *v *= 10;
    }

    m.retain(|_, v| *v > 10);
    let mut left: Vec<_> = m.into_iter().collect();
    left.sort();
    println!("after values_mut and retain {left:?}");
}

The entry API

fn demo_entry() {
    println!("-- entry --");

    let text = "the cat and the hat and the bat";
    let mut counts: HashMap<&str, usize> = HashMap::new();
    for w in text.split_whitespace() {
        *counts.entry(w).or_insert(0) += 1;
    }

    let mut top: Vec<_> = counts.into_iter().collect();
    top.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
    println!("word counts {top:?}");

    let mut words = HashMap::new();
    for w in ["a", "b", "a"] {
        words.entry(w).and_modify(|c| *c += 1).or_insert(1);
    }

    println!("and_modify a={} b={}", words["a"], words["b"]);

    // Group values under a key with or_default.
    let mut by_len: HashMap<usize, Vec<&str>> = HashMap::new();
    for w in ["fig", "kiwi", "pear", "plum", "date"] {
        by_len.entry(w.len()).or_default().push(w);
    }

    let mut groups: Vec<_> = by_len.into_iter().collect();
    groups.sort();
    println!("grouped {groups:?}");

    let mut cache: HashMap<u64, String> = HashMap::new();
    let v = cache.entry(7).or_insert_with(|| format!("computed {}", 7 * 6));
    println!("or_insert_with -> {v}");
}

Your own types as keys, and maps of structs

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
struct Pos {
    x: i32,
    y: i32,
}

fn demo_custom_keys() {
    println!("-- custom keys --");

    let mut board: HashMap<Pos, char> = HashMap::new();
    board.insert(Pos { x: 0, y: 0 }, 'X');
    board.insert(Pos { x: 1, y: 1 }, 'O');
    println!(
        "{:?} {:?}",
        board.get(&Pos { x: 0, y: 0 }),
        board.get(&Pos { x: 2, y: 2 })
    );

    let tuple_keys: HashMap<(i32, i32), &str> = [((0, 1), "north")].into();
    println!("{:?}", tuple_keys.get(&(0, 1)));

    // HashMap<f64, _> does not work: f64 is not Eq or Hash.
}

BTreeMap ordering and ranges

fn demo_btreemap() {
    println!("-- BTreeMap --");

    let mut m = BTreeMap::new();
    m.insert(2, "b");
    m.insert(1, "a");
    m.insert(5, "e");
    m.insert(3, "c");
    println!("in order {m:?}");
    println!("range 2.. {:?}", m.range(2..).collect::<Vec<_>>());
    println!("range ..=3 {:?}", m.range(..=3).map(|(k, _)| k).collect::<Vec<_>>());
    println!("first {:?} last {:?}", m.first_key_value(), m.last_key_value());
    println!("pop_first {:?} now {m:?}", m.pop_first());

    // Floor lookup: the greatest key <= 4.
    println!("floor(4) {:?}", m.range(..=4).next_back());
}

A time series with BTreeMap

fn demo_time_series() {
    println!("-- time series --");

    let mut temps: BTreeMap<u32, f64> = BTreeMap::new();
    for (hour, t) in [(9, 14.5), (12, 19.0), (15, 21.5), (18, 17.0), (21, 13.0)] {
        temps.insert(hour, t);
    }

    let afternoon: Vec<_> =
        temps.range(12..=18).map(|(h, t)| format!("{h}h:{t}")).collect();
    println!("12..=18 {afternoon:?}");

    let avg = temps.values().sum::<f64>() / temps.len() as f64;
    println!("avg {avg:.1}");

    let split = temps.split_off(&15);
    println!("before 15 {temps:?} from 15 {split:?}");
}

Merging maps and building them from iterators

fn demo_merge() {
    println!("-- merge --");

    let mut a: BTreeMap<&str, i32> = [("x", 1), ("y", 2)].into();
    let b: BTreeMap<&str, i32> = [("y", 10), ("z", 3)].into();
    for (k, v) in b {
        *a.entry(k).or_insert(0) += v;
    }

    println!("{a:?}");
    let inverted: BTreeMap<i32, &str> = a.iter().map(|(k, v)| (*v, *k)).collect();
    println!("inverted {inverted:?}");
}
$ cargo run --bin 37_maps
-- HashMap basics --
get Some(10) None
insert returns old Some(10)
contains ben? true len 2
remove Some(16) then None
indexing an existing key 11
-- iteration --
[("a", 1), ("b", 2), ("c", 3)]
keys ["a", "b", "c"] total 6
after values_mut and retain [("b", 20), ("c", 30)]
-- entry --
word counts [("the", 3), ("and", 2), ("bat", 1), ("cat", 1), ("hat", 1)]
and_modify a=2 b=1
grouped [(3, ["fig"]), (4, ["kiwi", "pear", "plum", "date"])]
or_insert_with -> computed 42
-- custom keys --
Some('X') None
Some("north")
-- BTreeMap --
in order {1: "a", 2: "b", 3: "c", 5: "e"}
range 2.. [(2, "b"), (3, "c"), (5, "e")]
range ..=3 [1, 2, 3]
first Some((1, "a")) last Some((5, "e"))
pop_first Some((1, "a")) now {2: "b", 3: "c", 5: "e"}
floor(4) Some((3, "c"))
-- time series --
12..=18 ["12h:19", "15h:21.5", "18h:17"]
avg 17.0
before 15 {9: 14.5, 12: 19.0} from 15 {15: 21.5, 18: 17.0, 21: 13.0}
-- merge --
{"x": 1, "y": 12, "z": 3}
inverted {1: "x", 3: "z", 12: "y"}

Full program

37_maps/main.rs · 189 lines
View on GitHub
// 37. HashMap, the entry API, and BTreeMap
//
// HashMap<K, V> maps keys to values with average O(1) lookups. Keys need Eq + Hash.
// insert overwrites and returns the old value; get returns Option<&V>; remove returns
// the old value. Iteration order is unspecified and can change between runs.
//
// The entry API looks a key up once and lets you act on the slot: or_insert writes a
// default when it is missing, or_insert_with builds the default lazily, or_default uses
// Default, and and_modify updates an existing value. It replaces the get-then-insert
// double lookup.
//
// BTreeMap keeps keys sorted. It needs Ord instead of Hash, iterates in key order, and
// can walk a key range. Reach for it when you need ordering or deterministic output.
//
// Run: cargo run --bin 37_maps

use std::collections::{BTreeMap, HashMap};

// Example 1: inserting, reading, removing.
fn demo_hashmap_basics() {
    println!("-- HashMap basics --");

    let mut scores = HashMap::new();
    scores.insert("ann", 10);
    scores.insert("ben", 8);
    println!("get {:?} {:?}", scores.get("ann"), scores.get("cy"));
    println!("insert returns old {:?}", scores.insert("ann", 11));
    println!("contains ben? {} len {}", scores.contains_key("ben"), scores.len());

    if let Some(s) = scores.get_mut("ben") {
        *s *= 2;
    }

    println!("remove {:?} then {:?}", scores.remove("ben"), scores.remove("ben"));

    // scores["cy"] panics on a missing key; get is the safe form.
    println!("indexing an existing key {}", scores["ann"]);
}

// Example 2: iteration (sorted here so the output is stable).
fn demo_iteration() {
    println!("-- iteration --");

    let m: HashMap<&str, i32> = [("b", 2), ("a", 1), ("c", 3)].into();
    let mut pairs: Vec<_> = m.iter().collect();
    pairs.sort();
    println!("{pairs:?}");

    let mut keys: Vec<_> = m.keys().copied().collect();
    keys.sort();

    let total: i32 = m.values().sum();
    println!("keys {keys:?} total {total}");

    let mut m = m;
    for v in m.values_mut() {
        *v *= 10;
    }

    m.retain(|_, v| *v > 10);
    let mut left: Vec<_> = m.into_iter().collect();
    left.sort();
    println!("after values_mut and retain {left:?}");
}

// Example 3: the entry API.
fn demo_entry() {
    println!("-- entry --");

    let text = "the cat and the hat and the bat";
    let mut counts: HashMap<&str, usize> = HashMap::new();
    for w in text.split_whitespace() {
        *counts.entry(w).or_insert(0) += 1;
    }

    let mut top: Vec<_> = counts.into_iter().collect();
    top.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
    println!("word counts {top:?}");

    let mut words = HashMap::new();
    for w in ["a", "b", "a"] {
        words.entry(w).and_modify(|c| *c += 1).or_insert(1);
    }

    println!("and_modify a={} b={}", words["a"], words["b"]);

    // Group values under a key with or_default.
    let mut by_len: HashMap<usize, Vec<&str>> = HashMap::new();
    for w in ["fig", "kiwi", "pear", "plum", "date"] {
        by_len.entry(w.len()).or_default().push(w);
    }

    let mut groups: Vec<_> = by_len.into_iter().collect();
    groups.sort();
    println!("grouped {groups:?}");

    let mut cache: HashMap<u64, String> = HashMap::new();
    let v = cache.entry(7).or_insert_with(|| format!("computed {}", 7 * 6));
    println!("or_insert_with -> {v}");
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
struct Pos {
    x: i32,
    y: i32,
}

// Example 4: your own types as keys, and maps of structs.
fn demo_custom_keys() {
    println!("-- custom keys --");

    let mut board: HashMap<Pos, char> = HashMap::new();
    board.insert(Pos { x: 0, y: 0 }, 'X');
    board.insert(Pos { x: 1, y: 1 }, 'O');
    println!(
        "{:?} {:?}",
        board.get(&Pos { x: 0, y: 0 }),
        board.get(&Pos { x: 2, y: 2 })
    );

    let tuple_keys: HashMap<(i32, i32), &str> = [((0, 1), "north")].into();
    println!("{:?}", tuple_keys.get(&(0, 1)));

    // HashMap<f64, _> does not work: f64 is not Eq or Hash.
}

// Example 5: BTreeMap ordering and ranges.
fn demo_btreemap() {
    println!("-- BTreeMap --");

    let mut m = BTreeMap::new();
    m.insert(2, "b");
    m.insert(1, "a");
    m.insert(5, "e");
    m.insert(3, "c");
    println!("in order {m:?}");
    println!("range 2.. {:?}", m.range(2..).collect::<Vec<_>>());
    println!("range ..=3 {:?}", m.range(..=3).map(|(k, _)| k).collect::<Vec<_>>());
    println!("first {:?} last {:?}", m.first_key_value(), m.last_key_value());
    println!("pop_first {:?} now {m:?}", m.pop_first());

    // Floor lookup: the greatest key <= 4.
    println!("floor(4) {:?}", m.range(..=4).next_back());
}

// Example 6: a time series with BTreeMap.
fn demo_time_series() {
    println!("-- time series --");

    let mut temps: BTreeMap<u32, f64> = BTreeMap::new();
    for (hour, t) in [(9, 14.5), (12, 19.0), (15, 21.5), (18, 17.0), (21, 13.0)] {
        temps.insert(hour, t);
    }

    let afternoon: Vec<_> =
        temps.range(12..=18).map(|(h, t)| format!("{h}h:{t}")).collect();
    println!("12..=18 {afternoon:?}");

    let avg = temps.values().sum::<f64>() / temps.len() as f64;
    println!("avg {avg:.1}");

    let split = temps.split_off(&15);
    println!("before 15 {temps:?} from 15 {split:?}");
}

// Example 7: merging maps and building them from iterators.
fn demo_merge() {
    println!("-- merge --");

    let mut a: BTreeMap<&str, i32> = [("x", 1), ("y", 2)].into();
    let b: BTreeMap<&str, i32> = [("y", 10), ("z", 3)].into();
    for (k, v) in b {
        *a.entry(k).or_insert(0) += v;
    }

    println!("{a:?}");
    let inverted: BTreeMap<i32, &str> = a.iter().map(|(k, v)| (*v, *k)).collect();
    println!("inverted {inverted:?}");
}

fn main() {
    demo_hashmap_basics();
    demo_iteration();
    demo_entry();
    demo_custom_keys();
    demo_btreemap();
    demo_time_series();
    demo_merge();
}