7

I would like to count the occurrences of each letter in a String. The goal is to build up a HashMap<char,i32> where the keys are all the characters in the string and the values are counts of occurrences.

Assume I am looping over char values from a String or an input file. For each char, if it has not yet been encountered, I need to add it to the HashMap as a new key with a value of 1, but if it has been previously seen, I need to increment the value.

Here is code that works. Bear with me, I am very new to Rust:

use std::collections::HashMap;

fn main() {
    let mut letter_counts: HashMap<char,i32> = HashMap::new();

    let input_string = "Hello, world!";
    let char_vec: Vec<char> = input_string.to_lowercase().chars().collect();
    for c in char_vec {
        if let Some(x) = letter_counts.get_mut(&c) {
            *x = *x + 1;
        } else {
            letter_counts.insert(c,1);
        }
    }
    println!("{:?}",letter_counts);
}

What I'd like to know is, is there an idiomatic way to do this in Rust? By idiomatic, I mean is there a standard library type (like Python's defaultdict), or a method on HashMap (like Java's HashMap.computeIfAbsent) that would make this simpler, clearer, and/or less error-prone than hand-coding the algorithm as I have done?

workerjoe
  • 2,421
  • 1
  • 26
  • 49
  • There is a [frequency_hashmap crate](https://docs.rs/frequency-hashmap/1.1.0/frequency_hashmap/index.html) (I haven't used it). – John Kugelman Oct 02 '20 at 21:20
  • @JohnKugelman Similar question, but the asker accepted an answer which makes the code "shorter" rather than idiomatic. Sort of a "code golf" answer. – workerjoe Oct 02 '20 at 21:23

1 Answers1

12

This may be a little easier for what you want to do and maybe a little more idiomatic, if you use the Entry interface:

use std::collections::HashMap;

fn main() {
    let mut letter_counts: HashMap<char,i32> = HashMap::new();

    let input_string = "Hello, world!";
    let char_vec: Vec<char> = input_string.to_lowercase().chars().collect();
    for c in char_vec {
        *letter_counts.entry(c).or_insert(0) += 1;
    }
    println!("{:?}",letter_counts);
}

That lets you create the entry if it doesn't exist and at the same time modify it.

If you want something a little more functional, you could do this:

use std::collections::HashMap;

fn main() {
    let input_string = "Hello, world!";
    let letter_counts: HashMap<char, i32> =
        input_string
            .to_lowercase()
            .chars()
            .fold(HashMap::new(), |mut map, c| {
                *map.entry(c).or_insert(0) += 1;
                map
            });
    println!("{:?}", letter_counts);
}

That uses a fold to accumulate the items.

If you're looking for a standard library function that counts the frequency of an item, there isn't one. The functional approach is elegant enough that I personally don't see that as a fault, and it's the approach I would typically use in this case for most normal, idiomatic Rust code. Using iterators is very common in Rust.

As others have mentioned, there are certainly alternative approaches that are more specialized for certain cases.

bk2204
  • 64,793
  • 6
  • 84
  • 100