diff --git a/README.md b/README.md index 0b6eeb2..9bc4533 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # range_bounds_map +[![Crates.io](https://img.shields.io/crates/v/range_bounds_set)](https://crates.io/crates/range_bounds_set) +[![Docs](https://docs.rs/range_bounds_set/badge)](https://docs.rs/range_bounds_set) +

range_bounds_map_logo

-[![Crates.io](https://img.shields.io/crates/v/range_bounds_set)](https://crates.io/crates/range_bounds_set) -[![Docs](https://docs.rs/range_bounds_set/badge)](https://docs.rs/range_bounds_set) - This crate provides [`RangeBoundsMap`] and [`RangeBoundsSet`]. [`RangeBoundsMap`] is similar to [`BTreeMap`] except [`RangeBoundsMap`] @@ -19,7 +19,7 @@ maintaining two invariants: [`RangeBoundsSet`] is like [`RangeBoundsMap`] except it uses `()` as values, as [`BTreeSet`] does for [`BTreeMap`] -# Example using [`Range`]s +## Example using [`Range`]s ```rust use range_bounds_map::RangeBoundsMap; @@ -34,7 +34,7 @@ assert_eq!(range_bounds_map.contains_point(&20), false); assert_eq!(range_bounds_map.contains_point(&5), true); ``` -# Example using a custom [`RangeBounds`] type +## Example using a custom [`RangeBounds`] type ```rust use std::ops::{Bound, RangeBounds}; @@ -118,9 +118,6 @@ To summarise: - `is_subset()` - etc... a bunch more - Sub-optimal use of unnecessary `cloned()` just to placate the borrow checker -- The library needs some benchmarks -- Insert should panic on "bad" Zero RangeBounds like when start_bound > - end_bound or Excluded(x)-Excluded(x) Excluded(x)-Included(x) Included(x)-Excluded(x) - The data structures are lacking a lot of useful traits, such as: - Serde: Serialize and Deserialize - FromIterator diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..8a0b6f8 Binary files /dev/null and b/logo.png differ diff --git a/src/lib.rs b/src/lib.rs index 46b0d30..bbd2880 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,9 +126,6 @@ along with range_bounds_map. If not, see . //! - `is_subset()` //! - etc... a bunch more //! - Sub-optimal use of unnecessary `cloned()` just to placate the borrow checker -//! - The library needs some benchmarks -//! - Insert should panic on "bad" Zero RangeBounds like when start_bound > -//! end_bound or Excluded(x)-Excluded(x) Excluded(x)-Included(x) Included(x)-Excluded(x) //! - The data structures are lacking a lot of useful traits, such as: //! - Serde: Serialize and Deserialize //! - FromIterator @@ -198,4 +195,5 @@ pub mod range_bounds_map; pub mod range_bounds_set; pub use crate::range_bounds_map::RangeBoundsMap; +pub use crate::range_bounds_map::InsertError; pub use crate::range_bounds_set::RangeBoundsSet; diff --git a/src/range_bounds_map.rs b/src/range_bounds_map.rs index fb4faa1..84cb7c6 100644 --- a/src/range_bounds_map.rs +++ b/src/range_bounds_map.rs @@ -63,7 +63,7 @@ use crate::bounds::StartBound; /// // An Exlusive-Exlusive range of [`f32`]s not provided by any /// // std::ops ranges /// // We use [`ordered_float::NotNan`]s as the inner type must be Ord -/// // similar to a normal [`BTreeSet`] +/// // similar to a normal [`BTreeMap`] /// #[derive(Debug, PartialEq)] /// struct ExEx { /// start: NotNan, @@ -117,6 +117,25 @@ pub struct RangeBoundsMap { starts: BTreeMap, (K, V)>, } +/// An error type to represent the possible errors from the +/// [`RangeBoundsMap::insert()`] function. +#[derive(PartialEq, Debug)] +pub enum InsertError { + /// A `RangeBounds` is invalid if both its `start_bound()` + /// AND `end_bound()` are (`Bound::Included` OR `Bound::Exluded`) + /// AND the `start` point is >= than the `end` + /// point. + /// + /// The one exception to this rule is if both `start_bound()` AND + /// `end_bound()` are `Bound::Included` and the `start` point is + /// == to the `end` point then it is considered valid despite + /// failing the previous rule. + InvalidRangeBounds, + /// The error given if you try to insert a `RangeBounds` that + /// overlaps one or more `RangeBounds` already in the map. + OverlapsPreexisting, +} + impl RangeBoundsMap where K: RangeBounds, @@ -158,25 +177,55 @@ where /// Adds a new (`RangeBounds` `Value`) pair to the map. /// /// If the new `RangeBounds` overlaps one or more `RangeBounds` - /// already in the set then `Err(())` is returned and the map is + /// already in the map then [`InsertError::OverlapsPreexisting`] + /// is returned and the map is not updated. + /// + /// If the new `RangeBounds` is invalid then + /// [`InsertError::InvalidRangeBounds`] is returned and the map is /// not updated. + /// See the [`InsertError::InvalidRangeBounds`] type + /// to see what constitutes as an "invalid" `RangeBounds`. /// /// # Examples /// ``` /// use range_bounds_map::RangeBoundsMap; + /// use range_bounds_map::InsertError; /// /// let mut range_bounds_map = RangeBoundsMap::new(); /// /// assert_eq!(range_bounds_map.insert(5..10, 9), Ok(())); - /// assert_eq!(range_bounds_map.insert(5..10, 2), Err(())); + /// assert_eq!( + /// range_bounds_map.insert(5..10, 2), + /// Err(InsertError::OverlapsPreexisting) + /// ); + /// assert_eq!( + /// range_bounds_map.insert(5..1, 8), + /// Err(InsertError::InvalidRangeBounds) + /// ); /// assert_eq!(range_bounds_map.len(), 1); /// ``` - pub fn insert(&mut self, range_bounds: K, value: V) -> Result<(), ()> { - if self.overlaps(&range_bounds) { - return Err(()); + pub fn insert( + &mut self, + range_bounds: K, + value: V, + ) -> Result<(), InsertError> { + if !is_valid_range_bounds(&range_bounds) { + return Err(InsertError::InvalidRangeBounds); } - //todo panic on invalid inputs + if self.overlaps(&range_bounds) { + return Err(InsertError::OverlapsPreexisting); + } + + //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()).as_end_bound(); + + if start > end { + panic!("Invalid search range bounds!"); + } self.starts.insert( //optimisation fix this without cloning @@ -239,6 +288,10 @@ where where Q: RangeBounds, { + if !is_valid_range_bounds(search_range_bounds) { + panic!("Invalid search range bounds!"); + } + //optimisation fix this without cloning let start = StartBound::from(search_range_bounds.start_bound().cloned()); @@ -246,10 +299,6 @@ where let end = StartBound::from(search_range_bounds.end_bound().cloned()) .as_end_bound(); - if start > end { - panic!("Invalid search range bounds!"); - } - let start_range_bounds = ( //Included is lossless regarding meta-bounds searches //which is what we want @@ -257,7 +306,7 @@ where Bound::Included(end), ); //this range will hold all the ranges we want except possibly - //the first RangeBound in the range + //the first RangeBounds in the range let most_range_bounds = self.starts.range(start_range_bounds); //then we check for this possibly missing range_bounds @@ -290,7 +339,7 @@ where } /// Returns a reference to the `Value` corresponding to the - /// `RangeBounds` in the set that overlaps the given point, if + /// `RangeBounds` in the map that overlaps the given point, if /// any. /// /// # Examples @@ -431,12 +480,26 @@ where } } +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, + } +} + impl TryFrom<[(K, V); N]> for RangeBoundsMap where K: RangeBounds, I: Ord + Clone, { - type Error = (); + type Error = InsertError; fn try_from(pairs: [(K, V); N]) -> Result { let mut range_bounds_map = RangeBoundsMap::new(); for (range_bounds, value) in pairs { diff --git a/src/range_bounds_set.rs b/src/range_bounds_set.rs index 862606f..662b698 100644 --- a/src/range_bounds_set.rs +++ b/src/range_bounds_set.rs @@ -20,6 +20,7 @@ along with range_bounds_map. If not, see . use std::ops::RangeBounds; use crate::range_bounds_map::RangeBoundsMap; +use crate::InsertError; /// An ordered set of [`RangeBounds`] based on [`BTreeSet`] /// @@ -133,20 +134,34 @@ where /// Adds a new `RangeBounds` to the set. /// /// If the new `RangeBounds` overlaps one or more `RangeBounds` - /// already in the set then `Err(())` is returned and the set is + /// already in the set then [`InsertError::OverlapsPreexisting`] + /// is returned and the set is not updated. + /// + /// If the new `RangeBounds` is invalid then + /// [`InsertError::InvalidRangeBounds`] is returned and the set is /// not updated. + /// See the [`InsertError::InvalidRangeBounds`] type + /// to see what constitutes as an "invalid" `RangeBounds`. /// /// # Examples /// ``` /// use range_bounds_map::RangeBoundsSet; + /// use range_bounds_map::InsertError; /// /// let mut range_bounds_set = RangeBoundsSet::new(); /// /// assert_eq!(range_bounds_set.insert(5..10), Ok(())); - /// assert_eq!(range_bounds_set.insert(5..10), Err(())); + /// assert_eq!( + /// range_bounds_set.insert(5..10), + /// Err(InsertError::OverlapsPreexisting) + /// ); + /// assert_eq!( + /// range_bounds_set.insert(5..1), + /// Err(InsertError::InvalidRangeBounds) + /// ); /// assert_eq!(range_bounds_set.len(), 1); /// ``` - pub fn insert(&mut self, range_bounds: K) -> Result<(), ()> { + pub fn insert(&mut self, range_bounds: K) -> Result<(), InsertError> { self.map.insert(range_bounds, ()) } @@ -217,7 +232,9 @@ where /// assert_eq!(range_bounds_set.get_at_point(&101), None); /// ``` pub fn get_at_point(&self, point: &I) -> Option<&K> { - self.map.get_range_bounds_value_at_point(point).map(|(key, _)| key) + self.map + .get_range_bounds_value_at_point(point) + .map(|(key, _)| key) } /// Returns `true` if the set contains a `RangeBounds` that @@ -265,7 +282,7 @@ where K: RangeBounds, I: Ord + Clone, { - type Error = (); + type Error = InsertError; fn try_from(range_bounds: [K; N]) -> Result { let mut range_bounds_set = RangeBoundsSet::new(); for range_bounds in range_bounds { diff --git a/todo.txt b/todo.txt index a895afc..d6751d0 100644 --- a/todo.txt +++ b/todo.txt @@ -1,20 +1,18 @@ -- add github badges including lib.rs -- make logo - -- update lines of code figures on docs - - use it in robot_Sweet_graph for a bit before publishing - add issues to github for all the caveats and cloned() optimisations - review caveats again +- implement specific insert error/overlap error + - run cargo fmt - 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