datastructures crate: intervalmap

This commit is contained in:
janis 2026-10-30 01:00:31 +01:00
parent 33b42533ef
commit ad22ff8c76
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
3 changed files with 478 additions and 1 deletions

View file

@ -12,7 +12,7 @@ members = [
"crates/window", "crates/window",
"crates/game", "crates/game",
"crates/text", "crates/text",
"crates/bevy_vulkan_render", "crates/renderer_device", "crates/renderer_resources", "crates/renderer_rendergraph", "crates/bevy_vulkan_render", "crates/renderer_device", "crates/renderer_resources", "crates/renderer_rendergraph", "crates/datastructures",
] ]
[dependencies] [dependencies]

View file

@ -0,0 +1,6 @@
[package]
name = "datastructures"
version = "0.1.0"
edition = "2024"
[dependencies]

View file

@ -0,0 +1,471 @@
//! A map that stores values over disjoint, half-open intervals.
use std::collections::BTreeMap;
/// Operations applied to the covered and uncovered parts of a range.
pub trait IntervalUpdateOps<K, V> {
/// Updates the value of a subrange that was already present in the map.
fn update(&mut self, range: core::range::Range<K>, value: &mut V);
/// Creates a value for a subrange that was not previously present.
fn infill(&mut self, range: core::range::Range<K>) -> V;
}
impl<K, V, T> IntervalUpdateOps<K, V> for &mut T
where
T: IntervalUpdateOps<K, V> + ?Sized,
{
fn update(&mut self, range: core::range::Range<K>, value: &mut V) {
(**self).update(range, value);
}
fn infill(&mut self, range: core::range::Range<K>) -> V {
(**self).infill(range)
}
}
/// An interval and its associated value.
#[derive(Debug, Clone)]
struct Entry<K, V> {
/// The covered interval; its start is always the BTreeMap key.
range: core::range::Range<K>,
value: V,
}
/// A map from disjoint, half-open ranges of ordered keys to values.
///
/// Stored ranges are pairwise disjoint and sorted by their start key.
#[derive(Debug, Clone)]
pub struct DisjointIntervalMap<K, V> {
map: BTreeMap<K, Entry<K, V>>,
}
impl<K, V> DisjointIntervalMap<K, V> {
/// Creates an empty interval map.
pub const fn new() -> Self {
Self {
map: BTreeMap::new(),
}
}
/// Applies operations to every existing and missing subrange within `range`.
///
/// On return, every point in the non-empty range is covered by exactly one
/// map entry. Existing values are updated, and missing intervals are
/// created through [`IntervalUpdateOps::infill`].
pub fn split_apply(
&mut self,
range: core::range::Range<K>,
mut ops: impl IntervalUpdateOps<K, V>,
) where
K: Ord + Copy,
V: Clone,
{
if range.is_empty() {
return;
}
self.split(range.start);
self.split(range.end);
let mut cursor = range.start;
let mut gaps = Vec::new();
for entry in self
.map
.range_mut(range.start..range.end)
.map(|(_, entry)| entry)
{
if cursor < entry.range.start {
gaps.push(core::range::Range {
start: cursor,
end: entry.range.start,
});
}
let entry_range = entry.range;
ops.update(entry_range, &mut entry.value);
cursor = entry_range.end;
}
if cursor < range.end {
gaps.push(core::range::Range {
start: cursor,
end: range.end,
});
}
for gap in gaps {
let value = ops.infill(gap);
self.map.insert(gap.start, Entry { range: gap, value });
}
}
/// Inserts `range` unless it is empty or overlaps an existing interval.
#[allow(clippy::result_unit_err)]
pub fn try_insert(&mut self, range: core::range::Range<K>, value: V) -> Result<(), ()>
where
K: Ord + Copy,
{
if range.is_empty() {
return Err(());
}
if self
.map
.range(..range.end)
.next_back()
.is_some_and(|(_, entry)| entry.range.end > range.start)
{
return Err(());
}
self.map.insert(range.start, Entry { range, value });
Ok(())
}
/// Removes all mapped portions covered by `range`.
pub fn remove(&mut self, range: core::range::Range<K>)
where
K: Ord + Copy,
V: Clone,
{
if range.is_empty() {
return;
}
self.split(range.start);
self.split(range.end);
let keys: Vec<K> = self
.map
.range(range.start..range.end)
.map(|(key, _)| *key)
.collect();
for key in keys {
self.map.remove(&key);
}
}
/// Splits the interval containing `pos` into two intervals at `pos`.
///
/// Does nothing if no interval contains `pos` or an interval already
/// starts there.
pub fn split(&mut self, pos: K)
where
K: Ord + Copy,
V: Clone,
{
let Some((key, interval)) = self
.map
.range_mut(..=pos)
.next_back()
.map(|(key, entry)| (*key, entry.range))
else {
return;
};
if key == pos || pos >= interval.end {
return;
}
let entry = self.map.remove(&key).expect("candidate entry must exist");
let right_value = entry.value.clone();
let left_range = core::range::Range {
start: key,
end: pos,
};
let right_range = core::range::Range {
start: pos,
end: entry.range.end,
};
self.map.insert(
key,
Entry {
range: left_range,
value: entry.value,
},
);
self.map.insert(
pos,
Entry {
range: right_range,
value: right_value,
},
);
}
/// Returns the value of the interval containing `pos`, if any.
pub fn get(&self, pos: K) -> Option<&V>
where
K: Ord,
{
let (_, entry) = self
.map
.range((std::ops::Bound::Unbounded, std::ops::Bound::Included(&pos)))
.next_back()?;
(pos < entry.range.end).then_some(&entry.value)
}
/// Returns a mutable reference to the value of the interval containing `pos`.
pub fn get_mut(&mut self, pos: K) -> Option<&mut V>
where
K: Ord,
{
let (_, entry) = self
.map
.range_mut((std::ops::Bound::Unbounded, std::ops::Bound::Included(&pos)))
.next_back()?;
(pos < entry.range.end).then_some(&mut entry.value)
}
/// Iterates over intervals and their values in start-key order.
pub fn iter(&self) -> impl Iterator<Item = (&core::range::Range<K>, &V)> {
self.map.values().map(|entry| (&entry.range, &entry.value))
}
/// Iterates over values in start-key order.
pub fn values(&self) -> impl Iterator<Item = &V> {
self.map.values().map(|entry| &entry.value)
}
/// Iterates over intervals in start-key order.
pub fn keys(&self) -> impl Iterator<Item = &core::range::Range<K>> {
self.map.values().map(|entry| &entry.range)
}
/// Returns the number of stored intervals.
pub fn len(&self) -> usize {
self.map.len()
}
/// Returns whether the map contains no intervals.
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
/// Removes all intervals from the map.
pub fn clear(&mut self) {
self.map.clear();
}
}
impl<K, V> Default for DisjointIntervalMap<K, V> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{DisjointIntervalMap, IntervalUpdateOps};
fn r(start: i32, end: i32) -> core::range::Range<i32> {
core::range::Range::from(start..end)
}
#[derive(Default)]
struct CountingOps {
updates: Vec<(i32, i32)>,
infills: Vec<(i32, i32)>,
}
impl IntervalUpdateOps<i32, i32> for CountingOps {
fn update(&mut self, range: core::range::Range<i32>, value: &mut i32) {
*value += 1;
self.updates.push((range.start, range.end));
}
fn infill(&mut self, range: core::range::Range<i32>) -> i32 {
self.infills.push((range.start, range.end));
100 + range.start
}
}
fn intervals(map: &DisjointIntervalMap<i32, i32>) -> Vec<(i32, i32, i32)> {
map.iter()
.map(|(range, value)| (range.start, range.end, *value))
.collect()
}
#[test]
fn try_insert_overlap_adjacency_and_lookup() {
let mut map = DisjointIntervalMap::new();
assert!(map.try_insert(r(5, 10), 1).is_ok());
assert!(map.try_insert(r(0, 5), 2).is_ok());
assert!(map.try_insert(r(10, 15), 3).is_ok());
for overlap in [r(4, 6), r(9, 12), r(6, 8), r(4, 11)] {
assert_eq!(map.try_insert(overlap, 9), Err(()));
}
assert_eq!(map.try_insert(r(2, 2), 9), Err(()));
assert_eq!(map.get(0), Some(&2));
assert_eq!(map.get(5), Some(&1));
assert_eq!(map.get(14), Some(&3));
assert_eq!(map.get(15), None);
assert_eq!(map.get(-1), None);
*map.get_mut(5).unwrap() = 7;
assert_eq!(map.get(5), Some(&7));
assert!(map.get_mut(15).is_none());
}
#[test]
fn split_boundaries_and_interior() {
let mut map = DisjointIntervalMap::new();
assert!(map.try_insert(r(0, 10), 4).is_ok());
map.split(5);
assert_eq!(intervals(&map), vec![(0, 5, 4), (5, 10, 4)]);
for pos in [0, 10, -1, 11] {
let before = intervals(&map);
map.split(pos);
assert_eq!(intervals(&map), before);
}
let mut empty = DisjointIntervalMap::<i32, i32>::new();
empty.split(1);
assert!(empty.is_empty());
}
#[test]
fn remove_exact_partial_spanning_and_noop_ranges() {
let mut exact = DisjointIntervalMap::new();
exact.try_insert(r(0, 5), 1).unwrap();
exact.remove(r(0, 5));
assert!(exact.is_empty());
let mut partial = DisjointIntervalMap::new();
partial.try_insert(r(0, 5), 1).unwrap();
partial.try_insert(r(5, 10), 2).unwrap();
partial.remove(r(3, 7));
assert_eq!(intervals(&partial), vec![(0, 3, 1), (7, 10, 2)]);
partial.remove(r(8, 12));
assert_eq!(intervals(&partial), vec![(0, 3, 1), (7, 8, 2)]);
let mut spanning = DisjointIntervalMap::new();
for start in [0, 5, 10] {
spanning.try_insert(r(start, start + 5), start).unwrap();
}
spanning.remove(r(2, 13));
assert_eq!(intervals(&spanning), vec![(0, 2, 0), (13, 15, 10)]);
let before = intervals(&spanning);
spanning.remove(r(20, 25));
spanning.remove(r(4, 4));
assert_eq!(intervals(&spanning), before);
}
#[test]
fn split_apply_on_empty_and_fully_covered_maps() {
let mut empty = DisjointIntervalMap::new();
let mut ops = CountingOps::default();
empty.split_apply(r(2, 6), &mut ops);
assert_eq!(intervals(&empty), vec![(2, 6, 102)]);
assert!(ops.updates.is_empty());
assert_eq!(ops.infills, vec![(2, 6)]);
let mut covered = DisjointIntervalMap::new();
covered.try_insert(r(0, 10), 5).unwrap();
let mut ops = CountingOps::default();
covered.split_apply(r(0, 10), &mut ops);
assert_eq!(intervals(&covered), vec![(0, 10, 6)]);
assert_eq!(ops.updates, vec![(0, 10)]);
assert!(ops.infills.is_empty());
}
#[test]
fn split_apply_mixed_coverage_splits_and_fills_gaps() {
let mut map = DisjointIntervalMap::new();
map.try_insert(r(0, 4), 1).unwrap();
map.try_insert(r(6, 10), 2).unwrap();
let mut ops = CountingOps::default();
map.split_apply(r(2, 8), &mut ops);
assert_eq!(
intervals(&map),
vec![(0, 2, 1), (2, 4, 2), (4, 6, 104), (6, 8, 3), (8, 10, 2)]
);
assert_eq!(ops.updates, vec![(2, 4), (6, 8)]);
assert_eq!(ops.infills, vec![(4, 6)]);
}
#[test]
fn split_apply_inside_one_entry_and_empty_range() {
let mut map = DisjointIntervalMap::new();
map.try_insert(r(0, 10), 7).unwrap();
let mut ops = CountingOps::default();
map.split_apply(r(3, 5), &mut ops);
assert_eq!(intervals(&map), vec![(0, 3, 7), (3, 5, 8), (5, 10, 7)]);
assert_eq!(ops.updates, vec![(3, 5)]);
assert!(ops.infills.is_empty());
let before = intervals(&map);
let prior_ops = ops.updates.clone();
let prior_infills = ops.infills.clone();
map.split_apply(r(4, 4), &mut ops);
assert_eq!(intervals(&map), before);
assert_eq!(ops.updates, prior_ops);
assert_eq!(ops.infills, prior_infills);
}
#[test]
fn split_apply_mutable_ops_accumulate_state() {
let mut map = DisjointIntervalMap::new();
let mut ops = CountingOps::default();
map.split_apply(r(0, 2), &mut ops);
map.split_apply(r(4, 6), &mut ops);
assert_eq!(ops.infills, vec![(0, 2), (4, 6)]);
assert_eq!(map.len(), 2);
}
#[test]
fn iteration_and_collection_basics() {
let mut map = DisjointIntervalMap::new();
assert!(map.is_empty());
map.try_insert(r(5, 8), 2).unwrap();
map.try_insert(r(0, 3), 1).unwrap();
assert_eq!(map.len(), 2);
assert!(!map.is_empty());
assert_eq!(
map.iter()
.map(|(range, value)| ((range.start, range.end), *value))
.collect::<Vec<_>>(),
vec![((0, 3), 1), ((5, 8), 2)]
);
assert_eq!(
map.keys()
.map(|range| (range.start, range.end))
.collect::<Vec<_>>(),
vec![(0, 3), (5, 8)]
);
assert_eq!(map.values().copied().collect::<Vec<_>>(), vec![1, 2]);
map.clear();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
}
#[test]
fn operations_preserve_disjoint_nonempty_sorted_invariant() {
let mut map = DisjointIntervalMap::new();
map.try_insert(r(0, 4), 1).unwrap();
map.try_insert(r(6, 10), 2).unwrap();
map.split(2);
map.remove(r(3, 7));
let mut ops = CountingOps::default();
map.split_apply(r(-2, 12), &mut ops);
map.try_insert(r(20, 24), 3).unwrap();
map.split(22);
map.remove(r(1, 2));
let ranges = map.keys().collect::<Vec<_>>();
for range in &ranges {
assert!(!range.is_empty());
}
for pair in ranges.windows(2) {
assert!(pair[0].start < pair[1].start);
assert!(pair[0].end <= pair[1].start);
}
}
}