在Rust中合并两个HashMap

37

我遇到了一些问题,想要合并两个HashMap。

在一行代码中完成它很容易:

fn inline() {
    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    let mut new_context = HashMap::new();
    for (key, value) in first_context.iter() {
        new_context.insert(*key, *value);
    }
    for (key, value) in second_context.iter() {
        new_context.insert(*key, *value);
    }
    println!("Inline:\t\t{}", new_context);
    println!("Inline:\t\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

创建一个函数很容易:

fn abstracted() {
    fn merge<'a>(first_context: &HashMap<&'a str, &'a str>, second_context: &HashMap<&'a str, &'a str>) -> HashMap<&'a str, &'a str> {
        let mut new_context = HashMap::new();
        for (key, value) in first_context.iter() {
            new_context.insert(*key, *value);
        }
        for (key, value) in second_context.iter() {
            new_context.insert(*key, *value);
        }
        new_context
    }

    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    println!("Abstracted:\t{}", merge(&first_context, &second_context));
    println!("Abstracted:\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

然而,我似乎无法让常规版本正常工作:
fn generic() {
    fn merge<'a, K: Hash + Eq, V>(first_context: &HashMap<&'a K, &'a V>, second_context: &HashMap<&'a K, &'a V>) -> HashMap<&'a K, &'a V> {
        let mut new_context = HashMap::new();
        for (key, value) in first_context.iter() {
            new_context.insert(*key, *value);
        }
        for (key, value) in second_context.iter() {
            new_context.insert(*key, *value);
        }
        new_context
    }

    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    println!("Generic:\t{}", merge(&first_context, &second_context));
    println!("Generic:\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

上述代码在 play.rust-lang.org 上的链接。

编译它:

error: the trait `core::kinds::Sized` is not implemented for the type `str`

我明白编译器对于泛型值的大小感到困惑,但我不确定为什么 "str" 没有严格的内存大小?我知道它是一个字符串切片而不是类型,但这应该行得通,对吗?这是一个 bug 吗?
我以为这将是一个相对简单的函数。如果有人有一个好的解决方案,我很想学习。实际上,我很想看到一个带有 trait Mergeable 的解决方案,并为 HashMap<&K, &V> 编写一个装饰器,使我可以调用 let new_context = first_context.merge(&second_context); 但这可能是另一个问题。
2个回答

70

来自这条推文的更加最新的答案:

use std::collections::HashMap;

// Mutating one map
fn merge1(map1: &mut HashMap<(), ()>, map2: HashMap<(), ()>) {
    map1.extend(map2);
}

// Without mutation
fn merge2(map1: HashMap<(), ()>, map2: HashMap<(), ()>) -> HashMap<(), ()> {
    map1.into_iter().chain(map2).collect()
}

// If you only have a reference to the map to be merged in
fn merge_from_ref(map: &mut HashMap<(), ()>, map_ref: &HashMap<(), ()>) {
    map.extend(map_ref.into_iter().map(|(k, v)| (k.clone(), v.clone())));
}

Rust Playground 链接


6

这个版本是可以工作的:

use std::collections::HashMap;
use std::hash::Hash;

fn main() {
    fn merge<K: Hash + Eq + Copy, V: Copy>(first_context: &HashMap<K, V>, second_context: &HashMap<K, V>) -> HashMap<K, V> {
        let mut new_context = HashMap::new();
        for (key, value) in first_context.iter() {
            new_context.insert(*key, *value);
        }
        for (key, value) in second_context.iter() {
            new_context.insert(*key, *value);
        }
        new_context
    }

    let mut first_context = HashMap::new();
    first_context.insert("Hello", "World");
    let mut second_context = HashMap::new();
    second_context.insert("Hey", "There");

    println!("Generic:\t{}", merge(&first_context, &second_context));
    println!("Generic:\t{}\t{} [Initial Maps Still Usable]", first_context, second_context);
}

区别在于merge()的签名。这是你的:

fn merge<'a, K: Hash + Eq, V>(first_context: &HashMap<&'a K, &'a V>, second_context: &HashMap<&'a K, &'a V>) -> HashMap<&'a K, &'a V>

这是我的:
fn merge<K: Hash + Eq + Copy, V: Copy>(first_context: &HashMap<K, V>, second_context: &HashMap<K, V>) -> HashMap<K, V>

由于您想要将HashMap<&str,&str> 抽象为HashMap<&K,&V>,但这并不正确,因为&str虽然是借用指针,但它是特殊的 - 它指向动态大小类型str。由于编译器不知道str的大小,因此只能通过指针使用它。因此,HashEq均未被实现为str,而是实现为&str。因此我已将HashMap<&'a K,&'a V>更改为HashMap<K,V>
第二个问题是通常情况下,如果函数仅接受映射的引用,则无法编写该函数。您的非泛型合并函数之所以有效,是因为&str是引用,引用可以隐式复制。然而,在一般情况下,键和值都可能是不可复制的,并且将它们合并到单个映射中将需要将这些映射移入函数中。添加Copy限定允许这样做。
您还可以添加Clone限定而不是Copy,并使用显式的clone()调用:
fn merge<K: Hash + Eq + Clone, V: Clone>(first_context: &HashMap<K, V>, second_context: &HashMap<K, V>) -> HashMap<K, V> {
    // ...
    for (key, value) in first_context.iter() {
        new_context.insert(key.clone(), value.clone());
    }
    // ...
}

然而,最常见的方法是将地图移入该函数中:
fn merge<K: Hash + Eq, V>(first_context: HashMap<K, V>, second_context: HashMap<K, V>) -> HashMap<K, V>  {
    // ...
    for (key, value) in first_context.into_iter() {
        new_context.insert(key, value);
    }
    // ...
}

注意 into_iter() 方法消耗了map,但返回一个元组值的迭代器而不是引用。


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