Skip to content

remove unnecessary contains check from insert function, rewrite recursive function to iterative functions, use less memory by saving buckit size only once #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
144 changes: 89 additions & 55 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(feature = "bench", feature(test))]
#![cfg_attr(test, feature(test))]
#![cfg_attr(test, feature(rand))]


//! A generic, n-dimensional quadtree for fast neighbor lookups on multiple axes.

Expand Down Expand Up @@ -38,19 +40,23 @@ pub trait Region<P>: Clone {
/// of attributes and quickly query for data that falls within a
/// specific range.
pub struct NTree<R, P> {
root: NTreeNode<R, P>,
bucket_limit: u8
}

struct NTreeNode<R, P>{
region: R,
kind: NTreeVariant<R, P>
kind: NTreeVariant<R, P>,
}

enum NTreeVariant<R, P> {
/// A leaf of the tree, which contains points.
Bucket {
points: Vec<P>,
bucket_limit: u8
},
/// An interior node of the tree, which contains n subtrees.
Branch {
subregions: Vec<NTree<R, P>>
subregions: Vec<NTreeNode<R, P>>
}
}

Expand All @@ -62,106 +68,134 @@ impl<P, R: Region<P>> NTree<R, P> {
/// the arity of the tree.
pub fn new(region: R, size: u8) -> NTree<R, P> {
NTree {
kind: Branch {
subregions: region
.split()
.into_iter()
.map(|r| NTree {
region: r,
kind: Bucket { points: vec![], bucket_limit: size }
})
.collect(),
root: NTreeNode{
region: region,
kind: Bucket {
points: vec![]
},
},
region: region
bucket_limit: size
}
}

/// Insert a point into the n-tree, returns true if the point
/// is within the n-tree and was inserted and false if not.
pub fn insert(&mut self, point: P) -> bool {
if !self.region.contains(&point) { return false }

match self.kind {
Bucket { ref mut points, ref bucket_limit } => {
if points.len() as u8 != *bucket_limit {
points.push(point);
return true
}
},
Branch { ref mut subregions } => {
match subregions.iter_mut().find(|r| r.contains(&point)) {
Some(ref mut subregion) => return subregion.insert(point),
None => return false
}
}
};

// Bucket is full
split_and_insert(self, point);
true
if !self.root.region.contains(&point) { return false }
self.root.insert(point, self.bucket_limit)
}

/// Get all the points which within the queried region.
///
/// Finds all points which are located in regions overlapping
/// the passed in region, then filters out all points which
/// are not strictly within the region.

pub fn range_query<'t, 'q>(&'t self, query: &'q R) -> RangeQuery<'t, 'q, R, P> {
RangeQuery {
query: query,
points: (&[]).iter(),
stack: vec![ref_slice::ref_slice(self).iter()],
stack: vec![ref_slice::ref_slice(&self.root).iter()],
}
}

/// Is the point contained in the n-tree?
pub fn contains(&self, point: &P) -> bool {
self.region.contains(point)
self.root.contains(point)
}


/// Get all the points nearby a specified point.
///
/// This will return no more than bucket_limit points.
pub fn nearby<'a>(&'a self, point: &P) -> Option<&'a[P]> {
if self.region.contains(point) {
match self.kind {
Bucket { ref points, .. } => Some(points.as_slice()),
Branch { ref subregions } => {
subregions
if !self.root.region.contains(&point) { return None }
Some(self.root.nearby(point))
}
}

impl<P, R: Region<P>> NTreeNode<R, P> {
/// Insert a point into the n-tree-node, returns true if the point
/// is within the n-tree and was inserted and false if not.
fn insert(&mut self, point: P, bucket_limit: u8) -> bool {
let mut current_node = self;
loop{
match {current_node} {
&mut NTreeNode {region: _, kind: Branch { ref mut subregions }} => {
current_node = subregions
.iter_mut()
.find(|sub_node| sub_node.region.contains(&point))
.unwrap(); //does always exist, due to invariant of R.split()
},
mut node => {
match node.kind {
Bucket {ref mut points} => {
if points.len() as u8 != bucket_limit {
points.push(point);
return true;
}
},
_ => unreachable!()
}
// Bucket is full
split_and_insert(&mut node, point, bucket_limit);
return true;
}
}
}
}
///Asumes that the point is contained in the tree.
///Therefore it always returns a bucket, and does not need Option in the return type.
fn nearby<'a>(&'a self, point: &P) -> &'a[P] {
let mut current_kind = & self.kind;
loop {
match {current_kind} {
& Bucket { ref points } => { return points.as_slice(); },
& Branch { ref subregions } => {
current_kind = & subregions
.iter()
.find(|r| r.contains(point))
.and_then(|r| r.nearby(point))
.unwrap() //does always exist, due to invariant of R.split()
.kind;

}
}
} else {
None
}
}
/// Is the point contained in the n-tree?
fn contains(&self, point: &P) -> bool {
self.region.contains(point)
}
}

fn split_and_insert<P, R: Region<P>>(bucket: &mut NTree<R, P>, point: P) {
fn split_and_insert<P, R: Region<P>>(node: &mut NTreeNode<R, P>, point: P, bucket_limit: u8) {
let old_points;
let old_bucket_limit;

match bucket.kind {
// Get the old region, points, and bucket limit.
Bucket { ref mut points, bucket_limit } => {
match node.kind {
// Get the old points.
Bucket { ref mut points } => {
old_points = mem::replace(points, vec![]);
old_bucket_limit = bucket_limit;
},
Branch { .. } => unreachable!()
}

// Replace the bucket with a split branch.
*bucket = NTree::new(bucket.region.clone(), old_bucket_limit);
node.kind = Branch { subregions: node.region
.split()
.into_iter()
.map(|r| NTreeNode {
region: r,
kind: Bucket { points: vec![] }
})
.collect()
};

// Insert all the old points into the right place.
for old_point in old_points.into_iter() {
bucket.insert(old_point);
node.insert(old_point, bucket_limit);
}

// Finally, insert the new point.
bucket.insert(point);
node.insert(point, bucket_limit);
}

/// An iterator over the points within a region.
Expand All @@ -173,7 +207,7 @@ fn split_and_insert<P, R: Region<P>>(bucket: &mut NTree<R, P>, point: P) {
pub struct RangeQuery<'t,'q, R: 'q + 't, P: 't> {
query: &'q R,
points: slice::Iter<'t, P>,
stack: Vec<slice::Iter<'t, NTree<R, P>>>
stack: Vec<slice::Iter<'t, NTreeNode<R, P>>>
}

impl<'t, 'q, R: Region<P>, P> Iterator for RangeQuery<'t, 'q, R, P> {
Expand Down
42 changes: 30 additions & 12 deletions src/test.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
#[cfg(feature = "bench")]
extern crate rand;

#[cfg(feature = "bench")]
extern crate test;

#[cfg(feature = "bench")]
use self::test::Bencher;

#[cfg(feature = "bench")]
use self::rand::{random, XorShiftRng, Rng};
use self::rand::{XorShiftRng, Rng};

use self::fixtures::{QuadTreeRegion, Vec2};
use {NTree, Region};
use {NTree};

#[test]
fn test_contains() {
Expand Down Expand Up @@ -82,9 +78,9 @@ fn test_range_query() {
Vec2 { x: 60.0, y: 20.0 }]);
}

#[cfg(feature = "bench")]

fn range_query_bench(b: &mut Bencher, n: usize) {
let mut rng: XorShiftRng = random();
let mut rng = XorShiftRng::new_unseeded();

let mut ntree = NTree::new(QuadTreeRegion::square(0.0, 0.0, 1.0), 4);
for _ in 0..n {
Expand All @@ -102,25 +98,47 @@ fn range_query_bench(b: &mut Bencher, n: usize) {
for p in ntree.range_query(&r) { test::black_box(p); }
})
}

#[cfg(feature = "bench")]
#[bench]
fn bench_range_query_small(b: &mut Bencher) {
range_query_bench(b, 10);
}

#[cfg(feature = "bench")]
#[bench]
fn bench_range_query_medium(b: &mut Bencher) {
range_query_bench(b, 100);
}

#[cfg(feature = "bench")]
#[bench]
fn bench_range_query_large(b: &mut Bencher) {
range_query_bench(b, 10000);
}


fn insert_bench(b: &mut Bencher, n: usize) {
let mut rng = XorShiftRng::new_unseeded();
b.iter(|| {
let mut ntree = NTree::new(QuadTreeRegion::square(0.0, 0.0, 1.0), 4);
for _ in 0..n {
ntree.insert(Vec2 { x: rng.gen(), y: rng.gen() });
}
})
}

#[bench]
fn bench_insert_small(b: &mut Bencher) {
insert_bench(b, 10);
}

#[bench]
fn bench_insert_medium(b: &mut Bencher) {
insert_bench(b, 100);
}

#[bench]
fn bench_insert_large(b: &mut Bencher) {
insert_bench(b, 10000);
}

mod fixtures {
use {Region};

Expand Down