utils: add RangeSet early implementation based on RBTree
This commit is contained in:
19
Cargo.lock
generated
19
Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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
19
utils/misc/Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
124
utils/misc/src/range.rs
Normal 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)])
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user