【问题标题】:In-place modification, insertion or removal in the same function for hash maps in RustRust 中哈希映射在同一函数中的就地修改、插入或删除
【发布时间】:2025-12-01 17:00:01
【问题描述】:

假设我有一个哈希映射 m: HashMap<K, V>、一个键 k: K 和一个值 v: V,并且想要执行以下操作:

  • 如果m 在索引k 处不包含值,则在索引k 处插入v
  • 如果m 在索引k 处包含值w,则将函数fn combine(x: V, y: V) -> Option<V> 应用于vw,并且:
    • 如果结果为None,则从m 中删除索引k 处的条目。
    • 如果结果为Some(u),则将索引k 处的值替换为u

有没有办法“就地”执行此操作,而无需调用多次访问、修改或删除 k 处的值的函数?

我还想避免复制数据,因此理想情况下,不需要克隆 v 来将克隆分别提供给 insertcombine

我可以重写combine 以使用(可变)引用(或内联它),但不复制数据的愿望仍然存在。

【问题讨论】:

    标签: rust hashmap


    【解决方案1】:

    深入研究Entry documentation,我注意到Entry 枚举的变体提供了就地修改、删除或插入条目的功能。

    std::collections::hash_map::Entry 纳入范围后,可以执行以下操作:

    match m.entry(k) {
        Entry::Occupied(mut oe) => {
            let w = oe.get_mut();
            match combine(v, w) {
                Some(u) => { *w = u; },
                None    => { oe.remove_entry(); },
            }
        },
        Entry::Vacant(ve) => { ve.insert(v); },
    }
    

    Here 是 Rust 操场上的 PoC。)

    然而,这需要combine 将(可变)引用作为其第二个参数(在我的情况下这很好)。

    【讨论】:

      【解决方案2】:

      在最坏的情况下,我设法完成了一次访问、一次写入和一次删除。最后一个键删除不应该是必要的,但我不确定它是否可以完成。到目前为止,我已经尽力了。我希望这会有所帮助!

      好的,所以我想我们想使用Entry API

      Entry 的完整方法列表是 here

      我认为我们应该按照以下顺序进行:

      • 如果m 在索引k 处包含值w:(再执行两步)
      • 或者在索引k处插入v

      这可以通过使用.and_modify 然后.or_insert 来完成。像这样的:

      let map = // ... Initialize the map
      
      // Do stuff to it
      // ...
      
      // Our important bit:
      
      let mut delete_entry = false;
      
      map.entry(k)
         .and_modify(|w| { // If the entry exists, we modify it
      
           let u = combine(v, w);
      
           match u {
      
             Some(y) => *w = y;
      
             None => delete_entry = true;
             }
           }
         )
         .or_insert(v); // If it doesn't, we insert v
      
      if delete_entry {
        map.remove(k);
      }
      

      如果没有最后一次map.remove 访问权限,我认为没有办法完成所有三件事,所以这是我目前最好的尝试。

      【讨论】:

      • 谢谢,这很接近,但问题是在之前的闭包中调用combine(v, w) 之后不能调用or_insert(v),因为两者都需要移动v。 (我还看到我可能需要更改combine 以使用对w 的(可变)引用,但我对此很好。但是,我仍然想避免复制/克隆v。 )
      • 哦,我明白了。如果您需要移动v,那么是的,您不走运。