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



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:

RUST
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] and votes[i] is implicit, making it difficult for new developers to understand.

(2) The Rust HashMap Approach

RUST
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:

TEXT 📖 Display only
Alice: 2 votes
Bob: 1 votes
Charlie: 1 votes
Alice the number of votes: 2

HashMap is a key-value mapping table: key -> value. The entry API elegantly handles scenarios where "if the key does not exist, insert the default value; if it does exist, update it." The get method 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

100%
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:

TEXT 📖 Display only
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 ---
RUST
// ============================================
// 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:

TEXT 📖 Display only
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. get returns Option<&V> and never causes a panic.


▶ Example 2: HashMap Ownership Rules and Value Types (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
<product>
Lookup table: <lookup>
Product: <p.name>, price: <p.price>
Not found
Updated stock: <product.stock>

Word count: <word_count>
RUST
// ============================================
// 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:

TEXT 📖 Display only
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. get returns a reference (&V) and does not transfer ownership. The key type must implement the Eq + Hash trait (primitive types and String implement this by default). The chained calls entry + and_modify + or_insert are an elegant pattern unique to Rust.


▶ Example 3: Removing Duplicates from a HashSet and Set Operations (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
--- 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>
RUST
// ============================================
// 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:

TEXT 📖 Display only
--- 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:

TEXT 📖 Display only
--- 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)>
RUST
// ============================================
// 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:

TEXT 📖 Display only
--- 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 📖 Display only
=== Text 1 Word Frequency ===
  '<word>': <count> times

=== Text 2 Word Frequency ===
  '<word>': <count> times

Common Vocabulary: <common>
Text1Exclusive: <only1>
Text2Exclusive: <only2>
RUST
// ============================================
// 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 📖 Display only
=== 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_frequency Count elegantly using the entry().or_insert() method; unique_words automatically remove duplicates using a HashSet; intersection/difference/union implement set operations. HashMap + HashSet is the golden combination for text analysis.


❓ FAQ

Q What trait must a HashMap key satisfy?
A A key must implement the 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.
Q What is the difference between the entry API and a direct insert?
A 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."
Q Is the iteration order of a HashMap fixed?
A No, it isn’t! The iteration order of a HashMap is unordered. It may vary with each run. If you need an ordered key-value mapping, you can use BTreeMap (sorted by key). If you just need fast lookups, the O(1) performance of a HashMap is better.
Q Which is more efficient for removing duplicates: HashSet or Vec?
A HashSet is much faster when dealing with large datasets. Removing duplicates with Vec takes O(n²) time (since each element must be compared with all previous ones), while the 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.
Q What does the entry method of HashMap return?
A It returns the 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.
Q When should you use a HashMap, and when should you use a BTreeMap?
A Use a HashMap (O(1)) for fast lookups, and use a BTreeMap (O(log n)) for ordered traversal. Keys in a HashMap are unordered but lookups are fast; keys in a BTreeMap are sorted (e.g., printed in alphabetical order), but lookups are slightly slower. If order is important, use a BTreeMap.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Create a HashMap<String, u32> to store fruit prices ("apple"=5, "banana"=3, "orange"=4). Write a function fn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32 to calculate the total price of the shopping cart. Test the shopping cart ["apple", "banana", "apple"] in the main function.

  2. 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 the entry API. In the main function, test the text "the quick brown fox jumps over the lazy dog the fox" and print the results.

  3. 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 function fn 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.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏