17·复合类型入门

HashMap 哈希表

HashMap 哈希表

学习目标

  1. 创建和操作 HashMap
  2. 掌握所有权规则
  3. 掌握常用 API
  4. 理解 Entry API

核心概念

创建

use std::collections::HashMap;

fn main() {
    let mut scores: HashMap<String, i32> = HashMap::new();

    scores.insert(String::from("Alice"), 95);
    scores.insert(String::from("Bob"), 87);

    // 从元组数组创建
    let pairs = [("Alice", 95), ("Bob", 87)];
    let scores: HashMap<&str, i32> = pairs.into_iter().collect();

    // 从两个 Vec 创建
    let keys = vec!["Alice", "Bob"];
    let values = vec![95, 87];
    let scores: HashMap<&str, i32> = keys.into_iter().zip(values).collect();
}

访问

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert("Alice", 95);

    // get 返回 Option<&V>
    let alice = scores.get("Alice");      // Some(&95)
    let unknown = scores.get("Unknown");  // None

    // 直接索引(不存在会 panic)
    // let x = scores["Unknown"];

    // 遍历
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }

    // 判断键是否存在
    let has_alice = scores.contains_key("Alice");
    println!("len: {}", scores.len());
    println!("is_empty: {}", scores.is_empty());
}

更新

use std::collections::HashMap;

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

    // 覆盖
    map.insert("key", 1);
    map.insert("key", 2);  // 覆盖旧值

    // 不覆盖时才插入
    map.entry("key").or_insert(3);     // key 已存在,不插入
    map.entry("new").or_insert(4);     // new 不存在,插入 4

    // 基于旧值更新
    let text = "hello world hello";
    let mut word_count = HashMap::new();
    for word in text.split_whitespace() {
        let count = word_count.entry(word).or_insert(0);
        *count += 1;
    }
    println!("{:?}", word_count);  // {"hello": 2, "world": 1}
}

所有权

use std::collections::HashMap;

fn main() {
    let key = String::from("color");
    let value = String::from("blue");

    let mut map = HashMap::new();
    map.insert(key, value);

    // println!("{}", key);    // ❌ key 已被移动
    // println!("{}", value);  // ❌ value 已被移动

    // 使用引用作为键/值
    let key = String::from("color");
    let value = String::from("blue");
    let mut map = HashMap::new();
    map.insert(&key, &value);  // 借用,不转移所有权
    println!("{}: {}", key, value);  // ✅
}

删除

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);

    // 删除
    map.remove("b");

    // 保留满足条件的
    map.retain(|_, v| *v > 1);
}

Entry API 详解

use std::collections::HashMap;

fn main() {
    let mut map: HashMap<&str, Vec<i32>> = HashMap::new();

    // or_insert:键不存在时插入默认值,返回值的可变引用
    map.entry("scores").or_insert_with(Vec::new).push(95);
    map.entry("scores").or_insert_with(Vec::new).push(87);
    map.entry("scores").or_insert_with(Vec::new).push(92);

    println!("{:?}", map["scores"]);  // [95, 87, 92]

    // or_insert_with:延迟计算默认值
    map.entry("grades").or_insert_with(|| vec![0]).push(100);

    // and_modify:键存在时修改
    map.entry("scores").and_modify(|v| v.push(100));

    // or_default:使用 Default trait 的默认值
    let mut map2: HashMap<&str, Vec<i32>> = HashMap::new();
    map2.entry("data").or_default().push(1);
}

实践练习

练习 1:词频统计

use std::collections::HashMap;

fn word_frequency(text: &str) -> HashMap<String, usize> {
    let mut freq = HashMap::new();
    for word in text.split_whitespace() {
        let word = word.to_lowercase();
        *freq.entry(word).or_insert(0) += 1;
    }
    freq
}

fn main() {
    let text = "the quick brown fox jumps over the lazy dog the fox";
    let freq = word_frequency(text);
    let mut sorted: Vec<_> = freq.iter().collect();
    sorted.sort_by(|a, b| b.1.cmp(a.1));
    for (word, count) in sorted.iter().take(5) {
        println!("{}: {}", word, count);
    }
}

练习 2:两数之和

use std::collections::HashMap;

fn two_sum(nums: &[i32], target: i32) -> Option<(usize, usize)> {
    let mut seen = HashMap::new();
    for (i, &num) in nums.iter().enumerate() {
        let complement = target - num;
        if let Some(&j) = seen.get(&complement) {
            return Some((j, i));
        }
        seen.insert(num, i);
    }
    None
}

fn main() {
    let nums = vec![2, 7, 11, 15];
    let target = 9;
    match two_sum(&nums, target) {
        Some((i, j)) => println!("索引: ({}, {})", i, j),
        None => println!("未找到"),
    }
}

练习 3:分组

use std::collections::HashMap;

fn group_by_length(words: &[&str]) -> HashMap<usize, Vec<&str>> {
    let mut groups = HashMap::new();
    for &word in words {
        groups.entry(word.len()).or_insert_with(Vec::new).push(word);
    }
    groups
}

fn main() {
    let words = ["hi", "hello", "hey", "world", "rust", "go", "abc"];
    let groups = group_by_length(&words);
    for (len, words) in &groups {
        println!("长度 {}: {:?}", len, words);
    }
}

练习 4:LRU 缓存(简化版)

use std::collections::HashMap;

struct Cache {
    data: HashMap<String, (String, u64)>,
    counter: u64,
    capacity: usize,
}

impl Cache {
    fn new(capacity: usize) -> Self {
        Cache {
            data: HashMap::new(),
            counter: 0,
            capacity,
        }
    }

    fn get(&mut self, key: &str) -> Option<&str> {
        if let Some((value, ts)) = self.data.get_mut(key) {
            self.counter += 1;
            *ts = self.counter;
            Some(value.as_str())
        } else {
            None
        }
    }

    fn put(&mut self, key: String, value: String) {
        if self.data.len() >= self.capacity {
            // 移除最久未访问的
            if let Some(oldest_key) = self.data.iter()
                .min_by_key(|(_, (_, ts))| ts)
                .map(|(k, _)| k.clone())
            {
                self.data.remove(&oldest_key);
            }
        }
        self.counter += 1;
        self.data.insert(key, (value, self.counter));
    }
}

fn main() {
    let mut cache = Cache::new(3);
    cache.put("a".into(), "1".into());
    cache.put("b".into(), "2".into());
    cache.put("c".into(), "3".into());
    cache.get("a");
    cache.put("d".into(), "4".into());  // "b" 应被淘汰
    println!("a: {:?}", cache.get("a"));
    println!("b: {:?}", cache.get("b"));
}

常见错误

1. 键类型不支持 Hash

// ❌ f64 没有实现 Hash
// let mut map: HashMap<f64, &str> = HashMap::new();

// ✅ 用 i32 或字符串做键

2. get 返回引用

let mut map = HashMap::new();
map.insert("key", vec![1, 2, 3]);

// ❌ 不能直接修改 get 返回的值
// map.get("key").unwrap().push(4);

// ✅ 用 get_mut
map.get_mut("key").unwrap().push(4);

小结

操作语法
创建HashMap::new()
插入insert(key, value)
访问get(key)Option<&V>
更新entry(key).or_insert(default)
删除remove(key)
遍历for (k, v) in &map

练习编辑器

rust
Loading...