HashMaps Exercises
Overview
These exercises cover HashMap<K, V> — Rust’s key-value store with the powerful Entry API.
hashmaps1 - Creating and Inserting
Concept: Basic HashMap operations
use std::collections::HashMap;
fn fruit_basket() -> HashMap<String, u32> {
let mut basket = HashMap::new();
basket.insert(String::from("banana"), 2);
basket.insert(String::from("apple"), 3);
basket.insert(String::from("mango"), 1);
basket
}Key Takeaways:
- Import with
use std::collections::HashMap - Create with
HashMap::new() - Insert with
.insert(key, value) - Keys must implement
Hash + Eq
hashmaps2 - Entry API: Insert If Not Exists
Concept: Using entry().or_insert() to conditionally insert
fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
let fruit_kinds = [Fruit::Apple, Fruit::Banana, Fruit::Mango, Fruit::Lychee, Fruit::Pineapple];
for fruit in fruit_kinds {
// Only insert if the key doesn't exist
basket.entry(fruit).or_insert(1);
}
}Why use Entry API instead of contains_key?
// ❌ Two lookups
if !basket.contains_key(&fruit) {
basket.insert(fruit, 1);
}
// ✅ One lookup
basket.entry(fruit).or_insert(1);Key Takeaways:
entry(key)returns anEntryenum (eitherOccupiedorVacant).or_insert(value)inserts only if vacant, returns&mut V- More efficient than checking + inserting separately
hashmaps3 - Entry API: Update Existing Values
Concept: Using entry().or_default() with struct values
The Problem
Parse soccer match results and build a scores table:
- Input:
"England,France,4,2"(England scored 4, France scored 2) - Track:
goals_scoredandgoals_concededfor each team
The Solution
#[derive(Default)]
struct TeamScores {
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
let mut scores = HashMap::new();
for line in results.lines() {
let mut split_iterator = line.split(',');
let team_1_name = split_iterator.next().unwrap();
let team_2_name = split_iterator.next().unwrap();
let team_1_score: u8 = split_iterator.next().unwrap().parse().unwrap();
let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap();
// Get or create team 1's entry, then update
let team_1 = scores.entry(team_1_name).or_default();
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
// Get or create team 2's entry, then update
let team_2 = scores.entry(team_2_name).or_default();
team_2.goals_scored += team_2_score;
team_2.goals_conceded += team_1_score;
}
scores
}Step-by-Step Execution
Let’s trace through the input data:
England,France,4,2
France,Italy,3,1
Poland,Spain,2,0
Germany,England,2,1
England,Spain,1,0
Match 1: “England,France,4,2”
Parsing:
team_1_name = "England"
team_2_name = "France"
team_1_score = 4
team_2_score = 2Step 1: scores.entry("England").or_default()
- HashMap is empty →
Entry::Vacant or_default()insertsTeamScores { goals_scored: 0, goals_conceded: 0 }- Returns
&mut TeamScores
Step 2: Update England
team_1.goals_scored += 4; // 0 + 4 = 4
team_1.goals_conceded += 2; // 0 + 2 = 2Step 3: scores.entry("France").or_default()
- “France” not in HashMap →
Entry::Vacant or_default()inserts default TeamScores- Returns
&mut TeamScores
Step 4: Update France
team_2.goals_scored += 2; // 0 + 2 = 2
team_2.goals_conceded += 4; // 0 + 4 = 4HashMap after Match 1:
{
"England": { goals_scored: 4, goals_conceded: 2 },
"France": { goals_scored: 2, goals_conceded: 4 }
}
Match 2: “France,Italy,3,1”
Parsing:
team_1_name = "France"
team_2_name = "Italy"
team_1_score = 3
team_2_score = 1Step 1: scores.entry("France").or_default()
- “France” already exists →
Entry::Occupied or_default()does nothing, returns existing&mut TeamScores
Step 2: Update France (adds to existing values!)
team_1.goals_scored += 3; // 2 + 3 = 5
team_1.goals_conceded += 1; // 4 + 1 = 5Step 3: scores.entry("Italy").or_default() → creates new entry
Step 4: Update Italy
team_2.goals_scored += 1; // 0 + 1 = 1
team_2.goals_conceded += 3; // 0 + 3 = 3HashMap after Match 2:
{
"England": { goals_scored: 4, goals_conceded: 2 },
"France": { goals_scored: 5, goals_conceded: 5 },
"Italy": { goals_scored: 1, goals_conceded: 3 }
}
Final State (after all 5 matches)
{
"England": { goals_scored: 6, goals_conceded: 4 },
"France": { goals_scored: 5, goals_conceded: 5 },
"Italy": { goals_scored: 1, goals_conceded: 3 },
"Poland": { goals_scored: 2, goals_conceded: 0 },
"Spain": { goals_scored: 0, goals_conceded: 3 },
"Germany": { goals_scored: 2, goals_conceded: 1 }
}
Key Concepts
| Concept | Explanation |
|---|---|
#[derive(Default)] | Allows or_default() to create TeamScores { 0, 0 } |
entry(key) | Returns Entry::Occupied or Entry::Vacant |
or_default() | If vacant, insert Default::default(); returns &mut V |
&mut V | Mutable reference lets you update the value in place |
Why This Works
- First lookup: If team doesn’t exist, create with zeros
- Same lookup: Returns mutable reference to update
- Accumulation:
+=adds to existing values, building totals across matches
Entry API Summary
| Method | When to Use |
|---|---|
.or_insert(value) | Insert specific value if vacant |
.or_insert_with(|| ...) | Insert computed value if vacant (lazy) |
.or_default() | Insert Default::default() if vacant |
.and_modify(|v| ...) | Modify if occupied |