10

I have a collection of items, with repetitions, and I want to create a HashMap that has the items as keys, and the count of how many times they appear in the original collection.

The specific example I'm working on is a string where I want to count how many times each character appears.

I can do something like this:

fn into_character_map(word: &str) -> HashMap<char, i32> {
    let mut characters = HashMap::new();

    for c in word.chars() {
        let entry = characters.entry(c).or_insert(0);
        *entry += 1;
    }

    characters
}

But I was wondering if there's a more elegant solution. I was thinking of using collect(), but it doesn't maintain state between items, so doesn't seem to support what I need.

This came up as I was writing my solution to the 'Anagram' problem on Exercism.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Hershi
  • 2,080
  • 2
  • 19
  • 24

2 Answers2

19

It's dubious if it's more elegant, but folding using a HashMap requires fewer lines:

fn into_character_map(word: &str) -> HashMap<char, i32> {
    word.chars().fold(HashMap::new(), |mut acc, c| {
        *acc.entry(c).or_insert(0) += 1;
        acc
    })
}
Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
1

If you need to count others type of items, you can create a Counter struct

use std::collections::HashMap;
use std::hash::Hash;
use std::iter::FromIterator;

#[derive(Debug)]
pub struct Counter<K: Hash + Eq> {
    map: HashMap<K, usize>
}

impl<K: Hash + Eq> Counter<K> {
    pub fn new() -> Self {
        Counter { map: HashMap::new() }
    }

    pub fn get(&self, key: &K) -> usize {
        if let Some(v) = self.map.get(key) {
            *v
        } else {
            0
        }
    }

    pub fn get_mut(&mut self, key: K) -> &mut usize {
        &mut *self.map.entry(key).or_insert(0)
    }

    pub fn inc(mut self, key: K) -> Self {
        *self.get_mut(key) += 1;
        self
    }
}

impl<K: Hash + Eq> FromIterator<K> for Counter<K> {
    fn from_iter<T>(iterator: T) -> Self where T: IntoIterator<Item=K> {
        iterator.into_iter().fold(Counter::new(), Counter::inc)
    }
}

fn main() {
    println!("{:?}", Counter::from_iter("test".chars()));
    println!("{:?}", Counter::from_iter(vec![3, 3, 2, 3].into_iter()));
}

Output

Counter { map: {'e': 1, 't': 2, 's': 1} }
Counter { map: {2: 1, 3: 3} }
malbarbo
  • 10,717
  • 1
  • 42
  • 57