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