datastructures crate: intervalmap
This commit is contained in:
parent
33b42533ef
commit
ad22ff8c76
|
|
@ -12,7 +12,7 @@ members = [
|
|||
"crates/window",
|
||||
"crates/game",
|
||||
"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]
|
||||
|
|
|
|||
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