mirror of
https://github.com/andreytkachenko/kdtree-rust.git
synced 2024-11-24 02:16:23 +04:00
Initial push to remote
This commit is contained in:
parent
662c916c88
commit
1942e4b0a0
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,8 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/.idea/
|
||||
*.iml
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "kdtree-rust"
|
||||
version = "0.1.0"
|
||||
authors = ["Aleksander Fular <ntszar@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "*"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.3"
|
79
src/kdtree/mod.rs
Normal file
79
src/kdtree/mod.rs
Normal file
@ -0,0 +1,79 @@
|
||||
mod test_common;
|
||||
mod partition;
|
||||
|
||||
pub trait KdtreePointTrait {
|
||||
fn dims(&self) -> &[f64];
|
||||
}
|
||||
|
||||
pub struct Kdtree<T> {
|
||||
nodes: Vec<KdtreeNode<T>>,
|
||||
}
|
||||
|
||||
impl<T: KdtreePointTrait> Kdtree<T> {
|
||||
pub fn new(points: Vec<T>) -> Kdtree<T> {
|
||||
if points.len() == 0 {
|
||||
panic!("empty vector point not allowed");
|
||||
}
|
||||
|
||||
Kdtree {
|
||||
nodes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn add_node(&mut self, p: T) {
|
||||
let node = KdtreeNode::new(p);
|
||||
|
||||
self.nodes.push(node);
|
||||
}
|
||||
|
||||
fn add_left_node(&mut self, for_node: usize, ) {
|
||||
{
|
||||
let len = self.nodes.len();
|
||||
let node = self.nodes.get_mut(for_node).unwrap();
|
||||
node.left_node = Some(len);
|
||||
}
|
||||
//self.nodes.push(KdtreeNode::new());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KdtreeNode<T> {
|
||||
left_node: Option<usize>,
|
||||
right_node: Option<usize>,
|
||||
|
||||
point: T,
|
||||
}
|
||||
|
||||
impl<T: KdtreePointTrait> KdtreeNode<T> {
|
||||
fn new(p: T) -> KdtreeNode<T> {
|
||||
KdtreeNode {
|
||||
left_node: None,
|
||||
right_node: None,
|
||||
|
||||
point: p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests3 {
|
||||
use ::kdtree::test_common::tests_utils::Point2WithId;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "empty vector point not allowed")]
|
||||
fn should_panic_given_empty_vector() {
|
||||
let empty_vec: Vec<Point2WithId> = vec![];
|
||||
|
||||
let tree = Kdtree::new(empty_vec);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test2() {
|
||||
let p1 = Point2WithId::new(1, 1., 2.);
|
||||
let p2 = Point2WithId::new(1, 1., 2.);
|
||||
let vec = vec![p1, p2];
|
||||
|
||||
let tree = Kdtree::new(vec);
|
||||
}
|
||||
}
|
226
src/kdtree/partition.rs
Normal file
226
src/kdtree/partition.rs
Normal file
@ -0,0 +1,226 @@
|
||||
use ::kdtree::*;
|
||||
|
||||
enum PointsWereOnSide {
|
||||
Left,
|
||||
Right,
|
||||
Both,
|
||||
}
|
||||
|
||||
struct PartitionPointHelper {
|
||||
points_were_on_side: PointsWereOnSide,
|
||||
midpoint_split_value: f64,
|
||||
index_of_splitter: usize,
|
||||
}
|
||||
|
||||
fn partition_sliding_midpoint_helper<T: KdtreePointTrait>(vec: &mut Vec<T>, midpoint_value: f64, partition_on_dimension: usize) -> PartitionPointHelper {
|
||||
let mut closest_index = 0;
|
||||
let mut closest_distance = (vec[0].dims()[partition_on_dimension] - midpoint_value).abs();
|
||||
|
||||
const HAS_POINTS_ON_LEFT_SIDE: i32 = 0b01;
|
||||
const HAS_POINTS_ON_RIGHT_SIDE: i32 = 0b10;
|
||||
const HAS_POINTS_ON_BOTH_SIDES: i32 = HAS_POINTS_ON_RIGHT_SIDE | HAS_POINTS_ON_LEFT_SIDE;
|
||||
let mut has_points_on_sides = 0;
|
||||
|
||||
for i in 0..vec.len() {
|
||||
let p = vec.get(i).unwrap();
|
||||
if p.dims()[partition_on_dimension] <= midpoint_value {
|
||||
has_points_on_sides |= HAS_POINTS_ON_LEFT_SIDE;
|
||||
} else {
|
||||
has_points_on_sides |= HAS_POINTS_ON_RIGHT_SIDE;
|
||||
}
|
||||
|
||||
let dist = (p.dims()[partition_on_dimension] - midpoint_value).abs();
|
||||
|
||||
if dist < closest_distance {
|
||||
closest_distance = dist;
|
||||
closest_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if has_points_on_sides != HAS_POINTS_ON_BOTH_SIDES {
|
||||
return PartitionPointHelper {
|
||||
index_of_splitter: closest_index,
|
||||
midpoint_split_value: vec.get(closest_index).unwrap().dims()[partition_on_dimension],
|
||||
points_were_on_side: if has_points_on_sides == HAS_POINTS_ON_LEFT_SIDE { PointsWereOnSide::Left } else { PointsWereOnSide::Right }
|
||||
}
|
||||
}
|
||||
return PartitionPointHelper {
|
||||
index_of_splitter: closest_index,
|
||||
midpoint_split_value: midpoint_value,
|
||||
points_were_on_side: PointsWereOnSide::Both
|
||||
}
|
||||
}
|
||||
|
||||
pub fn partition_sliding_midpoint<T: KdtreePointTrait>(vec: &mut Vec<T>, midpoint_value: f64, partition_on_dimension: usize) -> (usize, f64) {
|
||||
let vec_len = vec.len();
|
||||
debug_assert!(vec[0].dims().len() > partition_on_dimension);
|
||||
debug_assert!(vec.len() > 1);
|
||||
|
||||
let partition_point_data = partition_sliding_midpoint_helper(vec, midpoint_value, partition_on_dimension);
|
||||
|
||||
match partition_point_data.points_were_on_side {
|
||||
PointsWereOnSide::Left => {
|
||||
vec.swap(partition_point_data.index_of_splitter, vec_len - 1);
|
||||
(vec_len - 1, partition_point_data.midpoint_split_value)
|
||||
},
|
||||
PointsWereOnSide::Right => {
|
||||
vec.swap(partition_point_data.index_of_splitter, 0);
|
||||
(0, partition_point_data.midpoint_split_value)
|
||||
}
|
||||
PointsWereOnSide::Both => {
|
||||
let index_of_splitting_point = partition_kdtree(vec, partition_point_data.index_of_splitter, partition_on_dimension);
|
||||
(index_of_splitting_point, partition_point_data.midpoint_split_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn partition_kdtree<T: KdtreePointTrait>(vec: &mut Vec<T>, index_of_splitting_point: usize, partition_on_dimension: usize) -> usize {
|
||||
let pivot = vec[index_of_splitting_point].dims()[partition_on_dimension];
|
||||
let vec_len = vec.len();
|
||||
|
||||
vec.swap(index_of_splitting_point, vec_len - 1);
|
||||
|
||||
//using Lomuto variant of partition here, change it to hoare sometime?
|
||||
let mut store_index = 0;
|
||||
for left in 0..vec_len - 1 {
|
||||
if vec[left].dims()[partition_on_dimension] <= pivot {
|
||||
vec.swap(left, store_index);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
vec.swap(store_index, vec_len - 1);
|
||||
|
||||
store_index
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ::kdtree::*;
|
||||
use ::kdtree::test_common::tests_utils::*;
|
||||
|
||||
use ::rand::distributions::{IndependentSample, Range};
|
||||
use ::rand::*;
|
||||
|
||||
use super::*;
|
||||
use super::partition_kdtree;
|
||||
|
||||
#[test]
|
||||
fn parition_kdtree_works() {
|
||||
let p1 = Point2WithId::new(0, 1., 4.);
|
||||
let p2 = Point2WithId::new(1, 2., 6.);
|
||||
let p3 = Point2WithId::new(2, 3., 8.);
|
||||
let p4 = Point2WithId::new(3, 0., 8.);
|
||||
let p5 = Point2WithId::new(4, -1., 8.);
|
||||
let p6 = Point2WithId::new(5, 3., 8.);
|
||||
let p7 = Point2WithId::new(6, 4., 8.);
|
||||
|
||||
let mut vec = vec![p1, p2, p3, p4, p5, p6, p7];
|
||||
assert_eq! (1, partition_kdtree(&mut vec.clone(), 3, 0));
|
||||
|
||||
assert_eq! (6, partition_kdtree(&mut vec.clone(), 6, 0));
|
||||
|
||||
assert_eq! (0, partition_kdtree(&mut vec.clone(), 4, 0));
|
||||
|
||||
assert_eq! (5, partition_kdtree(&mut vec.clone(), 2, 0));
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn partition_kdtree_qc(xs: Vec<f64>) -> bool {
|
||||
let mut vec : Vec<Point1WithId> = vec![];
|
||||
|
||||
for i in 0 .. xs.len() {
|
||||
let p = Point1WithId::new(i as i32, xs[i]);
|
||||
vec.push(p);
|
||||
}
|
||||
|
||||
if(xs.len() == 0 ) {
|
||||
return true;
|
||||
}
|
||||
let between = Range::new(0, xs.len());
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for i in 0 .. 5 {
|
||||
let random_splitting_index = between.ind_sample(&mut rng);
|
||||
|
||||
let mut vec = vec.clone();
|
||||
|
||||
let index_of_splitting_point = partition_kdtree(&mut vec, random_splitting_index, 0);
|
||||
return assert_partition(&vec, index_of_splitting_point);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn partition_given_midpoint_exactly_in_between_points_returns_smaller_index() {
|
||||
let p1 = Point2WithId::new(1, 2., 4.);
|
||||
let p2 = Point2WithId::new(1, 4., 6.);
|
||||
let mut vec = vec![p1, p2];
|
||||
|
||||
assert_eq! ((0, 3.), partition_sliding_midpoint(&mut vec, 3., 0));
|
||||
assert_eq! ((0, 5.), partition_sliding_midpoint(&mut vec, 5., 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn partition_given_midpoint_which_has_all_points_on_one_side_slides_split_plane_and_returns_index_to_closest_element() {
|
||||
let p1 = Point2WithId::new(1, 2., 4.);
|
||||
let p2 = Point2WithId::new(2, 4., 6.);
|
||||
let p3 = Point2WithId::new(3, 3., 7.);
|
||||
let p4 = Point2WithId::new(4, 0., 8.);
|
||||
let mut vec = vec![p1, p2, p3];
|
||||
|
||||
assert_eq! ((0, 2.), partition_sliding_midpoint(&mut vec, 1.9, 0));
|
||||
assert_eq! (1, vec[0].id);
|
||||
assert_eq! (2, vec[1].id);
|
||||
assert_eq! (3, vec[2].id);
|
||||
|
||||
let mut vec = vec![p1, p2, p3];
|
||||
assert_eq! ((0, 2.), partition_sliding_midpoint(&mut vec, -5000., 0));
|
||||
assert_eq! (1, vec[0].id);
|
||||
assert_eq! (2, vec[1].id);
|
||||
assert_eq! (3, vec[2].id);
|
||||
|
||||
let mut vec = vec![p1, p2, p3];
|
||||
assert_eq! ((2, 4.), partition_sliding_midpoint(&mut vec, 10., 0));
|
||||
assert_eq! (1, vec[0].id);
|
||||
assert_eq! (3, vec[1].id);
|
||||
assert_eq! (2, vec[2].id);
|
||||
|
||||
|
||||
let mut vec = vec![p1, p2, p3];
|
||||
assert_eq! ((2, 7.), partition_sliding_midpoint(&mut vec, 10., 1));
|
||||
assert_eq! (1, vec[0].id);
|
||||
assert_eq! (2, vec[1].id);
|
||||
assert_eq! (3, vec[2].id);
|
||||
|
||||
let mut vec = vec![p1, p2, p3, p4];
|
||||
assert_eq! ((0, 0.), partition_sliding_midpoint(&mut vec, -5000., 0));
|
||||
assert_eq! (4, vec[0].id);
|
||||
assert_eq! (2, vec[1].id);
|
||||
assert_eq! (3, vec[2].id);
|
||||
assert_eq! (1, vec[3].id);
|
||||
}
|
||||
|
||||
fn assert_partition(v: &Vec<Point1WithId>, index_of_splitting_point: usize) -> bool {
|
||||
let pivot = v[index_of_splitting_point].dims()[0];
|
||||
|
||||
for i in 0 .. index_of_splitting_point {
|
||||
if v[i].dims()[0] > pivot {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for i in index_of_splitting_point + 1 .. v.len() {
|
||||
if v[i].dims()[0] < pivot {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
45
src/kdtree/test_common.rs
Normal file
45
src/kdtree/test_common.rs
Normal file
@ -0,0 +1,45 @@
|
||||
#[cfg(test)]
|
||||
pub mod tests_utils {
|
||||
use ::kdtree::*;
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Point2WithId {
|
||||
dims: [f64; 2],
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
impl Point2WithId {
|
||||
pub fn new(id: i32, x: f64, y: f64) -> Point2WithId {
|
||||
Point2WithId {
|
||||
dims: [x, y],
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KdtreePointTrait for Point2WithId {
|
||||
fn dims(&self) -> &[f64] {
|
||||
return &self.dims;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Point1WithId {
|
||||
dims: [f64; 1],
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
impl Point1WithId {
|
||||
pub fn new(id: i32, x: f64) -> Point1WithId {
|
||||
Point1WithId {
|
||||
dims: [x],
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KdtreePointTrait for Point1WithId {
|
||||
fn dims(&self) -> &[f64] {
|
||||
return &self.dims;
|
||||
}
|
||||
}
|
||||
}
|
7
src/lib.rs
Normal file
7
src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate quickcheck;
|
||||
|
||||
extern crate rand;
|
||||
|
||||
pub mod kdtree;
|
Loading…
Reference in New Issue
Block a user