diff --git a/src/impls.rs b/src/impls.rs new file mode 100644 index 0000000..afde736 --- /dev/null +++ b/src/impls.rs @@ -0,0 +1,12 @@ +mod add_iterable; +mod add_self; +mod create; +mod deref; +mod extend; +mod from_iterator; +mod index; +mod intersection; +mod into_iterator; +mod sub_iterable; +mod sub_self; +mod union; diff --git a/src/impls/add_iterable.rs b/src/impls/add_iterable.rs new file mode 100644 index 0000000..5ea08d2 --- /dev/null +++ b/src/impls/add_iterable.rs @@ -0,0 +1,55 @@ +use crate::Counter; + +use num_traits::{One, Zero}; + +use std::hash::Hash; +use std::ops::{Add, AddAssign}; + +impl Add for Counter +where + I: IntoIterator, + T: Hash + Eq, + N: AddAssign + Zero + One, +{ + type Output = Self; + /// Consume `self` producing a `Counter` like `self` updated with the counts of + /// the elements of `I`. + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let counter = Counter::init("abbccc".chars()); + /// + /// let new_counter = counter + "aeeeee".chars(); + /// let expected: HashMap = [('a', 2), ('b', 2), ('c', 3), ('e', 5)] + /// .iter().cloned().collect(); + /// assert_eq!(new_counter.into_map(), expected); + /// ``` + fn add(mut self, rhs: I) -> Self::Output { + self.update(rhs); + self + } +} + +impl AddAssign for Counter +where + I: IntoIterator, + T: Hash + Eq, + N: AddAssign + Zero + One, +{ + /// Directly add the counts of the elements of `I` to `self`. + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut counter = Counter::init("abbccc".chars()); + /// + /// counter += "aeeeee".chars(); + /// let expected: HashMap = [('a', 2), ('b', 2), ('c', 3), ('e', 5)] + /// .iter().cloned().collect(); + /// assert_eq!(counter.into_map(), expected); + /// ``` + fn add_assign(&mut self, rhs: I) { + self.update(rhs); + } +} diff --git a/src/impls/add_self.rs b/src/impls/add_self.rs new file mode 100644 index 0000000..ed2b38a --- /dev/null +++ b/src/impls/add_self.rs @@ -0,0 +1,62 @@ +use crate::Counter; + +use num_traits::Zero; + +use std::hash::Hash; +use std::ops::{Add, AddAssign}; + +impl Add for Counter +where + T: Clone + Hash + Eq, + N: AddAssign + Zero, +{ + type Output = Counter; + + /// Add two counters together. + /// + /// `out = c + d;` -> `out[x] == c[x] + d[x]` for all `x` + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// let e = c + d; + /// + /// let expect = [('a', 4), ('b', 3)].iter().cloned().collect::>(); + /// assert_eq!(e.into_map(), expect); + /// ``` + fn add(mut self, rhs: Counter) -> Self::Output { + self += rhs; + self + } +} + +impl AddAssign for Counter +where + T: Hash + Eq, + N: Zero + AddAssign, +{ + /// Add another counter to this counter. + /// + /// `c += d;` -> `c[x] += d[x]` for all `x` + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// c += d; + /// + /// let expect = [('a', 4), ('b', 3)].iter().cloned().collect::>(); + /// assert_eq!(c.into_map(), expect); + /// ``` + fn add_assign(&mut self, rhs: Self) { + for (key, value) in rhs.map { + let entry = self.map.entry(key).or_insert_with(N::zero); + *entry += value; + } + } +} diff --git a/src/impls/create.rs b/src/impls/create.rs new file mode 100644 index 0000000..552b329 --- /dev/null +++ b/src/impls/create.rs @@ -0,0 +1,33 @@ +use crate::Counter; + +use num_traits::Zero; + +use std::collections::HashMap; +use std::hash::Hash; + +impl Counter +where + T: Hash + Eq, + N: Zero, +{ + /// Create a new, empty `Counter` + pub fn new() -> Self { + Counter { + map: HashMap::new(), + zero: N::zero(), + } + } +} + +impl Default for Counter +where + T: Hash + Eq, + N: Default, +{ + fn default() -> Self { + Self { + map: Default::default(), + zero: Default::default(), + } + } +} diff --git a/src/impls/deref.rs b/src/impls/deref.rs new file mode 100644 index 0000000..464c980 --- /dev/null +++ b/src/impls/deref.rs @@ -0,0 +1,26 @@ +use crate::Counter; + +use std::collections::HashMap; +use std::hash::Hash; +use std::ops::{Deref, DerefMut}; + +type CounterMap = HashMap; + +impl Deref for Counter +where + T: Hash + Eq, +{ + type Target = CounterMap; + fn deref(&self) -> &CounterMap { + &self.map + } +} + +impl DerefMut for Counter +where + T: Hash + Eq, +{ + fn deref_mut(&mut self) -> &mut CounterMap { + &mut self.map + } +} diff --git a/src/impls/extend.rs b/src/impls/extend.rs new file mode 100644 index 0000000..a6c08b5 --- /dev/null +++ b/src/impls/extend.rs @@ -0,0 +1,77 @@ +use crate::Counter; + +use num_traits::{One, Zero}; + +use std::hash::Hash; +use std::ops::AddAssign; + +impl Extend for Counter +where + T: Hash + Eq, + N: AddAssign + Zero + One, +{ + /// Extend a `Counter` with an iterator of items. + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut counter = "abbccc".chars().collect::>(); + /// counter.extend("bccddd".chars()); + /// let expect = [('a', 1), ('b', 3), ('c', 5), ('d', 3)].iter().cloned().collect::>(); + /// assert_eq!(counter.into_map(), expect); + /// ``` + fn extend>(&mut self, iter: I) { + self.update(iter); + } +} + +impl Extend<(T, N)> for Counter +where + T: Hash + Eq, + N: AddAssign + Zero, +{ + /// Extend a counter with `(item, count)` tuples. + /// + /// The counts of duplicate items are summed. + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut counter = "abbccc".chars().collect::>(); + /// counter.extend([('a', 1), ('b', 2), ('c', 3), ('a', 4)].iter().cloned()); + /// let expect = [('a', 6), ('b', 4), ('c', 6)].iter() + /// .cloned().collect::>(); + /// assert_eq!(counter.into_map(), expect); + /// ``` + fn extend>(&mut self, iter: I) { + for (item, item_count) in iter { + let entry = self.map.entry(item).or_insert_with(N::zero); + *entry += item_count; + } + } +} + +impl<'a, T: 'a, N: 'a> Extend<(&'a T, &'a N)> for Counter +where + T: Hash + Eq + Clone, + N: AddAssign + Zero + Clone, +{ + /// Extend a counter with `(item, count)` tuples. + /// + /// You can extend a `Counter` with another `Counter`: + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut counter = "abbccc".chars().collect::>(); + /// let another = "bccddd".chars().collect::>(); + /// counter.extend(&another); + /// let expect = [('a', 1), ('b', 3), ('c', 5), ('d', 3)].iter() + /// .cloned().collect::>(); + /// assert_eq!(counter.into_map(), expect); + /// ``` + fn extend>(&mut self, iter: I) { + for (item, item_count) in iter { + let entry = self.map.entry(item.clone()).or_insert_with(N::zero); + *entry += item_count.clone(); + } + } +} diff --git a/src/impls/from_iterator.rs b/src/impls/from_iterator.rs new file mode 100644 index 0000000..aa76917 --- /dev/null +++ b/src/impls/from_iterator.rs @@ -0,0 +1,74 @@ +use crate::Counter; + +use num_traits::{One, Zero}; + +use std::hash::Hash; +use std::iter; +use std::ops::AddAssign; + +impl Counter +where + T: Hash + Eq, + N: AddAssign + Zero + One, +{ + /// Create a new `Counter` initialized with the given iterable. + pub fn init(iterable: I) -> Self + where + I: IntoIterator, + { + let mut counter = Counter::new(); + counter.update(iterable); + counter + } +} + +impl iter::FromIterator for Counter +where + T: Hash + Eq, + N: AddAssign + Zero + One, +{ + /// Produce a `Counter` from an iterator of items. This is called automatically + /// by [`Iterator::collect()`]. + /// + /// [`Iterator::collect()`]: + /// https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.collect + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let counter = "abbccc".chars().collect::>(); + /// let expect = [('a', 1), ('b', 2), ('c', 3)].iter().cloned().collect::>(); + /// assert_eq!(counter.into_map(), expect); + /// ``` + /// + fn from_iter>(iter: I) -> Self { + Counter::::init(iter) + } +} + +impl iter::FromIterator<(T, N)> for Counter +where + T: Hash + Eq, + N: AddAssign + Zero, +{ + /// Creates a counter from `(item, count)` tuples. + /// + /// The counts of duplicate items are summed. + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let counter = [('a', 1), ('b', 2), ('c', 3), ('a', 4)].iter() + /// .cloned().collect::>(); + /// let expect = [('a', 5), ('b', 2), ('c', 3)].iter() + /// .cloned().collect::>(); + /// assert_eq!(counter.into_map(), expect); + /// ``` + fn from_iter>(iter: I) -> Self { + let mut cnt = Counter::new(); + for (item, item_count) in iter { + let entry = cnt.map.entry(item).or_insert_with(N::zero); + *entry += item_count; + } + cnt + } +} diff --git a/src/impls/index.rs b/src/impls/index.rs new file mode 100644 index 0000000..c375a63 --- /dev/null +++ b/src/impls/index.rs @@ -0,0 +1,91 @@ +use crate::Counter; + +use num_traits::Zero; + +use std::borrow::Borrow; +use std::hash::Hash; +use std::ops::{Index, IndexMut}; + +impl Index<&'_ Q> for Counter +where + T: Hash + Eq + Borrow, + Q: Hash + Eq, + N: Zero, +{ + type Output = N; + + /// Index in immutable contexts. + /// + /// Returns a reference to a [`zero`] value for missing keys. + /// + /// [`zero`]: + /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero + /// + /// ``` + /// # use counter::Counter; + /// let counter = Counter::<_>::init("aabbcc".chars()); + /// assert_eq!(counter[&'a'], 2); + /// assert_eq!(counter[&'b'], 2); + /// assert_eq!(counter[&'c'], 2); + /// assert_eq!(counter[&'d'], 0); + /// ``` + /// + /// Note that the [`zero`] is a struct field but not one of the values of the inner + /// [`HashMap`]. This method does not modify any existing value. + /// + /// [`zero`]: + /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero + /// [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html + /// + /// ``` + /// # use counter::Counter; + /// let counter = Counter::<_>::init("".chars()); + /// assert_eq!(counter[&'a'], 0); + /// assert_eq!(counter.get(&'a'), None); // as `Deref>` + /// ``` + fn index(&self, key: &'_ Q) -> &N { + self.map.get(key).unwrap_or(&self.zero) + } +} + +impl IndexMut<&'_ Q> for Counter +where + T: Hash + Eq + Borrow, + Q: Hash + Eq + ToOwned, + N: Zero, +{ + /// Index in mutable contexts. + /// + /// If the given key is not present, creates a new entry and initializes it with a [`zero`] + /// value. + /// + /// [`zero`]: + /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero + /// + /// ``` + /// # use counter::Counter; + /// let mut counter = Counter::<_>::init("aabbcc".chars()); + /// counter[&'c'] += 1; + /// counter[&'d'] += 1; + /// assert_eq!(counter[&'c'], 3); + /// assert_eq!(counter[&'d'], 1); + /// ``` + /// + /// Unlike `Index::index`, the returned mutable reference to the [`zero`] is actually one of the + /// values of the inner [`HashMap`]. + /// + /// [`zero`]: + /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero + /// [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html + /// + /// ``` + /// # use counter::Counter; + /// let mut counter = Counter::<_>::init("".chars()); + /// assert_eq!(counter.get(&'a'), None); // as `Deref>` + /// let _ = &mut counter[&'a']; + /// assert_eq!(counter.get(&'a'), Some(&0)); + /// ``` + fn index_mut(&mut self, key: &'_ Q) -> &mut N { + self.map.entry(key.to_owned()).or_insert_with(N::zero) + } +} diff --git a/src/impls/intersection.rs b/src/impls/intersection.rs new file mode 100644 index 0000000..6cb9053 --- /dev/null +++ b/src/impls/intersection.rs @@ -0,0 +1,71 @@ +use crate::Counter; + +use num_traits::Zero; + +use std::hash::Hash; +use std::ops::{BitAnd, BitAndAssign}; + +impl BitAnd for Counter +where + T: Hash + Eq, + N: Ord + Zero, +{ + type Output = Counter; + + /// Returns the intersection of `self` and `rhs` as a new `Counter`. + /// + /// `out = c & d;` -> `out[x] == min(c[x], d[x])` + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// let e = c & d; + /// + /// let expect = [('a', 1), ('b', 1)].iter().cloned().collect::>(); + /// assert_eq!(e.into_map(), expect); + /// ``` + fn bitand(self, mut rhs: Counter) -> Self::Output { + use std::cmp::min; + + let mut counter = Counter::new(); + for (key, lhs_count) in self.map { + if let Some(rhs_count) = rhs.remove(&key) { + let count = min(lhs_count, rhs_count); + counter.map.insert(key, count); + } + } + counter + } +} + +impl BitAndAssign for Counter +where + T: Hash + Eq, + N: Ord + Zero, +{ + /// Updates `self` with the intersection of `self` and `rhs` + /// + /// `c &= d;` -> `c[x] == min(c[x], d[x])` + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// c &= d; + /// + /// let expect = [('a', 1), ('b', 1)].iter().cloned().collect::>(); + /// assert_eq!(c.into_map(), expect); + /// ``` + fn bitand_assign(&mut self, mut rhs: Counter) { + for (key, rhs_count) in rhs.drain() { + if rhs_count < self[&key] { + self.map.insert(key, rhs_count); + } + } + } +} diff --git a/src/impls/into_iterator.rs b/src/impls/into_iterator.rs new file mode 100644 index 0000000..634ba04 --- /dev/null +++ b/src/impls/into_iterator.rs @@ -0,0 +1,77 @@ +use crate::Counter; + +use std::hash::Hash; + +impl<'a, T, N> IntoIterator for &'a Counter +where + T: Hash + Eq, +{ + type Item = (&'a T, &'a N); + type IntoIter = std::collections::hash_map::Iter<'a, T, N>; + + fn into_iter(self) -> Self::IntoIter { + self.map.iter() + } +} + +impl IntoIterator for Counter +where + T: Hash + Eq, +{ + type Item = (T, N); + type IntoIter = std::collections::hash_map::IntoIter; + + /// Consumes the `Counter` to produce an iterator that owns the values it returns. + /// + /// # Examples + /// ```rust + /// # use counter::Counter; + /// + /// let counter: Counter<_> = "aaab".chars().collect(); + /// + /// let vec: Vec<_> = counter.into_iter().collect(); + /// + /// for (item, count) in &vec { + /// if item == &'a' { + /// assert_eq!(count, &3); + /// } + /// if item == &'b' { + /// assert_eq!(count, &1); + /// } + /// } + /// ``` + + fn into_iter(self) -> Self::IntoIter { + self.map.into_iter() + } +} + +impl<'a, T, N> IntoIterator for &'a mut Counter +where + T: Hash + Eq, +{ + type Item = (&'a T, &'a mut N); + type IntoIter = std::collections::hash_map::IterMut<'a, T, N>; + + /// Creates an iterator that provides mutable references to the counts, but keeps the keys immutable. + /// + /// # Examples + /// ```rust + /// # use counter::Counter; + /// + /// let mut counter: Counter<_> = "aaab".chars().collect(); + /// + /// for (item, count) in &mut counter { + /// if *item == 'a' { + /// // 'a' is so great it counts as 2 + /// *count *= 2; + /// } + /// } + /// + /// assert_eq!(counter[&'a'], 6); + /// assert_eq!(counter[&'b'], 1); + /// ``` + fn into_iter(self) -> Self::IntoIter { + self.map.iter_mut() + } +} diff --git a/src/impls/sub_iterable.rs b/src/impls/sub_iterable.rs new file mode 100644 index 0000000..15e5f81 --- /dev/null +++ b/src/impls/sub_iterable.rs @@ -0,0 +1,57 @@ +use crate::Counter; + +use num_traits::{One, Zero}; + +use std::hash::Hash; +use std::ops::{Sub, SubAssign}; + +impl Sub for Counter +where + I: IntoIterator, + T: Hash + Eq, + N: PartialOrd + SubAssign + Zero + One, +{ + type Output = Self; + /// Consume `self` producing a `Counter` like `self` with the counts of the + /// elements of `I` subtracted, keeping only positive values. + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let c = "aaab".chars().collect::>(); + /// let e = c - "abb".chars(); + /// + /// let expect = [('a', 2)].iter().cloned().collect::>(); + /// assert_eq!(e.into_map(), expect); + /// ``` + fn sub(mut self, rhs: I) -> Self::Output { + self.subtract(rhs); + self + } +} + +impl SubAssign for Counter +where + I: IntoIterator, + T: Hash + Eq, + N: PartialOrd + SubAssign + Zero + One, +{ + /// Directly subtract the counts of the elements of `I` from `self`, + /// keeping only items with a value greater than [`N::zero()`]. + /// + /// [`N::zero()`]: + /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut c = "aaab".chars().collect::>(); + /// c -= "abb".chars(); + /// + /// let expect = [('a', 2)].iter().cloned().collect::>(); + /// assert_eq!(c.into_map(), expect); + /// ``` + fn sub_assign(&mut self, rhs: I) { + self.subtract(rhs); + } +} diff --git a/src/impls/sub_self.rs b/src/impls/sub_self.rs new file mode 100644 index 0000000..a213527 --- /dev/null +++ b/src/impls/sub_self.rs @@ -0,0 +1,82 @@ +use crate::Counter; + +use num_traits::Zero; + +use std::hash::Hash; +use std::ops::{Sub, SubAssign}; + +impl Sub for Counter +where + T: Hash + Eq, + N: PartialOrd + PartialEq + SubAssign + Zero, +{ + type Output = Counter; + + /// Subtract (keeping only positive values). + /// + /// `out = c - d;` -> `out[x] == c[x] - d[x]` for all `x`, + /// keeping only items with a value greater than [`N::zero()`]. + /// + /// [`N::zero()`]: + /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// let e = c - d; + /// + /// let expect = [('a', 2)].iter().cloned().collect::>(); + /// assert_eq!(e.into_map(), expect); + /// ``` + fn sub(mut self, rhs: Counter) -> Self::Output { + self -= rhs; + self + } +} + +impl SubAssign for Counter +where + T: Hash + Eq, + N: PartialOrd + PartialEq + SubAssign + Zero, +{ + /// Subtract (keeping only positive values). + /// + /// `c -= d;` -> `c[x] -= d[x]` for all `x`, + /// keeping only items with a value greater than [`N::zero()`]. + /// + /// [`N::zero()`]: + /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// c -= d; + /// + /// let expect = [('a', 2)].iter().cloned().collect::>(); + /// assert_eq!(c.into_map(), expect); + /// ``` + fn sub_assign(&mut self, rhs: Self) { + for (key, value) in rhs.map { + let mut remove = false; + if let Some(entry) = self.map.get_mut(&key) { + if *entry >= value { + *entry -= value; + } else { + remove = true; + } + if *entry == N::zero() { + remove = true; + } + } + if remove { + self.map.remove(&key); + } + } + } +} diff --git a/src/impls/union.rs b/src/impls/union.rs new file mode 100644 index 0000000..2bb1b7b --- /dev/null +++ b/src/impls/union.rs @@ -0,0 +1,84 @@ +use crate::Counter; + +use num_traits::Zero; + +use std::hash::Hash; +use std::ops::{BitOr, BitOrAssign}; + +impl BitOr for Counter +where + T: Hash + Eq, + N: Ord + Zero, +{ + type Output = Counter; + + /// Returns the union of `self` and `rhs` as a new `Counter`. + /// + /// `out = c | d;` -> `out[x] == max(c[x], d[x])` + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// let e = c | d; + /// + /// let expect = [('a', 3), ('b', 2)].iter().cloned().collect::>(); + /// assert_eq!(e.into_map(), expect); + /// ``` + fn bitor(mut self, rhs: Counter) -> Self::Output { + for (key, rhs_value) in rhs.map { + let entry = self.map.entry(key).or_insert_with(N::zero); + // We want to update the value of the now occupied entry in `self` with the maximum of + // its current value and `rhs_value`. If that max is `rhs_value`, we can just update + // the value of the entry. If the max is the current value, we do nothing. Note that + // `Ord::max()` returns the second argument (here `rhs_value`) if its two arguments are + // equal, justifying the use of the weak inequality below instead of a strict + // inequality. + // + // Doing it this way with an inequality instead of actually using `std::cmp::max()` + // lets us avoid trying (and failing) to move the non-copy value out of the entry in + // order to pass it as an argument to `std::cmp::max()`, while still holding a mutable + // reference to the value slot in the entry. + // + // And while using the inequality seemingly only requires the bound `N: PartialOrd`, we + // nevertheless prefer to require `Ord` as though we were using `std::cmp::max()` + // because the semantics of `BitOr` for `Counter` really do not make sense if there are + // possibly non-comparable values of type `N`. + if rhs_value >= *entry { + *entry = rhs_value; + } + } + self + } +} + +impl BitOrAssign for Counter +where + T: Hash + Eq, + N: Ord + Zero, +{ + /// Updates `self` with the union of `self` and `rhs` + /// + /// `c |= d;` -> `c[x] == max(c[x], d[x])` + /// + /// ```rust + /// # use counter::Counter; + /// # use std::collections::HashMap; + /// let mut c = "aaab".chars().collect::>(); + /// let d = "abb".chars().collect::>(); + /// + /// c |= d; + /// + /// let expect = [('a', 3), ('b', 2)].iter().cloned().collect::>(); + /// assert_eq!(c.into_map(), expect); + /// ``` + fn bitor_assign(&mut self, mut rhs: Counter) { + for (key, rhs_count) in rhs.drain() { + if rhs_count > self[&key] { + self.map.insert(key, rhs_count); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c44b08b..677361a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -274,16 +274,16 @@ //! assert!(counter.into_map() == expected); //! ``` +mod impls; + use num_traits::{One, Zero}; -use std::borrow::Borrow; use std::collections::{BinaryHeap, HashMap}; use std::hash::Hash; use std::iter; -use std::ops::{ - Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, DerefMut, Index, IndexMut, - Sub, SubAssign, -}; +use std::ops::{AddAssign, SubAssign}; +#[cfg(test)] +mod unit_tests; type CounterMap = HashMap; @@ -328,35 +328,11 @@ where } } -impl Counter -where - T: Hash + Eq, - N: Zero, -{ - /// Create a new, empty `Counter` - pub fn new() -> Counter { - Counter { - map: HashMap::new(), - zero: N::zero(), - } - } -} - impl Counter where T: Hash + Eq, N: AddAssign + Zero + One, { - /// Create a new `Counter` initialized with the given iterable. - pub fn init(iterable: I) -> Counter - where - I: IntoIterator, - { - let mut counter = Counter::new(); - counter.update(iterable); - counter - } - /// Add the counts of the elements from the given iterable to this counter. pub fn update(&mut self, iterable: I) where @@ -566,151 +542,6 @@ where } } -impl Default for Counter -where - T: Hash + Eq, - N: Default, -{ - fn default() -> Self { - Self { - map: Default::default(), - zero: Default::default(), - } - } -} - -impl AddAssign for Counter -where - T: Hash + Eq, - N: Zero + AddAssign, -{ - /// Add another counter to this counter. - /// - /// `c += d;` -> `c[x] += d[x]` for all `x` - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// c += d; - /// - /// let expect = [('a', 4), ('b', 3)].iter().cloned().collect::>(); - /// assert_eq!(c.into_map(), expect); - /// ``` - fn add_assign(&mut self, rhs: Self) { - for (key, value) in rhs.map { - let entry = self.map.entry(key).or_insert_with(N::zero); - *entry += value; - } - } -} - -impl Add for Counter -where - T: Clone + Hash + Eq, - N: AddAssign + Zero, -{ - type Output = Counter; - - /// Add two counters together. - /// - /// `out = c + d;` -> `out[x] == c[x] + d[x]` for all `x` - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// let e = c + d; - /// - /// let expect = [('a', 4), ('b', 3)].iter().cloned().collect::>(); - /// assert_eq!(e.into_map(), expect); - /// ``` - fn add(mut self, rhs: Counter) -> Self::Output { - self += rhs; - self - } -} - -impl SubAssign for Counter -where - T: Hash + Eq, - N: PartialOrd + PartialEq + SubAssign + Zero, -{ - /// Subtract (keeping only positive values). - /// - /// `c -= d;` -> `c[x] -= d[x]` for all `x`, - /// keeping only items with a value greater than [`N::zero()`]. - /// - /// [`N::zero()`]: - /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// c -= d; - /// - /// let expect = [('a', 2)].iter().cloned().collect::>(); - /// assert_eq!(c.into_map(), expect); - /// ``` - fn sub_assign(&mut self, rhs: Self) { - for (key, value) in rhs.map { - let mut remove = false; - if let Some(entry) = self.map.get_mut(&key) { - if *entry >= value { - *entry -= value; - } else { - remove = true; - } - if *entry == N::zero() { - remove = true; - } - } - if remove { - self.map.remove(&key); - } - } - } -} - -impl Sub for Counter -where - T: Hash + Eq, - N: PartialOrd + PartialEq + SubAssign + Zero, -{ - type Output = Counter; - - /// Subtract (keeping only positive values). - /// - /// `out = c - d;` -> `out[x] == c[x] - d[x]` for all `x`, - /// keeping only items with a value greater than [`N::zero()`]. - /// - /// [`N::zero()`]: - /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// let e = c - d; - /// - /// let expect = [('a', 2)].iter().cloned().collect::>(); - /// assert_eq!(e.into_map(), expect); - /// ``` - fn sub(mut self, rhs: Counter) -> Self::Output { - self -= rhs; - self - } -} - impl Counter where T: Hash + Eq, @@ -766,968 +597,3 @@ where .all(|key| self[key] <= other[key]) } } - -impl BitAnd for Counter -where - T: Hash + Eq, - N: Ord + Zero, -{ - type Output = Counter; - - /// Returns the intersection of `self` and `rhs` as a new `Counter`. - /// - /// `out = c & d;` -> `out[x] == min(c[x], d[x])` - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// let e = c & d; - /// - /// let expect = [('a', 1), ('b', 1)].iter().cloned().collect::>(); - /// assert_eq!(e.into_map(), expect); - /// ``` - fn bitand(self, mut rhs: Counter) -> Self::Output { - use std::cmp::min; - - let mut counter = Counter::new(); - for (key, lhs_count) in self.map { - if let Some(rhs_count) = rhs.remove(&key) { - let count = min(lhs_count, rhs_count); - counter.map.insert(key, count); - } - } - counter - } -} - -impl BitAndAssign for Counter -where - T: Hash + Eq, - N: Ord + Zero, -{ - /// Updates `self` with the intersection of `self` and `rhs` - /// - /// `c &= d;` -> `c[x] == min(c[x], d[x])` - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// c &= d; - /// - /// let expect = [('a', 1), ('b', 1)].iter().cloned().collect::>(); - /// assert_eq!(c.into_map(), expect); - /// ``` - fn bitand_assign(&mut self, mut rhs: Counter) { - for (key, rhs_count) in rhs.drain() { - if rhs_count < self[&key] { - self.map.insert(key, rhs_count); - } - } - } -} - -impl BitOr for Counter -where - T: Hash + Eq, - N: Ord + Zero, -{ - type Output = Counter; - - /// Returns the union of `self` and `rhs` as a new `Counter`. - /// - /// `out = c | d;` -> `out[x] == max(c[x], d[x])` - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// let e = c | d; - /// - /// let expect = [('a', 3), ('b', 2)].iter().cloned().collect::>(); - /// assert_eq!(e.into_map(), expect); - /// ``` - fn bitor(mut self, rhs: Counter) -> Self::Output { - for (key, rhs_value) in rhs.map { - let entry = self.map.entry(key).or_insert_with(N::zero); - // We want to update the value of the now occupied entry in `self` with the maximum of - // its current value and `rhs_value`. If that max is `rhs_value`, we can just update - // the value of the entry. If the max is the current value, we do nothing. Note that - // `Ord::max()` returns the second argument (here `rhs_value`) if its two arguments are - // equal, justifying the use of the weak inequality below instead of a strict - // inequality. - // - // Doing it this way with an inequality instead of actually using `std::cmp::max()` - // lets us avoid trying (and failing) to move the non-copy value out of the entry in - // order to pass it as an argument to `std::cmp::max()`, while still holding a mutable - // reference to the value slot in the entry. - // - // And while using the inequality seemingly only requires the bound `N: PartialOrd`, we - // nevertheless prefer to require `Ord` as though we were using `std::cmp::max()` - // because the semantics of `BitOr` for `Counter` really do not make sense if there are - // possibly non-comparable values of type `N`. - if rhs_value >= *entry { - *entry = rhs_value; - } - } - self - } -} - -impl BitOrAssign for Counter -where - T: Hash + Eq, - N: Ord + Zero, -{ - /// Updates `self` with the union of `self` and `rhs` - /// - /// `c |= d;` -> `c[x] == max(c[x], d[x])` - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut c = "aaab".chars().collect::>(); - /// let d = "abb".chars().collect::>(); - /// - /// c |= d; - /// - /// let expect = [('a', 3), ('b', 2)].iter().cloned().collect::>(); - /// assert_eq!(c.into_map(), expect); - /// ``` - fn bitor_assign(&mut self, mut rhs: Counter) { - for (key, rhs_count) in rhs.drain() { - if rhs_count > self[&key] { - self.map.insert(key, rhs_count); - } - } - } -} - -impl Deref for Counter -where - T: Hash + Eq, -{ - type Target = CounterMap; - fn deref(&self) -> &CounterMap { - &self.map - } -} - -impl DerefMut for Counter -where - T: Hash + Eq, -{ - fn deref_mut(&mut self) -> &mut CounterMap { - &mut self.map - } -} - -impl<'a, T, N> IntoIterator for &'a Counter -where - T: Hash + Eq, -{ - type Item = (&'a T, &'a N); - type IntoIter = std::collections::hash_map::Iter<'a, T, N>; - - fn into_iter(self) -> Self::IntoIter { - self.map.iter() - } -} - -impl IntoIterator for Counter -where - T: Hash + Eq, -{ - type Item = (T, N); - type IntoIter = std::collections::hash_map::IntoIter; - - /// Consumes the `Counter` to produce an iterator that owns the values it returns. - /// - /// # Examples - /// ```rust - /// # use counter::Counter; - /// - /// let counter: Counter<_> = "aaab".chars().collect(); - /// - /// let vec: Vec<_> = counter.into_iter().collect(); - /// - /// for (item, count) in &vec { - /// if item == &'a' { - /// assert_eq!(count, &3); - /// } - /// if item == &'b' { - /// assert_eq!(count, &1); - /// } - /// } - /// ``` - - fn into_iter(self) -> Self::IntoIter { - self.map.into_iter() - } -} - -impl<'a, T, N> IntoIterator for &'a mut Counter -where - T: Hash + Eq, -{ - type Item = (&'a T, &'a mut N); - type IntoIter = std::collections::hash_map::IterMut<'a, T, N>; - - /// Creates an iterator that provides mutable references to the counts, but keeps the keys immutable. - /// - /// # Examples - /// ```rust - /// # use counter::Counter; - /// - /// let mut counter: Counter<_> = "aaab".chars().collect(); - /// - /// for (item, count) in &mut counter { - /// if *item == 'a' { - /// // 'a' is so great it counts as 2 - /// *count *= 2; - /// } - /// } - /// - /// assert_eq!(counter[&'a'], 6); - /// assert_eq!(counter[&'b'], 1); - /// ``` - fn into_iter(self) -> Self::IntoIter { - self.map.iter_mut() - } -} - -impl Index<&'_ Q> for Counter -where - T: Hash + Eq + Borrow, - Q: Hash + Eq, - N: Zero, -{ - type Output = N; - - /// Index in immutable contexts. - /// - /// Returns a reference to a [`zero`] value for missing keys. - /// - /// [`zero`]: - /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero - /// - /// ``` - /// # use counter::Counter; - /// let counter = Counter::<_>::init("aabbcc".chars()); - /// assert_eq!(counter[&'a'], 2); - /// assert_eq!(counter[&'b'], 2); - /// assert_eq!(counter[&'c'], 2); - /// assert_eq!(counter[&'d'], 0); - /// ``` - /// - /// Note that the [`zero`] is a struct field but not one of the values of the inner - /// [`HashMap`]. This method does not modify any existing value. - /// - /// [`zero`]: - /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero - /// [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html - /// - /// ``` - /// # use counter::Counter; - /// let counter = Counter::<_>::init("".chars()); - /// assert_eq!(counter[&'a'], 0); - /// assert_eq!(counter.get(&'a'), None); // as `Deref>` - /// ``` - fn index(&self, key: &'_ Q) -> &N { - self.map.get(key).unwrap_or(&self.zero) - } -} - -impl IndexMut<&'_ Q> for Counter -where - T: Hash + Eq + Borrow, - Q: Hash + Eq + ToOwned, - N: Zero, -{ - /// Index in mutable contexts. - /// - /// If the given key is not present, creates a new entry and initializes it with a [`zero`] - /// value. - /// - /// [`zero`]: - /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero - /// - /// ``` - /// # use counter::Counter; - /// let mut counter = Counter::<_>::init("aabbcc".chars()); - /// counter[&'c'] += 1; - /// counter[&'d'] += 1; - /// assert_eq!(counter[&'c'], 3); - /// assert_eq!(counter[&'d'], 1); - /// ``` - /// - /// Unlike `Index::index`, the returned mutable reference to the [`zero`] is actually one of the - /// values of the inner [`HashMap`]. - /// - /// [`zero`]: - /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero - /// [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html - /// - /// ``` - /// # use counter::Counter; - /// let mut counter = Counter::<_>::init("".chars()); - /// assert_eq!(counter.get(&'a'), None); // as `Deref>` - /// let _ = &mut counter[&'a']; - /// assert_eq!(counter.get(&'a'), Some(&0)); - /// ``` - fn index_mut(&mut self, key: &'_ Q) -> &mut N { - self.map.entry(key.to_owned()).or_insert_with(N::zero) - } -} - -impl AddAssign for Counter -where - I: IntoIterator, - T: Hash + Eq, - N: AddAssign + Zero + One, -{ - /// Directly add the counts of the elements of `I` to `self`. - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut counter = Counter::init("abbccc".chars()); - /// - /// counter += "aeeeee".chars(); - /// let expected: HashMap = [('a', 2), ('b', 2), ('c', 3), ('e', 5)] - /// .iter().cloned().collect(); - /// assert_eq!(counter.into_map(), expected); - /// ``` - fn add_assign(&mut self, rhs: I) { - self.update(rhs); - } -} - -impl Add for Counter -where - I: IntoIterator, - T: Hash + Eq, - N: AddAssign + Zero + One, -{ - type Output = Self; - /// Consume `self` producing a `Counter` like `self` updated with the counts of - /// the elements of `I`. - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let counter = Counter::init("abbccc".chars()); - /// - /// let new_counter = counter + "aeeeee".chars(); - /// let expected: HashMap = [('a', 2), ('b', 2), ('c', 3), ('e', 5)] - /// .iter().cloned().collect(); - /// assert_eq!(new_counter.into_map(), expected); - /// ``` - fn add(mut self, rhs: I) -> Self::Output { - self.update(rhs); - self - } -} - -impl SubAssign for Counter -where - I: IntoIterator, - T: Hash + Eq, - N: PartialOrd + SubAssign + Zero + One, -{ - /// Directly subtract the counts of the elements of `I` from `self`, - /// keeping only items with a value greater than [`N::zero()`]. - /// - /// [`N::zero()`]: - /// https://docs.rs/num-traits/latest/num_traits/identities/trait.Zero.html#tymethod.zero - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut c = "aaab".chars().collect::>(); - /// c -= "abb".chars(); - /// - /// let expect = [('a', 2)].iter().cloned().collect::>(); - /// assert_eq!(c.into_map(), expect); - /// ``` - fn sub_assign(&mut self, rhs: I) { - self.subtract(rhs); - } -} - -impl Sub for Counter -where - I: IntoIterator, - T: Hash + Eq, - N: PartialOrd + SubAssign + Zero + One, -{ - type Output = Self; - /// Consume `self` producing a `Counter` like `self` with the counts of the - /// elements of `I` subtracted, keeping only positive values. - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let c = "aaab".chars().collect::>(); - /// let e = c - "abb".chars(); - /// - /// let expect = [('a', 2)].iter().cloned().collect::>(); - /// assert_eq!(e.into_map(), expect); - /// ``` - fn sub(mut self, rhs: I) -> Self::Output { - self.subtract(rhs); - self - } -} - -impl iter::FromIterator for Counter -where - T: Hash + Eq, - N: AddAssign + Zero + One, -{ - /// Produce a `Counter` from an iterator of items. This is called automatically - /// by [`Iterator::collect()`]. - /// - /// [`Iterator::collect()`]: - /// https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.collect - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let counter = "abbccc".chars().collect::>(); - /// let expect = [('a', 1), ('b', 2), ('c', 3)].iter().cloned().collect::>(); - /// assert_eq!(counter.into_map(), expect); - /// ``` - /// - fn from_iter>(iter: I) -> Self { - Counter::::init(iter) - } -} - -impl iter::FromIterator<(T, N)> for Counter -where - T: Hash + Eq, - N: AddAssign + Zero, -{ - /// Creates a counter from `(item, count)` tuples. - /// - /// The counts of duplicate items are summed. - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let counter = [('a', 1), ('b', 2), ('c', 3), ('a', 4)].iter() - /// .cloned().collect::>(); - /// let expect = [('a', 5), ('b', 2), ('c', 3)].iter() - /// .cloned().collect::>(); - /// assert_eq!(counter.into_map(), expect); - /// ``` - fn from_iter>(iter: I) -> Self { - let mut cnt = Counter::new(); - for (item, item_count) in iter { - let entry = cnt.map.entry(item).or_insert_with(N::zero); - *entry += item_count; - } - cnt - } -} - -impl Extend for Counter -where - T: Hash + Eq, - N: AddAssign + Zero + One, -{ - /// Extend a `Counter` with an iterator of items. - /// - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut counter = "abbccc".chars().collect::>(); - /// counter.extend("bccddd".chars()); - /// let expect = [('a', 1), ('b', 3), ('c', 5), ('d', 3)].iter().cloned().collect::>(); - /// assert_eq!(counter.into_map(), expect); - /// ``` - fn extend>(&mut self, iter: I) { - self.update(iter); - } -} - -impl Extend<(T, N)> for Counter -where - T: Hash + Eq, - N: AddAssign + Zero, -{ - /// Extend a counter with `(item, count)` tuples. - /// - /// The counts of duplicate items are summed. - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut counter = "abbccc".chars().collect::>(); - /// counter.extend([('a', 1), ('b', 2), ('c', 3), ('a', 4)].iter().cloned()); - /// let expect = [('a', 6), ('b', 4), ('c', 6)].iter() - /// .cloned().collect::>(); - /// assert_eq!(counter.into_map(), expect); - /// ``` - fn extend>(&mut self, iter: I) { - for (item, item_count) in iter { - let entry = self.map.entry(item).or_insert_with(N::zero); - *entry += item_count; - } - } -} - -impl<'a, T: 'a, N: 'a> Extend<(&'a T, &'a N)> for Counter -where - T: Hash + Eq + Clone, - N: AddAssign + Zero + Clone, -{ - /// Extend a counter with `(item, count)` tuples. - /// - /// You can extend a `Counter` with another `Counter`: - /// ```rust - /// # use counter::Counter; - /// # use std::collections::HashMap; - /// let mut counter = "abbccc".chars().collect::>(); - /// let another = "bccddd".chars().collect::>(); - /// counter.extend(&another); - /// let expect = [('a', 1), ('b', 3), ('c', 5), ('d', 3)].iter() - /// .cloned().collect::>(); - /// assert_eq!(counter.into_map(), expect); - /// ``` - fn extend>(&mut self, iter: I) { - for (item, item_count) in iter { - let entry = self.map.entry(item.clone()).or_insert_with(N::zero); - *entry += item_count.clone(); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use maplit::hashmap; - use rand::Rng; - use std::collections::HashMap; - - #[test] - fn test_creation() { - let _: Counter = Counter::new(); - - let initializer = &[1]; - let counter = Counter::init(initializer); - - let mut expected = HashMap::new(); - static ONE: usize = 1; - expected.insert(&ONE, 1); - assert!(counter.map == expected); - } - - #[test] - fn test_update() { - let mut counter = Counter::init("abbccc".chars()); - let expected = hashmap! { - 'a' => 1, - 'b' => 2, - 'c' => 3, - }; - assert!(counter.map == expected); - - counter.update("aeeeee".chars()); - let expected = hashmap! { - 'a' => 2, - 'b' => 2, - 'c' => 3, - 'e' => 5, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_add_update_iterable() { - let mut counter = Counter::init("abbccc".chars()); - let expected = hashmap! { - 'a' => 1, - 'b' => 2, - 'c' => 3, - }; - assert!(counter.map == expected); - - counter += "aeeeee".chars(); - let expected = hashmap! { - 'a' => 2, - 'b' => 2, - 'c' => 3, - 'e' => 5, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_add_update_counter() { - let mut counter = Counter::init("abbccc".chars()); - let expected = hashmap! { - 'a' => 1, - 'b' => 2, - 'c' => 3, - }; - assert!(counter.map == expected); - - let other = Counter::init("aeeeee".chars()); - counter += other; - let expected = hashmap! { - 'a' => 2, - 'b' => 2, - 'c' => 3, - 'e' => 5, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_subtract() { - let mut counter = Counter::init("abbccc".chars()); - counter.subtract("bbccddd".chars()); - let expected = hashmap! { - 'a' => 1, - 'c' => 1, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_sub_update_iterable() { - let mut counter = Counter::init("abbccc".chars()); - counter -= "bbccddd".chars(); - let expected = hashmap! { - 'a' => 1, - 'c' => 1, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_sub_update_counter() { - let mut counter = Counter::init("abbccc".chars()); - let other = Counter::init("bbccddd".chars()); - counter -= other; - let expected = hashmap! { - 'a' => 1, - 'c' => 1, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_composite_add_sub() { - let mut counts = Counter::<_>::init( - "able babble table babble rabble table able fable scrabble".split_whitespace(), - ); - // add or subtract an iterable of the same type - counts += "cain and abel fable table cable".split_whitespace(); - // or add or subtract from another Counter of the same type - let other_counts = Counter::init("scrabble cabbie fable babble".split_whitespace()); - let _diff = counts - other_counts; - } - - #[test] - fn test_most_common() { - let counter = Counter::init("abbccc".chars()); - let by_common = counter.most_common(); - let expected = vec![('c', 3), ('b', 2), ('a', 1)]; - assert!(by_common == expected); - } - - #[test] - fn test_most_common_tiebreaker() { - let counter = Counter::init("eaddbbccc".chars()); - let by_common = counter.most_common_tiebreaker(|&a, &b| a.cmp(&b)); - let expected = vec![('c', 3), ('b', 2), ('d', 2), ('a', 1), ('e', 1)]; - assert!(by_common == expected); - } - - #[test] - fn test_most_common_tiebreaker_reversed() { - let counter = Counter::init("eaddbbccc".chars()); - let by_common = counter.most_common_tiebreaker(|&a, &b| b.cmp(&a)); - let expected = vec![('c', 3), ('d', 2), ('b', 2), ('e', 1), ('a', 1)]; - assert!(by_common == expected); - } - - // The main purpose of this test is to see that we can call `Counter::most_common_tiebreaker()` - // with a closure that is `FnMut` but not `Fn`. - #[test] - fn test_most_common_tiebreaker_fn_mut() { - let counter: Counter<_> = Counter::init("abracadabra".chars()); - // Count how many times the tiebreaker closure is called. - let mut num_ties = 0; - let sorted = counter.most_common_tiebreaker(|a, b| { - num_ties += 1; - a.cmp(b) - }); - let expected = vec![('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]; - assert_eq!(sorted, expected); - // We should have called the tiebreaker twice: once to resolve the tie between `'b'` and - // `'r'` and once to resolve the tie between `'c'` and `'d'`. - assert_eq!(num_ties, 2); - } - - #[test] - fn test_most_common_ordered() { - let counter = Counter::init("eaddbbccc".chars()); - let by_common = counter.most_common_ordered(); - let expected = vec![('c', 3), ('b', 2), ('d', 2), ('a', 1), ('e', 1)]; - assert!(by_common == expected); - } - - #[test] - fn test_k_most_common_ordered() { - let counter: Counter<_> = "abracadabra".chars().collect(); - let all = counter.most_common_ordered(); - for k in 0..=counter.len() { - let topk = counter.k_most_common_ordered(k); - assert_eq!(&topk, &all[..k]); - } - } - - /// This test is fundamentally the same as `test_k_most_common_ordered`, but it operates on - /// a wider variety of data. In particular, it tests both longer, narrower, and wider - /// distributions of data than the other test does. - #[test] - fn test_k_most_common_ordered_heavy() { - let mut rng = rand::thread_rng(); - - for container_size in [5, 10, 25, 100, 256] { - for max_value_factor in [0.25, 0.5, 1.0, 1.25, 2.0, 10.0, 100.0] { - let max_value = ((container_size as f64) * max_value_factor) as u32; - let mut values = vec![0; container_size]; - for value in values.iter_mut() { - *value = rng.gen_range(0..=max_value); - } - - let counter: Counter<_> = values.into_iter().collect(); - let all = counter.most_common_ordered(); - for k in 0..=counter.len() { - let topk = counter.k_most_common_ordered(k); - assert_eq!(&topk, &all[..k]); - } - } - } - } - - #[test] - fn test_total() { - let counter = Counter::init("".chars()); - let total: usize = counter.total(); - assert_eq!(total, 0); - - let counter = Counter::init("eaddbbccc".chars()); - let total: usize = counter.total(); - assert_eq!(total, 9); - } - - #[test] - fn test_add() { - let d = Counter::<_>::init("abbccc".chars()); - let e = Counter::<_>::init("bccddd".chars()); - - let out = d + e; - let expected = Counter::init("abbbcccccddd".chars()); - assert!(out == expected); - } - - #[test] - fn test_sub() { - let d = Counter::<_>::init("abbccc".chars()); - let e = Counter::<_>::init("bccddd".chars()); - - let out = d - e; - let expected = Counter::init("abc".chars()); - assert!(out == expected); - } - - #[test] - fn test_intersection() { - let d = Counter::<_>::init("abbccc".chars()); - let e = Counter::<_>::init("bccddd".chars()); - - let out = d & e; - let expected = Counter::init("bcc".chars()); - assert!(out == expected); - } - - #[test] - fn test_union() { - let d = Counter::<_>::init("abbccc".chars()); - let e = Counter::<_>::init("bccddd".chars()); - - let out = d | e; - let expected = Counter::init("abbcccddd".chars()); - assert!(out == expected); - } - - #[test] - fn test_delete_key_from_backing_map() { - let mut counter = Counter::<_>::init("aa-bb-cc".chars()); - counter.remove(&'-'); - assert!(counter == Counter::init("aabbcc".chars())); - } - - #[test] - fn test_from_iter_simple() { - let counter = "abbccc".chars().collect::>(); - let expected = hashmap! { - 'a' => 1, - 'b' => 2, - 'c' => 3, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_from_iter_tuple() { - let items = [('a', 1), ('b', 2), ('c', 3)]; - let counter = items.iter().cloned().collect::>(); - let expected: HashMap = items.iter().cloned().collect(); - assert_eq!(counter.map, expected); - } - - #[test] - fn test_from_iter_tuple_with_duplicates() { - let items = [('a', 1), ('b', 2), ('c', 3)]; - let counter = items - .iter() - .cycle() - .take(items.len() * 2) - .cloned() - .collect::>(); - let expected: HashMap = items.iter().map(|(c, n)| (*c, n * 2)).collect(); - assert_eq!(counter.map, expected); - } - - #[test] - fn test_extend_simple() { - let mut counter = "abbccc".chars().collect::>(); - counter.extend("bccddd".chars()); - let expected = hashmap! { - 'a' => 1, - 'b' => 3, - 'c' => 5, - 'd' => 3, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_extend_tuple() { - let mut counter = "bccddd".chars().collect::>(); - let items = [('a', 1), ('b', 2), ('c', 3)]; - counter.extend(items.iter().cloned()); - let expected = hashmap! { - 'a' => 1, - 'b' => 3, - 'c' => 5, - 'd' => 3, - }; - assert_eq!(counter.map, expected); - } - - #[test] - fn test_extend_tuple_with_duplicates() { - let mut counter = "ccc".chars().collect::>(); - let items = [('a', 1), ('b', 2), ('c', 3)]; - counter.extend(items.iter().cycle().take(items.len() * 2 - 1).cloned()); - let expected: HashMap = items.iter().map(|(c, n)| (*c, n * 2)).collect(); - assert_eq!(counter.map, expected); - } - - #[test] - fn test_count_minimal_type() { - #[derive(Debug, Hash, PartialEq, Eq)] - struct Inty { - i: usize, - } - - impl Inty { - pub fn new(i: usize) -> Inty { - Inty { i } - } - } - - // - let intys = vec![ - Inty::new(8), - Inty::new(0), - Inty::new(0), - Inty::new(8), - Inty::new(6), - Inty::new(7), - Inty::new(5), - Inty::new(3), - Inty::new(0), - Inty::new(9), - ]; - - let inty_counts = Counter::init(intys); - // println!("{:?}", inty_counts.map); // test runner blanks this - // {Inty { i: 8 }: 2, Inty { i: 0 }: 3, Inty { i: 9 }: 1, Inty { i: 3 }: 1, - // Inty { i: 7 }: 1, Inty { i: 6 }: 1, Inty { i: 5 }: 1} - assert!(inty_counts.map.get(&Inty { i: 8 }) == Some(&2)); - assert!(inty_counts.map.get(&Inty { i: 0 }) == Some(&3)); - assert!(inty_counts.map.get(&Inty { i: 6 }) == Some(&1)); - } - - #[test] - fn test_collect() { - let counter: Counter<_> = "abbccc".chars().collect(); - let expected = hashmap! { - 'a' => 1, - 'b' => 2, - 'c' => 3, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_non_usize_count() { - let counter: Counter<_, i8> = "abbccc".chars().collect(); - let expected = hashmap! { - 'a' => 1, - 'b' => 2, - 'c' => 3, - }; - assert!(counter.map == expected); - } - - #[test] - fn test_superset_non_usize_count() { - let mut a: Counter<_, i8> = "abbcccc".chars().collect(); - let mut b: Counter<_, i8> = "abb".chars().collect(); - assert!(a.is_superset(&b)); - // Negative values are possible, a is no longer a superset - a[&'e'] = -1; - assert!(!a.is_superset(&b)); - // Adjust b to make a a superset again - b[&'e'] = -2; - assert!(a.is_superset(&b)); - } - - #[test] - fn test_subset_non_usize_count() { - let mut a: Counter<_, i8> = "abb".chars().collect(); - let mut b: Counter<_, i8> = "abbcccc".chars().collect(); - assert!(a.is_subset(&b)); - // Negative values are possible; a is no longer a subset - b[&'e'] = -1; - assert!(!a.is_subset(&b)); - // Adjust a to make it a subset again - a[&'e'] = -2; - assert!(a.is_subset(&b)); - } -} diff --git a/src/unit_tests.rs b/src/unit_tests.rs new file mode 100644 index 0000000..c97a774 --- /dev/null +++ b/src/unit_tests.rs @@ -0,0 +1,235 @@ +use crate::Counter; +use maplit::hashmap; +use std::collections::HashMap; +#[test] +fn test_creation() { + let _: Counter = Counter::new(); + + let initializer = &[1]; + let counter = Counter::init(initializer); + + let mut expected = HashMap::new(); + static ONE: usize = 1; + expected.insert(&ONE, 1); + assert!(counter.map == expected); +} +#[test] +fn test_update() { + let mut counter = Counter::init("abbccc".chars()); + let expected = hashmap! { + 'a' => 1, + 'b' => 2, + 'c' => 3, + }; + assert!(counter.map == expected); + + counter.update("aeeeee".chars()); + let expected = hashmap! { + 'a' => 2, + 'b' => 2, + 'c' => 3, + 'e' => 5, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_add_update_iterable() { + let mut counter = Counter::init("abbccc".chars()); + let expected = hashmap! { + 'a' => 1, + 'b' => 2, + 'c' => 3, + }; + assert!(counter.map == expected); + + counter += "aeeeee".chars(); + let expected = hashmap! { + 'a' => 2, + 'b' => 2, + 'c' => 3, + 'e' => 5, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_add_update_counter() { + let mut counter = Counter::init("abbccc".chars()); + let expected = hashmap! { + 'a' => 1, + 'b' => 2, + 'c' => 3, + }; + assert!(counter.map == expected); + + let other = Counter::init("aeeeee".chars()); + counter += other; + let expected = hashmap! { + 'a' => 2, + 'b' => 2, + 'c' => 3, + 'e' => 5, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_subtract() { + let mut counter = Counter::init("abbccc".chars()); + counter.subtract("bbccddd".chars()); + let expected = hashmap! { + 'a' => 1, + 'c' => 1, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_sub_update_iterable() { + let mut counter = Counter::init("abbccc".chars()); + counter -= "bbccddd".chars(); + let expected = hashmap! { + 'a' => 1, + 'c' => 1, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_sub_update_counter() { + let mut counter = Counter::init("abbccc".chars()); + let other = Counter::init("bbccddd".chars()); + counter -= other; + let expected = hashmap! { + 'a' => 1, + 'c' => 1, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_from_iter_simple() { + let counter = "abbccc".chars().collect::>(); + let expected = hashmap! { + 'a' => 1, + 'b' => 2, + 'c' => 3, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_from_iter_tuple() { + let items = [('a', 1), ('b', 2), ('c', 3)]; + let counter = items.iter().cloned().collect::>(); + let expected: HashMap = items.iter().cloned().collect(); + assert_eq!(counter.map, expected); +} + +#[test] +fn test_from_iter_tuple_with_duplicates() { + let items = [('a', 1), ('b', 2), ('c', 3)]; + let counter = items + .iter() + .cycle() + .take(items.len() * 2) + .cloned() + .collect::>(); + let expected: HashMap = items.iter().map(|(c, n)| (*c, n * 2)).collect(); + assert_eq!(counter.map, expected); +} + +#[test] +fn test_extend_simple() { + let mut counter = "abbccc".chars().collect::>(); + counter.extend("bccddd".chars()); + let expected = hashmap! { + 'a' => 1, + 'b' => 3, + 'c' => 5, + 'd' => 3, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_extend_tuple() { + let mut counter = "bccddd".chars().collect::>(); + let items = [('a', 1), ('b', 2), ('c', 3)]; + counter.extend(items.iter().cloned()); + let expected = hashmap! { + 'a' => 1, + 'b' => 3, + 'c' => 5, + 'd' => 3, + }; + assert_eq!(counter.map, expected); +} + +#[test] +fn test_extend_tuple_with_duplicates() { + let mut counter = "ccc".chars().collect::>(); + let items = [('a', 1), ('b', 2), ('c', 3)]; + counter.extend(items.iter().cycle().take(items.len() * 2 - 1).cloned()); + let expected: HashMap = items.iter().map(|(c, n)| (*c, n * 2)).collect(); + assert_eq!(counter.map, expected); +} + +#[test] +fn test_count_minimal_type() { + #[derive(Debug, Hash, PartialEq, Eq)] + struct Inty { + i: usize, + } + + impl Inty { + pub fn new(i: usize) -> Inty { + Inty { i } + } + } + + // + let intys = vec![ + Inty::new(8), + Inty::new(0), + Inty::new(0), + Inty::new(8), + Inty::new(6), + Inty::new(7), + Inty::new(5), + Inty::new(3), + Inty::new(0), + Inty::new(9), + ]; + + let inty_counts = Counter::init(intys); + // println!("{:?}", inty_counts.map); // test runner blanks this + // {Inty { i: 8 }: 2, Inty { i: 0 }: 3, Inty { i: 9 }: 1, Inty { i: 3 }: 1, + // Inty { i: 7 }: 1, Inty { i: 6 }: 1, Inty { i: 5 }: 1} + assert!(inty_counts.map.get(&Inty { i: 8 }) == Some(&2)); + assert!(inty_counts.map.get(&Inty { i: 0 }) == Some(&3)); + assert!(inty_counts.map.get(&Inty { i: 6 }) == Some(&1)); +} + +#[test] +fn test_collect() { + let counter: Counter<_> = "abbccc".chars().collect(); + let expected = hashmap! { + 'a' => 1, + 'b' => 2, + 'c' => 3, + }; + assert!(counter.map == expected); +} + +#[test] +fn test_non_usize_count() { + let counter: Counter<_, i8> = "abbccc".chars().collect(); + let expected = hashmap! { + 'a' => 1, + 'b' => 2, + 'c' => 3, + }; + assert!(counter.map == expected); +} diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..69dd2dc --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,186 @@ +#[cfg(test)] +mod tests { + use counter::Counter; + use rand::Rng; + + #[test] + fn test_composite_add_sub() { + let mut counts = Counter::<_>::init( + "able babble table babble rabble table able fable scrabble".split_whitespace(), + ); + // add or subtract an iterable of the same type + counts += "cain and abel fable table cable".split_whitespace(); + // or add or subtract from another Counter of the same type + let other_counts = Counter::init("scrabble cabbie fable babble".split_whitespace()); + let _diff = counts - other_counts; + } + + #[test] + fn test_most_common() { + let counter = Counter::init("abbccc".chars()); + let by_common = counter.most_common(); + let expected = vec![('c', 3), ('b', 2), ('a', 1)]; + assert!(by_common == expected); + } + + #[test] + fn test_most_common_tiebreaker() { + let counter = Counter::init("eaddbbccc".chars()); + let by_common = counter.most_common_tiebreaker(|&a, &b| a.cmp(&b)); + let expected = vec![('c', 3), ('b', 2), ('d', 2), ('a', 1), ('e', 1)]; + assert!(by_common == expected); + } + + #[test] + fn test_most_common_tiebreaker_reversed() { + let counter = Counter::init("eaddbbccc".chars()); + let by_common = counter.most_common_tiebreaker(|&a, &b| b.cmp(&a)); + let expected = vec![('c', 3), ('d', 2), ('b', 2), ('e', 1), ('a', 1)]; + assert!(by_common == expected); + } + + // The main purpose of this test is to see that we can call `Counter::most_common_tiebreaker()` + // with a closure that is `FnMut` but not `Fn`. + #[test] + fn test_most_common_tiebreaker_fn_mut() { + let counter: Counter<_> = Counter::init("abracadabra".chars()); + // Count how many times the tiebreaker closure is called. + let mut num_ties = 0; + let sorted = counter.most_common_tiebreaker(|a, b| { + num_ties += 1; + a.cmp(b) + }); + let expected = vec![('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]; + assert_eq!(sorted, expected); + // We should have called the tiebreaker twice: once to resolve the tie between `'b'` and + // `'r'` and once to resolve the tie between `'c'` and `'d'`. + assert_eq!(num_ties, 2); + } + + #[test] + fn test_most_common_ordered() { + let counter = Counter::init("eaddbbccc".chars()); + let by_common = counter.most_common_ordered(); + let expected = vec![('c', 3), ('b', 2), ('d', 2), ('a', 1), ('e', 1)]; + assert!(by_common == expected); + } + + #[test] + fn test_k_most_common_ordered() { + let counter: Counter<_> = "abracadabra".chars().collect(); + let all = counter.most_common_ordered(); + for k in 0..=counter.len() { + let topk = counter.k_most_common_ordered(k); + assert_eq!(&topk, &all[..k]); + } + } + + /// This test is fundamentally the same as `test_k_most_common_ordered`, but it operates on + /// a wider variety of data. In particular, it tests both longer, narrower, and wider + /// distributions of data than the other test does. + #[test] + fn test_k_most_common_ordered_heavy() { + let mut rng = rand::thread_rng(); + + for container_size in [5, 10, 25, 100, 256] { + for max_value_factor in [0.25, 0.5, 1.0, 1.25, 2.0, 10.0, 100.0] { + let max_value = ((container_size as f64) * max_value_factor) as u32; + let mut values = vec![0; container_size]; + for value in values.iter_mut() { + *value = rng.gen_range(0..=max_value); + } + + let counter: Counter<_> = values.into_iter().collect(); + let all = counter.most_common_ordered(); + for k in 0..=counter.len() { + let topk = counter.k_most_common_ordered(k); + assert_eq!(&topk, &all[..k]); + } + } + } + } + + #[test] + fn test_total() { + let counter = Counter::init("".chars()); + let total: usize = counter.total(); + assert_eq!(total, 0); + + let counter = Counter::init("eaddbbccc".chars()); + let total: usize = counter.total(); + assert_eq!(total, 9); + } + + #[test] + fn test_add() { + let d = Counter::<_>::init("abbccc".chars()); + let e = Counter::<_>::init("bccddd".chars()); + + let out = d + e; + let expected = Counter::init("abbbcccccddd".chars()); + assert!(out == expected); + } + + #[test] + fn test_sub() { + let d = Counter::<_>::init("abbccc".chars()); + let e = Counter::<_>::init("bccddd".chars()); + + let out = d - e; + let expected = Counter::init("abc".chars()); + assert!(out == expected); + } + + #[test] + fn test_intersection() { + let d = Counter::<_>::init("abbccc".chars()); + let e = Counter::<_>::init("bccddd".chars()); + + let out = d & e; + let expected = Counter::init("bcc".chars()); + assert!(out == expected); + } + + #[test] + fn test_union() { + let d = Counter::<_>::init("abbccc".chars()); + let e = Counter::<_>::init("bccddd".chars()); + + let out = d | e; + let expected = Counter::init("abbcccddd".chars()); + assert!(out == expected); + } + + #[test] + fn test_delete_key_from_backing_map() { + let mut counter = Counter::<_>::init("aa-bb-cc".chars()); + counter.remove(&'-'); + assert!(counter == Counter::init("aabbcc".chars())); + } + + #[test] + fn test_superset_non_usize_count() { + let mut a: Counter<_, i8> = "abbcccc".chars().collect(); + let mut b: Counter<_, i8> = "abb".chars().collect(); + assert!(a.is_superset(&b)); + // Negative values are possible, a is no longer a superset + a[&'e'] = -1; + assert!(!a.is_superset(&b)); + // Adjust b to make a a superset again + b[&'e'] = -2; + assert!(a.is_superset(&b)); + } + + #[test] + fn test_subset_non_usize_count() { + let mut a: Counter<_, i8> = "abb".chars().collect(); + let mut b: Counter<_, i8> = "abbcccc".chars().collect(); + assert!(a.is_subset(&b)); + // Negative values are possible; a is no longer a subset + b[&'e'] = -1; + assert!(!a.is_subset(&b)); + // Adjust a to make it a subset again + a[&'e'] = -2; + assert!(a.is_subset(&b)); + } +}