我可以从 Vector 构建 HashMap 并在使用功能方式构建时进行修改吗?

Can I construct HashMap from Vector and modify while constructing with functional way?

这是exercise on the exercism

我只是想学习函数式方法。

use std::collections::HashMap;

pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {
    let mut words: HashMap<&str, i32> = HashMap::new();

    magazine.iter().map(|&w|
        words.entry(w)
            .and_modify(|e| *e += 1)
            .or_insert(1)
    );

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

    false
}

但是我遇到了这个奇怪的错误并用谷歌搜索但我无法解决。
我明白这样是不行的。

我想知道正确的方法。
谢谢

error: captured variable cannot escape `FnMut` closure body
  --> src/lib.rs:11:9
   |
8  |       let mut words: HashMap<&str, i32> = HashMap::new();
   |           --------- variable defined here
9  | 
10 |       let mut t = magazine.iter().map(|&w|
   |                                          - inferred to be a `FnMut` closure
11 |           words.entry(w)
   |           ^----
   |           |
   |  _________variable captured here
   | |
12 | |             .and_modify(|e| *e += 1)
13 | |             .or_insert(1)
   | |_________________________^ returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

error: aborting due to previous error

error: could not compile `magazine_cutout`

To learn more, run the command again with --verbose.

您的代码的一个问题是 Iterator::map() is lazy and converts the iterator to another iterator without actually iterating over either. Because of that ending the expression with map() is not very useful, you need to do something that will exhaust the iterator. If you want to do it the functional way, you probably want for_each().

另一个问题是 Entry::or_insert() return 是对 inserted/retrieved 值的可变引用。这通常用于链接修改值的操作,例如 or_insert(vec![]).push(item)。您的闭包不以 ; 结尾,因此它的 return 值是由 or_insert() 编辑的 return 引用。 Rust 将这样的 map() 调用解释为打算将单词上的迭代器转换为对其计数的可变引用上的迭代器。然后你就可以自由地对这些引用做任何你想做的事,也许将它们收集在一个向量中。这当然是一个大问题,因为您不允许同时对散列映射中的任何内容有一个以上的可变引用。这就是借用检查器抱怨引用从闭包中泄漏的原因。要解决这个问题,只需添加大括号并使用 ;,这样闭包 returns ()(顺便说一下,这是 for_each() 的唯一有效 return 类型).

这样编译:

use std::collections::HashMap;

pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {
    let mut words: HashMap<&str, i32> = HashMap::new();

    magazine.iter().for_each(|&w| {
        words.entry(w).and_modify(|e| *e += 1).or_insert(1);
    });

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

    false
}

Playground

正如其他人在评论中指出的那样,一种更实用的方法是使用 Iterator::fold(),它不需要对 hashmap 进行变异捕获。

如果您真的想要实用方式,您应该这样做:

use std::collections::HashMap;
fn main() {
    let s = "aasasdasdasdasdasdasdasdfesrewr";
    let map = s.chars().fold(HashMap::new(), |mut acc, c| {
        acc.entry(c).and_modify(|x| *x += 1).or_insert(1i32);
        acc
    });
    println!("{:?}", map);
}

函数式编程是一种解决问题的方法。这与语法无关。使用 map 修改外部可变 HashMap 不是函数式编程。这只是对 map 的滥用(映射应该没有副作用)。 for_each 没有任何功能。它只是 for...in 的另一种语法(在大多数情况下,IMO 是一种较差的语法)。

从广义上讲,函数式编程避免了变异,并以递归方式而不是循环方式思考问题。 Ömer Erden 的解决方案很好,因为它将突变封装在折叠内,但它基本上仍然是一个花哨的循环。没有太多的“功能性”。

思考这个问题的一种实用方法是递归。对两个列表中的单词进行排序。然后核心机制是:查看每个列表中的第一个单词。如果它们匹配,则递归每个列表中的下一个单词。如果他们不这样做,则在相同的目标列表和源列表中的下一个单词上递归。如果目标列表为空:成功。如果源列表为空:失败。请注意,那里从来没有“计数”步骤,也没有 HashMap。所以我跳过你的直接问题并专注于解决整个问题(因为你说你想探索功能方法)。

第一步是对单词进行排序。 std 中没有非变异 sorted 方法,但 IterTools 中有一个。尽管如此,我还是可以做一个简单的(非常草率和特殊的)。

fn sorted<'a>(items: &[&'a str]) -> Vec<&'a str> {
    let mut v = Vec::from(items);
    v.sort();
    v
}

请注意,此功能在内部不是“功能性的”。但它提供了一个功能接口。这在 FP 中很常见。函数式方法 有时 比 imperative/mutating 方法慢。我们可以通过封装mutation在提升性能的同时保持FP的价值

但是有了它,我可以构建一个功能齐全的解决方案。注意缺少任何 mut.

pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {

    // source and goal are sorted
    fn f(source: &[&str], goal: &[&str]) -> bool {

        // Split the source and goal into their heads and tails
        // Consider just the first elements.
        match (source.split_first(), goal.split_first()) {
            (_, None) => true,          // Can make nothing from anything

            (None, Some(_)) => false,   // Can't make something from nothing

            (Some((s, s_rest)), Some((g, g_rest))) => {
                match s.cmp(g) {
                    // If they match, then recurse on the tails
                    Ordering::Equal => f(s_rest, g_rest),

                    // If source < goal, they may match eventually, recurse with the next source element
                    Ordering::Less => f(s_rest, goal),

                    // If source > goal, it'll never work
                    Ordering::Greater => false,
                }
            }
        }
    }

    // Sort the initial lists
    let source = sorted(magazine);
    let goal = sorted(note);

    // And kick it off and return its result
    f(&source[..], &goal[..])
}

这是一个非常实用的解决问题的方法,可以作为教科书的例子。但请注意,在任何地方都没有单一的映射、缩减、折叠或过滤。这些是函数式编程中非常重要的工具,但它们并不是函数式 的意思

它不是很好的 Rust。如果这些列表很长,那么这可能会使堆栈崩溃,因为 Rust 没有尾调用优化(这是使递归真正可行的关键优化)。

但是,

递归总是可以变成一个循环。所以以少量可见突变为代价,这可以被重写。这不是递归调用 f(...),而是更改 sourcegoal 并循环。

pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {
    let mut source = &sorted(magazine)[..];
    let mut goal = &sorted(note)[..];

    // source and goal are sorted
    loop {
        // Split the source and goal into their heads and tails
        match (source.split_first(), goal.split_first()) {
            (_, None) => return true,          // Can make nothing from anything

            (None, Some(_)) => return false,   // Can't make something from nothing

            (Some((s, s_rest)), Some((g, g_rest))) => {
                match s.cmp(g) {
                    // If they match, then recurse on the tails
                    Ordering::Equal => {
                        source = s_rest;
                        goal = g_rest;
                        continue;
                    }

                    // If source < goal, they may match eventually, recurse with the next source element
                    Ordering::Less => {
                        source = s_rest;
                        continue;
                    }

                    // If source > goal, it'll never work
                    Ordering::Greater => return false,
                }
            }
        }
    }
}

对于 Ömer 在下面的评论,这就是您如何以函数式方式创建 HashMap 本身。这需要 +nightly 用于 GroupBy。

#![feature(slice_group_by)]
use std::iter::FromIterator;

fn word_count<'a>(strings: &[&'a str]) -> HashMap<&'a str, usize> {
    let sorted_strings = sorted(strings);
    let groups = sorted_strings
        .group_by(|a, b| a == b)
        .map(|g| (g[0], g.len()));
    HashMap::from_iter(groups)
}

我不担心这里仔细的生命周期管理。我只关注如何在 FP 中思考。这种方法对字符串进行排序,然后按相等性对字符串进行分组,然后将这些组映射到“字符串”和“副本数”的元组中。然后将该元组列表转换为 HashMap。不需要任何可变变量。