Rust: Rust 集合:HashMap 与 HashSet 详解

最后更新:2026-08-26

HashMap 和 HashSet 是 Rust 标准库中最常用的基于哈希的集合——HashMap 存储键值对映射,HashSet 存储不重复的元素集合。

如果说 Vec 是"按顺序存东西",那 HashMap 就是"按名字找东西"——你不需要记住索引,只需要知道键。而 HashSet 则是"有没有这个东西"的终极答案。


1. 你将学到


2. 一个投票系统的故事

(1) 痛苦:用两个 Vec 存票数

Anna 正在开发一个班级投票系统,需要统计每位候选人的得票数。

最开始她用两个 Vec:

RUST
let mut candidates = Vec::new();
let mut votes = Vec::new();

candidates.push("Alice");
votes.push(0);

candidates.push("Bob");
votes.push(0);

// 给 Alice 投票
let pos = candidates.iter().position(|&c| c == "Alice").unwrap();
votes[pos] += 1;

// 查询 Bob 的票数
let pos = candidates.iter().position(|&c| c == "Bob").unwrap();
println!("Bob 的票数: {}", votes[pos]);

用两个并行的 Vec 管理数据,问题显而易见:维护两个 Vec 的同步很脆弱——增删候选人时很容易忘记更新另一个 Vec。而且查找候选人需要 O(n) 的线性搜索,候选人越多越慢。从代码可读性上看,candidates[i]votes[i] 的关联关系是隐式的,新人很难理解。

(2) Rust HashMap 的方案

RUST
use std::collections::HashMap;

fn main() {
    let mut votes = HashMap::new();

    // 给候选人投票
    *votes.entry("Alice").or_insert(0) += 1;
    *votes.entry("Bob").or_insert(0) += 1;
    *votes.entry("Alice").or_insert(0) += 1;  // 再投 Alice
    *votes.entry("Charlie").or_insert(0) += 1;

    // 查询票数
    for (candidate, count) in &votes {
        println!("{}: {} 票", candidate, count);
    }

    // 查询特定候选人
    println!("Alice 的票数: {}", votes.get("Alice").unwrap());
}

输出:

TEXT 📖 仅展示
Alice: 2 票
Bob: 1 票
Charlie: 1 票
Alice 的票数: 2

HashMap 是一个键值对映射表:key -> valueentry API 优雅地处理"如果不存在就插入默认值,如果存在就更新"的场景。get 方法在 O(1) 时间复杂度内查找键。不用再维护两个同步的 Vec 了。


3. HashMap 与 HashSet 概览

(1) 概念图

100%
graph TB
    A[基于哈希的集合] --> B[HashMap<K, V>]
    A --> C[HashSet<T>]
    B --> B1[insert: 插入键值对]
    B --> B2[get: 通过键取值]
    B --> B3[entry: 优雅插入/更新]
    B --> B4[remove: 删除键值对]
    B --> B5[contains_key: 判断键是否存在]
    B --> B6[iter: 遍历所有键值对]
    C --> C1[insert: 添加元素]
    C --> C2[contains: 判断是否包含元素]
    C --> C3[union: 并集运算]
    C --> C4[intersection: 交集运算]
    C --> C5[difference: 差集运算]
    C --> C6[symmetric_difference: 对称差集]

(2) 集合类型对比

特性 Vec<T> HashMap<K, V> HashSet<T>
存储 有序序列 无序键值对 无序不重复元素
查找 O(n) 线性搜索 O(1) 哈希查找 O(1) 哈希查找
插入 O(1) 末尾追加 O(1) 平均 O(1) 平均
去重 手动检查 键自动去重 元素自动去重
内存 低(连续存储) 中(哈希表开销) 中(哈希表开销)
适用场景 按序访问、小数据量 键值映射、快速查找 集合运算、去重检查

(3) HashMap 常用方法速查

方法 返回类型 说明
insert(k, v) Option<V> 插入键值对,返回旧值
get(&k) Option<&V> 按键查找值
get_mut(&k) Option<&mut V> 按键查找可变引用
remove(&k) Option<V> 删除键值对,返回被删值
contains_key(&k) bool 键是否存在
entry(k) Entry<K,V> 获取 entry 做插入/更新
keys() Keys<K,V> 遍历所有键
values() Values<K,V> 遍历所有值
len() usize 键值对数量
is_empty() bool 是否为空
clear() () 清空所有键值对
drain() Drain<K,V> 移除并返回所有键值对

(4) HashSet 集合运算

运算 方法 数学符号 说明
并集 union(&other) A ∪ B 两集合所有元素
交集 intersection(&other) A ∩ B 两集合共有元素
差集 difference(&other) A - B 在 A 不在 B 的元素
对称差 symmetric_difference(&other) A △ B 只在一个集合中的元素
子集 is_subset(&other) A ⊆ B A 的元素全在 B 中
超集 is_superset(&other) A ⊇ B A 包含 B 的所有元素

4. HashMap 与 HashSet 示例

▶ 示例 1:HashMap 基础 API——投票统计系统(难度 ⭐⭐)

RUST
// ============================================
// 投票统计系统:展示 HashMap 基本 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);
    }
}

输出:

TEXT 📖 仅展示
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) 是 HashMap 最常用的惯用写法——如果键不存在,插入默认值并返回其引用;如果键存在,直接返回其引用。配合 *count += 1 可以一行完成"插入或累加"操作。get 返回 Option<&V>,永远不会 panic。


▶ 示例 2:HashMap 所有权规则与值类型(难度 ⭐⭐⭐)

RUST
// ============================================
// HashMap 所有权规则:什么类型能做 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);
}

输出:

TEXT 📖 仅展示
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 的所有权规则:插入时 key 和 value 的所有权转移给 HashMap。get 返回引用(&V),不会转移所有权。key 类型必须实现 Eq + Hash trait(基本类型和 String 默认实现了)。entry + and_modify + or_insert 链式调用是 Rust 特有的优雅模式。


▶ 示例 3:HashSet 去重与集合运算(难度 ⭐⭐)

RUST
// ============================================
// HashSet:去重、交集、并集、差集运算
// ============================================

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);
}

输出:

TEXT 📖 仅展示
--- 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"}

HashSet 的四大集合运算:union(并集——所有元素)、intersection(交集——共同元素)、difference(差集——A有B没有)、symmetric_difference(对称差集——不同时属于两者的元素)。这些方法返回迭代器,需要 .collect() 收集到新的 HashSet 中。


▶ 示例 4:遍历 HashMap 与集合选择策略(难度 ⭐⭐)

RUST
// ============================================
// 遍历 HashMap + 集合选择策略对比
// ============================================

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;

输出:

TEXT 📖 仅展示
--- 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

HashMap 的遍历方式:iter() 遍历所有键值对,keys() 只遍历键,values() 只遍历值,values_mut() 可变遍历值,drain() 消费并移除所有元素。选择集合类型时:需要有序、可重复、按索引访问 → Vec;需要键值映射、快速查找 → HashMap;需要去重、集合运算、成员检查 → HashSet。


▶ 示例 5:综合练习——词频统计与文本分析(难度 ⭐⭐⭐)

RUST
// ============================================
// 综合示例:HashMap + HashSet 文本分析
// ============================================

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!("=== 文本 1 词频 ===");
    let freq1 = word_frequency(text1);
    for (word, count) in top_n(&freq1, 5) {
        println!("  '{}': {} 次", word, count);
    }

    println!("\n=== 文本 2 词频 ===");
    let freq2 = word_frequency(text2);
    for (word, count) in top_n(&freq2, 5) {
        println!("  '{}': {} 次", word, count);
    }

    let words1 = unique_words(text1);
    let words2 = unique_words(text2);

    let common: HashSet<_> = words1.intersection(&words2).collect();
    println!("\n共同词汇: {:?}", common);

    let only1: HashSet<_> = words1.difference(&words2).collect();
    println!("文本1独有: {:?}", only1);

    let only2: HashSet<_> = words2.difference(&words1).collect();
    println!("文本2独有: {:?}", only2);

    let all: HashSet<_> = words1.union(&words2).collect();
    println!("所有词汇数: {}", all.len());
}

输出:

TEXT 📖 仅展示
=== 文本 1 词频 ===
  'the': 3 次
  'cat': 2 次
  'on': 2 次
  'mat': 2 次
  'sat': 1 次

=== 文本 2 词频 ===
  'the': 3 次
  'dog': 2 次
  'on': 2 次
  'grass': 1 次
  'ran': 1 次

共同词汇: {"the", "and", "on", "slept"}
文本1独有: {"mat", "cat", "sat"}
文本2独有: {"ran", "rug", "grass", "dog"}

所有词汇数: 11

word_frequencyentry().or_insert() 模式优雅地计数;unique_words 用 HashSet 自动去重;intersection/difference/union 实现集合运算。HashMap + HashSet 是文本分析的黄金组合。


❓ 常见问题

Q HashMap 的 key 必须满足什么 trait?
A key 必须实现 Eq + Hash trait。
Q entry API 和直接 insert 有什么区别?
A entry 不覆盖已有值,insert 直接覆盖。
Q HashMap 的遍历顺序是固定的吗?
A 不固定!HashMap 的迭代顺序是无序的。
Q HashSet 和 Vec 去重哪个效率高?
A 数据量大时 HashSet 快得多。
Q HashMap 的 entry 方法返回什么?
A 返回 Entry 枚举,有两个变体:Occupied(Entry)Vacant(Entry)
Q 什么时候用 HashMap 什么时候用 BTreeMap?
A 需要快速查找用 HashMap(O(1)),需要有序遍历用 BTreeMap(O(log n))。

📖 小节


📝 作业

  1. 难度 ⭐:创建一个 HashMap<String, u32> 存储水果价格("apple"=5, "banana"=3, "orange"=4)。写一个函数 fn total_cost(items: &[&str], prices: &HashMap<String, u32>) -> u32 计算购物车总价。在 main 中测试 ["apple", "banana", "apple"] 购物车。

  2. 难度 ⭐⭐:写一个函数 fn word_frequency(text: &str) -> HashMap<String, u32> 统计文本中每个单词出现的次数。使用 entry API。在 main 中测试 "the quick brown fox jumps over the lazy dog the fox" 并打印结果。

  3. 难度 ⭐⭐⭐:创建两个班级的学生名单(HashSet<&str>),Class A 有 ["Alice", "Bob", "Charlie", "David"],Class B 有 ["Charlie", "David", "Eve", "Frank"]。编写函数 fn analyze_classes(a: &HashSet<&str>, b: &HashSet<&str>) 打印:两班都有的学生(交集),只在 A 班的学生(差集),所有不重复的学生(并集),以及只在一个班的学生(对称差集)。在 main 中调用并输出结果。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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