【问题标题】:Removing entries from a HashMap based on value根据值从 HashMap 中删除条目
【发布时间】:2015-05-08 16:41:21
【问题描述】:

我编写了以下代码 (+ demo) 以根据值从 HashMap 中删除条目。 它有效,但我觉得我正在与借用检查器作斗争使用:

  • clone() 避免两次引用同一组键
  • 一个额外的let tmp = 绑定以增加我的临时值的生命周期

use std::collections::HashMap;

fn strip_empties(x: &mut HashMap<String, i8>) {
    let tmp = x.clone();
    let empties = tmp
         .iter()
         .filter(|&(_, &v)| v == 0)
         .map(|(k, _)| k);

    for k in empties { x.remove(k); }
}

fn main() {
    let mut x: HashMap<String, i8> = HashMap::new();
    x.insert("a".to_string(), 1);
    x.insert("b".to_string(), 0);
    strip_empties(&mut x);

    println!("Now down to {:?}" , x);
}

有没有更简洁、更惯用的方法来实现这一点?

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    其他答案已过时。从 Rust 1.27 开始,您可以使用 HashMap::retain 仅保留您感兴趣的元素。您可以使用闭包指定要保留的元素。

    x.retain(|_, v| *v != 0);
    

    【讨论】:

    • 一个展示如何在 OPs 代码中解决问题的示例将大大有助于解决这个问题。
    • 元问题:有没有办法以某种方式将更新的答案推到顶部?例如,也许将其更改为接受的答案?
    【解决方案2】:

    为什么HashMap会发生突变?只需创建一个新的(万岁不变):

    fn strip_empties(x: HashMap<String, i8>) -> HashMap<String, i8> {
        return x.into_iter()
            .filter(|&(_, v)| v != 0)
            .collect();
    }
    

    Playpen


    编辑:为什么这是可行的。

    当然,您必须考虑您的用例。最好的方法可能会有所不同,如果 你有一个大的 HashMap 或过滤很多/很少的元素。让我们比较一下 实现。

    use std::collections::HashMap;
    
    fn strip_empties_mutable(x: &mut HashMap<String, i8>) {
        let empties: Vec<_> = x
            .iter()
            .filter(|&(_, &v)| v == 0)
            .map(|(k, _)| k.clone())
            .collect();
        for empty in empties { x.remove(&empty); }
    }
    
    fn strip_empties_immutable(x: HashMap<String, i8>) -> HashMap<String, i8> {
        return x.into_iter()
            .filter(|&(_, v)| v != 0)
            .collect();
    }
    
    fn build_hashmap() -> HashMap<String, i8> {
        let mut map = HashMap::new();
        for chr in "abcdefghijklmnopqrstuvmxyz".chars() {
            map.insert(chr.to_string(), chr as i8 % 2);
        }
        return map;
    }
    
    #[cfg(mutable)]
    fn main() {
        let mut map = build_hashmap();
        strip_empties_mutable(&mut map);
        println!("Now down to {:?}" , map);
    }
    
    #[cfg(immutable)]
    fn main() {
        let mut map = build_hashmap();
        map = strip_empties_immutable(map);
        println!("Now down to {:?}" , map);
    }
    

    将此保存为hashmap.rs 并运行:

    rustc --cfg mutable -O -o mutable hashmap.rs
    rustc --cfg immutable -O -o immutable hashmap.rs
    

    如果我们查看不同的运行时(例如使用perf stat -r 1000 ./XXX),我们 没有发现显着差异。

    但是让我们看看分配的数量:

    valgrind --tool=callgrind --callgrind-out-file=callgrind_mutable ./mutable
    valgrind --tool=callgrind --callgrind-out-file=callgrind_immutable ./immutable
    callgrind_annotate callgrind_mutable | grep 'je_.*alloc'
    callgrind_annotate callgrind_immutable | grep 'je_.*alloc'
    
    • callgrind_mutable:

      7,000  ???:je_arena_malloc_small [$HOME/hashmap/mutable]
      6,457  ???:je_arena_dalloc_bin_locked [$HOME/hashmap/mutable]
      4,800  ???:je_mallocx [$HOME/hashmap/mutable]
      3,903  ???:je_sdallocx [$HOME/hashmap/mutable]
      2,520  ???:je_arena_dalloc_small [$HOME/hashmap/mutable]
        502  ???:je_rallocx [$HOME/hashmap/mutable]
        304  ???:je_arena_ralloc [$HOME/hashmap/mutable]
      
    • callgrind_immutable:

      5,114  ???:je_arena_malloc_small [$HOME/hashmap/immutable]
      4,725  ???:je_arena_dalloc_bin_locked [$HOME/hashmap/immutable]
      3,669  ???:je_mallocx [$HOME/hashmap/immutable]
      2,980  ???:je_sdallocx [$HOME/hashmap/immutable]
      1,845  ???:je_arena_dalloc_small [$HOME/hashmap/immutable]
        158  ???:je_rallocx [$HOME/hashmap/immutable]
      

    这并不令人惊讶,因为 clone() 在可变方法中调用 也分配内存。当然,可变版本可能会产生一个 HashMap 容量更大。

    【讨论】:

    • 虽然这在纸面上看起来非常不错,但它会涉及到 HashMap 条目的大量释放和分配。这就是为什么突变可能更可取的原因。
    • 我认为它只会涉及一次分配和释放(对于整个后备表)。
    • 当它消耗(破坏)源 hashmap 时,您能告诉我为什么称它为“不可变”吗?我理解这种方法并喜欢它——但我想弄清楚为什么“不可变”是正确的词。
    • @Bosh 在这种情况下,源 HashMap 被破坏了,是的。但如果使用另一个变量,它可以保持原来的形式。所以它是不可变的,因为原始的 HashMap 没有发生变异。
    • @lummax:我的理解是,即使你使用了另一个变量,比如let mut stripped_map = strip_empties_immutable(map);,原来的map 变量也将不再可用,因为into_iter 的行为(其中消耗东西)。对吗?
    【解决方案3】:

    由于借用限制,无法在迭代期间从 hashmap 中删除值(既不通过remove,也不通过Entry api),因此您的想法(收集要删除的键)非常接近正确的解决方案。

    你不需要克隆整个哈希表,只收集密钥副本就足够了:

    fn strip_empties(x: &mut HashMap<String, i8>) {
        let empties: Vec<_> = x
             .iter()
             .filter(|&(_, &v)| v == 0)
             .map(|(k, _)| k.clone())
             .collect();
        for empty in empties { x.remove(&empty); }
    }
    

    【讨论】:

    • 这提出了一个有趣的问题,为什么HashMap 没有返回Entry(或(Key, Entry) 元组)的迭代器。我看不出这不可能的任何原因。有谁知道这是否只是“好吧,还没有人费心实施它”的情况?
    • HashMapiter 函数(如上所述)确实Key, Entry 元组上创建了一个迭代器。我可能误解了你的问题...
    猜你喜欢
    • 2013-02-16
    • 2017-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-17
    • 2017-02-25
    相关资源
    最近更新 更多