| #![cfg(feature = "std")] |
| use core::hash::Hash; |
| use std::collections::HashMap; |
| |
| /// Multiset container inspired by Python's `collections.Counter`. |
| pub struct Counter<K> { |
| map: HashMap<K, usize>, |
| } |
| |
| impl<K> Counter<K> |
| where |
| K: Hash + Eq, |
| { |
| /// make an empty Counter |
| pub fn new() -> Counter<K> { |
| Counter { |
| map: HashMap::new(), |
| } |
| } |
| |
| /// Create a counter from a sequence. |
| pub fn from_iter<I>(iter: I) -> Counter<K> |
| where |
| I: IntoIterator<Item = K>, |
| { |
| let mut counter = Counter::new(); |
| counter.update(iter); |
| counter |
| } |
| |
| /// Merge items from a sequence into the Counter |
| pub fn update<I>(&mut self, iter: I) |
| where |
| I: IntoIterator<Item = K>, |
| { |
| for item in iter { |
| let entry = self.map.entry(item).or_insert(0); |
| *entry += 1; |
| } |
| } |
| |
| // How many items there are in total in the Counter. |
| pub fn count(&self) -> usize { |
| self.map.values().sum() |
| } |
| |
| /// Unique elements in the set |
| pub fn keys(&self) -> impl Iterator<Item = &K> { |
| self.map.keys() |
| } |
| |
| /// The count of things without the things |
| pub fn values(&self) -> impl Iterator<Item = &usize> { |
| self.map.values() |
| } |
| |
| /// . |
| pub fn get(&self, key: &K) -> Option<&usize> { |
| self.map.get(key) |
| } |
| |
| /// Merge two counters together. |
| pub fn merge<'a>(&'a self, rhs: &'a Counter<K>) -> Counter<&'a K> { |
| let mut result: HashMap<&K, usize> = HashMap::new(); |
| for (key, lhs_count) in &self.map { |
| let rhs_count = rhs.map.get(key).unwrap_or(&0); |
| result.insert(key, *lhs_count + rhs_count); |
| } |
| for (key, rhs_count) in &rhs.map { |
| if !self.map.contains_key(key) { |
| result.insert(key, *rhs_count); |
| } |
| } |
| Counter { map: result } |
| } |
| |
| /// How many there are common items in the given multisets. |
| pub fn intersect_count(&self, rhs: &Counter<K>) -> usize { |
| let mut result = 0; |
| for (key, lhs_count) in &self.map { |
| if let Some(rhs_count) = rhs.map.get(key) { |
| result += lhs_count.min(rhs_count); |
| } |
| } |
| result |
| } |
| |
| /// How many there are items in total in both multisets. |
| pub fn union_count(&self, rhs: &Counter<K>) -> usize { |
| let mut result = 0; |
| for (key, lhs_count) in &self.map { |
| let rhs_count = rhs.map.get(key).unwrap_or(&0); |
| result += lhs_count.max(rhs_count); |
| } |
| for (key, rhs_count) in &rhs.map { |
| if !self.map.contains_key(key) { |
| result += rhs_count; |
| } |
| } |
| result |
| } |
| |
| /// How many there are item in left that aren't in the right |
| pub fn diff_count(&self, rhs: &Counter<K>) -> usize { |
| let mut result = 0; |
| for (key, lhs_count) in &self.map { |
| let rhs_count = rhs.map.get(key).unwrap_or(&0); |
| if lhs_count > rhs_count { |
| result += lhs_count - rhs_count; |
| } |
| } |
| result |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use assert2::assert; |
| use rstest::rstest; |
| |
| pub fn eq<K: Hash + Eq>(lhs: &Counter<K>, rhs: &Counter<K>) -> bool { |
| for (key, lhs_count) in &lhs.map { |
| if let Some(rhs_count) = rhs.map.get(key) { |
| if lhs_count != rhs_count { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } |
| for (key, rhs_count) in &rhs.map { |
| if let Some(lhs_count) = lhs.map.get(key) { |
| if lhs_count != rhs_count { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } |
| true |
| } |
| |
| #[rstest] |
| fn smoke() { |
| let c1 = Counter::from_iter(1..=5); |
| let c2 = Counter::from_iter(3..=7); |
| assert!(eq(&c1, &c1)); |
| assert!(!eq(&c1, &c2)); |
| // assert!(eq(c1.intersect(&c2), &Counter::from_iter(3..=5))); |
| assert!(c1.intersect_count(&c2) == 3); |
| assert!(c1.union_count(&c2) == 7); |
| } |
| } |