Skip to content

Instantly share code, notes, and snippets.

@Rudxain
Last active June 24, 2024 23:10
Show Gist options
  • Save Rudxain/f9c8d3764e342b8234aaaeb62e2190f5 to your computer and use it in GitHub Desktop.
Save Rudxain/f9c8d3764e342b8234aaaeb62e2190f5 to your computer and use it in GitHub Desktop.
Counts how many instances of each value are present in an iterable, implemented in Rust and TypeScript
export const count_values = <T,>(it: Iterable<T>) => {
const counts: Map<T, bigint> = new Map
for (const x of it)
counts.set(x, (counts.get(x) ?? 0n) + 1n)
return counts
}
use std::{collections::HashMap, hash::Hash};
use num_bigint::BigUint; // 0.4
use num_integer::Integer; // 0.1
use num_traits::One; // 0.2
/*
`HashMap` is allocated inside the fns,
because further allocs will eventually happen,
as it's impossible to infer the final size before iterating.
This is the same reason why we use `new` instead of `with_capacity`.
There's no benefit in forcing the caller to pre-alloc.
In fact, if the caller passes an existing `HashMap`,
it may be "dirty" (intentionally or not),
making the fns impure.
`T` is preferred over `&'a T`,
because it allows the caller to choose
any `HashMap` key type
*/
/// `ExactSizeIterator` is necessary
/// to guarantee the counters won't overflow
pub fn count_values_sized<I, T>(it: I) -> HashMap<T, usize>
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
T: Eq + Hash,
{
let mut counts = HashMap::new();
for x in it {
counts
.entry(x)
// why `checked_add` causes inference to totally fail?
.and_modify(|c: &mut usize| *c = (*c).checked_add(1).unwrap_or_else(|| unreachable!()))
.or_insert(1);
}
counts
}
pub fn count_values_unbounded<I, T>(it: I) -> HashMap<T, BigUint>
where
I: IntoIterator<Item = T>,
T: Eq + Hash,
{
let mut counts = HashMap::new();
for x in it {
counts
.entry(x)
.and_modify(|c: &mut BigUint| c.inc())
.or_insert(BigUint::one());
}
counts
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment