finished implementing new remove_overlapping() and cut() functions

This commit is contained in:
ripytide 2022-12-02 18:38:17 +00:00
parent 49320f71aa
commit 1ca53523a0
6 changed files with 275 additions and 27 deletions

56
Cargo.lock generated
View File

@ -32,10 +32,66 @@ dependencies = [
"num-traits",
]
[[package]]
name = "proc-macro2"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "range_bounds_map"
version = "0.0.1"
dependencies = [
"either",
"ordered-float",
"serde",
]
[[package]]
name = "serde"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"

View File

@ -25,7 +25,7 @@ categories = ["data-structures"]
[dependencies]
either = "1.8.0"
ordered-float = "3.4.0"
serde = {version = "1.0.148", features = ["derive"]}
[dev-dependencies]
ordered-float = "3.4.0"

View File

@ -20,6 +20,8 @@ along with range_bounds_map. If not, see <https://www.gnu.org/licenses/>.
use std::cmp::Ordering;
use std::ops::Bound;
use serde::{Deserialize, Serialize};
/// An Ord newtype of [`Bound`] specific to [`start_bound()`].
///
/// This type is used to circumvent [`BTreeMap`]s (and rust collections
@ -31,7 +33,7 @@ use std::ops::Bound;
/// [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
/// [`comparator`]: https://stackoverflow.com/q/34028324
/// [`Cursor`]: https://github.com/rust-lang/rfcs/issues/1778
#[derive(PartialEq, Debug)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub(crate) enum StartBound<T> {
/// Mirror of [`Bound::Included`]
Included(T),
@ -68,6 +70,14 @@ impl<T> StartBound<T> {
_ => panic!("unsuitable operation"),
}
}
pub(crate) fn into_opposite(self) -> StartBound<T> {
match self {
StartBound::Included(point) => StartBound::Excluded(point),
StartBound::Excluded(point) => StartBound::Included(point),
_ => panic!("unsuitable operation"),
}
}
}
impl<T> Eq for StartBound<T> where T: PartialEq {}

View File

@ -23,6 +23,7 @@ use std::iter::once;
use std::ops::{Bound, RangeBounds};
use either::Either;
use serde::{Deserialize, Serialize};
use crate::bounds::StartBound;
@ -112,8 +113,11 @@ use crate::bounds::StartBound;
///
/// [`RangeBounds`]: https://doc.rust-lang.org/std/ops/trait.RangeBounds.html
/// [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
#[derive(Debug, Default)]
pub struct RangeBoundsMap<I, K, V> {
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct RangeBoundsMap<I, K, V>
where
I: PartialOrd,
{
starts: BTreeMap<StartBound<I>, (K, V)>,
}
@ -136,6 +140,17 @@ pub enum InsertError {
OverlapsPreexisting,
}
/// An error type to represent the possible errors from the
/// [`RangeBoundsMap::cut()`] function.
pub enum CutError {
/// When cutting out a `RangeBounds` from another `RangeBounds`
/// you may need to change the base `RangeBounds`' start and end
/// `Bound`s, when these need to change values that the underlying
/// `K`: `RangeBounds` type can't handle then this error is
/// returned.
NonConvertibleRangeBoundsProduced,
}
impl<I, K, V> RangeBoundsMap<I, K, V>
where
K: RangeBounds<I>,
@ -219,8 +234,8 @@ where
//optimisation fix this without cloning
let start = StartBound::from(range_bounds.start_bound().cloned());
//optimisation fix this without cloning
let end =
StartBound::from(range_bounds.end_bound().cloned()).into_end_bound();
let end = StartBound::from(range_bounds.end_bound().cloned())
.into_end_bound();
if start > end {
panic!("Invalid search range bounds!");
@ -283,7 +298,7 @@ where
pub fn overlapping<Q>(
&self,
search_range_bounds: &Q,
) -> impl Iterator<Item = (&K, &V)>
) -> impl DoubleEndedIterator<Item = (&K, &V)>
where
Q: RangeBounds<I>,
{
@ -453,8 +468,8 @@ where
.next();
}
/// Returns an iterator over every (`RangeBounds`, `Value`) pair in the map in
/// ascending order.
/// Returns an iterator over every (`RangeBounds`, `Value`) pair
/// in the map in ascending order.
///
/// # Examples
/// ```
@ -474,22 +489,106 @@ where
/// assert_eq!(iter.next(), Some((&(8..100), &false)));
/// assert_eq!(iter.next(), None);
/// ```
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = (&K, &V)> {
self.starts.iter().map(|(_, (key, value))| (key, value))
}
}
fn is_valid_range_bounds<Q, I>(range_bounds: &Q) -> bool
where
Q: RangeBounds<I>,
I: std::cmp::PartialOrd,
{
match (range_bounds.start_bound(), range_bounds.end_bound()) {
(Bound::Included(start), Bound::Included(end)) => start <= end,
(Bound::Included(start), Bound::Excluded(end)) => start < end,
(Bound::Excluded(start), Bound::Included(end)) => start < end,
(Bound::Excluded(start), Bound::Excluded(end)) => start < end,
_ => true,
///```
/// panic!()
/// ```
pub fn remove_overlapping<Q>(
&mut self,
range_bounds: &Q,
) -> impl DoubleEndedIterator<Item = (K, V)>
where
Q: RangeBounds<I>,
{
//optimisation do this whole function without cloning anything
//or collectiong anything, may depend on a nicer upstream
//BTreeMap remove_range function
let to_remove: Vec<StartBound<I>> = self
.overlapping(range_bounds)
.map(|(key, _)| (StartBound::from(key.start_bound().cloned())))
.collect();
let mut output = Vec::new();
for start_bound in to_remove {
output.push(self.starts.remove(&start_bound).unwrap());
}
return output.into_iter();
}
/// Cuts a given `RangeBounds` out of the map.
///
/// If the remaining `RangeBounds` left after the cut are not able
/// to be converted into the `K` type with `TryFrom<(Bound,
/// Bound)>` then a `CutError` will be returned.
/// ```
/// panic!();
/// ```
pub fn cut<Q>(&mut self, range_bounds: &Q) -> Result<(), CutError>
where
Q: RangeBounds<I>,
K: TryFrom<(Bound<I>, Bound<I>)>,
V: Clone,
{
// only the first and last range_bounds in overlapping stand a
// change of remaining after the cut so we don't need to
// collect the iterator and can just look at the first and
// last elements since range is a double ended iterator ;p
let mut overlapping = self.remove_overlapping(range_bounds);
let first_last = (overlapping.next(), overlapping.next_back());
let mut attempt_insert = |section, value| -> Result<(), CutError> {
match K::try_from(section)
.map_err(|_| CutError::NonConvertibleRangeBoundsProduced)
{
Ok(key) => {
self.insert(key, value).unwrap();
return Ok(());
}
Err(cut_error) => return Err(cut_error),
}
};
match first_last {
(Some(first), Some(last)) => {
match cut_range_bounds(&first.0, range_bounds) {
CutResult::Nothing => {}
CutResult::Single(left_section) => {
attempt_insert(left_section, first.1)?;
}
CutResult::Double(_, _) => unreachable!(),
}
match cut_range_bounds(&last.0, range_bounds) {
CutResult::Nothing => {}
CutResult::Single(right_section) => {
attempt_insert(right_section, last.1)?;
}
CutResult::Double(_, _) => unreachable!(),
}
}
(Some(first), None) => {
match cut_range_bounds(&first.0, range_bounds) {
CutResult::Nothing => {}
CutResult::Single(section) => {
attempt_insert(section, first.1)?;
}
CutResult::Double(left_section, right_section) => {
attempt_insert(left_section, first.1.clone())?;
attempt_insert(right_section, first.1)?;
}
}
}
(None, None) => {}
(None, Some(_)) => unreachable!(),
}
return Ok(());
}
}
@ -509,6 +608,75 @@ where
}
}
enum CutResult<I> {
Nothing,
Single((Bound<I>, Bound<I>)),
Double((Bound<I>, Bound<I>), (Bound<I>, Bound<I>)),
}
fn cut_range_bounds<I, B, C>(
base_range_bounds: &B,
cut_range_bounds: &C,
) -> CutResult<I>
where
B: RangeBounds<I>,
C: RangeBounds<I>,
I: PartialOrd + Clone,
{
if !overlaps(base_range_bounds, cut_range_bounds) {
return CutResult::Nothing;
}
let (base_start_bound, base_end_bound) = (
base_range_bounds.start_bound(),
base_range_bounds.end_bound(),
);
let (cut_start_bound, cut_end_bound) =
(cut_range_bounds.start_bound(), cut_range_bounds.end_bound());
let left_section = match StartBound::from(cut_start_bound)
> StartBound::from(base_start_bound)
{
false => None,
true => Some((
base_start_bound.cloned(),
Bound::from(StartBound::from(cut_start_bound).into_opposite())
.cloned(),
)),
};
let right_section = match StartBound::from(cut_end_bound).into_end_bound()
< StartBound::from(base_end_bound).into_end_bound()
{
false => None,
true => Some((
base_start_bound.cloned(),
Bound::from(StartBound::from(cut_start_bound).into_opposite())
.cloned(),
)),
};
match (left_section, right_section) {
(Some(left), Some(right)) => CutResult::Double(left, right),
(Some(left), None) => CutResult::Single(left),
(None, Some(right)) => CutResult::Single(right),
(None, None) => unreachable!(),
}
}
fn is_valid_range_bounds<Q, I>(range_bounds: &Q) -> bool
where
Q: RangeBounds<I>,
I: std::cmp::PartialOrd,
{
match (range_bounds.start_bound(), range_bounds.end_bound()) {
(Bound::Included(start), Bound::Included(end)) => start <= end,
(Bound::Included(start), Bound::Excluded(end)) => start < end,
(Bound::Excluded(start), Bound::Included(end)) => start < end,
(Bound::Excluded(start), Bound::Excluded(end)) => start < end,
_ => true,
}
}
fn overlaps<I, A, B>(a: &A, b: &B) -> bool
where
A: RangeBounds<I>,

View File

@ -19,6 +19,8 @@ along with range_bounds_map. If not, see <https://www.gnu.org/licenses/>.
use std::ops::RangeBounds;
use serde::{Deserialize, Serialize};
use crate::range_bounds_map::RangeBoundsMap;
use crate::InsertError;
@ -89,8 +91,11 @@ use crate::InsertError;
///
/// [`RangeBounds`]: https://doc.rust-lang.org/std/ops/trait.RangeBounds.html
/// [`BTreeSet`]: https://doc.rust-lang.org/std/collections/struct.BTreeSet.html
#[derive(Debug, Default)]
pub struct RangeBoundsSet<I, K> {
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct RangeBoundsSet<I, K>
where
I: PartialOrd,
{
map: RangeBoundsMap<I, K, ()>,
}

View File

@ -1,3 +1,13 @@
- make sure all returned iterators are DoubleEndedIterators and the
documentation mentions it
- write more tests for more complicated functions
- write docs for everything again
- check every function that takes range_Bounds to make sure they panic
on invalid range_bounds
- use it in robot_Sweet_graph for a bit before publishing
- add issues to github for all the caveats and cloned() optimisations
@ -7,10 +17,9 @@
- run and fix cargo clippy
- take a look around idiomatic rust for a bit first
- fill out github meta data
- update lines of code figures on docs
- PUBLISH
- add links to [`RangeBoundsSet`] and map after docs.rs is live with the docs
- add links to [`RangeBoundsSet`] and map after docs.rs is live with
the docs, and generally check for dead links on docs and readme