From 30b3b11114156fc662524af6cd897ff07491088a Mon Sep 17 00:00:00 2001 From: Ivan Chinenov Date: Fri, 1 Dec 2023 04:46:40 +0300 Subject: [PATCH 1/3] Move tree impl out of lib.rs and re-export crate API instead --- src/area.rs | 1 + src/lib.rs | 596 +---------------------------------------------- src/quadtree.rs | 601 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 608 insertions(+), 590 deletions(-) create mode 100644 src/quadtree.rs diff --git a/src/area.rs b/src/area.rs index 4b587aa..8b0e1b6 100644 --- a/src/area.rs +++ b/src/area.rs @@ -15,6 +15,7 @@ //! A rectangular region in the tree. use crate::point; +use derive_builder::Builder; use num::PrimInt; #[cfg(feature = "serde")] use serde::{ diff --git a/src/lib.rs b/src/lib.rs index 620742b..7be1378 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,10 +101,6 @@ // For extra-pedantic documentation tests. #![doc(test(attr(deny(warnings))))] -#[macro_use] -extern crate derive_builder; -extern crate num; - pub mod area; pub mod entry; pub mod iter; @@ -112,593 +108,13 @@ pub mod point; mod handle_iter; mod qtinner; +mod quadtree; mod traversal; mod types; -use crate::{ - area::{ - Area, - AreaBuilder, - }, - entry::Entry, - handle_iter::HandleIter, - iter::{ - IntoIter, - Iter, - Query, - Regions, - Values, - }, - point::Point, - qtinner::QTInner, - traversal::Traversal, - types::StoreType, -}; -use num::PrimInt; -#[cfg(feature = "serde")] -use serde::{ - Deserialize, - Serialize, -}; -use std::{ - collections::{ - HashMap, - HashSet, - }, - default::Default, - hash::Hash, +pub use area::{ + Area, + AreaBuilder, }; - -/// A data structure for storing and accessing data in 2d space. -/// -/// For historical context, other implementations, and potential uses of a -/// quadtree, see the [quadtree](https://en.wikipedia.org/wiki/Quadtree) -/// article on Wikipedia. -/// -/// ## Parameterization -/// -/// `Quadtree` is parameterized over -/// - `U`, the type of the coordinate, and -/// - `V`, the value being stored. -/// -/// `U` must implement `num::PrimInt` and a set of arithmetic operations necessary for coordinate -/// insertion and comparison. `U` must also implement `std::default` for [`derive_builder`] -/// semantics. -/// -/// ## Strictness -/// -/// Some methods ([`.query()`], [`.modify()`], and [`.delete()`]) have strict variants. While the -/// default behavior is for any operation to apply to all regions which _intersect_ some -/// operational region, the strict behavior is for the operation to apply only to those regions -/// which are _totally contained by_ the operational region. -/// -/// [`derive_builder`]: https://docs.rs/derive_builder/0.7.0/derive_builder/ -/// [`.query()`]: #method.query -/// [`.modify()`]: #method.modify -/// [`.delete()`]: #method.delete -// TODO(ambuc): Implement `.delete_by(anchor, dimensions, fn)`: `.retain()` is the inverse. -// TODO(ambuc): Implement `FromIterator<(K, V)>` for `Quadtree`. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, PartialEq, Eq)] -pub struct Quadtree -where - U: PrimInt + Default, -{ - inner: QTInner, - store: StoreType, -} - -impl Quadtree -where - U: PrimInt + Default, -{ - // pub - - /// Creates a new, empty quadtree with some depth. - /// A quadtree with depth `n` will accept coordinates in the range `[0, 2^n]`. - /// ``` - /// use quadtree_rs::{point::Point, Quadtree}; - /// - /// let qt = Quadtree::::new(/*depth=*/ 2); - /// - /// // The anchor of a rectangular region is its top-left coordinate. - /// // By default, quadtrees are anchored at (0, 0). - /// assert_eq!(qt.anchor(), Point {x: 0, y: 0}); - /// assert_eq!(qt.depth(), 2); - /// assert_eq!(qt.width(), 4); - /// assert_eq!(qt.height(), 4); - /// ``` - pub fn new(depth: usize) -> Self { - Self::new_with_anchor( - point::Point { - x: U::zero(), - y: U::zero(), - }, - depth, - ) - } - - /// Creates a new, empty quadtree with some depth and an explicit anchor. - /// - /// The anchor of a rectangular region is its upper-left coordinate. The - /// anchor argument is of type [`point::Point`], and can either be - /// explicit (`Point {x: 2, y: 4}`) or implicit (`(2, 4).into()`). - /// - /// [`point::Point`]: point/struct.Point.html - /// ``` - /// use quadtree_rs::{point::Point, Quadtree}; - /// - /// let anchor = Point {x: 2, y: 4}; - /// let depth = 3_usize; - /// let qt = Quadtree::::new_with_anchor(anchor, depth); - /// - /// assert_eq!(qt.depth(), 3); - /// assert_eq!(qt.anchor(), Point {x: 2, y: 4}); - /// assert_eq!(qt.width(), 8); - /// assert_eq!(qt.height(), 8); - /// ``` - pub fn new_with_anchor(anchor: point::Point, depth: usize) -> Self { - Self { - inner: QTInner::new(anchor, depth), - store: HashMap::new(), - } - } - - /// The top-left corner (anchor) of the region which this quadtree represents. - pub fn anchor(&self) -> point::Point { - self.inner.region().anchor() - } - - /// The width of the region which this quadtree represents. - pub fn width(&self) -> usize { - self.inner.region().width().to_usize().unwrap() - } - - /// The height of the region which this quadtree represents. - pub fn height(&self) -> usize { - self.inner.region().height().to_usize().unwrap() - } - - /// The depth of the quadtree. - pub fn depth(&self) -> usize { - self.inner.depth() - } - - /// The number of elements in the quadtree. - pub fn len(&self) -> usize { - self.store.len() - } - - /// Whether or not the quadtree is empty. - pub fn is_empty(&self) -> bool { - self.store.is_empty() - } - - /// Whether or not some trial region could fit in the region which this quadtree represents. - pub fn contains(&self, area: impl Into>) -> bool { - self.inner.region().contains(area.into()) - } - - /// Associate some value with a region in the quadtree. - /// - /// If insertion is successful, returns a unique handle to the value. - /// - /// If the region is too large for, or doesn't overlap with, the region which this quadtree - /// represents, returns `None`. - /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; - /// - /// let mut qt = Quadtree::::new(8); - /// - /// let region: Area = ((4,5),(2,3)).into(); - /// - /// let handle_a_1 = qt.insert(region, 5).unwrap(); - /// let handle_a_2 = qt.insert(region, 5).unwrap(); - /// - /// // Even though we inserted 5 at the same point in the quadtree, the - /// // two handles returned were not the same. - /// assert_ne!(handle_a_1, handle_a_2); - /// ``` - pub fn insert(&mut self, region: impl Into>, val: V) -> Option { - let region = region.into(); - if self.contains(region) { - return Some( - self.inner - .insert_val_at_region(region, val, &mut self.store), - ); - } - None - } - - /// Alias for [`.insert()`] which expects a [`Point`] instead of an [`Area`]. - /// - /// (An [`Area`] is really just a [`Point`] with dimensions `(1, 1)`, so - /// the point still has to fit within the region.) - /// - /// ``` - /// use quadtree_rs::{point::Point, Quadtree}; - /// - /// let mut qt = Quadtree::::new(2); - /// - /// assert!(qt.insert_pt(Point { x: 1, y: 2 }, 5_i8).is_some()); - /// ``` - /// - /// [`.insert()`]: #method.insert - /// [`Area`]: area/struct.Area.html - /// [`Point`]: point/struct.Point.html - pub fn insert_pt(&mut self, point: Point, val: V) -> Option { - if let Ok(area) = AreaBuilder::default().anchor(point).build() { - return self.insert(area, val); - } - None - } - - /// Given the handle from an [`.insert()`] operation, provides read-only - /// access to the associated [`Entry`] struct. - /// - /// Handles are unique and never re-used, so lookup of a handle to a now-deleted entry can - /// fail and return `None`. - /// - /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; - /// - /// let mut qt = Quadtree::::new(4); - /// - /// let region: Area = ((0,1),(2,3)).into(); - /// let handle = qt.insert(region, 9.87).unwrap(); - /// - /// let entry = qt.get(handle).unwrap(); - /// assert_eq!(entry.value_ref(), &9.87); - /// ``` - /// - /// [`.insert()`]: #method.insert - /// [`Entry`]: entry/struct.Entry.html - pub fn get(&self, handle: u64) -> Option<&Entry> { - self.store.get(&handle) - } - - /// A mutable variant of [`.get()`] which provides mutable access to the - /// associated [`Entry`] struct. - /// - /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; - /// - /// let mut qt = Quadtree::::new(4); - /// - /// let region: Area = ((0,1),(2,3)).into(); - /// let handle: u64 = qt.insert(region, 9.87).unwrap(); - /// - /// if let Some(entry) = qt.get_mut(handle) { - /// *entry.value_mut() += 1.0; - /// } - /// - /// assert_eq!(qt.get(handle).unwrap().value_ref(), &10.87); - /// - /// ``` - /// - /// [`.get()`]: #method.get - /// [`Entry`]: entry/struct.Entry.html - pub fn get_mut(&mut self, handle: u64) -> Option<&mut Entry> { - self.store.get_mut(&handle) - } - - /// Returns an iterator over [`&Entry`] structs representing values - /// within the query region. - /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; - /// - /// // 0123456 - /// // 0 ░░░░░░░ - /// // 1 ░░▒▒▒░░ (2,1)->3x2 - /// // 2 ░░▒▒▒░░ - /// // 3 ░░░░░░░ - /// // 4 ░▒▒▒░░░ (1,4)->3x1 - /// // 5 ░░░░░░░ - /// let mut qt = Quadtree::::new(4); - /// - /// let region_a: Area = ((2,1),(3,2)).into(); - /// qt.insert(region_a, 'a'); - /// - /// let region_b: Area = ((1,4),(3,1)).into(); - /// qt.insert(region_b, 'b'); - /// - /// // 0123456 - /// // 0 ░░░░░░░ - /// // 1 ░░▓▒▒░░ <-- Query over the region - /// // 2 ░░▒▒▒░░ (2,1)->1x1 - /// // 3 ░░░░░░░ - /// // 4 ░▒▒▒░░░ - /// // 5 ░░░░░░░ - /// let region_c: Area = (2,1).into(); - /// let mut query_a = qt.query(region_c); - /// - /// // We can use the Entry API to destructure the result. - /// let entry = query_a.next().unwrap(); - /// assert_eq!(entry.area().height(), 2); - /// assert_eq!(entry.value_ref(), &'a'); - /// - /// // But that was the only result. - /// assert!(query_a.next().is_none()); - /// - /// // 0123456 - /// // 0 ░░░░░░░ - /// // 1 ░▒▓▓▓▒░ <-- query over the region - /// // 2 ░▒▓▓▓▒░ (0,0)->6x6. - /// // 3 ░▒▒▒▒▒░ - /// // 4 ░▓▓▓▒▒░ - /// // 5 ░░░░░░░ - /// let region_d: Area = ((1,1),(4,4)).into(); - /// let query_b = qt.query(region_d); - /// - /// // It's unspecified what order the regions should - /// // return in, but there will be two of them. - /// assert_eq!(query_b.count(), 2); - /// ``` - /// - /// [`&Entry`]: entry/struct.Entry.html - /// [`.query()`]: #method.query - // TODO(ambuc): Settle on a stable return order to avoid breaking callers. - pub fn query(&self, area: impl Into>) -> Query { - Query::new(area, &self.inner, &self.store, Traversal::Overlapping) - } - - /// A strict variant of [`.query()`]. - /// - /// [`.query()`]: #method.query - pub fn query_strict(&self, area: impl Into>) -> Query { - Query::new(area, &self.inner, &self.store, Traversal::Strict) - } - - /// Accepts a modification lambda and applies it to all elements in the - /// quadtree which intersecting the described region. - /// - /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; - /// - /// let mut qt = Quadtree::::new(3); - /// - /// let region_a: Area = (0,0).into(); - /// let handle = qt.insert(region_a, true).unwrap(); - /// - /// // Run a modification lambda over all values in region_a... - /// qt.modify(region_a, |i| *i = false); - /// - /// // ...and verify that the value was applied. - /// assert_eq!(qt.get(handle).unwrap().value_ref(), &false); - /// ``` - pub fn modify(&mut self, area: impl Into>, f: F) - where - F: Fn(&mut V) + Copy, - { - let area = area.into(); - self.modify_region(|a| a.intersects(area), f); - } - - /// A strict variant of [`.modify()`]. - /// - /// [`.modify()`]: #method.modify - pub fn modify_strict(&mut self, area: Area, f: F) - where - F: Fn(&mut V) + Copy, - { - self.modify_region(|a| area.contains(a), f); - } - - /// Alias for [`.modify()`] which runs over the entire - /// quadtree. - /// - /// [`.modify()`]: #method.modify - pub fn modify_all(&mut self, f: F) - where - F: Fn(&mut V) + Copy, - { - for entry in self.store.values_mut() { - f(entry.value_mut()); - } - } - - /// Resets the quadtree to a totally empty state. - pub fn reset(&mut self) { - self.store.clear(); - self.inner.reset(); - } - - /// Deletes all value associations which overlap a region in the tree. - /// - /// Along the way, consumed [`Entry`] entries are collected and returned in an iterator - /// [`IntoIter`]. - /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; - /// - /// let mut qt = Quadtree::::new(4); - /// - /// let region_a: Area = ((0,0),(2,2)).into(); - /// qt.insert(region_a, 1.23); - /// - /// let region_b: Area = ((1,1),(3,2)).into(); - /// qt.insert(region_b, 4.56); - /// - /// // 0123 - /// // 0 ░░ - /// // 1 ░▓╳░ <-- ╳ is the deletion region - /// // 2 ░░░ - /// - /// let region_c: Area = (2,1).into(); - /// let mut returned_entries = qt.delete(region_c); - /// - /// // We've removed one object from the quadtree. - /// assert_eq!(returned_entries.next().unwrap().value_ref(), - /// &4.56); - /// - /// // And left one behind. - /// assert_eq!(qt.len(), 1); - /// ``` - /// - /// [`IntoIter`]: iter/struct.IntoIter.html - /// [`Entry`]: entry/struct.Entry.html - /// [`.delete()`]: #method.delete - pub fn delete(&mut self, area: impl Into>) -> IntoIter { - self.delete_handles_and_return(self.query(area).map(|e| e.handle()).collect()) - } - - /// A strict variant of [`.delete()`]. - /// - /// [`.delete()`]: #method.delete - pub fn delete_strict(&mut self, area: Area) -> IntoIter { - self.delete_handles_and_return(self.query_strict(area).map(|e| e.handle()).collect()) - } - - #[allow(clippy::needless_pass_by_value)] - fn delete_handles_and_return(&mut self, handles: HashSet) -> IntoIter { - let error: &'static str = "I tried to look up an handle in the store which I found in the tree, but it wasn't there!"; - - let mut entries: Vec> = vec![]; - - handles.iter().for_each(|u| { - // We were just passed a hashset of handles taken from this quadtree, so it is safe to - // assume they all still exist. - entries.push(self.store.remove(u).expect(error)); - }); - - IntoIter { entries } - } - - /// Given an handle, deletes a single item from the - /// Quadtree. If that handle was found, - /// `delete_by_handle()` returns an `Entry` - /// containing its former region and value. Otherwise, - /// returns `None`. - pub fn delete_by_handle(&mut self, handle: u64) -> Option> { - // Pop the Entry out of the @store, - if let Some(entry) = self.store.remove(&handle) { - // Use the now-known region to descend into the tree efficiently, - self.inner.delete_by_handle(handle, entry.area()); - // And return the Entry. - return Some(entry); - } - // If the handle wasn't in the @store, we don't need to perform a descent. - None - } - - // TODO(ambuc): Test this fn. - /// Retains only the elements specified by the predicate. - /// - /// In other words, remove all items such that `f(&mut v)` returns `false`. - pub fn retain(&mut self, mut f: F) -> IntoIter - where - F: FnMut(&mut V) -> bool, - U: Hash, - { - // TODO(ambuc): I think this is technically correct but it seems to be interweaving three - // routines. Is there a way to simplify this? - let mut doomed: HashSet<(u64, Area)> = HashSet::new(); - for (handle, entry) in &mut self.store { - if f(entry.value_mut()) { - doomed.insert((*handle, entry.area())); - } - } - // TODO(ambuc): There is an optimization here to do one traversal with many matches, over - // many traversals i.e. one per match. - let mut entries: Vec> = vec![]; - for (handle, region) in doomed { - entries.push(self.store.remove(&handle).unwrap()); - self.inner.delete_by_handle(handle, region); - } - - IntoIter { entries } - } - // TODO(ambuc): retain_within - - /// Returns an iterator ([`Iter`]) over all [`&'a Entry`] - /// region/value associations in the Quadtree. - /// - /// [`Iter`]: iter/struct.Iter.html - /// [`&'a Entry`]: entry/struct.Entry.html - pub fn iter(&self) -> Iter { - Iter::new(&self.inner, &self.store) - } - - /// Returns an iterator ([`Regions`]) over all [`Area`] regions - /// in the Quadtree. - /// - /// [`Regions`]: iter/struct.Regions.html - /// [`Area`]: area/struct.Area.html - pub fn regions(&self) -> Regions { - Regions { - inner: Iter::new(&self.inner, &self.store), - } - } - - /// Returns an iterator ([`Values`]) over all `&'a V` values in the - /// Quadtree. - /// - /// [`Values`]: iter/struct.Values.html - pub fn values(&self) -> Values { - Values { - inner: Iter::new(&self.inner, &self.store), - } - } - - // fn - - fn modify_region(&mut self, filter: F, modify: M) - where - F: Fn(Area) -> bool, - M: Fn(&mut V) + Copy, - { - let relevant_handles: Vec = - HandleIter::new(&self.inner, self.inner.region()).collect(); - for i in relevant_handles { - if let Some(entry) = self.store.get_mut(&i) { - if filter(entry.area()) { - modify(entry.value_mut()); - } - } - } - } -} - -/// `Extend<((U, U), V)>` will silently drop values whose coordinates do not fit in the region -/// represented by the Quadtree. It is the responsibility of the callsite to ensure these points -/// fit. -impl Extend<((U, U), V)> for Quadtree -where - U: PrimInt + Default, -{ - fn extend(&mut self, iter: T) - where - T: IntoIterator, - { - for ((x, y), val) in iter { - // Ignore errors. - self.insert((x, y), val); - } - } -} - -// Immutable iterator for the Quadtree, returning by-reference. -impl<'a, U, V> IntoIterator for &'a Quadtree -where - U: PrimInt + Default, -{ - type Item = &'a Entry; - type IntoIter = Iter<'a, U, V>; - - fn into_iter(self) -> Iter<'a, U, V> { - Iter::new(&self.inner, &self.store) - } -} - -impl IntoIterator for Quadtree -where - U: PrimInt + Default, -{ - type Item = Entry; - type IntoIter = IntoIter; - - fn into_iter(self) -> IntoIter { - IntoIter { - entries: self.store.into_values().collect(), - } - } -} +pub use point::Point; +pub use quadtree::Quadtree; diff --git a/src/quadtree.rs b/src/quadtree.rs new file mode 100644 index 0000000..73d3947 --- /dev/null +++ b/src/quadtree.rs @@ -0,0 +1,601 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + area::{ + Area, + AreaBuilder, + }, + entry::Entry, + handle_iter::HandleIter, + iter::{ + IntoIter, + Iter, + Query, + Regions, + Values, + }, + point::Point, + qtinner::QTInner, + traversal::Traversal, + types::StoreType, +}; +use num::PrimInt; +#[cfg(feature = "serde")] +use serde::{ + Deserialize, + Serialize, +}; +use std::{ + collections::{ + HashMap, + HashSet, + }, + default::Default, + hash::Hash, +}; + +/// A data structure for storing and accessing data in 2d space. +/// +/// For historical context, other implementations, and potential uses of a +/// quadtree, see the [quadtree](https://en.wikipedia.org/wiki/Quadtree) +/// article on Wikipedia. +/// +/// ## Parameterization +/// +/// `Quadtree` is parameterized over +/// - `U`, the type of the coordinate, and +/// - `V`, the value being stored. +/// +/// `U` must implement `num::PrimInt` and a set of arithmetic operations necessary for coordinate +/// insertion and comparison. `U` must also implement `std::default` for [`derive_builder`] +/// semantics. +/// +/// ## Strictness +/// +/// Some methods ([`.query()`], [`.modify()`], and [`.delete()`]) have strict variants. While the +/// default behavior is for any operation to apply to all regions which _intersect_ some +/// operational region, the strict behavior is for the operation to apply only to those regions +/// which are _totally contained by_ the operational region. +/// +/// [`derive_builder`]: https://docs.rs/derive_builder/0.7.0/derive_builder/ +/// [`.query()`]: #method.query +/// [`.modify()`]: #method.modify +/// [`.delete()`]: #method.delete +// TODO(ambuc): Implement `.delete_by(anchor, dimensions, fn)`: `.retain()` is the inverse. +// TODO(ambuc): Implement `FromIterator<(K, V)>` for `Quadtree`. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, PartialEq, Eq)] +pub struct Quadtree +where + U: PrimInt + Default, +{ + inner: QTInner, + store: StoreType, +} + +impl Quadtree +where + U: PrimInt + Default, +{ + // pub + + /// Creates a new, empty quadtree with some depth. + /// A quadtree with depth `n` will accept coordinates in the range `[0, 2^n]`. + /// ``` + /// use quadtree_rs::{point::Point, Quadtree}; + /// + /// let qt = Quadtree::::new(/*depth=*/ 2); + /// + /// // The anchor of a rectangular region is its top-left coordinate. + /// // By default, quadtrees are anchored at (0, 0). + /// assert_eq!(qt.anchor(), Point {x: 0, y: 0}); + /// assert_eq!(qt.depth(), 2); + /// assert_eq!(qt.width(), 4); + /// assert_eq!(qt.height(), 4); + /// ``` + pub fn new(depth: usize) -> Self { + Self::new_with_anchor( + Point { + x: U::zero(), + y: U::zero(), + }, + depth, + ) + } + + /// Creates a new, empty quadtree with some depth and an explicit anchor. + /// + /// The anchor of a rectangular region is its upper-left coordinate. The + /// anchor argument is of type [`point::Point`], and can either be + /// explicit (`Point {x: 2, y: 4}`) or implicit (`(2, 4).into()`). + /// + /// [`point::Point`]: point/struct.Point.html + /// ``` + /// use quadtree_rs::{point::Point, Quadtree}; + /// + /// let anchor = Point {x: 2, y: 4}; + /// let depth = 3_usize; + /// let qt = Quadtree::::new_with_anchor(anchor, depth); + /// + /// assert_eq!(qt.depth(), 3); + /// assert_eq!(qt.anchor(), Point {x: 2, y: 4}); + /// assert_eq!(qt.width(), 8); + /// assert_eq!(qt.height(), 8); + /// ``` + pub fn new_with_anchor(anchor: Point, depth: usize) -> Self { + Self { + inner: QTInner::new(anchor, depth), + store: HashMap::new(), + } + } + + /// The top-left corner (anchor) of the region which this quadtree represents. + pub fn anchor(&self) -> Point { + self.inner.region().anchor() + } + + /// The width of the region which this quadtree represents. + pub fn width(&self) -> usize { + self.inner.region().width().to_usize().unwrap() + } + + /// The height of the region which this quadtree represents. + pub fn height(&self) -> usize { + self.inner.region().height().to_usize().unwrap() + } + + /// The depth of the quadtree. + pub fn depth(&self) -> usize { + self.inner.depth() + } + + /// The number of elements in the quadtree. + pub fn len(&self) -> usize { + self.store.len() + } + + /// Whether or not the quadtree is empty. + pub fn is_empty(&self) -> bool { + self.store.is_empty() + } + + /// Whether or not some trial region could fit in the region which this quadtree represents. + pub fn contains(&self, area: impl Into>) -> bool { + self.inner.region().contains(area.into()) + } + + /// Associate some value with a region in the quadtree. + /// + /// If insertion is successful, returns a unique handle to the value. + /// + /// If the region is too large for, or doesn't overlap with, the region which this quadtree + /// represents, returns `None`. + /// ``` + /// use quadtree_rs::{area::Area, Quadtree}; + /// + /// let mut qt = Quadtree::::new(8); + /// + /// let region: Area = ((4,5),(2,3)).into(); + /// + /// let handle_a_1 = qt.insert(region, 5).unwrap(); + /// let handle_a_2 = qt.insert(region, 5).unwrap(); + /// + /// // Even though we inserted 5 at the same point in the quadtree, the + /// // two handles returned were not the same. + /// assert_ne!(handle_a_1, handle_a_2); + /// ``` + pub fn insert(&mut self, region: impl Into>, val: V) -> Option { + let region = region.into(); + if self.contains(region) { + return Some( + self.inner + .insert_val_at_region(region, val, &mut self.store), + ); + } + None + } + + /// Alias for [`.insert()`] which expects a [`Point`] instead of an [`Area`]. + /// + /// (An [`Area`] is really just a [`Point`] with dimensions `(1, 1)`, so + /// the point still has to fit within the region.) + /// + /// ``` + /// use quadtree_rs::{point::Point, Quadtree}; + /// + /// let mut qt = Quadtree::::new(2); + /// + /// assert!(qt.insert_pt(Point { x: 1, y: 2 }, 5_i8).is_some()); + /// ``` + /// + /// [`.insert()`]: #method.insert + /// [`Area`]: area/struct.Area.html + /// [`Point`]: point/struct.Point.html + pub fn insert_pt(&mut self, point: Point, val: V) -> Option { + if let Ok(area) = AreaBuilder::default().anchor(point).build() { + return self.insert(area, val); + } + None + } + + /// Given the handle from an [`.insert()`] operation, provides read-only + /// access to the associated [`Entry`] struct. + /// + /// Handles are unique and never re-used, so lookup of a handle to a now-deleted entry can + /// fail and return `None`. + /// + /// ``` + /// use quadtree_rs::{area::Area, Quadtree}; + /// + /// let mut qt = Quadtree::::new(4); + /// + /// let region: Area = ((0,1),(2,3)).into(); + /// let handle = qt.insert(region, 9.87).unwrap(); + /// + /// let entry = qt.get(handle).unwrap(); + /// assert_eq!(entry.value_ref(), &9.87); + /// ``` + /// + /// [`.insert()`]: #method.insert + /// [`Entry`]: entry/struct.Entry.html + pub fn get(&self, handle: u64) -> Option<&Entry> { + self.store.get(&handle) + } + + /// A mutable variant of [`.get()`] which provides mutable access to the + /// associated [`Entry`] struct. + /// + /// ``` + /// use quadtree_rs::{area::Area, Quadtree}; + /// + /// let mut qt = Quadtree::::new(4); + /// + /// let region: Area = ((0,1),(2,3)).into(); + /// let handle: u64 = qt.insert(region, 9.87).unwrap(); + /// + /// if let Some(entry) = qt.get_mut(handle) { + /// *entry.value_mut() += 1.0; + /// } + /// + /// assert_eq!(qt.get(handle).unwrap().value_ref(), &10.87); + /// + /// ``` + /// + /// [`.get()`]: #method.get + /// [`Entry`]: entry/struct.Entry.html + pub fn get_mut(&mut self, handle: u64) -> Option<&mut Entry> { + self.store.get_mut(&handle) + } + + /// Returns an iterator over [`&Entry`] structs representing values + /// within the query region. + /// ``` + /// use quadtree_rs::{area::Area, Quadtree}; + /// + /// // 0123456 + /// // 0 ░░░░░░░ + /// // 1 ░░▒▒▒░░ (2,1)->3x2 + /// // 2 ░░▒▒▒░░ + /// // 3 ░░░░░░░ + /// // 4 ░▒▒▒░░░ (1,4)->3x1 + /// // 5 ░░░░░░░ + /// let mut qt = Quadtree::::new(4); + /// + /// let region_a: Area = ((2,1),(3,2)).into(); + /// qt.insert(region_a, 'a'); + /// + /// let region_b: Area = ((1,4),(3,1)).into(); + /// qt.insert(region_b, 'b'); + /// + /// // 0123456 + /// // 0 ░░░░░░░ + /// // 1 ░░▓▒▒░░ <-- Query over the region + /// // 2 ░░▒▒▒░░ (2,1)->1x1 + /// // 3 ░░░░░░░ + /// // 4 ░▒▒▒░░░ + /// // 5 ░░░░░░░ + /// let region_c: Area = (2,1).into(); + /// let mut query_a = qt.query(region_c); + /// + /// // We can use the Entry API to destructure the result. + /// let entry = query_a.next().unwrap(); + /// assert_eq!(entry.area().height(), 2); + /// assert_eq!(entry.value_ref(), &'a'); + /// + /// // But that was the only result. + /// assert!(query_a.next().is_none()); + /// + /// // 0123456 + /// // 0 ░░░░░░░ + /// // 1 ░▒▓▓▓▒░ <-- query over the region + /// // 2 ░▒▓▓▓▒░ (0,0)->6x6. + /// // 3 ░▒▒▒▒▒░ + /// // 4 ░▓▓▓▒▒░ + /// // 5 ░░░░░░░ + /// let region_d: Area = ((1,1),(4,4)).into(); + /// let query_b = qt.query(region_d); + /// + /// // It's unspecified what order the regions should + /// // return in, but there will be two of them. + /// assert_eq!(query_b.count(), 2); + /// ``` + /// + /// [`&Entry`]: entry/struct.Entry.html + /// [`.query()`]: #method.query + // TODO(ambuc): Settle on a stable return order to avoid breaking callers. + pub fn query(&self, area: impl Into>) -> Query { + Query::new(area, &self.inner, &self.store, Traversal::Overlapping) + } + + /// A strict variant of [`.query()`]. + /// + /// [`.query()`]: #method.query + pub fn query_strict(&self, area: impl Into>) -> Query { + Query::new(area, &self.inner, &self.store, Traversal::Strict) + } + + /// Accepts a modification lambda and applies it to all elements in the + /// quadtree which intersecting the described region. + /// + /// ``` + /// use quadtree_rs::{area::Area, Quadtree}; + /// + /// let mut qt = Quadtree::::new(3); + /// + /// let region_a: Area = (0,0).into(); + /// let handle = qt.insert(region_a, true).unwrap(); + /// + /// // Run a modification lambda over all values in region_a... + /// qt.modify(region_a, |i| *i = false); + /// + /// // ...and verify that the value was applied. + /// assert_eq!(qt.get(handle).unwrap().value_ref(), &false); + /// ``` + pub fn modify(&mut self, area: impl Into>, f: F) + where + F: Fn(&mut V) + Copy, + { + let area = area.into(); + self.modify_region(|a| a.intersects(area), f); + } + + /// A strict variant of [`.modify()`]. + /// + /// [`.modify()`]: #method.modify + pub fn modify_strict(&mut self, area: Area, f: F) + where + F: Fn(&mut V) + Copy, + { + self.modify_region(|a| area.contains(a), f); + } + + /// Alias for [`.modify()`] which runs over the entire + /// quadtree. + /// + /// [`.modify()`]: #method.modify + pub fn modify_all(&mut self, f: F) + where + F: Fn(&mut V) + Copy, + { + for entry in self.store.values_mut() { + f(entry.value_mut()); + } + } + + /// Resets the quadtree to a totally empty state. + pub fn reset(&mut self) { + self.store.clear(); + self.inner.reset(); + } + + /// Deletes all value associations which overlap a region in the tree. + /// + /// Along the way, consumed [`Entry`] entries are collected and returned in an iterator + /// [`IntoIter`]. + /// ``` + /// use quadtree_rs::{area::Area, Quadtree}; + /// + /// let mut qt = Quadtree::::new(4); + /// + /// let region_a: Area = ((0,0),(2,2)).into(); + /// qt.insert(region_a, 1.23); + /// + /// let region_b: Area = ((1,1),(3,2)).into(); + /// qt.insert(region_b, 4.56); + /// + /// // 0123 + /// // 0 ░░ + /// // 1 ░▓╳░ <-- ╳ is the deletion region + /// // 2 ░░░ + /// + /// let region_c: Area = (2,1).into(); + /// let mut returned_entries = qt.delete(region_c); + /// + /// // We've removed one object from the quadtree. + /// assert_eq!(returned_entries.next().unwrap().value_ref(), + /// &4.56); + /// + /// // And left one behind. + /// assert_eq!(qt.len(), 1); + /// ``` + /// + /// [`IntoIter`]: iter/struct.IntoIter.html + /// [`Entry`]: entry/struct.Entry.html + /// [`.delete()`]: #method.delete + pub fn delete(&mut self, area: impl Into>) -> IntoIter { + self.delete_handles_and_return(self.query(area).map(|e| e.handle()).collect()) + } + + /// A strict variant of [`.delete()`]. + /// + /// [`.delete()`]: #method.delete + pub fn delete_strict(&mut self, area: Area) -> IntoIter { + self.delete_handles_and_return(self.query_strict(area).map(|e| e.handle()).collect()) + } + + #[allow(clippy::needless_pass_by_value)] + fn delete_handles_and_return(&mut self, handles: HashSet) -> IntoIter { + let error: &'static str = "I tried to look up an handle in the store which I found in the tree, but it wasn't there!"; + + let mut entries: Vec> = vec![]; + + handles.iter().for_each(|u| { + // We were just passed a hashset of handles taken from this quadtree, so it is safe to + // assume they all still exist. + entries.push(self.store.remove(u).expect(error)); + }); + + IntoIter { entries } + } + + /// Given an handle, deletes a single item from the + /// Quadtree. If that handle was found, + /// `delete_by_handle()` returns an `Entry` + /// containing its former region and value. Otherwise, + /// returns `None`. + pub fn delete_by_handle(&mut self, handle: u64) -> Option> { + // Pop the Entry out of the @store, + if let Some(entry) = self.store.remove(&handle) { + // Use the now-known region to descend into the tree efficiently, + self.inner.delete_by_handle(handle, entry.area()); + // And return the Entry. + return Some(entry); + } + // If the handle wasn't in the @store, we don't need to perform a descent. + None + } + + // TODO(ambuc): Test this fn. + /// Retains only the elements specified by the predicate. + /// + /// In other words, remove all items such that `f(&mut v)` returns `false`. + pub fn retain(&mut self, mut f: F) -> IntoIter + where + F: FnMut(&mut V) -> bool, + U: Hash, + { + // TODO(ambuc): I think this is technically correct but it seems to be interweaving three + // routines. Is there a way to simplify this? + let mut doomed: HashSet<(u64, Area)> = HashSet::new(); + for (handle, entry) in &mut self.store { + if f(entry.value_mut()) { + doomed.insert((*handle, entry.area())); + } + } + // TODO(ambuc): There is an optimization here to do one traversal with many matches, over + // many traversals i.e. one per match. + let mut entries: Vec> = vec![]; + for (handle, region) in doomed { + entries.push(self.store.remove(&handle).unwrap()); + self.inner.delete_by_handle(handle, region); + } + + IntoIter { entries } + } + // TODO(ambuc): retain_within + + /// Returns an iterator ([`Iter`]) over all [`&'a Entry`] + /// region/value associations in the Quadtree. + /// + /// [`Iter`]: iter/struct.Iter.html + /// [`&'a Entry`]: entry/struct.Entry.html + pub fn iter(&self) -> Iter { + Iter::new(&self.inner, &self.store) + } + + /// Returns an iterator ([`Regions`]) over all [`Area`] regions + /// in the Quadtree. + /// + /// [`Regions`]: iter/struct.Regions.html + /// [`Area`]: area/struct.Area.html + pub fn regions(&self) -> Regions { + Regions { + inner: Iter::new(&self.inner, &self.store), + } + } + + /// Returns an iterator ([`Values`]) over all `&'a V` values in the + /// Quadtree. + /// + /// [`Values`]: iter/struct.Values.html + pub fn values(&self) -> Values { + Values { + inner: Iter::new(&self.inner, &self.store), + } + } + + // fn + + fn modify_region(&mut self, filter: F, modify: M) + where + F: Fn(Area) -> bool, + M: Fn(&mut V) + Copy, + { + let relevant_handles: Vec = + HandleIter::new(&self.inner, self.inner.region()).collect(); + for i in relevant_handles { + if let Some(entry) = self.store.get_mut(&i) { + if filter(entry.area()) { + modify(entry.value_mut()); + } + } + } + } +} + +/// `Extend<((U, U), V)>` will silently drop values whose coordinates do not fit in the region +/// represented by the Quadtree. It is the responsibility of the callsite to ensure these points +/// fit. +impl Extend<((U, U), V)> for Quadtree +where + U: PrimInt + Default, +{ + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + for ((x, y), val) in iter { + // Ignore errors. + self.insert((x, y), val); + } + } +} + +// Immutable iterator for the Quadtree, returning by-reference. +impl<'a, U, V> IntoIterator for &'a Quadtree +where + U: PrimInt + Default, +{ + type Item = &'a Entry; + type IntoIter = Iter<'a, U, V>; + + fn into_iter(self) -> Iter<'a, U, V> { + Iter::new(&self.inner, &self.store) + } +} + +impl IntoIterator for Quadtree +where + U: PrimInt + Default, +{ + type Item = Entry; + type IntoIter = IntoIter; + + fn into_iter(self) -> IntoIter { + IntoIter { + entries: self.store.into_values().collect(), + } + } +} From 89232b9deafa4295012f7f55905bf5a13fb54a2c Mon Sep 17 00:00:00 2001 From: Ivan Chinenov Date: Fri, 1 Dec 2023 16:09:43 +0300 Subject: [PATCH 2/3] Get rid of the derive-builder --- Cargo.toml | 11 ++- src/entry.rs | 4 +- src/{area.rs => geometry.rs} | 168 ++++++++++++++++++++++++----------- src/handle_iter.rs | 2 +- src/iter.rs | 2 +- src/lib.rs | 8 +- src/point.rs | 114 ------------------------ src/qtinner.rs | 12 +-- src/quadtree.rs | 12 +-- src/traversal.rs | 2 +- tests/area_tests.rs | 16 ++-- tests/point_tests.rs | 6 +- tests/query_tests.rs | 4 +- tests/util.rs | 9 +- 14 files changed, 154 insertions(+), 216 deletions(-) rename src/{area.rs => geometry.rs} (61%) delete mode 100644 src/point.rs diff --git a/Cargo.toml b/Cargo.toml index 1a082ff..14d40e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,7 @@ maintenance = { status = "actively-developed" } [dependencies] num = "0.2" -derive_builder = "0.7" -serde = { version = "1.0.152", features = ["derive"], optional=true} +serde = { version = "1.0.152", features = ["derive"], optional = true } [features] serde = ["dep:serde"] @@ -39,5 +38,9 @@ serde = ["dep:serde"] [dev-dependencies.cargo-husky] version = "1" default-features = false # Disable features which are enabled by default -features = ["precommit-hook", "run-cargo-test", "run-cargo-clippy", "run-cargo-fmt"] - +features = [ + "precommit-hook", + "run-cargo-test", + "run-cargo-clippy", + "run-cargo-fmt", +] diff --git a/src/entry.rs b/src/entry.rs index a5b552d..7057de3 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -16,8 +16,8 @@ // Influenced by https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html. use crate::{ - area::Area, - point::Point, + geometry::Area, + Point, }; use num::PrimInt; #[cfg(feature = "serde")] diff --git a/src/area.rs b/src/geometry.rs similarity index 61% rename from src/area.rs rename to src/geometry.rs index 8b0e1b6..b86901d 100644 --- a/src/area.rs +++ b/src/geometry.rs @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A rectangular region in the tree. - -use crate::point; -use derive_builder::Builder; use num::PrimInt; #[cfg(feature = "serde")] use serde::{ @@ -26,6 +22,10 @@ use std::{ cmp::PartialOrd, default::Default, fmt::Debug, + ops::{ + Add, + Sub, + }, }; /// A rectangular region in 2d space. @@ -37,35 +37,15 @@ use std::{ /// quadrant. /// - The width and height must both be positive and nonzero. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(PartialEq, Eq, Clone, Copy, Hash, Builder)] -#[builder(build_fn(validate = "Self::validate"))] +#[derive(PartialEq, Eq, Clone, Copy, Hash)] pub struct Area where U: PrimInt + Default + PartialOrd, { - #[builder(setter(into))] - anchor: point::Point, - #[builder(default = "(U::one(), U::one())")] + anchor: Point, dimensions: (U, U), } -impl AreaBuilder -where - U: PrimInt + Default + PartialOrd, -{ - fn validate(&self) -> Result<(), String> { - if let Some((w, h)) = self.dimensions { - if w <= U::zero() { - return Err("Areas may not have nonpositive widths.".to_string()); - } - if h <= U::zero() { - return Err("Areas may not have nonpositive heights.".to_string()); - } - } - Ok(()) - } -} - impl Debug for Area where U: PrimInt + Default + Debug, @@ -107,8 +87,36 @@ impl Area where U: PrimInt + Default, { + /// Construct a new [`Area`]. + /// # Panics + /// Panics if either width or height is negative. + pub fn new(width: U, height: U) -> Self { + assert!(width > U::zero() && height > U::zero()); + Self { + anchor: (U::one(), U::one()).into(), + dimensions: (width, height), + } + } + + /// Unit area with width and height of one. + pub fn unit() -> Self { + Self::new(U::one(), U::one()) + } + + /// Returns a new area with it's top-left point set to `anchor` + pub fn at(self, anchor: impl Into>) -> Self { + let Self { + anchor: _, + dimensions, + } = self; + Self { + anchor: anchor.into(), + dimensions, + } + } + /// The top-left coordinate (anchor) of the region. - pub fn anchor(&self) -> point::Point { + pub fn anchor(&self) -> Point { self.anchor } @@ -124,22 +132,22 @@ where /// The coordinate of the top edge of the region. pub fn top_edge(&self) -> U { - self.anchor().y() + self.anchor().y } /// The coordinate of the bottom edge of the region. pub fn bottom_edge(&self) -> U { - self.anchor().y() + self.height() + self.anchor().y + self.height() } /// The coordinate of the left edge of the region. pub fn left_edge(&self) -> U { - self.anchor().x() + self.anchor().x } /// The coordinate of the right edge of the region. pub fn right_edge(&self) -> U { - self.anchor().x() + self.width() + self.anchor().x + self.width() } /// Whether or not an area intersects another area. @@ -161,21 +169,15 @@ where } /// Whether or not an area contains a point. - pub fn contains_pt(self, pt: impl Into>) -> bool { - self.contains( - AreaBuilder::default() - .anchor(pt) - .dimensions((U::one(), U::one())) - .build() - .expect("Unexpected error in Area::contains_pt."), - ) + pub fn contains_pt(self, pt: impl Into>) -> bool { + self.contains(Self::unit().at(pt)) } // NB: The center point is an integer and thus rounded, i.e. a 2x2 region at (0,0) has a center // at (0,0), when in reality the center would be at (0.5, 0.5). - pub(crate) fn center_pt(&self) -> point::Point { + pub(crate) fn center_pt(&self) -> Point { self.anchor() - + point::Point { + + Point { x: self.width() / Self::two(), y: self.height() / Self::two(), } @@ -193,24 +195,90 @@ where impl From<(P, (U, U))> for Area where - P: Into>, + P: Into>, U: PrimInt + Default + PartialOrd, { - fn from((anchor, dimensions): (P, (U, U))) -> Self { - AreaBuilder::default() - .anchor(anchor) - .dimensions(dimensions) - .build() - .unwrap() + fn from((anchor, (width, height)): (P, (U, U))) -> Self { + Self::new(width, height).at(anchor) } } impl From

for Area where - P: Into>, + P: Into>, U: PrimInt + Default + PartialOrd, { fn from(anchor: P) -> Self { - AreaBuilder::default().anchor(anchor).build().unwrap() + Self::unit().at(anchor) + } +} + +/// A type representing a point in space. Should be passed by value. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(PartialEq, Eq, Clone, Copy, Hash)] +pub struct Point { + pub x: U, // The x-coordinate of the point. + pub y: U, // The y-coordinate of the point. +} + +impl Debug for Point +where + U: PrimInt + Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}x{:?}", self.x, self.y) + } +} + +impl From<(U, U)> for Point +where + U: PrimInt, +{ + fn from((x, y): (U, U)) -> Self { + Self { x, y } + } +} + +impl From<&(U, U)> for Point +where + U: PrimInt, +{ + fn from((x, y): &(U, U)) -> Self { + Self { x: *x, y: *y } + } +} + +impl From> for (U, U) +where + U: PrimInt, +{ + fn from(value: Point) -> Self { + (value.x, value.y) + } +} + +impl Add for Point +where + U: PrimInt, +{ + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + x: self.x.saturating_add(other.x), + y: self.y.saturating_add(other.y), + } + } +} + +impl Sub for Point +where + U: PrimInt, +{ + type Output = Self; + fn sub(self, other: Self) -> Self { + Self { + x: self.x.saturating_sub(other.x), + y: self.y.saturating_sub(other.y), + } } } diff --git a/src/handle_iter.rs b/src/handle_iter.rs index 7c8d2ca..0de35d7 100644 --- a/src/handle_iter.rs +++ b/src/handle_iter.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::{ - area::Area, + geometry::Area, qtinner::QTInner, traversal::Traversal, }; diff --git a/src/iter.rs b/src/iter.rs index 5cda2c3..2b79b73 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -13,8 +13,8 @@ // limitations under the License. use crate::{ - area::Area, entry::Entry, + geometry::Area, handle_iter::HandleIter, qtinner::QTInner, traversal::Traversal, diff --git a/src/lib.rs b/src/lib.rs index 7be1378..978d98d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,10 +101,9 @@ // For extra-pedantic documentation tests. #![doc(test(attr(deny(warnings))))] -pub mod area; pub mod entry; +pub mod geometry; pub mod iter; -pub mod point; mod handle_iter; mod qtinner; @@ -112,9 +111,8 @@ mod quadtree; mod traversal; mod types; -pub use area::{ +pub use geometry::{ Area, - AreaBuilder, + Point, }; -pub use point::Point; pub use quadtree::Quadtree; diff --git a/src/point.rs b/src/point.rs deleted file mode 100644 index b9455eb..0000000 --- a/src/point.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! A point region in the tree. - -use num::PrimInt; -#[cfg(feature = "serde")] -use serde::{ - Deserialize, - Serialize, -}; -use std::{ - fmt::Debug, - ops::{ - Add, - Sub, - }, -}; - -/// A type representing a point in space. Should be passed by value. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(PartialEq, Eq, Clone, Copy, Hash)] -pub struct Point { - pub x: U, // The x-coordinate of the point. - pub y: U, // The y-coordinate of the point. -} - -impl Debug for Point -where - U: PrimInt + Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}x{:?}", self.x, self.y) - } -} - -impl From<(U, U)> for Point -where - U: PrimInt, -{ - fn from((x, y): (U, U)) -> Self { - Self { x, y } - } -} - -impl From<&(U, U)> for Point -where - U: PrimInt, -{ - fn from((x, y): &(U, U)) -> Self { - Self { x: *x, y: *y } - } -} - -impl From> for (U, U) -where - U: PrimInt, -{ - fn from(value: Point) -> Self { - (value.x, value.y) - } -} - -impl Add for Point -where - U: PrimInt, -{ - type Output = Self; - fn add(self, other: Self) -> Self { - Self { - x: self.x().saturating_add(other.x()), - y: self.y().saturating_add(other.y()), - } - } -} - -impl Sub for Point -where - U: PrimInt, -{ - type Output = Self; - fn sub(self, other: Self) -> Self { - Self { - x: self.x().saturating_sub(other.x()), - y: self.y().saturating_sub(other.y()), - } - } -} - -impl Point -where - U: PrimInt, -{ - /// The x-coordinate of the point. - pub fn x(&self) -> U { - self.x - } - - /// The y-coordinate of the point. - pub fn y(&self) -> U { - self.y - } -} diff --git a/src/qtinner.rs b/src/qtinner.rs index 04e5f86..cf41b26 100644 --- a/src/qtinner.rs +++ b/src/qtinner.rs @@ -13,10 +13,10 @@ // limitations under the License. use crate::{ - area::Area, entry::Entry, - point::Point, types::StoreType, + Area, + Point, }; use num::PrimInt; #[cfg(feature = "serde")] @@ -200,8 +200,8 @@ where // Northeast Box::new(Self::new( Point { - x: p.x(), - y: self.region.anchor().y(), + x: p.x, + y: self.region.anchor().y, }, self.depth - 1, )), @@ -212,8 +212,8 @@ where // Southwest Box::new(Self::new( Point { - x: self.region.anchor().x(), - y: p.y(), + x: self.region.anchor().x, + y: p.y, }, self.depth - 1, )), diff --git a/src/quadtree.rs b/src/quadtree.rs index 73d3947..448db8f 100644 --- a/src/quadtree.rs +++ b/src/quadtree.rs @@ -13,11 +13,8 @@ // limitations under the License. use crate::{ - area::{ - Area, - AreaBuilder, - }, entry::Entry, + geometry::Area, handle_iter::HandleIter, iter::{ IntoIter, @@ -26,10 +23,10 @@ use crate::{ Regions, Values, }, - point::Point, qtinner::QTInner, traversal::Traversal, types::StoreType, + Point, }; use num::PrimInt; #[cfg(feature = "serde")] @@ -224,10 +221,7 @@ where /// [`Area`]: area/struct.Area.html /// [`Point`]: point/struct.Point.html pub fn insert_pt(&mut self, point: Point, val: V) -> Option { - if let Ok(area) = AreaBuilder::default().anchor(point).build() { - return self.insert(area, val); - } - None + self.insert(Area::from(point), val) } /// Given the handle from an [`.insert()`] operation, provides read-only diff --git a/src/traversal.rs b/src/traversal.rs index c61b315..3fe7c7b 100644 --- a/src/traversal.rs +++ b/src/traversal.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::area::Area; +use crate::geometry::Area; use num::PrimInt; use std::default::Default; diff --git a/tests/area_tests.rs b/tests/area_tests.rs index 9441d76..c8fccb4 100644 --- a/tests/area_tests.rs +++ b/tests/area_tests.rs @@ -13,10 +13,7 @@ // limitations under the License. mod area_tests { - use quadtree_rs::area::{ - Area, - AreaBuilder, - }; + use quadtree_rs::geometry::Area; mod builder { use super::*; @@ -29,19 +26,16 @@ mod area_tests { } #[test] + #[should_panic] fn bad_dims() { - for dims in [(-1, 4), (1, -4), (0, 4), (1, 0)].iter() { - debug_assert!(AreaBuilder::default() - .anchor((0, 0)) - .dimensions(*dims) - .build() - .is_err()); + for (h, w) in [(-1, 4), (1, -4), (0, 4), (1, 0)] { + Area::new(h, w); } } #[test] fn point_in_all_quadrants() { - for p in [(1, 1), (-1, 1), (1, -1), (-1, -1)].iter() { + for p in [(1, 1), (-1, 1), (1, -1), (-1, -1)] { let _a: Area = p.into(); } } diff --git a/tests/point_tests.rs b/tests/point_tests.rs index f4aef24..9b4e1df 100644 --- a/tests/point_tests.rs +++ b/tests/point_tests.rs @@ -13,13 +13,13 @@ // limitations under the License. mod point_tests { - use quadtree_rs::point::Point; + use quadtree_rs::Point; #[test] fn builder() { let p: Point = (1, 2).into(); - debug_assert_eq!(p.x(), 1); - debug_assert_eq!(p.y(), 2); + debug_assert_eq!(p.x, 1); + debug_assert_eq!(p.y, 2); } #[test] diff --git a/tests/query_tests.rs b/tests/query_tests.rs index 96e029e..6a8def5 100644 --- a/tests/query_tests.rs +++ b/tests/query_tests.rs @@ -36,8 +36,8 @@ mod query_tests { let entry = iter1.next().unwrap(); let entry_area = entry.area(); - debug_assert_eq!(entry_area.anchor().x(), 0); - debug_assert_eq!(entry_area.anchor().y(), 0); + debug_assert_eq!(entry_area.anchor().x, 0); + debug_assert_eq!(entry_area.anchor().y, 0); debug_assert_eq!(entry_area.width(), 1); debug_assert_eq!(entry_area.height(), 1); diff --git a/tests/util.rs b/tests/util.rs index 5fa4c2a..a5bb440 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -2,7 +2,7 @@ use num::{ cast::FromPrimitive, PrimInt, }; -use quadtree_rs::area::AreaBuilder; +use quadtree_rs::Area; use std::{ collections::HashSet, default::Default, @@ -43,12 +43,7 @@ where print!("│"); for j in 0..qt.height() { match qt - .query( - AreaBuilder::default() - .anchor((U::from_usize(i).unwrap(), U::from_usize(j).unwrap())) - .build() - .unwrap(), - ) + .query(Area::unit().at((U::from_usize(i).unwrap(), U::from_usize(j).unwrap()))) .count() { 0 => print!(" "), From 19a638c6b3446bde7002aecd93783f77b0b64e49 Mon Sep 17 00:00:00 2001 From: Ivan Chinenov Date: Fri, 1 Dec 2023 16:17:45 +0300 Subject: [PATCH 3/3] Fix docs --- README.md | 12 +++--------- src/entry.rs | 16 +++++----------- src/geometry.rs | 17 ++++------------- src/lib.rs | 9 +++------ src/quadtree.rs | 40 ++++++++++++++-------------------------- tests/util.rs | 13 ++----------- 6 files changed, 31 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 78990ca..2305fd0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ For documentation, see [docs.rs/quadtree_rs](https://docs.rs/quadtree_rs/). # Quick Start ```rust -use quadtree_rs::{area::AreaBuilder, point::Point, Quadtree}; +use quadtree_rs::{Area, Point, Quadtree}; // Instantiate a new quadtree which associates String values with u64 // coordinates. @@ -24,17 +24,11 @@ let mut qt = Quadtree::::new(/*depth=*/4); assert_eq!(qt.width(), 16); // Associate the value "foo" with a rectangle of size 2x1, anchored at (0, 0). -let region_a = AreaBuilder::default() - .anchor(Point {x: 0, y: 0}) - .dimensions((2, 1)) - .build().unwrap(); +let region_a = Area::new(2, 1).at(Point {x: 0, y: 0}); qt.insert(region_a, "foo".to_string()); // Query over a region of size 2x2, anchored at (1, 0). -let region_b = AreaBuilder::default() - .anchor(Point {x: 1, y: 0}) - .dimensions((2, 2)) - .build().unwrap(); +let region_b = Area::new(2, 2).at(Point {x: 1, y: 0}); let mut query = qt.query(region_b); // The query region (region_b) intersects the region "foo" is associated with diff --git a/src/entry.rs b/src/entry.rs index 7057de3..f2d8f3f 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -15,16 +15,10 @@ //! A view into a single entry in the Quadtree. // Influenced by https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html. -use crate::{ - geometry::Area, - Point, -}; +use crate::{geometry::Area, Point}; use num::PrimInt; #[cfg(feature = "serde")] -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use std::default::Default; /// A region/value association in the [`Quadtree`]. @@ -37,7 +31,7 @@ use std::default::Default; /// [`.delete()`]: ../struct.Quadtree.html#method.delete /// ``` /// use quadtree_rs::{ -/// area::Area, +/// Area, /// Quadtree, /// }; /// @@ -55,8 +49,8 @@ use std::default::Default; /// /// let entry = returned_entries.next().unwrap(); /// -/// assert_eq!(entry.anchor().x(), 1); -/// assert_eq!(entry.anchor().y(), 1); +/// assert_eq!(entry.anchor().x, 1); +/// assert_eq!(entry.anchor().y, 1); /// assert_eq!(entry.width(), 3); /// assert_eq!(entry.height(), 2); /// diff --git a/src/geometry.rs b/src/geometry.rs index b86901d..cd7c56b 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -14,18 +14,12 @@ use num::PrimInt; #[cfg(feature = "serde")] -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use std::{ cmp::PartialOrd, default::Default, fmt::Debug, - ops::{ - Add, - Sub, - }, + ops::{Add, Sub}, }; /// A rectangular region in 2d space. @@ -64,12 +58,9 @@ where /// Why this custom From<>? Useful for type coercion: /// /// ``` -/// use quadtree_rs::{area::{Area, AreaBuilder}, point::Point}; +/// use quadtree_rs::{Area, Point}; /// -/// let area: Area<_> = AreaBuilder::default() -/// .anchor(Point{x:1, y:2}) -/// .dimensions((3,4)) -/// .build().unwrap(); +/// let area: Area<_> = Area::new(3,4).at(Point{x:1, y:2}); /// let (anchor, dims) = area.into(); /// assert_eq!(anchor, (1,2)); /// assert_eq!(dims, (3,4)); diff --git a/src/lib.rs b/src/lib.rs index 978d98d..fd0ec4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ //! //! # Quick Start //! ``` -//! use quadtree_rs::{area::Area, Quadtree}; +//! use quadtree_rs::{Area, Quadtree}; //! //! // Instantiate a new quadtree which associates String values with u64 coordinates. //! let mut qt = Quadtree::::new(/*depth=*/4); @@ -39,7 +39,7 @@ //! //! # Implementation //! ``` -//! use quadtree_rs::{area::Area, point::Point, Quadtree}; +//! use quadtree_rs::{Area, Point, Quadtree}; //! //! let mut qt = Quadtree::::new(2); //! @@ -111,8 +111,5 @@ mod quadtree; mod traversal; mod types; -pub use geometry::{ - Area, - Point, -}; +pub use geometry::{Area, Point}; pub use quadtree::Quadtree; diff --git a/src/quadtree.rs b/src/quadtree.rs index 448db8f..1acc12c 100644 --- a/src/quadtree.rs +++ b/src/quadtree.rs @@ -16,13 +16,7 @@ use crate::{ entry::Entry, geometry::Area, handle_iter::HandleIter, - iter::{ - IntoIter, - Iter, - Query, - Regions, - Values, - }, + iter::{IntoIter, Iter, Query, Regions, Values}, qtinner::QTInner, traversal::Traversal, types::StoreType, @@ -30,15 +24,9 @@ use crate::{ }; use num::PrimInt; #[cfg(feature = "serde")] -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use std::{ - collections::{ - HashMap, - HashSet, - }, + collections::{HashMap, HashSet}, default::Default, hash::Hash, }; @@ -91,7 +79,7 @@ where /// Creates a new, empty quadtree with some depth. /// A quadtree with depth `n` will accept coordinates in the range `[0, 2^n]`. /// ``` - /// use quadtree_rs::{point::Point, Quadtree}; + /// use quadtree_rs::{Point, Quadtree}; /// /// let qt = Quadtree::::new(/*depth=*/ 2); /// @@ -115,12 +103,12 @@ where /// Creates a new, empty quadtree with some depth and an explicit anchor. /// /// The anchor of a rectangular region is its upper-left coordinate. The - /// anchor argument is of type [`point::Point`], and can either be + /// anchor argument is of type [`Point`], and can either be /// explicit (`Point {x: 2, y: 4}`) or implicit (`(2, 4).into()`). /// - /// [`point::Point`]: point/struct.Point.html + /// [`Point`]: point/struct.Point.html /// ``` - /// use quadtree_rs::{point::Point, Quadtree}; + /// use quadtree_rs::{Point, Quadtree}; /// /// let anchor = Point {x: 2, y: 4}; /// let depth = 3_usize; @@ -180,7 +168,7 @@ where /// If the region is too large for, or doesn't overlap with, the region which this quadtree /// represents, returns `None`. /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; + /// use quadtree_rs::{Area, Quadtree}; /// /// let mut qt = Quadtree::::new(8); /// @@ -210,7 +198,7 @@ where /// the point still has to fit within the region.) /// /// ``` - /// use quadtree_rs::{point::Point, Quadtree}; + /// use quadtree_rs::{Point, Quadtree}; /// /// let mut qt = Quadtree::::new(2); /// @@ -231,7 +219,7 @@ where /// fail and return `None`. /// /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; + /// use quadtree_rs::{Area, Quadtree}; /// /// let mut qt = Quadtree::::new(4); /// @@ -252,7 +240,7 @@ where /// associated [`Entry`] struct. /// /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; + /// use quadtree_rs::{Area, Quadtree}; /// /// let mut qt = Quadtree::::new(4); /// @@ -276,7 +264,7 @@ where /// Returns an iterator over [`&Entry`] structs representing values /// within the query region. /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; + /// use quadtree_rs::{Area, Quadtree}; /// /// // 0123456 /// // 0 ░░░░░░░ @@ -344,7 +332,7 @@ where /// quadtree which intersecting the described region. /// /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; + /// use quadtree_rs::{Area, Quadtree}; /// /// let mut qt = Quadtree::::new(3); /// @@ -399,7 +387,7 @@ where /// Along the way, consumed [`Entry`] entries are collected and returned in an iterator /// [`IntoIter`]. /// ``` - /// use quadtree_rs::{area::Area, Quadtree}; + /// use quadtree_rs::{Area, Quadtree}; /// /// let mut qt = Quadtree::::new(4); /// diff --git a/tests/util.rs b/tests/util.rs index a5bb440..eeb1ae4 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -1,15 +1,6 @@ -use num::{ - cast::FromPrimitive, - PrimInt, -}; +use num::{cast::FromPrimitive, PrimInt}; use quadtree_rs::Area; -use std::{ - collections::HashSet, - default::Default, - fmt::Debug, - hash::Hash, - iter::FromIterator, -}; +use std::{collections::HashSet, default::Default, fmt::Debug, hash::Hash, iter::FromIterator}; // Inspired by google/googletest's UnorderedElementsAre(). // https://github.com/google/googletest/blob/master/googlemock/docs/CheatSheet.md#container-matchers