如何使用 HashMap 作为累加器进行折叠? [英] How to fold using a HashMap as an accumulator?

查看:55
本文介绍了如何使用 HashMap 作为累加器进行折叠?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此代码有效:

let stdin = std::io::stdin();让 mut rdr = csv::Reader::from_reader(stdin);让 mut hmap = HashMap::::new();rdr.records().map(|r| r.unwrap()).fold((), |_, item| {//TODO: 有没有办法不用每次都复制 item[col]?让计数器 = hmap.entry(item[col].to_string()).or_insert(0);*计数器 += 1;});

此代码失败并显示消息:无法移出acc,因为它已被借用"

let stdin = std::io::stdin();让 mut rdr = csv::Reader::from_reader(stdin);让 hmap = rdr.records().map(|r| r.unwrap()).fold(HashMap::::new(), |mut acc, item| {//TODO: 有没有办法不用每次都复制 item[col]?让计数器 = acc.entry(item[col].to_string()).or_insert(0);*计数器 += 1;acc});

解决方案

你不能从闭包中返回 acc 因为你有一个仍然存在的可变借用(counter>).

这是 Rust 编译器(特别是 借用检查器)的限制.启用非词法生存期后,您的原始代码将起作用:

#![feature(nll​​)]使用 std::collections::HashMap;fn 主(){让 hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {let counter = acc.entry("foo".to_string()).or_insert(0);*计数器 += 1;acc});println!("{:?}", hmap);}

在 NLL 之前,编译器对借用的持续时间过于保守.要解决此问题,您可以引入一个新范围来限制可变借用:

使用 std::collections::HashMap;fn 主(){让 hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {{let counter = acc.entry("foo".to_string()).or_insert(0);*计数器 += 1;}acc});println!("{:?}", hmap);}

您还可以防止借用超出所需的行:

使用 std::collections::HashMap;fn 主(){让 hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {*acc.entry("foo".to_string()).or_insert(0) += 1;acc});println!("{:?}", hmap);}

<块引用>

我认为 Rust 会知道 counter 一旦返回 acc 就会超出范围

这是可以理解的,并且与非词法生命周期的讨论有关.好"的消息是,当被引用的事物移动时,Rust 对引用的工作方式保持一致.在这种情况下,您将累加器移动到输出槽"中.您也可以使用普通函数看到这一点:

fn foo(mut s: Vec) ->Vec u8 ;{让借 = &mut s[0];秒}fn main() {}

但实际上,这与移动引用变量完全相同:

fn main() {让 mut s = Vec::::new();让借 = &mut s[0];让 s2 = s;}

这两个都在 NLL 之前失败并在之后工作.

This code works:

let stdin = std::io::stdin();
let mut rdr = csv::Reader::from_reader(stdin);
let mut hmap = HashMap::<String, u64>::new();

rdr.records()
    .map(|r| r.unwrap())
    .fold((), |_, item| {
        // TODO: Is there a way not to have to copy item[col] every time?
        let counter = hmap.entry(item[col].to_string()).or_insert(0);
        *counter += 1;
    });

This code fails with the message: "cannot move out of acc because it is borrowed"

let stdin = std::io::stdin();
let mut rdr = csv::Reader::from_reader(stdin);
let hmap = rdr.records()
    .map(|r| r.unwrap())
    .fold(HashMap::<String, u64>::new(), |mut acc, item| {
        // TODO: Is there a way not to have to copy item[col] every time?
        let counter = acc.entry(item[col].to_string()).or_insert(0);
        *counter += 1;
        acc
    });

解决方案

You cannot return acc from the closure because you have a mutable borrow to it that still exists (counter).

This is a limitation of the Rust compiler (specifically the borrow checker). When non-lexical lifetimes are enabled, your original code will work:

#![feature(nll)]

use std::collections::HashMap;

fn main() {
    let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
        let counter = acc.entry("foo".to_string()).or_insert(0);
        *counter += 1;
        acc
    });

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

Before NLL, the compiler is overly conservative about how long a borrow will last. To work around this, you can introduce a new scope to constrain the mutable borrow:

use std::collections::HashMap;

fn main() {
    let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
        {
            let counter = acc.entry("foo".to_string()).or_insert(0);
            *counter += 1;
        }
        acc
    });

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

You can also prevent the borrow from lasting beyond the line it's needed in:

use std::collections::HashMap;

fn main() {
    let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
        *acc.entry("foo".to_string()).or_insert(0) += 1;
        acc
    });

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

I assumed Rust would know that counter would go out of scope once acc was returned

This is understandable and relates to the non-lexical lifetimes discussion. The "good" news is that Rust is being consistent about how references work when the thing being referenced moves. In this case, you are moving the accumulator into an "output slot". You can see this with plain functions as well:

fn foo(mut s: Vec<u8>) -> Vec<u8> {
    let borrow = &mut s[0];
    s
}

fn main() {}

But really, it's the same as moving a referred-to variable at all:

fn main() {
    let mut s = Vec::<u8>::new();
    let borrow = &mut s[0];
    let s2 = s;
}

Both of these fail before NLL and work afterwards.

这篇关于如何使用 HashMap 作为累加器进行折叠?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆