diff --git a/Cargo.lock b/Cargo.lock index 36220c9..1ff57a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 050592c..f9bc9ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/bounds.rs b/src/bounds.rs index 2318461..c4750f1 100644 --- a/src/bounds.rs +++ b/src/bounds.rs @@ -20,6 +20,8 @@ along with range_bounds_map. If not, see . 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 { /// Mirror of [`Bound::Included`] Included(T), @@ -68,6 +70,14 @@ impl StartBound { _ => panic!("unsuitable operation"), } } + + pub(crate) fn into_opposite(self) -> StartBound { + match self { + StartBound::Included(point) => StartBound::Excluded(point), + StartBound::Excluded(point) => StartBound::Included(point), + _ => panic!("unsuitable operation"), + } + } } impl Eq for StartBound where T: PartialEq {} diff --git a/src/range_bounds_map.rs b/src/range_bounds_map.rs index 26627fd..7473aa3 100644 --- a/src/range_bounds_map.rs +++ b/src/range_bounds_map.rs @@ -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 { +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct RangeBoundsMap +where + I: PartialOrd, +{ starts: BTreeMap, (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 RangeBoundsMap where K: RangeBounds, @@ -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( &self, search_range_bounds: &Q, - ) -> impl Iterator + ) -> impl DoubleEndedIterator where Q: RangeBounds, { @@ -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 { + pub fn iter(&self) -> impl DoubleEndedIterator { self.starts.iter().map(|(_, (key, value))| (key, value)) } -} -fn is_valid_range_bounds(range_bounds: &Q) -> bool -where - Q: RangeBounds, - 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( + &mut self, + range_bounds: &Q, + ) -> impl DoubleEndedIterator + where + Q: RangeBounds, + { + //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> = 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(&mut self, range_bounds: &Q) -> Result<(), CutError> + where + Q: RangeBounds, + K: TryFrom<(Bound, Bound)>, + 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 { + Nothing, + Single((Bound, Bound)), + Double((Bound, Bound), (Bound, Bound)), +} + +fn cut_range_bounds( + base_range_bounds: &B, + cut_range_bounds: &C, +) -> CutResult +where + B: RangeBounds, + C: RangeBounds, + 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(range_bounds: &Q) -> bool +where + Q: RangeBounds, + 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(a: &A, b: &B) -> bool where A: RangeBounds, diff --git a/src/range_bounds_set.rs b/src/range_bounds_set.rs index 2bcc203..0b80bd5 100644 --- a/src/range_bounds_set.rs +++ b/src/range_bounds_set.rs @@ -19,6 +19,8 @@ along with range_bounds_map. If not, see . 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 { +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct RangeBoundsSet +where + I: PartialOrd, +{ map: RangeBoundsMap, } diff --git a/todo.txt b/todo.txt index f4d3bf9..a42616a 100644 --- a/todo.txt +++ b/todo.txt @@ -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