Rust: Rust Collections
Last updated: 2026-08-26
HashMap and HashSet are the most commonly used hash-based collections in the Rust standard library—HashMap stores key-value mappings, while HashSet stores a set of unique elements.
If Vec is about "storing things in order," then HashMap is about "finding things by name"—you don't need to remember the index; you just need to know the key. As for HashSet, it provides the definitive answer to the question, "Is this item present?"
1. What You'll Learn
- Use the
HashMap::new(),insert, andgetkey-value pairs - Use the
entryAPI to gracefully handle "insert or update" logic - Understand the ownership rules for HashMap (which types can be used as keys and values)
- Use a HashSet to remove duplicates and perform intersection, union, and set difference operations
- Various Ways to Iterate Through a HashMap
- Choose the appropriate collection type (Vec vs. HashMap vs. HashSet) based on the scenario
2. The Story of a Voting System
(1) Pain: Using two Vecs to store the number of tickets
Anna is developing a class voting system and needs to tally the number of votes each candidate receives.
At first, she used two Vec objects:
let mut candidates = Vec::new();
let mut votes = Vec::new();
candidates.push("Alice");
votes.push(0);
candidates.push("Bob");
votes.push(0);
// Vote for Alice
let pos = candidates.iter().position(|&c| c == "Alice").unwrap();
votes[pos] += 1;
// Search Bob the number of votes
let pos = candidates.iter().position(|&c| c == "Bob").unwrap();
println!("Bob the number of votes: {}", votes[pos]);
Managing data with two parallel Vecs presents an obvious problem: keeping the two Vecs in sync is fragile—it’s easy to forget to update one Vec when adding or removing candidates. Furthermore, searching for a candidate requires an O(n) linear search, which slows down as the number of candidates increases. In terms of code readability, the relationship between
candidates[i]andvotes[i]is implicit, making it difficult for new developers to understand.
(2) The Rust HashMap Approach
use std::collections::HashMap;
fn main() {
let mut votes = HashMap::new();
// Vote for a candidate
*votes.entry("Alice").or_insert(0) += 1;
*votes.entry("Bob").or_insert(0) += 1;
*votes.entry("Alice").or_insert(0) += 1; // Re-submit Alice
*votes.entry("Charlie").or_insert(0) += 1;
// Check the number of votes
for (candidate, count) in &votes {
println!("{}: {} votes", candidate, count);
}
// Search for a Specific Candidate
println!("Alice the number of votes: {}", votes.get("Alice").unwrap());
}
Output:
Alice: 2 votes
Bob: 1 votes
Charlie: 1 votes
Alice the number of votes: 2
HashMap is a key-value mapping table:
key -> value. TheentryAPI elegantly handles scenarios where "if the key does not exist, insert the default value; if it does exist, update it." Thegetmethod looks up keys in O(1) time complexity. There is no longer a need to maintain two synchronized Vecs.
3. Overview of HashMap and HashSet
(1) Concept Map
graph TB
A[Hash-Based Sets] --> B[HashMap<K, V>]
A --> C[HashSet<T>]
B --> B1[insert: Insert a key-value pair]
B --> B2[get: Retrieving a Value by Key]
B --> B3[entry: Elegant Insertion/Update]
B --> B4[remove: Delete a key-value pair]
B --> B5[contains_key: Check if a key exists]
B --> B6[iter: Iterate through all key-value pairs]
C --> C1[insert: Add an element]
C --> C2[contains: Check if an element is included]
C --> C3[union: Union Operation]
C --> C4[intersection: Set Intersection]
C --> C5[difference: Difference Set Operations]
C --> C6[symmetric_difference: Symmetric difference set]
(2) Comparison of Set Types
| Feature | Vec<T> | HashMap<K, V> | HashSet<T> |
|---|---|---|---|
| Storage | Ordered sequences | Unordered key-value pairs | Unordered unique elements |
| Lookup | O(n) linear search | O(1) hash lookup | O(1) hash lookup |
| Insertion | O(1) Append to end | O(1) on average | O(1) on average |
| Duplicate Removal | Manual Check | Automatic Key Duplicate Removal | Automatic Element Duplicate Removal |
| Memory | Low (contiguous storage) | Medium (hash table overhead) | Medium (hash table overhead) |
| Use Cases | Sequential access, small data sets | Key-value mapping, fast lookups | Set operations, deduplication |
(3) Quick Reference for Common HashMap Methods
| Method | Return Type | Description |
|---|---|---|
insert(k, v) |
Option<V> |
Insert a key-value pair and return the old value |
get(&k) |
Option<&V> |
Search by Key |
get_mut(&k) |
Option<&mut V> |
Find Variable References by Key |
remove(&k) |
Option<V> |
Delete a key-value pair and return the deleted value |
contains_key(&k) |
bool |
Does the key exist? |
entry(k) |
Entry<K,V> |
Retrieve an entry to insert/update |
keys() |
Keys<K,V> |
Iterate through all keys |
values() |
Values<K,V> |
Iterate through all values |
len() |
usize |
Number of key-value pairs |
is_empty() |
bool |
Is empty? |
clear() |
() |
Clear all key-value pairs |
drain() |
Drain<K,V> |
Remove and return all key-value pairs |
(4) HashSet Set Operations
| Operation | Method | Mathematical Symbol | Description |
|---|---|---|---|
| Union | union(&other) |
A ∪ B | All elements of the two sets |
| Intersection | intersection(&other) |
A ∩ B | Elements common to both sets |
| Set Difference | difference(&other) |
A - B | Elements in A but not in B |
| Symmetry Difference | symmetric_difference(&other) |
A △ B | Elements in only one set |
| Subset | is_subset(&other) |
A ⊆ B | All elements of A are in B |
| Superset | is_superset(&other) |
A ⊇ B | A contains all elements of B |
4. Examples of HashMap and HashSet
▶ Example 1: Basic HashMap API—Voting Tally System (Difficulty ⭐⭐)
Output:
After initial insert:
Alice's votes (via get): <count>
Alice not found
--- Voting round ---
Voted for <name> (total: <count>)
--- Checking candidates ---
<name> is a candidate with <vote_counts.get(*name).unwrap()> votes
<name> is NOT a candidate
Total candidates: <vote_counts.len()>
Is empty: <vote_counts.is_empty()>
--- Final Results ---
// ============================================
// Voting Tally System:Display HashMap Basic API
// ============================================
use std::collections::HashMap;
fn main() {
// Create a new empty HashMap
let mut vote_counts: HashMap<String, u32> = HashMap::new();
// --- insert ---
// Insert key-value pairs (overwrites existing value)
vote_counts.insert(String::from("Alice"), 0);
vote_counts.insert(String::from("Bob"), 0);
vote_counts.insert(String::from("Charlie"), 0);
println!("After initial insert:");
print_votes(&vote_counts);
// --- get ---
// Get a value by key (returns Option<&V>)
let alice_votes = vote_counts.get("Alice");
match alice_votes {
Some(count) => println!("Alice's votes (via get): {}", count),
None => println!("Alice not found"),
}
// --- entry API ---
// The idiomatic way: insert or update
// entry() returns an Entry enum, or_insert() inserts default if missing
println!("\n--- Voting round ---");
let candidates = ["Alice", "Bob", "Alice", "Charlie", "Alice", "Bob", "David"];
for name in &candidates {
let count = vote_counts.entry(String::from(*name)).or_insert(0);
*count += 1;
println!("Voted for {} (total: {})", name, count);
}
// --- contains_key ---
println!("\n--- Checking candidates ---");
for name in &["Alice", "David", "Eve"] {
if vote_counts.contains_key(*name) {
println!("{} is a candidate with {} votes", name, vote_counts.get(*name).unwrap());
} else {
println!("{} is NOT a candidate", name);
}
}
// --- len and is_empty ---
println!("\nTotal candidates: {}", vote_counts.len());
println!("Is empty: {}", vote_counts.is_empty());
// --- Final results ---
println!("\n--- Final Results ---");
print_votes(&vote_counts);
}
fn print_votes(votes: &HashMap<String, u32>) {
// Note: HashMap iteration order is NOT guaranteed
for (name, count) in votes {
println!(" {}: {} votes", name, count);
}
}
Output:
After initial insert:
Alice: 0 votes
Charlie: 0 votes
Bob: 0 votes
Alice's votes (via get): 0
--- Voting round ---
Voted for Alice (total: 1)
Voted for Bob (total: 1)
Voted for Alice (total: 2)
Voted for Charlie (total: 1)
Voted for Alice (total: 3)
Voted for Bob (total: 2)
Voted for David (total: 1)
--- Checking candidates ---
Alice is a candidate with 3 votes
David is a candidate with 1 votes
Eve is NOT a candidate
Total candidates: 4
Is empty: false
--- Final Results ---
Alice: 3 votes
Charlie: 1 votes
David: 1 votes
Bob: 2 votes
entry(key).or_insert(default)is the most common idiomatic way to use HashMap—if the key does not exist, it inserts a default value and returns a reference to it; if the key exists, it simply returns a reference to it. Combined with*count += 1, it allows you to perform an "insert or update" operation in a single line.getreturnsOption<&V>and never causes a panic.
▶ Example 2: HashMap Ownership Rules and Value Types (Difficulty ⭐⭐⭐)
Output:
<product>
Lookup table: <lookup>
Product: <p.name>, price: <p.price>
Not found
Updated stock: <product.stock>
Word count: <word_count>
// ============================================
// HashMap Ownership Rules:What types are eligible? key/value
// ============================================
use std::collections::HashMap;
#[derive(Debug, Hash, Eq, PartialEq)]
struct ProductId(u32);
#[derive(Debug, Clone)]
struct Product {
name: String,
price: f64,
stock: u32,
}
fn main() {
// --- Rule 1: Owned types as keys ---
// String (owned) can be a key; &str (borrowed) needs lifetime management
let mut inventory: HashMap<String, Product> = HashMap::new();
let product = Product {
name: String::from("Rust Book"),
price: 29.99,
stock: 100,
};
// insert takes ownership of key and value
inventory.insert(String::from("RB-001"), product);
// println!("{:?}", product); // ❌ product was moved into the HashMap
// --- Rule 2: Inserting a reference ---
// Borrowed keys need lifetime annotations on the HashMap
// This works because the string literals have 'static lifetime
let mut lookup: HashMap<&str, u32> = HashMap::new();
lookup.insert("apple", 5);
lookup.insert("banana", 3);
println!("Lookup table: {:?}", lookup);
// --- Rule 3: Getting values returns references ---
// get() returns Option<&V>, not V
let stock_ref = inventory.get("RB-001");
match stock_ref {
Some(p) => println!("Product: {}, price: {}", p.name, p.price),
None => println!("Not found"),
}
// inventory is still valid (we only borrowed)
// --- Rule 4: Custom types as keys ---
// Keys must implement Eq + Hash
let mut product_map: HashMap<ProductId, String> = HashMap::new();
product_map.insert(ProductId(1), String::from("Laptop"));
product_map.insert(ProductId(2), String::from("Mouse"));
// --- Rule 5: Updating values with get_mut ---
// get_mut() returns Option<&mut V> for mutable access
if let Some(product) = inventory.get_mut("RB-001") {
product.stock -= 1; // Sell one unit
println!("Updated stock: {}", product.stock);
}
// --- Rule 6: The entry API for sophisticated updates ---
let mut word_count: HashMap<String, u32> = HashMap::new();
let text = "hello world hello rust hello again";
for word in text.split_whitespace() {
// or_insert returns &mut V, which we dereference and increment
let counter = word_count.entry(String::from(word)).or_insert(0);
*counter += 1;
}
println!("\nWord count: {:?}", word_count);
// Advanced: modify entry with and_modify + or_insert
let mut scores: HashMap<String, u32> = HashMap::new();
for team in &["red", "blue", "red", "green", "blue", "red"] {
scores.entry(String::from(*team))
.and_modify(|count| *count += 1) // if exists, increment
.or_insert(1); // if not, insert 1
}
println!("Scores: {:?}", scores);
}
Output:
Lookup table: {"banana": 3, "apple": 5}
Product: Rust Book, price: 29.99
Updated stock: 99
Word count: {"again": 1, "hello": 2, "rust": 1, "world": 1}
Scores: {"green": 1, "blue": 2, "red": 3}
HashMap ownership rules: Upon insertion, ownership of the key and value is transferred to the HashMap.
getreturns a reference (&V) and does not transfer ownership. The key type must implement theEq + Hashtrait (primitive types andStringimplement this by default). The chained callsentry+and_modify+or_insertare an elegant pattern unique to Rust.
▶ Example 3: Removing Duplicates from a HashSet and Set Operations (Difficulty ⭐⭐)
Output:
--- HashSet Deduplication ---
Original: <&numbers[..]>
Unique: <unique_numbers>
Count: <unique_numbers.len()> (original: 11)
--- Contains Check ---
<n> is in the set
<n> is NOT in the set
--- Set Operations ---
Set A: <set_a>
Set B: <set_b>
Union (A ∪ B): <union>
Intersection (A ∩ B): <intersection>
Difference (A - B): <diff_ab>
Difference (B - A): <diff_ba>
Symmetric Difference: <sym_diff>
--- Practical: Common Friends ---
Alice's friends: <alice_friends>
Bob's friends: <bob_friends>
Mutual friends: <mutual>
Only Alice knows: <alice_only>
All unique friends: <all_friends>
// ============================================
// HashSet:Remove duplicates、Intersection、Union、Difference Set Operations
// ============================================
use std::collections::HashSet;
fn main() {
// --- Basic HashSet: deduplication ---
println!("--- HashSet Deduplication ---");
let mut unique_numbers: HashSet<i32> = HashSet::new();
let numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
for &n in &numbers {
unique_numbers.insert(n);
}
println!("Original: {:?}", &numbers[..]);
println!("Unique: {:?}", unique_numbers);
println!("Count: {} (original: {})", unique_numbers.len(), numbers.len());
// --- contains ---
println!("\n--- Contains Check ---");
for &n in &[1, 7, 9] {
if unique_numbers.contains(&n) {
println!("{} is in the set", n);
} else {
println!("{} is NOT in the set", n);
}
}
// --- Set operations ---
println!("\n--- Set Operations ---");
let set_a: HashSet<i32> = [1, 2, 3, 4, 5].iter().cloned().collect();
let set_b: HashSet<i32> = [4, 5, 6, 7, 8].iter().cloned().collect();
println!("Set A: {:?}", set_a);
println!("Set B: {:?}", set_b);
// Union: elements in A OR B
let union: HashSet<&i32> = set_a.union(&set_b).collect();
println!("Union (A ∪ B): {:?}", union);
// Intersection: elements in A AND B
let intersection: HashSet<&i32> = set_a.intersection(&set_b).collect();
println!("Intersection (A ∩ B): {:?}", intersection);
// Difference: elements in A but NOT in B
let diff_ab: HashSet<&i32> = set_a.difference(&set_b).collect();
println!("Difference (A - B): {:?}", diff_ab);
let diff_ba: HashSet<&i32> = set_b.difference(&set_a).collect();
println!("Difference (B - A): {:?}", diff_ba);
// Symmetric difference: elements in A or B but NOT both
let sym_diff: HashSet<&i32> = set_a.symmetric_difference(&set_b).collect();
println!("Symmetric Difference: {:?}", sym_diff);
// --- Practical example: finding common friends ---
println!("\n--- Practical: Common Friends ---");
let alice_friends: HashSet<&str> =
["Bob", "Charlie", "David", "Eve"].iter().cloned().collect();
let bob_friends: HashSet<&str> =
["Alice", "Charlie", "Eve", "Frank"].iter().cloned().collect();
println!("Alice's friends: {:?}", alice_friends);
println!("Bob's friends: {:?}", bob_friends);
// Mutual friends (intersection)
let mutual: HashSet<&&str> = alice_friends.intersection(&bob_friends).collect();
println!("Mutual friends: {:?}", mutual);
// Friends only Alice knows (difference)
let alice_only: HashSet<&&str> = alice_friends.difference(&bob_friends).collect();
println!("Only Alice knows: {:?}", alice_only);
// All unique friends (union)
let all_friends: HashSet<&&str> = alice_friends.union(&bob_friends).collect();
println!("All unique friends: {:?}", all_friends);
}
Output:
--- HashSet Deduplication ---
Original: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
Unique: {3, 2, 1, 6, 4, 9, 5}
Count: 7 (original: 11)
--- Contains Check ---
1 is in the set
7 is NOT in the set
9 is in the set
--- Set Operations ---
Set A: {2, 3, 4, 5, 1}
Set B: {4, 7, 6, 5, 8}
Union (A ∪ B): {7, 2, 3, 6, 4, 5, 1, 8}
Intersection (A ∩ B): {4, 5}
Difference (A - B): {2, 3, 1}
Difference (B - A): {6, 7, 8}
Symmetric Difference: {1, 2, 3, 6, 7, 8}
--- Practical: Common Friends ---
Alice's friends: {"Charlie", "David", "Eve", "Bob"}
Bob's friends: {"Charlie", "Frank", "Alice", "Eve"}
Mutual friends: {"Charlie", "Eve"}
Only Alice knows: {"David", "Bob"}
All unique friends: {"Charlie", "David", "Frank", "Alice", "Eve", "Bob"}
The four main set operations of HashSet:
union(union—all elements),intersection(intersection—common elements),difference(difference—elements in A but not in B),symmetric_difference(symmetric difference—elements not in both sets). These methods return iterators; you need to use.collect()to collect the results into a new HashSet.
▶ Example 4: Iterating Through a HashMap and Selection Strategies for Collections (Difficulty ⭐⭐)
Output:
--- All Products (iter) ---
<revenue>: $<product>
--- Product Names (keys) ---
- <product>
--- Revenue Values (values) ---
Total revenue: $<total>
Average: $<total / sales.len() as f64>
--- Apply 10% Discount (values_mut) ---
<revenue>: $<product>
--- Drain (consumes HashMap) ---
Removed: <revenue> ($<product>)
backup is empty: <backup.is_empty()>
--- Collection Selection Guide ---
Vec (ordered todo list):
<i + 1>. <item>
HashMap (phone book):
Alice's number: <phone_book.get("Alice").unwrap()>
HashSet (admin check):
Is 'admin' admin? <admin_users.contains(user)>
// ============================================
// Iterate HashMap + Comparison of Set Selection Strategies
// ============================================
use std::collections::HashMap;
fn main() {
// --- Build a sample dataset ---
let mut sales: HashMap<String, f64> = HashMap::new();
sales.insert(String::from("Laptop"), 1200.0);
sales.insert(String::from("Mouse"), 25.0);
sales.insert(String::from("Keyboard"), 80.0);
sales.insert(String::from("Monitor"), 350.0);
sales.insert(String::from("Headphones"), 150.0);
// --- Method 1: Iterate over key-value pairs ---
println!("--- All Products (iter) ---");
for (product, revenue) in &sales {
println!(" {}: ${:.2}", product, revenue);
}
// --- Method 2: Iterate over keys only ---
println!("\n--- Product Names (keys) ---");
for product in sales.keys() {
println!(" - {}", product);
}
// --- Method 3: Iterate over values only ---
println!("\n--- Revenue Values (values) ---");
let total: f64 = sales.values().sum();
println!(" Total revenue: ${:.2}", total);
println!(" Average: ${:.2}", total / sales.len() as f64);
// --- Method 4: Mutable iteration over values ---
println!("\n--- Apply 10% Discount (values_mut) ---");
for revenue in sales.values_mut() {
*revenue *= 0.9; // Apply 10% discount
}
for (product, revenue) in &sales {
println!(" {}: ${:.2}", product, revenue);
}
// --- Method 5: drain to consume the HashMap ---
let mut backup = sales.clone();
println!("\n--- Drain (consumes HashMap) ---");
while let Some((product, revenue)) = backup.drain().next() {
println!(" Removed: {} (${:.2})", product, revenue);
}
println!(" backup is empty: {}", backup.is_empty());
// --- When to use what: Collection selection guide ---
println!("\n--- Collection Selection Guide ---");
// Scenario 1: Vec (ordered, indexed access)
let mut todo_list: Vec<&str> = Vec::new();
todo_list.push("Buy milk");
todo_list.push("Write report");
todo_list.push("Call mom");
println!("Vec (ordered todo list):");
for (i, item) in todo_list.iter().enumerate() {
println!(" {}. {}", i + 1, item);
}
// Scenario 2: HashMap (key-value lookup)
let mut phone_book: HashMap<&str, &str> = HashMap::new();
phone_book.insert("Alice", "123-4567");
phone_book.insert("Bob", "987-6543");
println!("HashMap (phone book):");
println!(" Alice's number: {}", phone_book.get("Alice").unwrap());
// Scenario 3: HashSet (membership check)
let mut admin_users: HashSet<&str> = HashSet::new();
admin_users.insert("admin");
admin_users.insert("root");
let user = "admin";
println!("HashSet (admin check):");
println!(" Is '{}' admin? {}", user, admin_users.contains(user));
}
// Import HashSet for the last scenario
use std::collections::HashSet;
Output:
--- All Products (iter) ---
Laptop: $1200.00
Mouse: $25.00
Keyboard: $80.00
Monitor: $350.00
Headphones: $150.00
--- Product Names (keys) ---
- Laptop
- Mouse
- Keyboard
- Monitor
- Headphones
--- Revenue Values (values) ---
Total revenue: $1805.00
Average: $361.00
--- Apply 10% Discount (values_mut) ---
Laptop: $1080.00
Mouse: $22.50
Keyboard: $72.00
Monitor: $315.00
Headphones: $135.00
--- Drain (consumes HashMap) ---
Removed: Laptop ($1080.00)
Removed: Mouse ($22.50)
Removed: Keyboard ($72.00)
Removed: Monitor ($315.00)
Removed: Headphones ($135.00)
backup is empty: true
--- Collection Selection Guide ---
Vec (ordered todo list):
1. Buy milk
2. Write report
3. Call mom
HashMap (phone book):
Alice's number: 123-4567
HashSet (admin check):
Is 'admin' admin? true
Ways to iterate over a HashMap:
iter()Iterates over all key-value pairs;keys()Iterates only over keys;values()Iterates only over values;values_mut()Iterates over values in a variable order;drain()Consumes and removes all elements. When choosing a collection type: If you need an ordered, duplicable collection with index-based access → Vec; if you need key-value mapping and fast lookups → HashMap; if you need deduplication, collection operations, and membership checks → HashSet.
▶ Example 5: Comprehensive Exercise—Word Frequency Analysis and Text Analysis (Difficulty ⭐⭐⭐)
Output:
=== Text 1 Word Frequency ===
'<word>': <count> times
=== Text 2 Word Frequency ===
'<word>': <count> times
Common Vocabulary: <common>
Text1Exclusive: <only1>
Text2Exclusive: <only2>
// ============================================
// Comprehensive Example:HashMap + HashSet Text Analysis
// ============================================
use std::collections::{HashMap, HashSet};
fn word_frequency(text: &str) -> HashMap<String, u32> {
let mut freq: HashMap<String, u32> = HashMap::new();
for word in text.split_whitespace() {
let clean: String = word.chars()
.filter(|c| c.is_alphabetic())
.map(|c| c.to_lowercase().next().unwrap())
.collect();
if !clean.is_empty() {
*freq.entry(clean).or_insert(0) += 1;
}
}
freq
}
fn unique_words(text: &str) -> HashSet<String> {
text.split_whitespace()
.map(|w| w.to_lowercase())
.collect()
}
fn top_n(freq: &HashMap<String, u32>, n: usize) -> Vec<(&str, u32)> {
let mut entries: Vec<_> = freq.iter().map(|(k, &v)| (k.as_str(), v)).collect();
entries.sort_by(|a, b| b.1.cmp(&a.1));
entries.into_iter().take(n).collect()
}
fn main() {
let text1 = "the cat sat on the mat and the cat slept on the mat";
let text2 = "the dog ran on the grass and the dog slept on the rug";
println!("=== Text 1 Word Frequency ===");
let freq1 = word_frequency(text1);
for (word, count) in top_n(&freq1, 5) {
println!(" '{}': {} times", word, count);
}
println!("\n=== Text 2 Word Frequency ===");
let freq2 = word_frequency(text2);
for (word, count) in top_n(&freq2, 5) {
println!(" '{}': {} times", word, count);
}
let words1 = unique_words(text1);
let words2 = unique_words(text2);
let common: HashSet<_> = words1.intersection(&words2).collect();
println!("\nCommon Vocabulary: {:?}", common);
let only1: HashSet<_> = words1.difference(&words2).collect();
println!("Text1Exclusive: {:?}", only1);
let only2: HashSet<_> = words2.difference(&words1).collect();
println!("Text2Exclusive: {:?}", only2);
let all: HashSet<_> = words1.union(&words2).collect();
println!("Total number of words: {}", all.len());
}
Output:
=== Text 1 Word Frequency ===
'the': 3 times
'cat': 2 times
'on': 2 times
'mat': 2 times
'sat': 1 times
=== Text 2 Word Frequency ===
'the': 3 times
'dog': 2 times
'on': 2 times
'grass': 1 times
'ran': 1 times
Common Vocabulary: {"the", "and", "on", "slept"}
Text1Exclusive: {"mat", "cat", "sat"}
Text2Exclusive: {"ran", "rug", "grass", "dog"}
Total number of words: 11
word_frequencyCount elegantly using theentry().or_insert()method;unique_wordsautomatically remove duplicates using a HashSet;intersection/difference/unionimplement set operations. HashMap + HashSet is the golden combination for text analysis.
❓ FAQ
Eq + Hash trait. Primitive types (i32, u32, String, bool) all implement it. Custom types require #[derive(Hash, Eq, PartialEq)]. f64 does not implement Eq (because NaN != NaN), so it cannot be used directly as a key.entry API and a direct insert?entry does not overwrite existing values, while insert overwrites them directly. entry(key).or_insert(value) Inserts only if the key does not exist; if it does exist, it returns a reference to the existing value. insert Always overwrites the old value and returns Option<V> (the old value). entry is the conventional way to write "insert or update."BTreeMap (sorted by key). If you just need fast lookups, the O(1) performance of a HashMap is better.insert operation in HashSet has an average time complexity of O(1). However, HashSet consumes more memory and does not preserve order. If you need to preserve order, consider using Vec combined with HashSet.entry method of HashMap return?Entry enum, which has two variants: Occupied(Entry) and Vacant(Entry). or_insert(default) inserts a default value and returns a reference when the slot is Vacant, and returns a reference to the existing value when it is Occupied. and_modify(fn) modifies the value when the slot is Occupied. These methods can be chained.📖 Summary
HashMap<K, V>Stores key-value mappings and provides lookups with an average time complexity of O(1)- entry API is a Rust-specific "insert or update" idiom (
entry(k).or_insert(v)) - When inserting into a HashMap, ownership is transferred; the key must implement the
Eq + Hashtrait HashSet<T>is essentiallyHashMap<T, ()>, used for deduplication and set operations- HashSet supports union, intersection, difference, and symmetric_difference
- Collection selection strategies: Sorted/indexed → Vec, key-value lookup → HashMap, deduplication/membership check → HashSet
📝 Exercises
-
Difficulty ⭐: Create a
HashMap<String, u32>to store fruit prices ("apple"=5, "banana"=3, "orange"=4). Write a functionfn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32to calculate the total price of the shopping cart. Test the shopping cart["apple", "banana", "apple"]in the main function. -
Difficulty ⭐⭐: Write a function
fn word_frequency(text: &str) -> HashMap<String, u32>that counts the number of times each word appears in a text. Use theentryAPI. In the main function, test the text "the quick brown fox jumps over the lazy dog the fox" and print the results. -
Difficulty ⭐⭐⭐: Create a list of students for two classes (
HashSet<&str>). Class A has ["Alice", "Bob", "Charlie", "David"], and Class B has ["Charlie", "David", "Eve", "Frank"]. Write a functionfn analyze_classes(a: &HashSet<&str>, b: &HashSet<&str>)that prints: students in both classes (intersection), students only in Class A (difference), all unique students (union), and students in only one class (symmetric difference). Call the function in the main function and print the results.