【问题标题】:How can I create a HashMap with values moved from two other HashMaps?如何使用从其他两个 HashMap 移动的值创建一个 HashMap?
【发布时间】:2019-12-30 03:08:53
【问题描述】:

我有两个具有相同键的HashMap<&str, String>,我希望创建一个具有相同键的HashMap,其中组合了值。我不想保留对前两个HashMaps 的引用,但想将Strings 移动到新的HashMap

use std::collections::HashMap;

#[derive(Debug)]
struct Contact {
    phone: String,
    address: String,
}

fn main() {
    let mut phones: HashMap<&str, String> = HashMap::new();
    phones.insert("Daniel", "798-1364".into());
    phones.insert("Ashley", "645-7689".into());
    phones.insert("Katie", "435-8291".into());
    phones.insert("Robert", "956-1745".into());

    let mut addresses: HashMap<&str, String> = HashMap::new();
    addresses.insert("Daniel", "12 A Street".into());
    addresses.insert("Ashley", "12 B Street".into());
    addresses.insert("Katie", "12 C Street".into());
    addresses.insert("Robert", "12 D Street".into());

    let contacts: HashMap<&str, Contact> = phones.keys().fold(HashMap::new(), |mut acc, value| {
        acc.entry(value).or_insert(Contact {
            phone: *phones.get(value).unwrap(),
            address: *addresses.get(value).unwrap(),
        });
        acc
    });

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

但我有一个错误

error[E0507]: cannot move out of a shared reference
  --> src/main.rs:24:20
   |
24 |             phone: *phones.get(value).unwrap(),
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `std::string::String`, which does not implement the `Copy` trait

error[E0507]: cannot move out of a shared reference
  --> src/main.rs:25:22
   |
25 |             address: *addresses.get(value).unwrap(),
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `std::string::String`, which does not implement the `Copy` trait

Playground

【问题讨论】:

    标签: rust hashmap


    【解决方案1】:

    HashMap::get 返回一个Option&lt;&amp;V&gt;,即对映射内部值的引用。除非V 实现Copy,否则您不能移出带有* 的引用。您需要一种将值移出映射的不同方法,即HashMap::remove(注意它返回Option&lt;V&gt;)。

    如果你尝试使用remove 重写相同的算法,你会得到一个不同的错误:

        let contacts: HashMap<&str, Contact> = phones.keys().fold(HashMap::new(), |mut acc, value| {
            acc.entry(value).or_insert(Contact {
                phone: phones.remove(value).unwrap(),
                address: addresses.remove(value).unwrap(),
            });
            acc
        });
    
    error[E0502]: cannot borrow `phones` as mutable because it is also borrowed as immutable
      --> src/main.rs:22:79
       |
    22 |     let contacts: HashMap<&str, Contact> = phones.keys().fold(HashMap::new(), |mut acc, value| {
       |                                            ------        ----                 ^^^^^^^^^^^^^^^^ mutable borrow occurs here
       |                                            |             |
       |                                            |             immutable borrow later used by call
       |                                            immutable borrow occurs here
    23 |         acc.entry(value).or_insert(Contact {
    24 |             phone: phones.remove(value).unwrap(),
       |                    ------ second borrow occurs due to use of `phones` in closure
    
    error: aborting due to previous error
    
    For more information about this error, try `rustc --explain E0502`.
    

    此错误告诉您在迭代数据结构时不能对其进行更改,因为更改数据结构可能会使迭代器无效。 Sometimes you can solve this with interior mutability,但在这种情况下,您不需要做任何类似的事情。只需拨打phones.into_iter() 即可在迭代时将电话号码移出地图。然后很容易使用map 来创建(&amp;str, Contact) 元组,最后,collect 全部返回到HashMap

        let contacts: HashMap<_, _> = phones
            .into_iter()
            .map(|(key, phone)| {
                (
                    key,
                    Contact {
                        phone,
                        address: addresses.remove(key).unwrap(),
                    },
                )
            })
            .collect();
    

    Playground

    【讨论】:

    • 我自己喜欢the flat_map version
    • 如果某些键属于phones 但在addresses 中不存在怎么办,我在这里遇到了崩溃。但我不知道要避免这种情况。
    • @prehistoricpenguin 你想用这些键做什么?使用 flat_map 尝试 Shepmaster 的版本,它会简单地跳过它们。
    【解决方案2】:

    zip 是您的朋友。但是这里的“业务逻辑”规定它只适用于排序的地图。因此,如果您可以使用BTreeMap 而不是HashMap,则可以使用以下方法:

    fn main() {
        let mut phones: BTreeMap<&str, String> = BTreeMap::new();
        ...
    
        let mut addresses: BTreeMap<&str, String> = BTreeMap::new();
        ...
    
        let contacts: BTreeMap<&str, Contact> = phones
            .into_iter()
            .zip(addresses.into_iter())
            .map(|((name, phone), (_, addr))| {
                (
                    name,
                    Contact {
                        phone: phone,
                        address: addr,
                    },
                )
            })
            .collect();
    
        println!("{:#?}", contacts);
    }
    

    【讨论】:

    • 是的,但这看起来很奇怪,什么情况下你有两个完全相同的大小和键映射?为什么不直接创建联系人而不创建两个过去的地图?
    • 好吧,我认为这种不变性只是 OP 的前提。
    猜你喜欢
    • 1970-01-01
    • 2023-04-03
    • 2020-06-07
    • 2017-11-11
    • 2012-11-23
    • 2021-07-07
    • 2013-01-18
    • 1970-01-01
    相关资源
    最近更新 更多