diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2445e..5b9e915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,21 @@ + ### v1.0.3 (2022-06-11) + +- **Fixes**: + - Fix `Gaps` iterator for `RangeMap` yielding an empty gap for an empty outer range. Simplified gaps logic and expanded fuzz testing to better cover this and similar cases. + + +### v1.0.2 (2022-05-17) + +- **Fixes**: + - Fix empty gaps returned by `Gaps` iterator for `RangeInclusiveMap`. Added fuzz tests for `Gaps` iterators. + + ### v1.0.1 (2022-01-29) - **Fixes**: - Fix empty gaps returned by `Gaps` iterator for `RangeMap`, and incorrect gaps returned by `Gaps` iterator for `RangeInclusiveMap`. + ### v1.0.0 (2022-01-28) It's time. (No functional change.) diff --git a/Cargo.toml b/Cargo.toml index 966af99..b467921 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "rangemap" -version = "1.0.1" +name = "vulkano-rangemap" +version = "1.0.3" authors = ["Jeff Parsons "] edition = "2018" license = "MIT/Apache-2.0" readme = "README.md" -repository = "https://github.com/jeffparsons/rangemap" -documentation = "https://docs.rs/rangemap" -homepage = "https://github.com/jeffparsons/rangemap" +repository = "https://github.com/vulkano-rs/rangemap" +documentation = "https://docs.rs/vulkano-rangemap" +homepage = "https://github.com/vulkano-rs/rangemap" description = """ Map and set data structures whose keys are stored as ranges. diff --git a/README.fuzz.md b/README.fuzz.md index 4d22591..e320189 100644 --- a/README.fuzz.md +++ b/README.fuzz.md @@ -7,4 +7,6 @@ Run one of these: ``` cargo +nightly fuzz run rangemap_coalesce cargo +nightly fuzz run rangemap_inclusive_coalesce +cargo +nightly fuzz run rangemap_gaps +cargo +nightly fuzz run rangemap_inclusive_gaps ``` diff --git a/README.md b/README.md index 7eb17f0..303b6e2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# rangemap +## ⚠️ This crate is a fork of [`rangemap`](https://github.com/jeffparsons/rangemap/). -[![Crate](https://img.shields.io/crates/v/rangemap.svg)](https://crates.io/crates/rangemap) -[![Docs](https://docs.rs/rangemap/badge.svg)](https://docs.rs/rangemap) -[![Build status](https://github.com/jeffparsons/rangemap/workflows/CI/badge.svg)](https://github.com/jeffparsons/rangemap/actions) -[![Rust](https://img.shields.io/badge/rust-1.46%2B-blue.svg?maxAge=3600)](https://github.com/jeffparsons/rangemap) +The Vulkano organization has created a fork of rangemap with additional functionality for use in [`vulkano`](https://github.com/vulkano-rs/vulkano). These changes will hopefully be upstreamed in the future. + +## rangemap [`RangeMap`] and [`RangeInclusiveMap`] are map data structures whose keys are stored as ranges. Contiguous and overlapping ranges that map to the same diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 3ef06c2..63fe51a 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -31,3 +31,15 @@ name = "rangemap_inclusive_coalesce" path = "fuzz_targets/rangemap_inclusive_coalesce.rs" test = false doc = false + +[[bin]] +name = "rangemap_gaps" +path = "fuzz_targets/rangemap_gaps.rs" +test = false +doc = false + +[[bin]] +name = "rangemap_inclusive_gaps" +path = "fuzz_targets/rangemap_inclusive_gaps.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/rangemap_gaps.rs b/fuzz/fuzz_targets/rangemap_gaps.rs new file mode 100644 index 0000000..5e52ff6 --- /dev/null +++ b/fuzz/fuzz_targets/rangemap_gaps.rs @@ -0,0 +1,94 @@ +#![feature(array_windows)] +#![no_main] +use libfuzzer_sys::fuzz_target; + +use arbitrary::{Arbitrary, Unstructured}; +use rangemap::RangeMap; +use std::ops::Range; + +#[derive(Clone, Debug, Arbitrary)] +enum Op { + Insert(Range, u8), + Remove(Range), +} + +impl Op { + fn apply(self, map: &mut RangeMap) { + match self { + Op::Insert(r, v) if r.start < r.end => map.insert(r, v), + Op::Remove(r) if r.start < r.end => map.remove(r), + _ => (), + } + } +} + +#[derive(Clone, Debug)] +struct Input { + ops: Vec, + outer_range: Range, +} + +impl Arbitrary for Input { + fn arbitrary(u: &mut Unstructured) -> arbitrary::Result { + Ok(Self { + ops: u.arbitrary()?, + // Larger margins than these are too + // far away from boundary conditions to be interesting. + // ("Oh, the fools! If only they'd built it with 6,001 hulls." -- Philip J. Fry) + // + // NOTE: Not using `int_in_range` because of . + outer_range: *u.choose(&[0, 1, 2, 3, 100, 101, 102, 103])? + ..*u.choose(&[100, 101, 102, 103, 252, 253, 254, 255])?, + }) + } +} + +fuzz_target!(|input: Input| { + let Input { ops, outer_range } = input; + + let mut map = RangeMap::new(); + + for op in ops { + op.apply(&mut map); + } + + // Check that the combination of gaps and keys fills the entire outer range. + let gaps: Vec> = map.gaps(&outer_range).collect(); + // TODO: Replace the filtering and mapping with a `range` iterator + // on the map itself. + let mut keys: Vec> = map + .into_iter() + .map(|(k, _v)| k) + .filter(|Range { start, end }| { + // Reject anything with zero of its width inside the outer range. + *end > outer_range.start && *start < outer_range.end + }) + .map(|Range { start, end }| { + // Truncate anything straddling either edge. + u8::max(start, outer_range.start)..u8::min(end, outer_range.end) + }) + .filter(|range| { + // Reject anything that is now empty after being truncated. + !range.is_empty() + }) + .collect(); + + keys.extend(gaps.into_iter()); + keys.sort_by_key(|key| key.start); + + if outer_range.is_empty() { + // There should be no gaps or keys returned if the outer range is empty, + // because empty ranges cover no values. + assert!(keys.is_empty()); + return; + } + + // Gaps and keys combined should span whole outer range. + assert_eq!(keys.first().unwrap().start, outer_range.start); + assert_eq!(keys.last().unwrap().end, outer_range.end); + + // Each gap/key should start where the previous one ended. + for [a, b] in keys.array_windows::<2>() { + assert_eq!(a.end, b.start); + } +}); diff --git a/fuzz/fuzz_targets/rangemap_inclusive_gaps.rs b/fuzz/fuzz_targets/rangemap_inclusive_gaps.rs new file mode 100644 index 0000000..0cda15d --- /dev/null +++ b/fuzz/fuzz_targets/rangemap_inclusive_gaps.rs @@ -0,0 +1,93 @@ +#![feature(array_windows)] +#![no_main] +use libfuzzer_sys::fuzz_target; + +use arbitrary::{Arbitrary, Unstructured}; +use rangemap::RangeInclusiveMap; +use std::ops::RangeInclusive; + +#[derive(Clone, Debug, Arbitrary)] +enum Op { + Insert(RangeInclusive, u8), + Remove(RangeInclusive), +} + +impl Op { + fn apply(self, map: &mut RangeInclusiveMap) { + match self { + Op::Insert(r, v) => map.insert(r, v), + Op::Remove(r) => map.remove(r), + } + } +} + +#[derive(Clone, Debug)] +struct Input { + ops: Vec, + outer_range: RangeInclusive, +} + +impl Arbitrary for Input { + fn arbitrary(u: &mut Unstructured) -> arbitrary::Result { + Ok(Self { + ops: u.arbitrary()?, + // Larger margins than these are too + // far away from boundary conditions to be interesting. + // ("Oh, the fools! If only they'd built it with 6,001 hulls." -- Philip J. Fry) + // + // NOTE: Not using `int_in_range` because of . + outer_range: *u.choose(&[0, 1, 2, 3, 100, 101, 102, 103])? + ..=*u.choose(&[100, 101, 102, 103, 252, 253, 254, 255])?, + }) + } +} + +fuzz_target!(|input: Input| { + let Input { ops, outer_range } = input; + + let mut map = RangeInclusiveMap::new(); + + for op in ops { + op.apply(&mut map); + } + + // Check that the combination of gaps and keys fills the entire outer range. + let gaps: Vec> = map.gaps(&outer_range).collect(); + // TODO: Replace the filtering and mapping with a `range` iterator + // on the map itself. + let mut keys: Vec> = map + .into_iter() + .map(|(k, _v)| k) + .filter(|range| { + // Reject anything with zero of its width inside the outer range. + *range.end() >= *outer_range.start() && *range.start() <= *outer_range.end() + }) + .map(|range| { + // Truncate anything straddling either edge. + u8::max(*range.start(), *outer_range.start()) + ..=u8::min(*range.end(), *outer_range.end()) + }) + .filter(|range| { + // Reject anything that is now empty after being truncated. + !range.is_empty() + }) + .collect(); + keys.extend(gaps.into_iter()); + keys.sort_by_key(|key| *key.start()); + + if outer_range.is_empty() { + // There should be no gaps or keys returned if the outer range is empty, + // because empty ranges cover no values. + assert!(keys.is_empty()); + return; + } + + // Gaps and keys combined should span whole outer range. + assert_eq!(keys.first().unwrap().start(), outer_range.start()); + assert_eq!(keys.last().unwrap().end(), outer_range.end()); + + // Each gap/key should start where the previous one ended. + for [a, b] in keys.array_windows::<2>() { + assert_eq!(*a.end() + 1, *b.start()); + } +}); diff --git a/src/dense.rs b/src/dense.rs new file mode 100644 index 0000000..3097e92 --- /dev/null +++ b/src/dense.rs @@ -0,0 +1,158 @@ +use alloc::{collections::BTreeMap, vec::Vec}; +use core::ops::{Range, RangeInclusive}; + +use super::{RangeInclusiveMap, RangeMap}; + +// A simple but infeasibly slow and memory-hungry +// version of `RangeInclusiveMap` for testing. +// +// Only understands `u32` keys, so that we don't +// have to be generic over `step`. This is just for +// testing, so it's fine. +// +// Note that even though this mirrors the semantics +// of `RangeInclusiveMap`, it can also be used to +// test `RangeMap` just fine. +#[derive(Eq, PartialEq, Debug)] +pub struct DenseU32RangeMap { + // Inner B-Tree map. Stores values and their keys + // directly rather than as ranges. + btm: BTreeMap, +} + +impl DenseU32RangeMap +where + V: Eq + Clone, +{ + pub fn new() -> DenseU32RangeMap { + DenseU32RangeMap { + btm: BTreeMap::new(), + } + } + + pub fn insert(&mut self, range: RangeInclusive, value: V) { + for k in range { + self.btm.insert(k, value.clone()); + } + } + + pub fn iter(&self) -> Iter<'_, V> { + Iter { + inner: self.btm.iter(), + current: None, + } + } + + pub fn end_exclusive_iter(&self) -> EndExclusiveIter<'_, V> { + EndExclusiveIter { inner: self.iter() } + } + + // Vecs are easier to use for assertions than iterators, + // because you don't have to consume them to compare them. + pub fn to_vec(&self) -> Vec<(RangeInclusive, V)> { + self.iter() + .map(|(range, value)| (range, value.clone())) + .collect() + } + + pub fn to_end_exclusive_vec(&self) -> Vec<(Range, V)> { + self.end_exclusive_iter() + .map(|(range, value)| (range, value.clone())) + .collect() + } +} + +impl From> for DenseU32RangeMap +where + V: Eq + Clone, +{ + fn from(range_map: RangeMap) -> Self { + let mut dense = Self::new(); + for (range, value) in range_map.iter() { + // Convert to inclusive end. + // (This is only valid because u32 has + // the "successor function".) + // + // NOTE: Clippy's `range_minus_one` lint is a bit overzealous here, + // because we _can't_ pass an open-ended range to `insert`. + #[allow(clippy::range_minus_one)] + dense.insert(range.start..=(range.end - 1), value.clone()); + } + dense + } +} + +impl From> for DenseU32RangeMap +where + V: Eq + Clone, +{ + fn from(range_inclusive_map: RangeInclusiveMap) -> Self { + let mut dense = Self::new(); + for (range, value) in range_inclusive_map.iter() { + dense.insert(range.clone(), value.clone()); + } + dense + } +} + +pub struct Iter<'a, V> { + inner: alloc::collections::btree_map::Iter<'a, u32, V>, + // Current range being built for output. + // We modify it as we iterate through the underlying + // dense map. + current: Option<(RangeInclusive, &'a V)>, +} + +// Coalesce items from the underlying dense map as we go. +impl<'a, V> Iterator for Iter<'a, V> +where + V: 'a + Eq, +{ + type Item = (RangeInclusive, &'a V); + + fn next(&mut self) -> Option<(RangeInclusive, &'a V)> { + if let Some(next_dense) = self.inner.next() { + if let Some(current) = &mut self.current { + // We're already building a range. Can we extend it? + if current.0.end() + 1 == *next_dense.0 && *current.1 == *next_dense.1 { + // This immediately follows the last item and the value is the same; + // we can extend it. + *current = (*current.0.start()..=*next_dense.0, current.1); + // Recurse until we find a way to flush it. + self.next() + } else { + // There's a gap or the value differs; flush the one we were working on and start a new one. + let item_to_yield = current.clone(); + *current = (*next_dense.0..=*next_dense.0, next_dense.1); + Some(item_to_yield) + } + } else { + // We're not building a range yet; start a new one. + self.current = Some((*next_dense.0..=*next_dense.0, next_dense.1)); + // Recurse until we find a way to flush it. + self.next() + } + } else { + // We reached the end of the underlying dense map. + // Flush the item we were building, if any. + self.current.take() + } + } +} + +pub struct EndExclusiveIter<'a, V> { + inner: Iter<'a, V>, +} + +impl<'a, V> Iterator for EndExclusiveIter<'a, V> +where + V: 'a + Eq, +{ + type Item = (Range, &'a V); + + fn next(&mut self) -> Option<(Range, &'a V)> { + self.inner + .next() + .map(|(range, value)| ((*range.start())..(*range.end() + 1), value)) + } +} diff --git a/src/inclusive_map.rs b/src/inclusive_map.rs index c268265..5024bb3 100644 --- a/src/inclusive_map.rs +++ b/src/inclusive_map.rs @@ -116,6 +116,18 @@ where self.get(key).is_some() } + /// Returns `true` if any part of the provided range overlaps with a range in the map. + #[inline] + pub fn contains_any<'a>(&self, range: &'a RangeInclusive) -> bool { + self.range(range).next().is_some() + } + + /// Returns `true` if all of the provided range is covered by ranges in the map. + #[inline] + pub fn contains_all<'a>(&self, range: &'a RangeInclusive) -> bool { + self.gaps(range).next().is_none() + } + /// Gets an iterator over all pairs of key range and value, /// ordered by key range. /// @@ -427,58 +439,42 @@ where } } + /// Gets an iterator over all pairs of key range and value, where the key range overlaps with + /// the provided range. + /// + /// The iterator element type is `(&RangeInclusive, &V)`. + pub fn range(&self, range: &RangeInclusive) -> RangeIter<'_, K, V> { + use core::ops::Bound; + + let start = self + .get_key_value(range.start()) + .map_or(range.start(), |(k, _v)| k.start()); + let end = range.end(); + + RangeIter { + inner: self.btm.range(( + Bound::Included(RangeInclusiveStartWrapper::new( + start.clone()..=start.clone(), + )), + Bound::Included(RangeInclusiveStartWrapper::new(end.clone()..=end.clone())), + )), + } + } + /// Gets an iterator over all the maximally-sized ranges /// contained in `outer_range` that are not covered by /// any range stored in the map. /// /// The iterator element type is `RangeInclusive`. - /// - /// NOTE: Calling `gaps` eagerly finds the first gap, - /// even if the iterator is never consumed. pub fn gaps<'a>(&'a self, outer_range: &'a RangeInclusive) -> Gaps<'a, K, V, StepFnsT> { - let mut keys = self.btm.keys().peekable(); - - // Find the first potential gap. - let mut candidate_start = outer_range.start().clone(); - // We might be already done from the start, - // but not be able to represent it using - // `candidate_start` alone if we're at the end - // of the key domain. - let mut done = false; - while let Some(item) = keys.peek() { - if item.range.end() < outer_range.start() { - // This range sits entirely before the start of - // the outer range; just skip it. - keys.next() - .expect("We just checked that there is a next element"); - } else if item.range.start() <= outer_range.start() { - // This range overlaps the start of the - // outer range, so the first possible candidate - // range begins immediately after its end. - if item.range.end() >= outer_range.end() { - // There's a risk of overflowing; - // use our extra "done" flag to represent - // that the iterator is already done. - // (Don't worry about `candidate_start` - // we'll ignore everything else if `done` - // is `true`.) - done = true; - } else { - candidate_start = StepFnsT::add_one(item.range.end()); - } - keys.next() - .expect("We just checked that there is a next element"); - } else { - // The rest of the items might contribute to gaps. - break; - } - } - Gaps { - done, + done: false, outer_range, - keys, - candidate_start, + keys: self.btm.keys(), + // We'll start the candidate range at the start of the outer range + // without checking what's there. Each time we yield an item, + // we'll skip any ranges we find before the next gap. + candidate_start: outer_range.start().clone(), _phantom: PhantomData, } } @@ -652,6 +648,36 @@ where } } +/// An iterator over entries of a `RangeInclusiveMap` whose range overlaps with a specified range. +/// +/// The iterator element type is `(&'a RangeInclusive, &'a V)`. +/// +/// This `struct` is created by the [`range`] method on [`RangeInclusiveMap`]. See its +/// documentation for more. +/// +/// [`range`]: RangeInclusiveMap::range +pub struct RangeIter<'a, K, V> { + inner: alloc::collections::btree_map::Range<'a, RangeInclusiveStartWrapper, V>, +} + +impl<'a, K, V> core::iter::FusedIterator for RangeIter<'a, K, V> where K: Ord + Clone {} + +impl<'a, K, V> Iterator for RangeIter<'a, K, V> +where + K: 'a, + V: 'a, +{ + type Item = (&'a RangeInclusive, &'a V); + + fn next(&mut self) -> Option { + self.inner.next().map(|(by_start, v)| (&by_start.range, v)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + /// An iterator over all ranges not covered by a `RangeInclusiveMap`. /// /// The iterator element type is `RangeInclusive`. @@ -667,9 +693,7 @@ pub struct Gaps<'a, K, V, StepFnsT> { /// All other things here are ignored if `done` is `true`. done: bool, outer_range: &'a RangeInclusive, - keys: core::iter::Peekable< - alloc::collections::btree_map::Keys<'a, RangeInclusiveStartWrapper, V>, - >, + keys: alloc::collections::btree_map::Keys<'a, RangeInclusiveStartWrapper, V>, candidate_start: K, _phantom: PhantomData, } @@ -690,71 +714,49 @@ where type Item = RangeInclusive; fn next(&mut self) -> Option { - if self.done || self.candidate_start > *self.outer_range.end() { + if self.done { // We've already passed the end of the outer range; // there are no more gaps to find. return None; } - // Figure out where this gap ends. - let (end, mut next_candidate_start) = if let Some(next_item) = self.keys.next() { - if next_item.range.start() <= self.outer_range.end() { - // The gap goes up until just before the start of the next item, - // and the next candidate starts after it. - ( - StepFnsT::sub_one(next_item.range.start()), - StepFnsT::add_one(next_item.range.end()), - ) - } else { - // The item sits after the end of the outer range, - // so this gap ends at the end of the outer range. - // This also means there will be no more gaps. - self.done = true; - ( - self.outer_range.end().clone(), - // This value will be ignored. - self.candidate_start.clone(), - ) - } - } else { - // There's no next item; the end is at the - // end of the outer range. - // This also means there will be no more gaps. - self.done = true; - ( - self.outer_range.end().clone(), - // This value will be ignored. - self.candidate_start.clone(), - ) - }; - - if !self.done { - // Find the start of the next gap. - while let Some(next_item) = self.keys.peek() { - if *next_item.range.start() == next_candidate_start { - // There's another item at the start of our candidate range. - // Gaps can't have zero width, so skip to the end of this - // item and try again. - if next_item.range.start() == self.outer_range.end() { - // There are no more gaps; avoid trying to find successor - // so we don't overflow. - self.done = true; - } else { - next_candidate_start = StepFnsT::add_one(next_item.range.end()); - self.keys.next().expect("We just checked that this exists"); - } + for item in &mut self.keys { + let range = &item.range; + if *range.end() < self.candidate_start { + // We're already completely past it; ignore it. + } else if *range.start() <= self.candidate_start { + // We're inside it; move past it. + if *range.end() >= *self.outer_range.end() { + // Special case: it goes all the way to the end so we + // can't safely skip past it. (Might overflow.) + self.done = true; + return None; + } + self.candidate_start = StepFnsT::add_one(range.end()); + } else if *range.start() <= *self.outer_range.end() { + // It starts before the end of the outer range, + // so move past it and then yield a gap. + let gap = self.candidate_start.clone()..=StepFnsT::sub_one(range.start()); + if *range.end() >= *self.outer_range.end() { + // Special case: it goes all the way to the end so we + // can't safely skip past it. (Might overflow.) + self.done = true; } else { - // We found an item that actually has a gap before it. - break; + self.candidate_start = StepFnsT::add_one(range.end()); } + return Some(gap); } } - // Move the next candidate gap start past the end - // of this gap, and yield the gap we found. - let gap = self.candidate_start.clone()..=end; - self.candidate_start = next_candidate_start; - Some(gap) + // Now that we've run out of items, the only other possible + // gap is one at the end of the outer range. + self.done = true; + if self.candidate_start <= *self.outer_range.end() { + // There's a gap at the end! + Some(self.candidate_start.clone()..=self.outer_range.end().clone()) + } else { + None + } } } @@ -934,7 +936,7 @@ mod tests { #[test] // Test every permutation of a bunch of touching and overlapping ranges. fn lots_of_interesting_ranges() { - use crate::stupid_range_map::StupidU32RangeMap; + use crate::dense::DenseU32RangeMap; use permutator::Permutation; let mut ranges_with_values = [ @@ -954,16 +956,17 @@ mod tests { ranges_with_values.permutation().for_each(|permutation| { let mut range_map: RangeInclusiveMap = RangeInclusiveMap::new(); - let mut stupid: StupidU32RangeMap = StupidU32RangeMap::new(); + let mut dense: DenseU32RangeMap = DenseU32RangeMap::new(); for (k, v) in permutation { // Insert it into both maps. range_map.insert(k.clone(), v); - stupid.insert(k, v); + dense.insert(k, v); // At every step, both maps should contain the same stuff. - let stupid2: StupidU32RangeMap = range_map.clone().into(); - assert_eq!(stupid, stupid2); + let sparse = range_map.to_vec(); + let dense = dense.to_vec(); + assert_eq!(sparse, dense); } }); } @@ -1397,6 +1400,24 @@ mod tests { range_map.gaps(&(250..=255)); } + #[test] + fn adjacent_unit_width_items() { + // Items two items next to each other at the start, and at the end. + let mut range_map: RangeInclusiveMap = RangeInclusiveMap::new(); + range_map.insert(0..=0, false); + range_map.insert(1..=1, true); + range_map.insert(254..=254, false); + range_map.insert(255..=255, true); + + let outer_range = 0..=255; + let mut gaps = range_map.gaps(&outer_range); + // Should yield one big gap in the middle. + assert_eq!(gaps.next(), Some(2..=253)); + // Gaps iterator should be fused. + assert_eq!(gaps.next(), None); + assert_eq!(gaps.next(), None); + } + /// /// impl Debug /// diff --git a/src/inclusive_set.rs b/src/inclusive_set.rs index caf77b2..fdd3e52 100644 --- a/src/inclusive_set.rs +++ b/src/inclusive_set.rs @@ -78,6 +78,18 @@ where self.rm.contains_key(value) } + /// Returns `true` if any part of the provided range overlaps with a range in the map. + #[inline] + pub fn contains_any<'a>(&self, range: &'a RangeInclusive) -> bool { + self.range(range).next().is_some() + } + + /// Returns `true` if all of the provided range is covered by ranges in the map. + #[inline] + pub fn contains_all<'a>(&self, range: &'a RangeInclusive) -> bool { + self.gaps(range).next().is_none() + } + /// Gets an ordered iterator over all ranges, /// ordered by range. pub fn iter(&self) -> Iter<'_, T> { @@ -112,14 +124,18 @@ where self.rm.remove(range); } + /// Gets an iterator over all ranges that overlap with the provided range. + pub fn range(&self, range: &RangeInclusive) -> RangeIter<'_, T> { + RangeIter { + inner: self.rm.range(range), + } + } + /// Gets an iterator over all the maximally-sized ranges /// contained in `outer_range` that are not covered by /// any range stored in the set. /// /// The iterator element type is `RangeInclusive`. - /// - /// NOTE: Calling `gaps` eagerly finds the first gap, - /// even if the iterator is never consumed. pub fn gaps<'a>(&'a self, outer_range: &'a RangeInclusive) -> Gaps<'a, T, StepFnsT> { Gaps { inner: self.rm.gaps(outer_range), @@ -281,6 +297,33 @@ where } } +/// An iterator over ranges of a `RangeInclusiveSet` that overlap with a specified range. +/// +/// This `struct` is created by the [`range`] method on [`RangeInclusiveSet`]. See its +/// documentation for more. +/// +/// [`range`]: RangeInclusiveSet::range +pub struct RangeIter<'a, T> { + inner: crate::inclusive_map::RangeIter<'a, T, ()>, +} + +impl<'a, T> core::iter::FusedIterator for RangeIter<'a, T> where T: Ord + Clone {} + +impl<'a, T> Iterator for RangeIter<'a, T> +where + T: 'a, +{ + type Item = &'a RangeInclusive; + + fn next(&mut self) -> Option { + self.inner.next().map(|(range, _)| range) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + /// An iterator over all ranges not covered by a `RangeInclusiveSet`. /// /// This `struct` is created by the [`gaps`] method on [`RangeInclusiveSet`]. See its diff --git a/src/lib.rs b/src/lib.rs index 303c1b0..c2e7998 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,10 +136,10 @@ pub mod inclusive_set; pub mod map; pub mod set; +#[cfg(test)] +mod dense; mod range_wrapper; mod std_ext; -#[cfg(test)] -mod stupid_range_map; pub use inclusive_map::RangeInclusiveMap; pub use inclusive_set::RangeInclusiveSet; diff --git a/src/map.rs b/src/map.rs index bfbd154..9f50058 100644 --- a/src/map.rs +++ b/src/map.rs @@ -78,6 +78,18 @@ where self.get(key).is_some() } + /// Returns `true` if any part of the provided range overlaps with a range in the map. + #[inline] + pub fn contains_any<'a>(&self, range: &'a Range) -> bool { + self.range(range).next().is_some() + } + + /// Returns `true` if all of the provided range is covered by ranges in the map. + #[inline] + pub fn contains_all<'a>(&self, range: &'a Range) -> bool { + self.gaps(range).next().is_none() + } + /// Gets an iterator over all pairs of key range and value, /// ordered by key range. /// @@ -88,6 +100,16 @@ where } } + /// Gets a mutable iterator over all pairs of key range and value, + /// ordered by key range. + /// + /// The iterator element type is `(&'a Range, &'a mut V)`. + pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { + IterMut { + inner: self.btm.iter_mut(), + } + } + /// Insert a pair of key range and value into the map. /// /// If the inserted range partially or completely overlaps any @@ -353,40 +375,97 @@ where } } + /// Splits a range in two at the provided key. + /// + /// Does nothing if no range exists at the key, or if the key is at a range boundary. + pub fn split_at(&mut self, key: &K) { + use core::ops::Bound; + + // Find a range that contains the key, but doesn't start or end with the key. + let bounds = ( + Bound::Unbounded, + Bound::Excluded(RangeStartWrapper::new(key.clone()..key.clone())), + ); + let range_to_remove = match self + .btm + .range(bounds) + .next_back() + .filter(|(range_start_wrapper, _value)| range_start_wrapper.range.contains(key)) + { + Some((k, _v)) => k.clone(), + None => return, + }; + + // Remove the range, and re-insert two new ranges with the same value, separated by the key. + let value = self.btm.remove(&range_to_remove).unwrap(); + self.btm.insert( + RangeStartWrapper::new(range_to_remove.range.start..key.clone()), + value.clone(), + ); + self.btm.insert( + RangeStartWrapper::new(key.clone()..range_to_remove.range.end), + value, + ); + } + + /// Gets an iterator over all pairs of key range and value, where the key range overlaps with + /// the provided range. + /// + /// The iterator element type is `(&Range, &V)`. + pub fn range(&self, range: &Range) -> RangeIter<'_, K, V> { + use core::ops::Bound; + + let start = self + .get_key_value(&range.start) + .map_or(&range.start, |(k, _v)| &k.start); + let end = &range.end; + + RangeIter { + inner: self.btm.range(( + Bound::Included(RangeStartWrapper::new(start.clone()..start.clone())), + Bound::Excluded(RangeStartWrapper::new(end.clone()..end.clone())), + )), + } + } + + /// Gets a mutable iterator over all pairs of key range and value, where the key range overlaps + /// with the provided range. + /// + /// The iterator element type is `(&Range, &mut V)`. + pub fn range_mut(&mut self, range: &Range) -> RangeMutIter<'_, K, V> { + use core::ops::Bound; + + let start = self + .get_key_value(&range.start) + .map_or(&range.start, |(k, _v)| &k.start); + let end = &range.end; + let bounds = ( + Bound::Included(RangeStartWrapper::new(start.clone()..start.clone())), + Bound::Excluded(RangeStartWrapper::new(end.clone()..end.clone())), + ); + + RangeMutIter { + inner: self.btm.range_mut(bounds), + } + } + /// Gets an iterator over all the maximally-sized ranges /// contained in `outer_range` that are not covered by /// any range stored in the map. /// - /// The iterator element type is `Range`. + /// If the start and end of the outer range are the same + /// and it does not overlap any stored range, then a single + /// empty gap will be returned. /// - /// NOTE: Calling `gaps` eagerly finds the first gap, - /// even if the iterator is never consumed. + /// The iterator element type is `Range`. pub fn gaps<'a>(&'a self, outer_range: &'a Range) -> Gaps<'a, K, V> { - let mut keys = self.btm.keys().peekable(); - - // Find the first potential gap. - let mut candidate_start = &outer_range.start; - while let Some(item) = keys.peek() { - if item.range.end <= outer_range.start { - // This range sits entirely before the start of - // the outer range; just skip it. - let _ = keys.next(); - } else if item.range.start <= outer_range.start { - // This range overlaps the start of the - // outer range, so the first possible candidate - // range begins at its end. - candidate_start = &item.range.end; - let _ = keys.next(); - } else { - // The rest of the items might contribute to gaps. - break; - } - } - Gaps { outer_range, - keys, - candidate_start, + keys: self.btm.keys(), + // We'll start the candidate range at the start of the outer range + // without checking what's there. Each time we yield an item, + // we'll skip any ranges we find before the next gap. + candidate_start: &outer_range.start, } } } @@ -419,6 +498,42 @@ where } } +impl<'a, K, V> core::iter::FusedIterator for Iter<'a, K, V> where K: Ord + Clone {} + +impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> where K: Ord + Clone {} + +/// An iterator over the entries of a `RangeMap`, ordered by key range. +/// +/// The iterator element type is `(&'a Range, &'a V)`. +/// +/// This `struct` is created by the [`iter`] method on [`RangeMap`]. See its +/// documentation for more. +/// +/// [`iter`]: RangeMap::iter +pub struct IterMut<'a, K, V> { + inner: alloc::collections::btree_map::IterMut<'a, RangeStartWrapper, V>, +} + +impl<'a, K, V> Iterator for IterMut<'a, K, V> +where + K: 'a, + V: 'a, +{ + type Item = (&'a Range, &'a mut V); + + fn next(&mut self) -> Option<(&'a Range, &'a mut V)> { + self.inner.next().map(|(by_start, v)| (&by_start.range, v)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl<'a, K, V> core::iter::FusedIterator for IterMut<'a, K, V> where K: Ord + Clone {} + +impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> where K: Ord + Clone {} + /// An owning iterator over the entries of a `RangeMap`, ordered by key range. /// /// The iterator element type is `(Range, V)`. @@ -451,6 +566,10 @@ impl Iterator for IntoIter { } } +impl core::iter::FusedIterator for IntoIter where K: Ord + Clone {} + +impl ExactSizeIterator for IntoIter where K: Ord + Clone {} + // We can't just derive this automatically, because that would // expose irrelevant (and private) implementation details. // Instead implement it in the same way that the underlying BTreeMap does. @@ -559,6 +678,66 @@ where } } +/// An iterator over entries of a `RangeMap` whose range overlaps with a specified range. +/// +/// The iterator element type is `(&'a Range, &'a V)`. +/// +/// This `struct` is created by the [`range`] method on [`RangeMap`]. See its +/// documentation for more. +/// +/// [`range`]: RangeMap::range +pub struct RangeIter<'a, K, V> { + inner: alloc::collections::btree_map::Range<'a, RangeStartWrapper, V>, +} + +impl<'a, K, V> core::iter::FusedIterator for RangeIter<'a, K, V> where K: Ord + Clone {} + +impl<'a, K, V> Iterator for RangeIter<'a, K, V> +where + K: 'a, + V: 'a, +{ + type Item = (&'a Range, &'a V); + + fn next(&mut self) -> Option { + self.inner.next().map(|(by_start, v)| (&by_start.range, v)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +/// A mutable iterator over entries of a `RangeMap` whose range overlaps with a specified range. +/// +/// The iterator element type is `(&'a Range, &'a mut V)`. +/// +/// This `struct` is created by the [`range_mut`] method on [`RangeMap`]. See its +/// documentation for more. +/// +/// [`range_mut`]: RangeMap::range_mut +pub struct RangeMutIter<'a, K, V> { + inner: alloc::collections::btree_map::RangeMut<'a, RangeStartWrapper, V>, +} + +impl<'a, K, V> core::iter::FusedIterator for RangeMutIter<'a, K, V> where K: Ord + Clone {} + +impl<'a, K, V> Iterator for RangeMutIter<'a, K, V> +where + K: 'a, + V: 'a, +{ + type Item = (&'a Range, &'a mut V); + + fn next(&mut self) -> Option { + self.inner.next().map(|(by_start, v)| (&by_start.range, v)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + /// An iterator over all ranges not covered by a `RangeMap`. /// /// The iterator element type is `Range`. @@ -569,7 +748,7 @@ where /// [`gaps`]: RangeMap::gaps pub struct Gaps<'a, K, V> { outer_range: &'a Range, - keys: core::iter::Peekable, V>>, + keys: alloc::collections::btree_map::Keys<'a, RangeStartWrapper, V>, candidate_start: &'a K, } @@ -583,50 +762,34 @@ where type Item = Range; fn next(&mut self) -> Option { - if *self.candidate_start >= self.outer_range.end { - // We've already passed the end of the outer range; - // there are no more gaps to find. - return None; + for item in &mut self.keys { + let range = &item.range; + if range.end <= *self.candidate_start { + // We're already completely past it; ignore it. + } else if range.start <= *self.candidate_start { + // We're inside it; move past it. + self.candidate_start = &range.end; + } else if range.start < self.outer_range.end { + // It starts before the end of the outer range, + // so move past it and then yield a gap. + let gap = self.candidate_start.clone()..range.start.clone(); + self.candidate_start = &range.end; + return Some(gap); + } } - // Figure out where this gap ends. - let (gap_end, mut next_candidate_start) = if let Some(next_item) = self.keys.next() { - if next_item.range.start < self.outer_range.end { - // The gap goes up until the start of the next item, - // and the next candidate starts after it. - (&next_item.range.start, &next_item.range.end) - } else { - // The item sits after the end of the outer range, - // so this gap ends at the end of the outer range. - // This also means there will be no more gaps. - (&self.outer_range.end, &self.outer_range.end) - } + // Now that we've run out of items, the only other possible + // gap is at the end of the outer range. + if *self.candidate_start < self.outer_range.end { + // There's a gap at the end! + let gap = self.candidate_start.clone()..self.outer_range.end.clone(); + // We're done; skip to the end so we don't try to find any more. + self.candidate_start = &self.outer_range.end; + Some(gap) } else { - // There's no next item; the end is at the - // end of the outer range. - // This also means there will be no more gaps. - (&self.outer_range.end, &self.outer_range.end) - }; - - // Find the start of the next gap. - while let Some(next_item) = self.keys.peek() { - if next_item.range.start == *next_candidate_start { - // There's another item at the start of our candidate range. - // Gaps can't have zero width, so skip to the end of this - // item and try again. - next_candidate_start = &next_item.range.end; - self.keys.next().expect("We just checked that this exists"); - } else { - // We found an item that actually has a gap before it. - break; - } + // We got to the end; there can't be any more gaps. + None } - - // Move the next candidate gap start past the end - // of this gap, and yield the gap we found. - let gap = self.candidate_start.clone()..gap_end.clone(); - self.candidate_start = next_candidate_start; - Some(gap) } } @@ -806,7 +969,7 @@ mod tests { #[test] // Test every permutation of a bunch of touching and overlapping ranges. fn lots_of_interesting_ranges() { - use crate::stupid_range_map::StupidU32RangeMap; + use crate::dense::DenseU32RangeMap; use permutator::Permutation; let mut ranges_with_values = [ @@ -826,7 +989,7 @@ mod tests { ranges_with_values.permutation().for_each(|permutation| { let mut range_map: RangeMap = RangeMap::new(); - let mut stupid: StupidU32RangeMap = StupidU32RangeMap::new(); + let mut dense: DenseU32RangeMap = DenseU32RangeMap::new(); for (k, v) in permutation { // Insert it into both maps. @@ -834,11 +997,12 @@ mod tests { // NOTE: Clippy's `range_minus_one` lint is a bit overzealous here, // because we _can't_ pass an open-ended range to `insert`. #[allow(clippy::range_minus_one)] - stupid.insert(k.start..=(k.end - 1), v); + dense.insert(k.start..=(k.end - 1), v); // At every step, both maps should contain the same stuff. - let stupid2: StupidU32RangeMap = range_map.clone().into(); - assert_eq!(stupid, stupid2); + let sparse = range_map.to_vec(); + let dense = dense.to_end_exclusive_vec(); + assert_eq!(sparse, dense); } }); } @@ -1150,11 +1314,10 @@ mod tests { // ◌ ◌ ◌ ◌ ◆ ◌ ◌ ◌ ◌ ◌ let outer_range = 4..4; let mut gaps = range_map.gaps(&outer_range); - // Should yield no gaps. + // Should not yield any gaps, because a zero-width outer range covers no values. assert_eq!(gaps.next(), None); // Gaps iterator should be fused. assert_eq!(gaps.next(), None); - assert_eq!(gaps.next(), None); } #[test] @@ -1219,6 +1382,41 @@ mod tests { assert_eq!(gaps.next(), None); } + #[test] + fn adjacent_small_items() { + // Items two items next to each other at the start, and at the end. + let mut range_map: RangeMap = RangeMap::new(); + range_map.insert(0..1, false); + range_map.insert(1..2, true); + range_map.insert(253..254, false); + range_map.insert(254..255, true); + + let outer_range = 0..255; + let mut gaps = range_map.gaps(&outer_range); + // Should yield one big gap in the middle. + assert_eq!(gaps.next(), Some(2..253)); + // Gaps iterator should be fused. + assert_eq!(gaps.next(), None); + assert_eq!(gaps.next(), None); + } + + // This test fails in v1.0.2 + #[test] + fn outer_range_lies_within_first_of_two_stored_ranges() { + let mut range_map: RangeMap = RangeMap::new(); + // 0 1 2 3 4 5 6 7 8 9 + // ◆----------◇ ◌ ◌ ◌ ◌ + range_map.insert(0..5, ()); + // 0 1 2 3 4 5 6 7 8 9 + // ◌ ◌ ◌ ◌◌ ◌ ◆---◇ ◌ + range_map.insert(6..8, ()); + // 0 1 2 3 4 5 6 7 8 9 + // ◌ ◆--◇ ◌ ◌ ◌ ◌ ◌ + let outer_range: Range = 1..3; + let mut gaps = range_map.gaps(&outer_range); + assert_eq!(gaps.next(), None); + } + /// /// impl Debug /// diff --git a/src/set.rs b/src/set.rs index 03f12ce..6b6002c 100644 --- a/src/set.rs +++ b/src/set.rs @@ -54,6 +54,18 @@ where self.rm.contains_key(value) } + /// Returns `true` if any part of the provided range overlaps with a range in the map. + #[inline] + pub fn contains_any<'a>(&self, range: &'a Range) -> bool { + self.range(range).next().is_some() + } + + /// Returns `true` if all of the provided range is covered by ranges in the map. + #[inline] + pub fn contains_all<'a>(&self, range: &'a Range) -> bool { + self.gaps(range).next().is_none() + } + /// Gets an ordered iterator over all ranges, /// ordered by range. pub fn iter(&self) -> Iter<'_, T> { @@ -88,14 +100,22 @@ where self.rm.remove(range); } + /// Gets an iterator over all ranges that overlap with the provided range. + pub fn range(&self, range: &Range) -> RangeIter<'_, T> { + RangeIter { + inner: self.rm.range(range), + } + } + /// Gets an iterator over all the maximally-sized ranges /// contained in `outer_range` that are not covered by /// any range stored in the set. /// - /// The iterator element type is `Range`. + /// If the start and end of the outer range are the same + /// and it does not overlap any stored range, then a single + /// empty gap will be returned. /// - /// NOTE: Calling `gaps` eagerly finds the first gap, - /// even if the iterator is never consumed. + /// The iterator element type is `Range`. pub fn gaps<'a>(&'a self, outer_range: &'a Range) -> Gaps<'a, T> { Gaps { inner: self.rm.gaps(outer_range), @@ -257,6 +277,33 @@ where } } +/// An iterator over ranges of a `RangeSet` that overlap with a specified range. +/// +/// This `struct` is created by the [`range`] method on [`RangeSet`]. See its +/// documentation for more. +/// +/// [`range`]: RangeSet::range +pub struct RangeIter<'a, T> { + inner: crate::map::RangeIter<'a, T, ()>, +} + +impl<'a, T> core::iter::FusedIterator for RangeIter<'a, T> where T: Ord + Clone {} + +impl<'a, T> Iterator for RangeIter<'a, T> +where + T: 'a, +{ + type Item = &'a Range; + + fn next(&mut self) -> Option { + self.inner.next().map(|(range, _)| range) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + /// An iterator over all ranges not covered by a `RangeSet`. /// /// This `struct` is created by the [`gaps`] method on [`RangeSet`]. See its diff --git a/src/stupid_range_map.rs b/src/stupid_range_map.rs deleted file mode 100644 index f765ede..0000000 --- a/src/stupid_range_map.rs +++ /dev/null @@ -1,71 +0,0 @@ -use alloc::collections::BTreeMap; -use core::ops::RangeInclusive; - -use super::{RangeInclusiveMap, RangeMap}; - -// A simple but infeasibly slow and memory-hungry -// version of `RangeInclusiveMap` for testing. -// -// Only understands `u32` keys, so that we don't -// have to be generic over `step`. This is just for -// testing, so it's fine. -// -// Note that even though this mirrors the semantics -// of `RangeInclusiveMap`, it can also be used to -// test `RangeMap` just fine. -#[derive(Eq, PartialEq, Debug)] -pub struct StupidU32RangeMap { - // Inner B-Tree map. Stores values and their keys - // directly rather than as ranges. - btm: BTreeMap, -} - -impl StupidU32RangeMap -where - V: Eq + Clone, -{ - pub fn new() -> StupidU32RangeMap { - StupidU32RangeMap { - btm: BTreeMap::new(), - } - } - - pub fn insert(&mut self, range: RangeInclusive, value: V) { - for k in range { - self.btm.insert(k, value.clone()); - } - } -} - -impl From> for StupidU32RangeMap -where - V: Eq + Clone, -{ - fn from(range_map: RangeMap) -> Self { - let mut stupid = Self::new(); - for (range, value) in range_map.iter() { - // Convert to inclusive end. - // (This is only valid because u32 has - // the "successor function".) - // - // NOTE: Clippy's `range_minus_one` lint is a bit overzealous here, - // because we _can't_ pass an open-ended range to `insert`. - #[allow(clippy::range_minus_one)] - stupid.insert(range.start..=(range.end - 1), value.clone()); - } - stupid - } -} - -impl From> for StupidU32RangeMap -where - V: Eq + Clone, -{ - fn from(range_inclusive_map: RangeInclusiveMap) -> Self { - let mut stupid = Self::new(); - for (range, value) in range_inclusive_map.iter() { - stupid.insert(range.clone(), value.clone()); - } - stupid - } -}