utils: add RangeSet early implementation based on RBTree

This commit is contained in:
2025-12-05 18:02:17 -08:00
parent 2158b8cf35
commit 324923b284
6 changed files with 168 additions and 2 deletions

19
Cargo.lock generated
View File

@@ -202,6 +202,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "intrusive-collections"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
dependencies = [
"memoffset",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.14.0" version = "0.14.0"
@@ -233,10 +242,20 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "misc" name = "misc"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"intrusive-collections",
"num-traits", "num-traits",
] ]

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "grid" name = "grid"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]

19
utils/misc/Cargo.lock generated
View File

@@ -8,10 +8,29 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "intrusive-collections"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
dependencies = [
"memoffset",
]
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "misc" name = "misc"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"intrusive-collections",
"num-traits", "num-traits",
] ]

View File

@@ -1,7 +1,8 @@
[package] [package]
name = "misc" name = "misc"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
intrusive-collections = "0.9.7"
num-traits = "0.2.19" num-traits = "0.2.19"

View File

@@ -2,6 +2,8 @@ use num_traits::Signed;
use std::fmt::Display; use std::fmt::Display;
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
pub mod range;
const POW10MAX: usize = u64::MAX.ilog10() as usize; const POW10MAX: usize = u64::MAX.ilog10() as usize;
pub const POW10: [u64; POW10MAX] = pow10_lut(); pub const POW10: [u64; POW10MAX] = pow10_lut();
@@ -105,6 +107,7 @@ mod tests {
#[test] #[test]
fn test_pow10() { fn test_pow10() {
#[allow(clippy::needless_range_loop)]
for i in 0..POW10MAX { for i in 0..POW10MAX {
assert_eq!(POW10[i], 10u64.pow(i as u32)) assert_eq!(POW10[i], 10u64.pow(i as u32))
} }

124
utils/misc/src/range.rs Normal file
View File

@@ -0,0 +1,124 @@
use intrusive_collections::Bound as IBound;
use intrusive_collections::{KeyAdapter, intrusive_adapter};
use intrusive_collections::{RBTreeLink, rbtree::RBTree};
use std::cmp::{max, min};
use std::ops::{Add, Bound, RangeBounds, Sub};
#[derive(Debug)]
pub struct NaiveRange<T> {
link: RBTreeLink,
pub start: T,
pub end: T,
}
intrusive_adapter!(
pub NaiveRangeAdapter<T> = Box<NaiveRange<T>>: NaiveRange<T> { link: RBTreeLink }
);
impl<'a, T: Ord + Clone> KeyAdapter<'a> for NaiveRangeAdapter<T> {
type Key = T;
fn get_key(&self, node: &'a NaiveRange<T>) -> T {
node.start.clone()
}
}
impl<T: Ord> NaiveRange<T> {
fn new(start: T, end: T) -> Self {
Self {
link: RBTreeLink::new(),
start,
end,
}
}
}
pub struct RangeSet<T: Ord> {
pub store: RBTree<NaiveRangeAdapter<T>>,
}
fn normalize_range<R: RangeBounds<T>, T: Copy + Ord + Add<Output = T> + From<u8>>(
r: &R,
) -> NaiveRange<T> {
let start = match r.start_bound() {
Bound::Included(x) => *x,
Bound::Excluded(x) => *x + T::from(1),
Bound::Unbounded => panic!(),
};
let end = match r.end_bound() {
Bound::Included(x) => *x + T::from(1),
Bound::Excluded(x) => *x,
Bound::Unbounded => panic!(),
};
NaiveRange::new(start, end)
}
impl<T: Copy + Ord + Add<Output = T> + Sub<Output = T> + From<u8>> Default for RangeSet<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Copy + Ord + Add<Output = T> + Sub<Output = T> + From<u8>> RangeSet<T> {
pub fn new() -> Self {
Self {
store: RBTree::new(NaiveRangeAdapter::<T>::new()),
}
}
pub fn add<R: RangeBounds<T>>(&mut self, r: &R) {
//normalize r to Range
let mut new_r = normalize_range(r);
// Find the position of nearest entry >= and <= than new_r's start
let mut cur = self.store.lower_bound_mut(IBound::Included(&new_r.start));
while let Some(val) = cur.get()
&& new_r.end >= val.start
{
// then remove them and update our new range
new_r.start = min(new_r.start, val.start);
new_r.end = max(new_r.end, val.end);
cur.remove(); // moves the cursor ahead
}
// Move the cursor back one (this will be the element before our insertion point, 'below' start)
cur.move_prev();
while let Some(val) = cur.get()
&& new_r.start <= val.end
{
new_r.start = min(new_r.start, val.start);
new_r.end = max(new_r.end, val.end);
cur.remove();
cur.move_prev();
}
// We are already in position, we just checked the previous element and it's before us
// since we fell out of the loop, so we need to go after this position
cur.insert_after(Box::new(new_r));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ranges() {
let mut set = RangeSet::new();
// edge cases are tricky to construct, run the aoc2025 day 5 puzzle and compare :)
set.add(&(0..10));
set.add(&(100..1000));
set.add(&(50..100));
set.add(&(50..100));
set.add(&(1001..2000));
set.add(&(2000..2001));
set.add(&(-10..-2));
set.add(&(-1..-1));
let end = set
.store
.iter()
.map(|r| (r.start, r.end))
.collect::<Vec<_>>();
assert_eq!(end, [(-10, 10), (50, 2001)])
}
}