Creating a HashMap in Rust using the fold iterator method

Viewed 315

I am trying to create a word count HashMap from an array of words using Entry and iterators in Rust. When I try as below,

use std::collections::HashMap;
fn main () {
    let corpus = ["foo", "bar", "bar", "grok", "blah", "foo", "bar"];
    let mut word_count = corpus.iter().fold(HashMap::new(), |mut acc, word| *acc.entry(word).or_insert(0) += 1);
    println!("{:?}", word_count);
}

I get the error: expected struct HashMap, found (), type mismatch.

I can get it to work by creating a new scope and explicitly returning the accumulator as:

use std::collections::HashMap;
fn main () {
    let corpus = ["foo", "bar", "bar", "grok", "blah", "foo", "bar"];
    let mut word_count = corpus.iter().fold(HashMap::new(), |mut acc, word|{
        *acc.entry(word).or_insert(0) += 1;
        acc
    });
    println!("{:?}", word_count);
}

and I get the expected {"foo": 2, "bar": 3, "blah": 1, "grok": 1}.

Usually fold returns the last state of the accumulator, so I was expecting the first approach to work, and am unclear why it does not. Would really appreciate some clarification and any suggestions on alternative approaches (using iterators).

1 Answers

Like explained in the comment from @PitaJ, *acc.entry(word).or_insert(0) += 1 has the type () while the compiler expects fold()'s callback to return the next state each time (it is sometimes called reduce() in other languages, e.g. JavaScript; in Rust; fold() allows you to specify the start value while reduce() takes it from the first item in the iterator).

Because of this, I feel like it is more appropriate use case for a loop, since you don't need to return a new state but to update the map:

let mut word_count = HashMap::new();
for word in &corpus {
    *word_count.entry(word).or_insert(0) += 1;
}

Playground.

However, if you want, there is a method in the itertools crate that does exactly that: counts():

let word_count = corpus.iter().counts();

Playground.

Related