Skip to main content

Crate hash_histogram

Crate hash_histogram 

Source
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)]);

Structs§

HashHistogram

Traits§

CounterType
KeyType
RandomRanger

Functions§

mode
mode_values