Expand description
§Overview
This library provides struct HashHistogram. It wraps HashMap to provide a
straightforward histogram facility.
use hash_histogram::HashHistogram;
// Record and inspect histogram counts.
let mut h = HashHistogram::new();
for s in ["a", "b", "a", "b", "c", "b", "a", "b"].iter() {
h.bump(s);
}
for (s, c) in [("a", 3), ("b", 4), ("c", 1), ("d", 0)].iter() {
assert_eq!(h.count(s), *c);
}
assert_eq!(h.total_count(), 8);
// Iteration
let mut iterated: Vec<(&str,usize)> = h.iter().map(|(s,c)| (*s, *c)).collect();
iterated.sort();
assert_eq!(iterated, vec![("a", 3), ("b", 4), ("c", 1)]);
// Iterating over counts only
let mut counts: Vec<usize> = h.counts().collect();
counts.sort();
assert_eq!(counts, vec![1, 3, 4]);
// Ranked ordering
assert_eq!(h.ranking(), vec!["b", "a", "c"]);
// Ranked ordering with counts
assert_eq!(h.ranking_with_counts(), vec![("b", 4), ("a", 3), ("c", 1)]);
// Mode
assert_eq!(h.mode(), Some("b"));
// Incrementing larger counts
for (s, count) in [("a", 2), ("b", 3), ("c", 10), ("d", 5)].iter() {
h.bump_by(s, *count);
}
for (s, count) in [("a", 5), ("b", 7), ("c", 11), ("d", 5)].iter() {
assert_eq!(h.count(s), *count);
}Counts can be of any numerical type, including floating-point values.
use hash_histogram::HashHistogram;
let mut h = HashHistogram::new();
for (s, weight) in [("a", 0.25), ("b", 0.5), ("a", 0.3), ("c", 0.4), ("b", 0.1)].iter() {
h.bump_by(s, *weight);
}
for (s, total) in [("a", 0.55), ("b", 0.6), ("c", 0.4)].iter() {
assert_eq!(h.count(s), *total);
}
assert_eq!(h.ranking_with_counts(), vec![("b", 0.6), ("a", 0.55), ("c", 0.4)]);Histograms can be normalized so that all their counts add up to a target value.
In the doc-tests below, we use the Decimal type from the
rust_decimal crate to enable reliable assertions
of equality. The example would work similarly with f64 counts.
use hash_histogram::HashHistogram;
use rust_decimal_macros::dec;
let mut h = HashHistogram::new();
for (s, weight) in [("a", dec!(0.6)), ("b", dec!(2.6)), ("c", dec!(1.0)), ("a", dec!(0.6)), ("c", dec!(0.8)), ("b", dec!(0.4))].iter() {
h.bump_by(s, *weight);
}
assert_eq!(h.ranking_with_counts(), vec![("b", dec!(3.0)), ("c", dec!(1.8)), ("a", dec!(1.2))]);
h.normalize(dec!(1.0));
assert_eq!(h.ranking_with_counts(), vec![("b", dec!(0.5)), ("c", dec!(0.3)), ("a", dec!(0.2))]);By selecting suitable target values, normalization can be useful with integer counts as well.
use hash_histogram::HashHistogram;
let mut h = HashHistogram::new();
for s in ["a", "b", "a", "b", "c", "b", "a", "b", "c", "d"].iter() {
h.bump(s);
}
h.normalize(100);
assert_eq!(h.ranking_with_counts(), vec![("b", 40), ("a", 30), ("c", 20), ("d", 10)]);Calculating the mode is sufficiently useful on its own that the mode() and mode_values()
functions are provided. Use mode() with iterators containing references to values in
containers, and mode_values() for iterators that own the values they return.
They each use a HashHistogram to calculate a mode from an object of any type that has the
IntoIterator trait:
use hash_histogram::{mode, mode_values};
let chars = vec!["a", "b", "c", "d", "a", "b", "a"];
// Directly passing the container.
assert_eq!(mode(&chars).unwrap(), "a");
// Passing an iterator from the container.
assert_eq!(mode(chars.iter()).unwrap(), "a");
// Use mode_values() when using an iterator generating values in place.
let nums = vec![100, 200, 100, 200, 300, 200, 100, 200];
assert_eq!(mode_values(nums.iter().map(|n| n + 1)).unwrap(), 201);The counts from a HashHistogram can be interpreted as weights for a probability
distribution. The pick_random_key() method will select a key with a probability
derived from the counts.
use hash_histogram::HashHistogram;
let distro: HashHistogram<&str, usize> = ["a", "a", "a", "a", "b", "b"].iter().collect();
for _ in 0..100 {
let mut counter: HashHistogram<&str, usize> = HashHistogram::default();
for _ in 0..10000 {
counter.bump(&distro.pick_random_key());
}
assert!(counter.count(&"a")> 6000 && counter.count(&"a") < 7000);
assert!(counter.count(&"b")> 3000 && counter.count(&"b") < 4000);
}HashHistogram supports common Rust data structure operations. It implements the
FromIterator and Extend traits, and derives serde:
use hash_histogram::HashHistogram;
// Initialization from an iterator:
let mut h1: HashHistogram<isize> = [100, 200, 100, 200, 300, 200, 100, 200].iter().collect();
let mut h2: HashHistogram<isize, usize> = [(100, 2), (200, 3), (300, 1), (100, 1), (200, 1)].iter().copied().collect();
let expected_ranking = vec![(200, 4), (100, 3), (300, 1)];
assert_eq!(h1.ranking_with_counts(), expected_ranking);
assert_eq!(h2.ranking_with_counts(), expected_ranking);
// Extension from an iterator
h1.extend([200, 300, 200, 400, 200].iter());
h2.extend([(200, 1), (300, 1), (400, 1), (200, 2)].iter());
let expected_ranking_extended = vec![(200, 7), (100, 3), (300, 2), (400, 1)];
assert_eq!(h1.ranking_with_counts(), expected_ranking_extended);
assert_eq!(h2.ranking_with_counts(), expected_ranking_extended);
// Serialization
let serialized = serde_json::to_string(&h1).unwrap();
// Deserialization
let deserialized: HashHistogram<isize> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, h1);We can combine histograms of the same type using the += operator:
use hash_histogram::HashHistogram;
let mut h1: HashHistogram<&str> = ["a", "b", "c", "d", "b", "d", "c", "b", "d"].iter().collect();
let h2: HashHistogram<&str> = ["e", "b", "c", "d", "d", "e"].iter().collect();
h1 += &h2;
assert_eq!(h1.ranking_with_counts(), vec![("d", 5), ("b", 4), ("c", 3), ("e", 2), ("a", 1)]);