如何在可变HashMap中更新值?

172

这是我想要做的事情:

use std::collections::HashMap;

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

    my_map["a"] += 10;
    // I expect my_map becomes {"b": 3, "a": 11}
}

但是这会产生一个错误:

Rust 2015

error[E0594]: cannot assign to immutable indexed content
 --> src/main.rs:8:5
  |
8 |     my_map["a"] += 10;
  |     ^^^^^^^^^^^^^^^^^ cannot borrow as mutable
  |
  = help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `std::collections::HashMap<&str, i32>`

Rust 2018

error[E0594]: cannot assign to data in a `&` reference
 --> src/main.rs:8:5
  |
8 |     my_map["a"] += 10;
  |     ^^^^^^^^^^^^^^^^^ cannot assign

我其实不太明白那是什么意思,因为我将 HashMap 设为了可变的。当我尝试更新 vector 中的一个元素时,我得到了预期的结果:

let mut my_vec = vec![1, 2, 3];

my_vec[0] += 10;
println! {"{:?}", my_vec};
// [11, 2, 3]

HashMap 有什么不同,导致我收到了上面的错误?有没有一种方法可以更新值?

4个回答

218
提供不可变索引和可变索引是由两个不同的trait实现:IndexIndexMut
目前,HashMap 没有实现 IndexMut,而Vec 却实现了。 移除了 HashMapIndexMut 实现的提交记录 中写道:

这个提交移除了 HashMap 和 BTreeMap 上的 IndexMut 实现, 以便将API未来版本中包含的 IndexSet trait。

我理解假设的 IndexSet trait 将允许您为 HashMap 分配全新的值,而不仅仅是读取或更改现有条目。
let mut my_map = HashMap::new();
my_map["key"] = "value";

目前,您可以使用get_mut方法:

*my_map.get_mut("a").unwrap() += 10;

Or the entry API:

*my_map.entry("a").or_insert(42) += 10;

6
IndexMut是否有望在未来实现? - Luke Dupin
3
@LukeDupin 表达了怀疑。取而代之的,将实现假设的“IndexSet”特性。 - Shepmaster
3
这行代码的意思是:如果 "a" 这个键存在于 my_map 中,就将它对应的值加 10;否则在 my_map 中插入一个新的键值对 "a": 42,并将值设为 52(即 42+10)。而 * 的作用是对指针进行解引用操作,获取指针所指向的变量的值。在这里,*my_map.entry("a").or_insert(42) 等价于访问 key 为 "a" 的哈希表元素的值,这个值可能是已存在的值或者是刚被创建并初始化为 42 的值。 - Eftekhari
3
@Eftekhari 的代码中添加了括号:(*(my_map.entry("a").or_insert(42))) += 10;。该代码的意思是:如果 "a" 这个键存在于 my_map 字典中,那么就将它对应的值加上 10;如果 "a" 这个键不存在于 my_map 字典中,则将键值对 "a:42" 添加到字典中,然后将值加上 10。 - Shepmaster
1
@GermanFaller let tmp = my_map.entry("a").or_insert(some_default); tmp.a += 1; tmp.b += 2. @GermanFaller 让tmp=my_map.entry("a").or_insert(some_default); tmp.a += 1; tmp.b += 2 - Shepmaster
显示剩余3条评论

36

考虑到:

let mut m = std::collections::HashMap::new();
m.insert("a", 1);
m.insert("b", 3);
let k = "c";

如果键已经存在:

    m.insert(k, 10 + m[k] );

如果键不存在:

  1. 你可以更新键的值:
    m.insert(k, 10 + if m.contains_key(k) { m[k] } else { 0 });
  1. 如果键不存在,则首先仅插入一个键:
    m.entry(k).or_insert(0);
    m.insert(k, 200 + m[k]);
  1. 或者更新一个键,防止该键可能未被设置:
    *m.entry(k).or_insert(0) += 3000;

最后打印出数值:

    println!("{}", m[k]); // 3210

See:
https://doc.rust-lang.org/std/collections/struct.HashMap.html


6
你可以使用.and_modify来实现这一点。
let mut my_map = HashMap::new();
my_map.insert("a", 1);
my_map.entry("a").and_modify(|k| *k += 10);
assert_eq!(my_map[&"a"], 11);

6

我会分享我的答案,因为我曾经遇到过这个问题,但是我在使用结构体时工作,所以在我的情况下有点棘手。

use std::collections::HashMap;

#[derive(Debug)]
struct ExampleStruct {
    pub field1: usize,
    pub field2: f64,
}

fn main() {
    let mut example_map = HashMap::new();
    &example_map.insert(1usize, ExampleStruct { field1: 50, field2: 184.0});
    &example_map.insert(6usize, ExampleStruct { field1: 60, field2: 486.0});

    //First Try
    (*example_map.get_mut(&1).unwrap()).field1 += 55; //50+55=105
    (*example_map.get_mut(&6).unwrap()).field1 -= 25; //60-25=35

    //Spliting lines
    let op_elem = example_map.get_mut(&6);
    let elem = op_elem.unwrap();
    (*elem).field2 = 200.0;

    let op_ok_elem = example_map.get_mut(&1);
    let elem = op_ok_elem.unwrap_or_else(|| panic!("This msg should not appear"));
    (*elem).field2 = 777.0;

    println!("Map at this point: {:?}", example_map);
    let op_err_elem = example_map.get_mut(&8);
    let _elem = op_err_elem.unwrap_or_else(|| panic!("Be careful, check you key"));

    println!("{:?}", example_map);
}

您可以在 Rust Playground上进行操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接