diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..3c13d1b --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore index a9d37c5..4a1e9db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.idea/ diff --git a/Cargo.toml b/Cargo.toml index d2a231a..a3c72c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,13 @@ [package] -name = "smallset" -version = "0.1.1" -authors = ["Chris Fallin "] -description = "An unordered set of elements optimized for small sizes" -repository = "https://github.com/cfallin/rust-smallset" -documentation = "https://cfallin.github.io/rust-smallset/smallset/" +name = "smolset" +version = "1.1.0" +authors = ["Chris Fallin ", "Hanif Bin Ariffin "] +description = """" +An unordered set of elements optimized for small sizes. +This is a fork of the original library with overhauled internals, better fallback perforamance (O(1) insert and find) and more features! +""" +repository = "https://github.com/hbina/smolset" license = "MIT" [dependencies] -smallvec = "0.1" +smallvec = "1.4.2" diff --git a/README.md b/README.md index 05c628a..5f55d41 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,18 @@ -`smallset`: a small unordered set -================================= +# SmolSet -[![Build Status](https://travis-ci.org/cfallin/boolean_expression.svg?branch=master)](https://travis-ci.org/cfallin/rust-smallset) +[![Crate](https://img.shields.io/crates/v/smolset.svg)](https://crates.io/crates/smolset) -[crates.io](https://crates.io/crates/smallset/) +This crate implements a small unordered-set data structure implemented using +[smallvec](https://crates.io/crates/smallvec/). +It initially stores set elements in a simple unordered array. +When the set is smaller than a parameterizable size, no allocations will be performed. +The data structure is thus very space-efficient for sets of only a few elements, much more so than a tree-based or hash-table-based set data structure. +It is also fast when the set is small: queries and inserts perform a linear scan, which is more cache-friendly than a pointer-chasing search through a tree. -[Documentation](https://cfallin.github.io/rust-smallset/smallset/) +However, as the set grows, it will transform internally into a `std::collections::HashSet`. -This crate implements a small unordered-set data structure implemented using -[smallvec](https://crates.io/crates/smallvec/). It stores set elements in a -simple unordered array, and when the set is smaller than a parameterizable -size, the elements are stored completely inline (i.e., with zero heap -allocations). The data structure is thus very space-efficient for sets of only -a few elements, much more so than a tree-based or hash-table-based set data -structure. It is also fast when the set is small: queries and inserts perform -a linear scan, which is more cache-friendly than a pointer-chasing search -through a tree. -`smallset` should be used where minimizing heap allocations is of primary -importance and where it is expected that no more than a few elements will be -present. If the set grows large, then it will exhibit poor (`O(n)` queries and -inserts) performance. +## Note + +This is a fork of the original library here: [rust-smallset](https://github.com/cfallin/rust-smallset). +I have rewritten the internals completely to not have such a bad fallback mode and added more features (and their tests and documentations). diff --git a/src/lib.rs b/src/lib.rs index 2cbcf20..13eb17b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,21 +4,23 @@ // Copyright (c) 2016 Chris Fallin . Released under the MIT license. // +extern crate smallvec; + use std::fmt; use std::iter::{FromIterator, IntoIterator}; -use std::slice::Iter; -extern crate smallvec; use smallvec::{Array, SmallVec}; +use std::collections::HashSet; +use std::hash::Hash; -/// A `SmallSet` is an unordered set of elements. It is designed to work best +/// A `SmolSet` is an unordered set of elements. It is designed to work best /// for very small sets (no more than ten or so elements). In order to support /// small sets very efficiently, it stores elements in a simple unordered array. /// When the set is smaller than the size of the array `A`, all elements are /// stored inline, without heap allocation. This is accomplished by using a /// `smallvec::SmallVec`. /// -/// The insert, remove, and query methods on `SmallSet` have `O(n)` time +/// The insert, remove, and query methods on `SmolSet` have `O(n)` time /// complexity in the current set size: they perform a linear scan to determine /// if the element in question is present. This is inefficient for large sets, /// but fast and cache-friendly for small sets. @@ -26,177 +28,563 @@ use smallvec::{Array, SmallVec}; /// Example usage: /// /// ``` -/// use smallset::SmallSet; +/// use smolset::SmolSet; /// /// // `s` and its elements will be completely stack-allocated in this example. -/// let mut s: SmallSet<[u32; 4]> = SmallSet::new(); +/// let mut s: SmolSet<[u32; 4]> = SmolSet::new(); /// s.insert(1); /// s.insert(2); /// s.insert(3); -/// assert!(s.len() == 3); +/// assert_eq!(s.len(), 3); /// assert!(s.contains(&1)); /// ``` -pub struct SmallSet +/// +/// TODO: Add the ability to switch modes explicitly. +/// +pub struct SmolSet +where + A::Item: PartialEq + Eq, +{ + inner: InnerSmolSet, +} + +impl Default for SmolSet +where + A::Item: PartialEq + Eq + Hash, +{ + fn default() -> Self { + SmolSet::new() + } +} + +/// Internal (and true) representation of the `SmolSet`. +/// Created so that user are not aware of the sum type. +pub enum InnerSmolSet where A::Item: PartialEq + Eq, { - elements: SmallVec, + Stack(SmallVec), + Heap(std::collections::HashSet), } -impl SmallSet +impl Default for InnerSmolSet where A::Item: PartialEq + Eq, { - /// Creates a new, empty `SmallSet`. - pub fn new() -> SmallSet { - SmallSet { - elements: SmallVec::new(), + fn default() -> Self { + InnerSmolSet::Stack(SmallVec::new()) + } +} + +impl Clone for InnerSmolSet +where + A::Item: PartialEq + Eq + Clone, +{ + fn clone(&self) -> Self { + match &self { + InnerSmolSet::Stack(elements) => InnerSmolSet::Stack(elements.clone()), + InnerSmolSet::Heap(elements) => InnerSmolSet::Heap(elements.clone()), } } +} + +impl PartialEq for SmolSet +where + A::Item: Eq + PartialEq + Hash, +{ + fn eq(&self, other: &Self) -> bool { + fn set_same(stack: &SmallVec, heap: &HashSet) -> bool + where + A::Item: Eq + PartialEq, + { + stack.len() == heap.len() && heap.iter().all(|x| stack.contains(x)) + } + + match (&self.inner, &other.inner) { + (InnerSmolSet::Stack(lhs), InnerSmolSet::Stack(rhs)) => lhs.eq(rhs), + (InnerSmolSet::Heap(lhs), InnerSmolSet::Heap(rhs)) => lhs.eq(rhs), + (InnerSmolSet::Stack(stack), InnerSmolSet::Heap(heap)) => set_same(stack, heap), + (InnerSmolSet::Heap(heap), InnerSmolSet::Stack(stack)) => set_same(stack, heap), + } + } +} + +#[derive(PartialEq, Debug)] +pub enum SetMode { + Stack, + Heap, +} + +impl SmolSet +where + A::Item: PartialEq + Eq + Hash, +{ + /// Creates a new, empty `SmolSet`. + pub fn new() -> SmolSet { + SmolSet { + inner: InnerSmolSet::Stack(SmallVec::new()), + } + } + + pub fn mode(&self) -> SetMode { + match self.inner { + InnerSmolSet::Stack(_) => SetMode::Stack, + InnerSmolSet::Heap(_) => SetMode::Heap, + } + } + + /// Returns the number of elements in this set. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } /// Inserts `elem` into the set if not yet present. Returns `true` if the /// set did not have this element present, or `false` if it already had this /// element present. pub fn insert(&mut self, elem: A::Item) -> bool { - if !self.contains(&elem) { - self.elements.push(elem); - true - } else { - false + match &mut self.inner { + InnerSmolSet::Stack(ref mut elements) => { + if elements.contains(&elem) { + false + } else { + if elements.len() + 1 <= A::size() { + elements.push(elem); + } else { + let mut ee = HashSet::::with_capacity(elements.len() + 1); + while !elements.is_empty() { + ee.insert(elements.remove(0)); + } + ee.insert(elem); + self.inner = InnerSmolSet::Heap(ee); + } + true + } + } + InnerSmolSet::Heap(ref mut elements) => elements.insert(elem), } } /// Removes `elem` from the set. Returns `true` if the element was removed, /// or `false` if it was not found. pub fn remove(&mut self, elem: &A::Item) -> bool { - if let Some(pos) = self.elements.iter().position(|e| *e == *elem) { - self.elements.remove(pos); - true - } else { - false + match &mut self.inner { + InnerSmolSet::Stack(ref mut elements) => { + if let Some(pos) = elements.iter().position(|e| *e == *elem) { + elements.remove(pos); + true + } else { + false + } + } + InnerSmolSet::Heap(ref mut elements) => elements.remove(elem), } } /// Tests whether `elem` is present. Returns `true` if it is present, or /// `false` if not. pub fn contains(&self, elem: &A::Item) -> bool { - self.elements.iter().any(|e| *e == *elem) + match &self.inner { + InnerSmolSet::Stack(ref elements) => elements.iter().any(|e| *e == *elem), + InnerSmolSet::Heap(ref elements) => elements.contains(elem), + } } /// Returns an iterator over the set elements. Elements will be returned in /// an arbitrary (unsorted) order. - pub fn iter(&self) -> Iter { - self.elements.iter() + pub fn iter(&self) -> SmolSetIter { + match &self.inner { + InnerSmolSet::Stack(element) => SmolSetIter { + inner: InnerSmolSetIter::Stack(element.iter()), + }, + InnerSmolSet::Heap(element) => SmolSetIter { + inner: InnerSmolSetIter::Heap(element.iter()), + }, + } } /// Returns the current length of the set. pub fn len(&self) -> usize { - self.elements.len() + match &self.inner { + InnerSmolSet::Stack(elements) => elements.len(), + InnerSmolSet::Heap(elements) => elements.len(), + } } /// Clears the set. pub fn clear(&mut self) { - self.elements.clear(); + match &mut self.inner { + InnerSmolSet::Stack(ref mut elements) => elements.clear(), + InnerSmolSet::Heap(ref mut elements) => { + elements.clear(); + self.inner = Default::default(); + } + } + } + + /// If the given `elem` exists in the set, returns the reference to the value inside the set. + /// Where they are equal (in the case where the set is in stack mode) or they hash equally (if the set is in heap mode). + pub fn get(&self, elem: &A::Item) -> Option<&A::Item> { + match &self.inner { + InnerSmolSet::Stack(elements) => elements.iter().find(|x| (elem).eq(&x)), + InnerSmolSet::Heap(elements) => elements.iter().find(|x| (elem).eq(&x)), + } + } + + /// If the given `elem` exists in the set, returns the value inside the set where they are either equal or hash equally. + /// Then, remove that value from the set. + pub fn take(&mut self, value: &A::Item) -> Option { + match &mut self.inner { + InnerSmolSet::Stack(ref mut elements) => { + if let Some(pos) = elements.iter().position(|e| *e == *value) { + let result = elements.remove(pos); + Some(result) + } else { + None + } + } + InnerSmolSet::Heap(ref mut elements) => elements.take(value), + } + } + + /// Adds a value to the set, replacing the existing value, if any, that is equal to the given one. Returns the replaced value. + pub fn replace(&mut self, value: A::Item) -> Option { + match &mut self.inner { + InnerSmolSet::Stack(ref mut elements) => { + if let Some(pos) = elements.iter().position(|e| *e == value) { + let result = elements.remove(pos); + elements.insert(pos, value); + Some(result) + } else { + None + } + } + InnerSmolSet::Heap(ref mut elements) => elements.replace(value), + } + } + + /// Empties the set and returns an iterator over it. + pub fn drain(&mut self) -> SmallDrain { + match &mut self.inner { + InnerSmolSet::Stack(ref mut elements) => { + // TODO: Clean up this garbage... + let mut ee = Vec::::with_capacity(elements.len() + 1); + while !elements.is_empty() { + ee.push(elements.remove(0)); + } + SmallDrain { data: ee, index: 0 } + } + InnerSmolSet::Heap(ref mut elements) => { + let drain = elements.drain().collect::>(); + SmallDrain { + data: drain, + index: 0, + } + } + } + } + + /// Removes all elements in the set that does not satisfy the given predicate `f`. + pub fn retain(&mut self, f: F) + where + F: FnMut(&mut A::Item) -> bool + for<'r> FnMut(&'r ::Item) -> bool, + { + match &mut self.inner { + InnerSmolSet::Stack(ref mut elements) => elements.retain(f), + InnerSmolSet::Heap(ref mut elements) => elements.retain(f), + } + } + + /// Returns an iterator over the intersection of the 2 sets. + pub fn intersection<'a>(&'a self, other: &'a Self) -> SmallIntersection<'a, A::Item> { + match &self.inner { + InnerSmolSet::Stack(ref elements) => { + let result = elements + .iter() + .filter(|x| other.contains(x)) + .collect::>(); + SmallIntersection { + data: result, + index: 0, + } + } + + InnerSmolSet::Heap(ref elements) => { + let result = elements + .iter() + .filter(|x| other.contains(x)) + .collect::>(); + SmallIntersection { + data: result, + index: 0, + } + } + } } + + /// Returns an iterator over the union of the 2 sets. + pub fn union<'a>(&'a self, other: &'a Self) -> SmallUnion<'a, A::Item> { + match &self.inner { + InnerSmolSet::Stack(ref elements) => { + let mut lhs = elements.iter().collect::>(); + let mut rhs = other + .iter() + .filter(|x| !lhs.contains(x)) + .collect::>(); + lhs.append(&mut rhs); + SmallUnion { + data: lhs, + index: 0, + } + } + + InnerSmolSet::Heap(ref elements) => { + let mut lhs = elements.iter().collect::>(); + let mut rhs = other + .iter() + .filter(|x| !lhs.contains(x)) + .collect::>(); + lhs.append(&mut rhs); + SmallUnion { + data: rhs, + index: 0, + } + } + } + } + + /// Returns an iterator over the difference of the 2 sets. + pub fn difference<'a>(&'a self, other: &'a Self) -> SmallDifference<'a, A::Item> { + match &self.inner { + InnerSmolSet::Stack(ref elements) => { + let lhs = elements + .iter() + .filter(|x| !other.contains(x)) + .collect::>(); + SmallDifference { + data: lhs, + index: 0, + } + } + + InnerSmolSet::Heap(ref elements) => { + let lhs = elements + .iter() + .filter(|x| !other.contains(x)) + .collect::>(); + SmallDifference { + data: lhs, + index: 0, + } + } + } + } + + /// Returns an iterator over the symmetric difference of the 2 sets. + pub fn symmetric_difference<'a>( + &'a self, + other: &'a Self, + ) -> SmallSymmetricDifference<'a, A::Item> { + match &self.inner { + InnerSmolSet::Stack(ref elements) => { + let mut lhs = elements + .iter() + .filter(|x| !other.contains(x)) + .collect::>(); + let mut rhs = other + .iter() + .filter(|x| !elements.contains(x)) + .collect::>(); + lhs.append(&mut rhs); + SmallSymmetricDifference { + data: lhs, + index: 0, + } + } + + InnerSmolSet::Heap(ref elements) => { + let mut lhs = elements + .iter() + .filter(|x| other.contains(x)) + .collect::>(); + let mut rhs = other + .iter() + .filter(|x| elements.contains(x)) + .collect::>(); + lhs.append(&mut rhs); + SmallSymmetricDifference { + data: lhs, + index: 0, + } + } + } + } +} + +/// Iterator returned upon calling `drain`. +pub struct SmallDrain { + data: Vec, + index: usize, } -impl Clone for SmallSet +impl Iterator for SmallDrain { + type Item = T; + + fn next(&mut self) -> Option { + if self.index == self.data.len() { + None + } else { + let ptr = self.data.as_ptr(); + self.index += 1; + unsafe { Some(std::ptr::read(ptr.add(self.index - 1))) } + } + } +} + +/// Iterator returned upon calling `intersection`. +pub struct SmallIntersection<'a, T> { + data: Vec<&'a T>, + index: usize, +} + +impl<'a, T> Iterator for SmallIntersection<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.index == self.data.len() { + None + } else { + let ptr = self.data.as_ptr(); + self.index += 1; + unsafe { Some(std::ptr::read(ptr.add(self.index - 1))) } + } + } +} + +/// Iterator returned upon calling `union`. +pub struct SmallUnion<'a, T> { + data: Vec<&'a T>, + index: usize, +} + +impl<'a, T> Iterator for SmallUnion<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.index == self.data.len() { + None + } else { + let ptr = self.data.as_ptr(); + self.index += 1; + unsafe { Some(std::ptr::read(ptr.add(self.index - 1))) } + } + } +} + +/// Iterator returned upon calling `difference`. +pub struct SmallDifference<'a, T> { + data: Vec<&'a T>, + index: usize, +} + +impl<'a, T> Iterator for SmallDifference<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.index == self.data.len() { + None + } else { + let ptr = self.data.as_ptr(); + self.index += 1; + unsafe { Some(std::ptr::read(ptr.add(self.index - 1))) } + } + } +} + +/// Iterator returned upon calling `symmteric_difference`. +pub struct SmallSymmetricDifference<'a, T> { + data: Vec<&'a T>, + index: usize, +} + +impl<'a, T> Iterator for SmallSymmetricDifference<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.index == self.data.len() { + None + } else { + let ptr = self.data.as_ptr(); + self.index += 1; + unsafe { Some(std::ptr::read(ptr.add(self.index - 1))) } + } + } +} + +impl Clone for SmolSet where A::Item: PartialEq + Eq + Clone, { - fn clone(&self) -> SmallSet { - SmallSet { - elements: self.elements.clone(), + fn clone(&self) -> SmolSet { + SmolSet { + inner: self.inner.clone(), } } } -impl fmt::Debug for SmallSet +impl fmt::Debug for SmolSet where A::Item: PartialEq + Eq + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.elements.fmt(f) + match &self.inner { + InnerSmolSet::Stack(elements) => write!(f, "{:?}", elements.as_slice()), + InnerSmolSet::Heap(elements) => write!(f, "{:?}", elements), + } } } -impl FromIterator for SmallSet +impl FromIterator for SmolSet where - A::Item: PartialEq + Eq, + A::Item: PartialEq + Eq + Hash, { fn from_iter(iter: T) -> Self where T: IntoIterator, { - SmallSet { - elements: SmallVec::from_iter(iter), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use std::fmt::Write; - - #[test] - fn test_basic_set() { - let mut s: SmallSet<[u32; 2]> = SmallSet::new(); - assert!(s.insert(1) == true); - assert!(s.insert(2) == true); - assert!(s.insert(2) == false); - assert!(s.insert(3) == true); - assert!(s.insert(2) == false); - assert!(s.insert(3) == false); - assert!(s.contains(&1)); - assert!(s.contains(&2)); - assert!(s.contains(&3)); - assert!(!s.contains(&4)); - assert!(s.len() == 3); - assert!(s.iter().map(|r| *r).collect::>() == vec![1, 2, 3]); - s.clear(); - assert!(!s.contains(&1)); - } - - #[test] - fn test_remove() { - let mut s: SmallSet<[u32; 2]> = SmallSet::new(); - assert!(s.insert(1) == true); - assert!(s.insert(2) == true); - assert!(s.len() == 2); - assert!(s.contains(&1)); - assert!(s.remove(&1) == true); - assert!(s.remove(&1) == false); - assert!(s.len() == 1); - assert!(!s.contains(&1)); - assert!(s.insert(1) == true); - assert!(s.iter().map(|r| *r).collect::>() == vec![2, 1]); - } - - #[test] - fn test_clone() { - let mut s: SmallSet<[u32; 2]> = SmallSet::new(); - s.insert(1); - s.insert(2); - let c = s.clone(); - assert!(c.contains(&1)); - assert!(c.contains(&2)); - assert!(!c.contains(&3)); - } - - #[test] - fn test_debug() { - let mut s: SmallSet<[u32; 2]> = SmallSet::new(); - s.insert(1); - s.insert(2); - let mut buf = String::new(); - write!(buf, "{:?}", s).unwrap(); - assert!(&buf == "[1, 2]"); - } - - #[test] - fn test_fromiter() { - let s: SmallSet<[usize; 4]> = vec![1, 2, 3, 4].into_iter().collect(); - assert!(s.len() == 4); + iter.into_iter().fold(SmolSet::new(), |mut acc, x| { + acc.insert(x); + acc + }) + } +} + +/// Iterator of the set returned upon calling `iter`. +/// This is required to be an abstraction over the enum. +pub struct SmolSetIter<'a, A: Array> +where + A::Item: PartialEq + Eq + Hash + 'a, +{ + inner: InnerSmolSetIter<'a, A>, +} + +pub enum InnerSmolSetIter<'a, A: Array> +where + A::Item: PartialEq + Eq + Hash + 'a, +{ + Stack(std::slice::Iter<'a, A::Item>), + Heap(std::collections::hash_set::Iter<'a, A::Item>), +} + +impl<'a, A: Array> Iterator for SmolSetIter<'a, A> +where + A::Item: PartialEq + Eq + Hash + 'a, +{ + type Item = &'a A::Item; + + fn next(&mut self) -> Option { + match &mut self.inner { + InnerSmolSetIter::Stack(ref mut iter) => iter.next(), + InnerSmolSetIter::Heap(ref mut iter) => iter.next(), + } } } diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..ed41160 --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,258 @@ +extern crate smolset; + +use smolset::{SetMode, SmolSet}; +use std::fmt::Write; +use std::hash::{Hash, Hasher}; +use std::iter::FromIterator; + +#[test] +fn test_basic_set() { + let mut s: SmolSet<[u32; 2]> = SmolSet::new(); + assert_eq!(s.insert(1), true); + assert_eq!(s.insert(2), true); + assert_eq!(s.insert(2), false); + assert_eq!(s.insert(3), true); + assert_eq!(s.insert(2), false); + assert_eq!(s.insert(3), false); + assert!(s.contains(&1)); + assert!(s.contains(&2)); + assert!(s.contains(&3)); + assert!(!s.contains(&4)); + let expected = vec![1, 2, 3]; + assert_eq!(s.len(), expected.len()); + assert!(s + .iter() + .map(|r| *r) + .collect::>() + .iter() + .all(|x| expected.contains(x))); + s.clear(); + assert!(!s.contains(&1)); +} + +#[test] +fn test_remove() { + let mut s: SmolSet<[u32; 2]> = SmolSet::new(); + assert_eq!(s.insert(1), true); + assert_eq!(s.insert(2), true); + assert_eq!(s.len(), 2); + assert!(s.contains(&1)); + assert_eq!(s.remove(&1), true); + assert_eq!(s.remove(&1), false); + assert_eq!(s.len(), 1); + assert!(!s.contains(&1)); + assert_eq!(s.insert(1), true); + let expected = vec![1, 2, 3]; + assert_eq!(s.len(), expected.len()); + assert!(s + .iter() + .map(|r| *r) + .collect::>() + .iter() + .all(|x| expected.contains(x))); +} + +#[test] +fn test_clone() { + let mut s: SmolSet<[u32; 2]> = SmolSet::new(); + s.insert(1); + s.insert(2); + let c = s.clone(); + assert!(c.contains(&1)); + assert!(c.contains(&2)); + assert!(!c.contains(&3)); +} + +#[test] +fn test_debug_small() { + let mut s: SmolSet<[u32; 2]> = SmolSet::new(); + s.insert(1); + s.insert(2); + let mut buf = String::new(); + write!(buf, "{:?}", s).unwrap(); + assert_eq!(&buf, "[1, 2]"); +} + +#[test] +fn test_from_iter() { + let s: SmolSet<[usize; 4]> = vec![1, 2, 3, 4].into_iter().collect(); + assert_eq!(s.len(), 4); +} + +#[test] +fn test_replace() { + struct RingOf7 { + pub value: u32, + } + + impl PartialEq for RingOf7 { + fn eq(&self, other: &Self) -> bool { + self.value % 7 == other.value % 7 + } + + fn ne(&self, other: &Self) -> bool { + self.value % 7 != other.value % 7 + } + } + + impl From for u32 { + fn from(value: RingOf7) -> Self { + value.value + } + } + + impl Hash for RingOf7 { + fn hash(&self, state: &mut H) { + self.value.hash(state) + } + } + + impl Eq for RingOf7 {} + + let mut lhs = SmolSet::<[RingOf7; 4]>::new(); + lhs.insert(RingOf7 { value: 1 }); + lhs.insert(RingOf7 { value: 2 }); + lhs.insert(RingOf7 { value: 3 }); + lhs.insert(RingOf7 { value: 4 }); + + lhs.replace(RingOf7 { value: 8 }); + lhs.replace(RingOf7 { value: 9 }); + lhs.replace(RingOf7 { value: 10 }); + lhs.replace(RingOf7 { value: 11 }); + + let expected = vec![8, 9, 10, 11]; + assert_eq!(lhs.len(), expected.len()); + assert!(lhs + .iter() + .map(|x| x.value) + .collect::>() + .iter() + .all(|x| expected.contains(x))); +} + +#[test] +fn test_eq_both_stack() { + let mut lhs = SmolSet::<[u32; 4]>::new(); + lhs.insert(1); + lhs.insert(2); + + let mut rhs = SmolSet::<[u32; 4]>::new(); + rhs.insert(1); + rhs.insert(2); + + assert_eq!(lhs, rhs); +} + +#[test] +fn test_eq_both_heap() { + let expected = (0..100).collect::>(); + let lhs = SmolSet::<[u32; 4]>::from_iter(expected.clone()); + let rhs = SmolSet::<[u32; 4]>::from_iter(expected.clone()); + + assert_eq!(lhs, rhs); +} + +#[test] +fn test_eq_stack_heap() { + let expected = (0..5).collect::>(); + let mut lhs = SmolSet::<[u32; 10]>::from_iter(expected.clone()); + let rhs = SmolSet::<[u32; 10]>::from_iter(expected.clone()); + + (100..200).for_each(|x| assert!(lhs.insert(x))); + (100..200).for_each(|x| assert!(lhs.remove(&x))); + + assert_eq!(lhs.mode(), SetMode::Heap); + assert_eq!(rhs.mode(), SetMode::Stack); + + assert_eq!(lhs, rhs); +} + +#[test] +fn test_intersection() { + let mut lhs = SmolSet::<[u32; 4]>::new(); + lhs.insert(1); + lhs.insert(3); + lhs.insert(5); + lhs.insert(4); + lhs.insert(8); + lhs.insert(10); + + let mut rhs = SmolSet::<[u32; 4]>::new(); + rhs.insert(4); + rhs.insert(8); + rhs.insert(10); + + assert!(lhs.intersection(&rhs).all(|x| x % 2 == 0)); +} + +#[test] +fn test_union() { + let mut lhs = SmolSet::<[u32; 4]>::new(); + lhs.insert(1); + lhs.insert(2); + lhs.insert(3); + lhs.insert(4); + + let mut rhs = SmolSet::<[u32; 4]>::new(); + rhs.insert(3); + rhs.insert(4); + rhs.insert(5); + rhs.insert(6); + + let union = lhs.union(&rhs).collect::>(); + let expected = vec![1, 2, 3, 4, 5, 6]; + assert_eq!(union.len(), expected.len()); + assert!(expected + .iter() + .collect::>() + .iter() + .all(|x| union.contains(x))); +} + +#[test] +fn test_difference() { + let mut lhs = SmolSet::<[u32; 4]>::new(); + lhs.insert(1); + lhs.insert(2); + lhs.insert(3); + lhs.insert(4); + + let mut rhs = SmolSet::<[u32; 4]>::new(); + rhs.insert(3); + rhs.insert(4); + rhs.insert(5); + rhs.insert(6); + + let union = lhs.difference(&rhs).collect::>(); + let expected = vec![1, 2]; + assert_eq!(union.len(), expected.len()); + assert!(expected + .iter() + .collect::>() + .iter() + .all(|x| union.contains(x))); +} + +#[test] +fn test_symmetric_difference() { + let mut lhs = SmolSet::<[u32; 4]>::new(); + lhs.insert(1); + lhs.insert(2); + lhs.insert(3); + lhs.insert(4); + + let mut rhs = SmolSet::<[u32; 4]>::new(); + rhs.insert(3); + rhs.insert(4); + rhs.insert(5); + rhs.insert(6); + + let symmetric_difference = lhs.symmetric_difference(&rhs).collect::>(); + let expected = vec![1, 2, 5, 6]; + assert_eq!(symmetric_difference.len(), expected.len()); + assert!(expected + .iter() + .collect::>() + .iter() + .all(|x| { symmetric_difference.contains(x) })); +}