datastructures crate: intervalmap
This commit is contained in:
parent
33b42533ef
commit
ad22ff8c76
|
|
@ -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]
|
||||||
|
|
|
||||||
6
crates/datastructures/Cargo.toml
Normal file
6
crates/datastructures/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "datastructures"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
471
crates/datastructures/src/lib.rs
Normal file
471
crates/datastructures/src/lib.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue