Frequencies

tl;dr frequencies ftw

A function I frequently find useful takes a list of values, whatever they may be, and returns a map of those values to the number of times they appear in the list. Concretely, it takes a list like this:

["a", "b", "c", "b", "a", "d", "a"]

And returns a dictionary like this:

{ "a": 3, "b": 2, "c": 1, "d": 1 }

Let’s call such a hypothetical function frequencies, and take a look at how we might implement it in a few languages.

Ruby

def frequencies(xs)
  freqs = Hash.new { |hash, key| hash[key] = 0 }
  xs.each_with_object(freqs) do |x, freqs|
    freqs[x] += 1
  end
end

strs = %w[a b c b a d a]
p frequencies(strs)

Not bad! I love Ruby’s facility for providing a block to set bthe default value for a hash entry.

Update

I guess Ruby’s authors realized how useful frequencies is, because it’s been added in Ruby 2.7! (In release candidates at the time of writing.) In Ruby, it’s called tally. ❤️

%w[a b c b a d a].tally
# => {"a"=>3, "b"=>2, "c"=>1, "d"=>1}

Go

package main

import "fmt"

func frequencies(things []interface{}) map[interface{}]int {
	freqs := make(map[interface{}]int)
	for _, thing := range things {
		freqs[thing] += 1
	}
	return freqs
}

func main() {
	strs := []string{"a", "b", "c", "b", "a", "d", "a"}
	var whatevs []interface{}
	for _, str := range strs {
		whatevs = append(whatevs, str)
	}
	fmt.Printf("%v\n", frequencies(whatevs))
}

Not as terse as Ruby, but I wouldn’t expect that from Go. A thing I like is leaning on the default value of the int type; I don’t need to initialize every value to 0 before incrementing the count.

Rust

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

fn frequencies<T: Copy + Eq + Hash>(xs: &Vec<T>) -> HashMap<T, i32> {
    let mut freqs = HashMap::new();
    for x in xs {
        let count = freqs.get(x).unwrap_or(&0) + 1;
        freqs.insert(*x, count);
    }
    freqs
}

fn main() {
    let strs = vec!["a", "b", "c", "b", "a", "d", "a"];
    println!("{:?}", frequencies(&strs));
}

A little shorter than Go, and of course it handles generics a little more robustly than Go. However, this version will only work on a homogeneous collection. (All the other examples here, Go’s included, will work with whatever you give ’em.) There are probably ways to handle that in Rust, but my burgeoning Rust-fu is not up to the task.

Clojure

(println (frequencies ["a" "b" "c" "b" "a" "d" "a"]))

Oh, look at that: frequencies is built into Clojure. Was this entire blog post elaborate theater, motivated by building to this cherry-picked, trollish point? You be the judge.

Tools Used

Clojure
1.10.1
Go
1.13.1
Ruby
2.6.3p62
Rust
1.39.0