[no description]
bendn 9 days ago
commit 056e7c0
-rw-r--r--.gitignore1
-rw-r--r--Cargo.toml13
-rw-r--r--rustfmt.toml7
-rw-r--r--src/lib.rs139
4 files changed, 160 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..03314f7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..479068b
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "btree_multiset"
+version = "0.1.0"
+edition = "2024"
+description = "btreeset with more capacity"
+authors = ["bendn <[email protected]>"]
+license = "MIT"
+repository = "https://git.bendn.org/btree_multiset"
+keywords = ["btree", "multiset"]
+rust-version = "1.95.0"
+
+[dependencies]
+smolvec = "1.0.0"
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..c39da03
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,7 @@
+max_width = 75
+format_strings = true
+group_imports = "StdExternalCrate"
+imports_granularity = "Module"
+match_arm_blocks = false
+style_edition = "2024"
+use_small_heuristics = "Max"
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..472df7e
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,139 @@
+#![feature(allocator_api, extend_one, btreemap_alloc)]
+//! [`BTreeSet`](std::collections::BTreeSet) but with duplicates.
+use std::alloc::{Allocator, Global};
+use std::collections::BTreeMap;
+use std::collections::btree_map::{self, Entry};
+use std::fmt::Debug;
+use std::iter::FlatMap;
+
+use smolvec::SmolVec;
+
+#[derive(Clone)]
+/// Like a btree set, but with duplicates.
+/// Currently immutable cause i havent thought about it yet.
+pub struct BTreeMultiset<K, T, F, A: Allocator + Clone> {
+ inner: BTreeMap<K, SmolVec<T>, A>,
+ f: F,
+}
+impl<K: Ord, T: Debug, F: Fn(&T) -> K, A: Allocator + Clone> Debug
+ for BTreeMultiset<K, T, F, A>
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_set().entries(self.iter()).finish()
+ }
+}
+impl<K, T, F: Fn(&T) -> K, A: Allocator + Clone> IntoIterator
+ for BTreeMultiset<K, T, F, A>
+{
+ type Item = T;
+
+ type IntoIter = FlatMap<
+ btree_map::IntoIter<K, SmolVec<T>, A>,
+ SmolVec<T>,
+ fn((K, SmolVec<T>)) -> SmolVec<T>,
+ >;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.inner.into_iter().flat_map(|x| x.1)
+ }
+}
+impl<K: Ord, T, F: Fn(&T) -> K, A: Allocator + Clone> Extend<T>
+ for BTreeMultiset<K, T, F, A>
+{
+ #[inline]
+ fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
+ iter.into_iter().for_each(move |x| {
+ self.insert(x);
+ });
+ }
+
+ #[inline]
+ fn extend_one(&mut self, x: T) {
+ self.insert(x);
+ }
+}
+impl<K: Ord, T, F: Fn(&T) -> K> BTreeMultiset<K, T, F, Global> {
+ pub fn new(f: F) -> Self {
+ Self { inner: BTreeMap::new_in(Global), f }
+ }
+}
+type Iter<'a, K, T> = FlatMap<
+ btree_map::Iter<'a, K, SmolVec<T>>,
+ &'a SmolVec<T>,
+ fn((&'a K, &'a SmolVec<T>)) -> &'a SmolVec<T>,
+>;
+type Range<'a, K, T> = FlatMap<
+ btree_map::Range<'a, K, SmolVec<T>>,
+ &'a SmolVec<T>,
+ fn((&'a K, &'a SmolVec<T>)) -> &'a SmolVec<T>,
+>;
+impl<K, T, F, A: Allocator + Clone> BTreeMultiset<K, T, F, A> {
+ pub fn into_inner(self) -> BTreeMap<K, SmolVec<T>, A> {
+ self.inner
+ }
+}
+impl<K: Ord, T, F: Fn(&T) -> K, A: Allocator + Clone>
+ BTreeMultiset<K, T, F, A>
+{
+ pub fn new_in(f: F, a: A) -> Self {
+ Self { inner: BTreeMap::new_in(a), f }
+ }
+ /// Gets an iterator that visits the elements in the [`BTreeMultiset`] in ascending order.
+ /// Similar to `range(..)`.
+ pub fn iter<'a>(&'a self) -> Iter<'a, K, T> {
+ self.inner.iter().flat_map(|x| x.1)
+ }
+ /// Adds a value to the multi set.
+ pub fn insert(&mut self, t: T) -> bool {
+ match self.inner.entry((self.f)(&t)) {
+ Entry::Vacant(vacant_entry) => {
+ vacant_entry.insert(SmolVec::from_iter([t]));
+ false
+ }
+ Entry::Occupied(occupied_entry) => {
+ occupied_entry.into_mut().push(t);
+ true
+ }
+ }
+ }
+
+ /// Constructs a double-ended iterator over a sub-range of elements in the multiset.
+ /// The simplest way is to use the range syntax `min..max`, thus `range(min..max)` will
+ /// yield elements from min (inclusive) to max (exclusive).
+ /// The range may also be entered as `(Bound<T>, Bound<T>)`, so for example
+ /// `range((Excluded(4), Included(10)))` will yield a left-exclusive, right-inclusive
+ /// range from 4 to 10.
+ ///
+ /// # Panics
+ ///
+ /// Panics if range `start > end`.
+ /// Panics if range `start == end` and both bounds are `Excluded`.
+ pub fn range<'a>(
+ &'a self,
+ r: impl std::ops::RangeBounds<K>,
+ ) -> Range<'a, K, T> {
+ self.inner.range(r).flat_map(|x| x.1)
+ }
+
+ // pub fn range_mut<'a, R>(
+ // &'a mut self,
+ // r: impl std::ops::RangeBounds<K>,
+ // ) -> FlatMap<
+ // RangeMut<'a, K, SmolVec<T>>,
+ // &'a mut SmolVec<T>,
+ // fn((&'a K, &'a mut SmolVec<T>)) -> &'a mut SmolVec<T>,
+ // > {
+ // self.inner.range_mut(r).flat_map(|x| x.1)
+ // }
+}
+#[test]
+fn works() {
+ let mut x = BTreeMultiset::new(|x: &u32| *x);
+
+ x.insert(4);
+ x.insert(3);
+ x.insert(7);
+ x.insert(3);
+ assert_eq!(x.iter().copied().collect::<Vec<_>>(), vec![3, 3, 4, 7]);
+ assert_eq!(x.range(0..=4).copied().collect::<Vec<_>>(), vec![3, 3, 4]);
+}