rust 如何将一个vecMap到hashmap而不需要使用像_ref和clone一样多的函数?

s5a0g9ez  于 2022-12-19  发布在  其他
关注(0)|答案(1)|浏览(268)

假设我有这些结构体:

struct Data {
        url: String,
        timestamp: u32
    }
    struct AuthorAndData {
        url: String,
        timestamp: u32,
        author: String
    }

我想把Data的向量转换成一个哈希Map,其中AuthorAndData是值。
基本上是这样的:

let input: Vec<(Result<Data, String>, String)> = vec![(Ok(Data {
        url: "http://google.com".to_string(),
        timestamp: 1234566,
    }), "john".to_string()), (Ok(Data {
        url: "http://yahoo.com".to_string(),
        timestamp: 2333333,
    }), "doe".to_string()), (Ok(Data {
        url: "http://google.com".to_string(),
        timestamp: 2333353,
    }), "doe".to_string())];

我想把它转换成一个哈希Map,其中URL是键,值是AuthorAndData的vec。
基本上从Vec<(Result<Data, String>, String)>HashMap<String, Vec<AuthorAndData>>
我的第一个尝试是这样的:

let mut mappped: HashMap<String, Vec<AuthorAndData>> = HashMap::new();

    input.iter().for_each(|entry| {
        mappped.entry(entry.0.unwrap().url).and_modify(|value| value.push(AuthorAndData {
            url: entry.0.unwrap().url,
            timestamp: entry.0.unwrap().timestamp,
            author: entry.1
        })).or_insert(vec![AuthorAndData {
            url: entry.0.unwrap().url,
            timestamp: entry.0.unwrap().timestamp,
            author: entry.1
        }]);
    });

但编译失败,出现错误:

error[E0507]: cannot move out of `entry.0` which is behind a shared reference
    --> src/main.rs:60:18
     |
60   |             url: entry.0.unwrap().url,
     |                  ^^^^^^^ -------- `entry.0` moved due to this method call
     |                  |
     |                  help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents
     |                  move occurs because `entry.0` has type `Result<Data, std::string::String>`, which does not implement the `Copy` trait
     |

在通过添加.as_ref()修复这些错误后,我又遇到了一些与添加clone()有关的错误。

let mut mappped: HashMap<String, Vec<AuthorAndData>> = HashMap::new();

    input.iter().for_each(|entry| {
        mappped.entry(entry.0.as_ref().unwrap().url).and_modify(|value| value.push(AuthorAndData {
            url: entry.0.as_ref().unwrap().url,
            timestamp: entry.0.unwrap().timestamp,
            author: entry.1
        })).or_insert(vec![AuthorAndData {
            url: entry.0.unwrap().url,
            timestamp: entry.0.unwrap().timestamp,
            author: entry.1
        }]);
    });

失败

error[E0507]: cannot move out of a shared reference
  --> src/main.rs:60:18
   |
60 |             url: entry.0.as_ref().unwrap().url,
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `std::string::String`, which does not implement the `Copy` trait

但是附加具有作为entry.0.as_ref().unwrap().url.clone()的行的clone()IE固定了它。
我想要的最终版本如下所示:

fn main() {
    #[derive(Debug)]
    struct Data {
        url: String,
        timestamp: u32
    }
    #[derive(Debug)]
    struct AuthorAndData {
        url: String,
        timestamp: u32,
        author: String
    }

    let input: Vec<(Result<Data, String>, String)> = vec![(Ok(Data {
        url: "http://google.com".to_string(),
        timestamp: 1234566,
    }), "john".to_string()), (Ok(Data {
        url: "http://yahoo.com".to_string(),
        timestamp: 2333333,
    }), "doe".to_string()), (Ok(Data {
        url: "http://google.com".to_string(),
        timestamp: 2333353,
    }), "doe".to_string())];

    let mut mappped: HashMap<String, Vec<AuthorAndData>> = HashMap::new();

    input.iter().for_each(|entry| {
        mappped.entry(entry.0.as_ref().unwrap().url.clone()).and_modify(|value| value.push(AuthorAndData {
            url: entry.0.as_ref().unwrap().url.clone(),
            timestamp: entry.0.as_ref().unwrap().timestamp.clone(),
            author: entry.1.clone()
        })).or_insert(vec![AuthorAndData {
            url: entry.0.as_ref().unwrap().url.clone(),
            timestamp: entry.0.as_ref().unwrap().timestamp.clone(),
            author: entry.1.clone()
        }]);
    });

    dbg!(mappped);
}

运行这个打印正确的结果,但我怀疑这是否是最好的方法给所有的as_refclone,我不得不洒在整个地方得到这个工作。
任何人都知道:
1.为什么需要所有as_refclone
1.更好的方法是不需要所有这些as_refclone

zbwhf8kr

zbwhf8kr1#

1.为什么需要所有as_refclone
因为只有来自iter()的共享引用,所以不能移出它。unwrap()移出Option,所以需要as_ref()。移动字段也是不可能的,所以需要clone()
1.更好的方法是不需要所有这些as_refclone
如果要将iter()更改为into_iter(),可以更改or_insert()以移动值(此外,首选or_insert_with(),以便在条目已满时不分配向量):

.or_insert_with(|| {
    let entry_0 = entry.0.unwrap();
    vec![AuthorAndData {
        url: entry_0.url,
        timestamp: entry_0.timestamp,
        author: entry.1,
    }]
});

如果你将and_modify().or_insert()进一步修改为match,向借位检查器证明只有一个分支可以执行,你可以避免在两个分支中进行克隆:

input.into_iter().for_each(|entry| {
    match mappped.entry(entry.0.as_ref().unwrap().url.clone()) {
        std::collections::hash_map::Entry::Occupied(mut map_entry) => {
            let entry_0 = entry.0.unwrap();
            map_entry.get_mut().push(AuthorAndData {
                url: entry_0.url,
                timestamp: entry_0.timestamp,
                author: entry.1,
            })
        }
        std::collections::hash_map::Entry::Vacant(map_entry) => {
            let entry_0 = entry.0.unwrap();
            map_entry.insert(vec![AuthorAndData {
                url: entry_0.url,
                timestamp: entry_0.timestamp,
                author: entry.1,
            }]);
        }
    }
});

更好的是,您可以在修改现有条目之前使用or_insert(),从而大大简化代码:

input.into_iter().for_each(|entry| {
    mappped
        .entry(entry.0.as_ref().unwrap().url.clone())
        .or_default()
        .push({
            let entry_0 = entry.0.unwrap();
            AuthorAndData {
                url: entry_0.url,
                timestamp: entry_0.timestamp,
                author: entry.1,
            }
        })
});

相关问题