|
9 | 9 | |
10 | 10 | First, put the points into the tree: |
11 | 11 | |
12 | | - kd = Containers::KDTree.new([ [4, 3], [3, 4], [-1, 2], [6, 4], [3, -5], [-2, -5] ]) |
| 12 | + kdtree = Containers::KDTree.new( {0 => [4, 3], 1 => [3, 4], 2 => [-1, 2], 3 => [6, 4], |
| 13 | + 4 => [3, -5], 5 => [-2, -5] }) |
13 | 14 | |
14 | 15 | Then, query on the tree: |
15 | 16 | |
16 | | - puts kd.find_nearest([0, 0], 2) => [[0, 6], [0, 3]] |
| 17 | + puts kd.find_nearest([0, 0], 2) => [[5, 2], [9, 1]] |
| 18 | + |
| 19 | + The result is an array of [distance, id] pairs. There seems to be a bug in this version. |
17 | 20 | |
18 | 21 | Note that the point queried on does not have to exist in the tree. However, if it does exist, |
19 | 22 | it will be returned. |
|
23 | 26 | class Containers::KDTree |
24 | 27 | Node = Struct.new(:id, :coords, :left, :right) |
25 | 28 |
|
| 29 | + # Points is a hash of id => [coord, coord] pairs. |
26 | 30 | def initialize(points) |
27 | | - @root = build_tree(points) |
| 31 | + raise "must pass in a hash" unless points.kind_of?(Hash) |
| 32 | + @dimensions = points[ points.keys.first ].size |
| 33 | + @root = build_tree(points.to_a) |
| 34 | + @nearest = [] |
| 35 | + end |
| 36 | + |
| 37 | + # Find k closest points to given coordinates |
| 38 | + def find_nearest(target, k_nearest) |
28 | 39 | @nearest = [] |
| 40 | + nearest(@root, target, k_nearest, 0) |
29 | 41 | end |
30 | 42 |
|
31 | | - # Build a kd-tree |
| 43 | + # points is an array |
32 | 44 | def build_tree(points, depth=0) |
33 | 45 | return if points.empty? |
34 | | - |
35 | | - axis = depth % 2 |
36 | | - |
37 | | - points.sort! { |a, b| a[1][axis] <=> b[1][axis] } |
| 46 | + |
| 47 | + axis = depth % @dimensions |
| 48 | + |
| 49 | + points.sort! { |a, b| a.last[axis] <=> b.last[axis] } |
38 | 50 | median = points.size / 2 |
39 | | - |
40 | | - node = Node.new(points[median][0], points[median][1], nil, nil) |
| 51 | + |
| 52 | + node = Node.new(points[median].first, points[median].last, nil, nil) |
41 | 53 | node.left = build_tree(points[0...median], depth+1) |
42 | 54 | node.right = build_tree(points[median+1..-1], depth+1) |
43 | 55 | node |
@@ -65,14 +77,9 @@ def check_nearest(nearest, node, target, k_nearest) |
65 | 77 | end |
66 | 78 | private :check_nearest |
67 | 79 |
|
68 | | - # Find k closest points to given coordinates |
69 | | - def find_nearest(target, k_nearest) |
70 | | - @nearest = [] |
71 | | - nearest(@root, target, k_nearest, 0) |
72 | | - end |
73 | | - |
| 80 | + # Recursively find nearest coordinates, going down the appropriate branch as needed |
74 | 81 | def nearest(node, target, k_nearest, depth) |
75 | | - axis = depth % 2 |
| 82 | + axis = depth % @dimensions |
76 | 83 |
|
77 | 84 | if node.left.nil? && node.right.nil? # Leaf node |
78 | 85 | @nearest = check_nearest(@nearest, node, target, k_nearest) |
|
0 commit comments