15

我编写了以下代码 ( + demoHashMap ) 以从基于值的值中删除条目。它有效,但我觉得我正在努力使用借用检查器

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

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

4

3 回答 3

18

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

x.retain(|_, v| *v != 0);
于 2018-09-30T21:12:52.727 回答
14

为什么HashMap会发生突变?只需创建一个新的(所有冰雹不变性):

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

围栏


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

当然,您必须考虑您的用例。如果您有一个大的 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

如果我们查看不同的运行时(例如 using 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'
  • 调用grind_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]
    
  • 调用grind_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。

于 2015-03-07T11:53:41.073 回答
5

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

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

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); }
}
于 2015-03-07T10:01:53.730 回答