kdtree 0.4 → 0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/LICENSE +1 -1
- data/README.md +53 -37
- data/ext/kdtree/kdtree.c +45 -53
- data/kdtree.gemspec +26 -17
- metadata +14 -47
- data/.gitignore +0 -6
- data/.travis.yml +0 -7
- data/Gemfile +0 -2
- data/Rakefile +0 -41
- data/test/test_kdtree.rb +0 -148
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8fa26faf959cacb1da1692bef2539c89031037f4a2c5f715eadc5fed72fa5590
|
4
|
+
data.tar.gz: 3377e4521781a8a675840bf5ac7a34998bbd43783643f0b2ab4ee9988d85465d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f50f4e1721d3ad2359ac3c3ebf8436a5716a4b333e4cc6de3f1443624417b5cecdabb9a25a76f52baf89014b3a70ca699ae4920657ec384305275fcc01a2c3d
|
7
|
+
data.tar.gz: 2075eea9b92b86c547e70a5802df0c2af13554ec43ac92ddedd85d4012d02219ddfa646dabed4e81edb125af363ecc26ad5357b797931184a0a8f3dd8eba53b3
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,28 +1,31 @@
|
|
1
|
-
## Kdtree
|
1
|
+
## Kdtree [](https://github.com/gurgeous/kdtree/actions/workflows/test.yml)
|
2
2
|
|
3
|
-
[](https://travis-ci.org/gurgeous/kdtree)
|
4
3
|
|
5
4
|
A kd tree is a data structure that recursively partitions the world in order to rapidly answer nearest neighbor queries. A generic kd tree can support any number of dimensions, and can return either the nearest neighbor or a set of N nearest neighbors.
|
6
5
|
|
7
6
|
This gem is a blazingly fast, native, 2d kdtree. It's specifically built to find the nearest neighbor when searching millions of points. It's used in production at Urbanspoon and several other companies.
|
8
7
|
|
9
|
-
The first version of this gem was released back in 2009.
|
8
|
+
The first version of this gem was released back in 2009. Wikipedia has a great [article on kdtrees](http://en.wikipedia.org/wiki/K-d_tree).
|
10
9
|
|
11
|
-
Note: kdtree
|
10
|
+
Note: kdtree obsoletes these forks: ghazel-kdtree, groupon-kdtree, tupalo-kdree. Thanks guys!
|
12
11
|
|
13
|
-
###
|
12
|
+
### Installation
|
14
13
|
|
15
|
-
|
14
|
+
```ruby
|
15
|
+
# install gem
|
16
|
+
$ gem install kdtree
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
# or add to your Gemfile
|
19
|
+
gem "kdtree"
|
19
20
|
```
|
20
21
|
|
22
|
+
### Usage
|
23
|
+
|
21
24
|
It's easy to use:
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
- **Kdtree.new(points)** - construct a new tree. Each point should be of the form `[x, y, id]`, where `x/y` are floats and `id` is an int. Not a string, not an object, **just an int**.
|
27
|
+
- **kd.nearest(x, y)** - find the nearest point. Returns an id.
|
28
|
+
- **kd.nearestk(x, y, k)** - find the nearest `k` points. Returns an array of ids.
|
26
29
|
|
27
30
|
For example:
|
28
31
|
|
@@ -50,50 +53,63 @@ kd2 = File.open("treefile") { |f| Kdtree.new(f) }
|
|
50
53
|
|
51
54
|
### Performance
|
52
55
|
|
53
|
-
Kdtree is fast. How fast? Using a tree with 1 million points on my
|
56
|
+
Kdtree is fast. How fast? Using a tree with 1 million points on my M1:
|
54
57
|
|
55
58
|
```
|
56
|
-
build (init)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
nearest
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
build (init) 0.96s
|
60
|
+
persist 0.000814s
|
61
|
+
read (init) 0.009236s
|
62
|
+
|
63
|
+
nearest point 0.000002s
|
64
|
+
nearest 5 points 0.000002s
|
65
|
+
nearest 50 points 0.000006s
|
66
|
+
nearest 255 points 0.000026s
|
64
67
|
```
|
65
68
|
|
66
69
|
### Limitations
|
67
70
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
71
|
+
- No **editing** allowed! Once you construct a tree you're stuck with it.
|
72
|
+
- The tree is stored in **one big memory block**, 20 bytes per point. A tree with one million points will allocate a single 19mb block to store its nodes.
|
73
|
+
- Persisted trees are **architecture dependent**, and may not work across different machines due to endian issues.
|
74
|
+
- nearestk is limited to **255 results**
|
72
75
|
|
73
76
|
### Contributors
|
74
77
|
|
75
78
|
Since this gem was originally released, several folks have contributed important patches:
|
76
79
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
80
|
+
- @antifuchs (thread safety)
|
81
|
+
- @evanphx (native cleanups, perf)
|
82
|
+
- @ghazel (C89 compliance)
|
83
|
+
- @mcerna (1.9 compat)
|
81
84
|
|
82
85
|
### Changelog
|
83
86
|
|
84
|
-
|
87
|
+
Note: This gem is stable, maintained and continues to work great with all modern versions of Ruby MRI. Our CI tests through Ruby 3.4. No need for new releases until something breaks!
|
88
|
+
|
89
|
+
#### 0.5 - May 2025
|
90
|
+
|
91
|
+
- justfile
|
92
|
+
- hygiene - updated deps, format/lint, modernize rakefile
|
93
|
+
- moved to ruby 3.x or higher, tested with ruby 3.4
|
94
|
+
- updated benchmark numbers (still real fast)
|
95
|
+
|
96
|
+
#### 0.4 - Mar 2017
|
97
|
+
|
98
|
+
- this is mostly housekeeping - test on more rubies, fix a few warnings
|
99
|
+
|
100
|
+
#### 0.3 - Oct 2012
|
85
101
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
102
|
+
- Ruby 1.9.x compatibility (@mcerna and others)
|
103
|
+
- renamed KDTree to the more idiomatic Kdtree
|
104
|
+
- use IO methods directly instead of rooting around in rb_io
|
105
|
+
- thread safe, no more statics (@antifuchs)
|
106
|
+
- C90 compliance, no warnings (@ghazel)
|
107
|
+
- native cleanups (@evanphx)
|
92
108
|
|
93
109
|
#### 0.2
|
94
110
|
|
95
111
|
skipped this version to prevent confusion with other flavors of the gem
|
96
112
|
|
97
|
-
#### 0.1
|
113
|
+
#### 0.1 - Jan 2010
|
98
114
|
|
99
|
-
|
115
|
+
- Original release
|
data/ext/kdtree/kdtree.c
CHANGED
@@ -5,16 +5,14 @@
|
|
5
5
|
//
|
6
6
|
|
7
7
|
// the tree itself
|
8
|
-
typedef struct kdtree_data
|
9
|
-
{
|
8
|
+
typedef struct kdtree_data {
|
10
9
|
int root;
|
11
10
|
int len;
|
12
11
|
struct kdtree_node *nodes;
|
13
12
|
} kdtree_data;
|
14
13
|
|
15
14
|
// a node in the tree
|
16
|
-
typedef struct kdtree_node
|
17
|
-
{
|
15
|
+
typedef struct kdtree_node {
|
18
16
|
float x, y;
|
19
17
|
int id;
|
20
18
|
int left;
|
@@ -28,7 +26,7 @@ typedef struct kresult {
|
|
28
26
|
} kresult;
|
29
27
|
|
30
28
|
// helper macro for digging out our struct
|
31
|
-
#define KDTREEP
|
29
|
+
#define KDTREEP \
|
32
30
|
struct kdtree_data *kdtreep; \
|
33
31
|
Data_Get_Struct(kdtree, struct kdtree_data, kdtreep);
|
34
32
|
|
@@ -43,8 +41,10 @@ static VALUE kdtree_to_s(VALUE kdtree);
|
|
43
41
|
|
44
42
|
// kdtree helpers
|
45
43
|
static int kdtree_build(struct kdtree_data *kdtreep, int min, int max, int depth);
|
46
|
-
static void kdtree_nearest0(struct kdtree_data *kdtreep, int i, float x, float y, int depth,
|
47
|
-
|
44
|
+
static void kdtree_nearest0(struct kdtree_data *kdtreep, int i, float x, float y, int depth,
|
45
|
+
int *n_index, float *n_dist);
|
46
|
+
static void kdtree_nearestk0(struct kdtree_data *kdtreep, int i, float x, float y, int k, int depth,
|
47
|
+
kresult *k_list, int *k_len, float *k_dist);
|
48
48
|
|
49
49
|
// io helpers
|
50
50
|
static void read_all(VALUE io, void *buf, int len);
|
@@ -59,16 +59,14 @@ static ID id_read, id_write, id_binmode;
|
|
59
59
|
// implementation
|
60
60
|
//
|
61
61
|
|
62
|
-
static VALUE kdtree_alloc(VALUE klass)
|
63
|
-
{
|
62
|
+
static VALUE kdtree_alloc(VALUE klass) {
|
64
63
|
struct kdtree_data *kdtreep;
|
65
64
|
VALUE obj = Data_Make_Struct(klass, struct kdtree_data, 0, kdtree_free, kdtreep);
|
66
65
|
kdtreep->root = -1;
|
67
66
|
return obj;
|
68
67
|
}
|
69
68
|
|
70
|
-
static void kdtree_free(struct kdtree_data *kdtreep)
|
71
|
-
{
|
69
|
+
static void kdtree_free(struct kdtree_data *kdtreep) {
|
72
70
|
if (kdtreep) {
|
73
71
|
free(kdtreep->nodes);
|
74
72
|
}
|
@@ -95,15 +93,14 @@ static void kdtree_free(struct kdtree_data *kdtreep)
|
|
95
93
|
* kdtree. This makes it possible to build the tree in advance, persist it, and
|
96
94
|
* start it up quickly later. See persist for more information.
|
97
95
|
*/
|
98
|
-
static VALUE kdtree_initialize(VALUE kdtree, VALUE arg)
|
99
|
-
{
|
96
|
+
static VALUE kdtree_initialize(VALUE kdtree, VALUE arg) {
|
100
97
|
KDTREEP;
|
101
98
|
|
102
99
|
if (TYPE(arg) == T_ARRAY) {
|
103
100
|
// init from array of pints
|
104
101
|
VALUE points = arg;
|
105
102
|
int i;
|
106
|
-
kdtreep->len = RARRAY_LEN(points);
|
103
|
+
kdtreep->len = (int)RARRAY_LEN(points);
|
107
104
|
kdtreep->nodes = ALLOC_N(struct kdtree_node, kdtreep->len);
|
108
105
|
|
109
106
|
for (i = 0; i < RARRAY_LEN(points); ++i) {
|
@@ -147,23 +144,20 @@ static VALUE kdtree_initialize(VALUE kdtree, VALUE arg)
|
|
147
144
|
return kdtree;
|
148
145
|
}
|
149
146
|
|
150
|
-
static int comparex(const void *pa, const void *pb)
|
151
|
-
|
152
|
-
float
|
153
|
-
float b = ((const struct kdtree_node*)pb)->x;
|
147
|
+
static int comparex(const void *pa, const void *pb) {
|
148
|
+
float a = ((const struct kdtree_node *)pa)->x;
|
149
|
+
float b = ((const struct kdtree_node *)pb)->x;
|
154
150
|
return (a < b) ? -1 : ((a > b) ? 1 : 0);
|
155
151
|
}
|
156
152
|
|
157
|
-
static int comparey(const void *pa, const void *pb)
|
158
|
-
|
159
|
-
float
|
160
|
-
float b = ((const struct kdtree_node*)pb)->y;
|
153
|
+
static int comparey(const void *pa, const void *pb) {
|
154
|
+
float a = ((const struct kdtree_node *)pa)->y;
|
155
|
+
float b = ((const struct kdtree_node *)pb)->y;
|
161
156
|
return (a < b) ? -1 : ((a > b) ? 1 : 0);
|
162
157
|
}
|
163
158
|
|
164
|
-
static int kdtree_build(struct kdtree_data *kdtreep, int min, int max, int depth)
|
165
|
-
|
166
|
-
int(*compar)(const void *, const void *);
|
159
|
+
static int kdtree_build(struct kdtree_data *kdtreep, int min, int max, int depth) {
|
160
|
+
int (*compar)(const void *, const void *);
|
167
161
|
struct kdtree_node *m;
|
168
162
|
int median;
|
169
163
|
if (max <= min) {
|
@@ -198,8 +192,7 @@ static int kdtree_build(struct kdtree_data *kdtreep, int min, int max, int depth
|
|
198
192
|
* # which city is closest to Boston?
|
199
193
|
* kd.nearest(42.4, -71.1) #=> 2
|
200
194
|
*/
|
201
|
-
static VALUE kdtree_nearest(VALUE kdtree, VALUE x, VALUE y)
|
202
|
-
{
|
195
|
+
static VALUE kdtree_nearest(VALUE kdtree, VALUE x, VALUE y) {
|
203
196
|
int n_index;
|
204
197
|
float n_dist;
|
205
198
|
KDTREEP;
|
@@ -214,8 +207,8 @@ static VALUE kdtree_nearest(VALUE kdtree, VALUE x, VALUE y)
|
|
214
207
|
return INT2NUM((kdtreep->nodes + n_index)->id);
|
215
208
|
}
|
216
209
|
|
217
|
-
static void kdtree_nearest0(struct kdtree_data *kdtreep, int i, float x, float y, int depth,
|
218
|
-
{
|
210
|
+
static void kdtree_nearest0(struct kdtree_data *kdtreep, int i, float x, float y, int depth,
|
211
|
+
int *n_index, float *n_dist) {
|
219
212
|
struct kdtree_node *n;
|
220
213
|
float ad;
|
221
214
|
int near, far;
|
@@ -234,11 +227,13 @@ static void kdtree_nearest0(struct kdtree_data *kdtreep, int i, float x, float y
|
|
234
227
|
//
|
235
228
|
|
236
229
|
if (ad <= 0) {
|
237
|
-
near = n->left;
|
230
|
+
near = n->left;
|
231
|
+
far = n->right;
|
238
232
|
} else {
|
239
|
-
near = n->right;
|
233
|
+
near = n->right;
|
234
|
+
far = n->left;
|
240
235
|
}
|
241
|
-
kdtree_nearest0(kdtreep, near,
|
236
|
+
kdtree_nearest0(kdtreep, near, x, y, depth + 1, n_index, n_dist);
|
242
237
|
if (ad * ad < *n_dist) {
|
243
238
|
kdtree_nearest0(kdtreep, far, x, y, depth + 1, n_index, n_dist);
|
244
239
|
}
|
@@ -280,9 +275,9 @@ static void kdtree_nearest0(struct kdtree_data *kdtreep, int i, float x, float y
|
|
280
275
|
* # which two cities are closest to San Francisco?
|
281
276
|
* kd.nearestk(34.1, -118.2, 2) #=> [2, 1]
|
282
277
|
*/
|
283
|
-
static VALUE kdtree_nearestk(VALUE kdtree, VALUE x, VALUE y, VALUE k)
|
284
|
-
|
285
|
-
//
|
278
|
+
static VALUE kdtree_nearestk(VALUE kdtree, VALUE x, VALUE y, VALUE k) {
|
279
|
+
// note I leave an extra slot here at the end because of the way our binary
|
280
|
+
// insert works
|
286
281
|
kresult k_list[MAX_K + 1];
|
287
282
|
int k_len = 0;
|
288
283
|
float k_dist = INT_MAX;
|
@@ -296,7 +291,8 @@ static VALUE kdtree_nearestk(VALUE kdtree, VALUE x, VALUE y, VALUE k)
|
|
296
291
|
} else if (ki > MAX_K) {
|
297
292
|
ki = MAX_K;
|
298
293
|
}
|
299
|
-
kdtree_nearestk0(kdtreep, kdtreep->root, NUM2DBL(x), NUM2DBL(y), ki, 0, k_list, &k_len,
|
294
|
+
kdtree_nearestk0(kdtreep, kdtreep->root, NUM2DBL(x), NUM2DBL(y), ki, 0, k_list, &k_len,
|
295
|
+
&k_dist);
|
300
296
|
|
301
297
|
// convert result to ruby array
|
302
298
|
ary = rb_ary_new();
|
@@ -306,8 +302,8 @@ static VALUE kdtree_nearestk(VALUE kdtree, VALUE x, VALUE y, VALUE k)
|
|
306
302
|
return ary;
|
307
303
|
}
|
308
304
|
|
309
|
-
static void kdtree_nearestk0(struct kdtree_data *kdtreep, int i, float x, float y, int k, int depth,
|
310
|
-
{
|
305
|
+
static void kdtree_nearestk0(struct kdtree_data *kdtreep, int i, float x, float y, int k, int depth,
|
306
|
+
kresult *k_list, int *k_len, float *k_dist) {
|
311
307
|
struct kdtree_node *n;
|
312
308
|
float ad;
|
313
309
|
int near, far;
|
@@ -327,11 +323,13 @@ static void kdtree_nearestk0(struct kdtree_data *kdtreep, int i, float x, float
|
|
327
323
|
//
|
328
324
|
|
329
325
|
if (ad <= 0) {
|
330
|
-
near = n->left;
|
326
|
+
near = n->left;
|
327
|
+
far = n->right;
|
331
328
|
} else {
|
332
|
-
near = n->right;
|
329
|
+
near = n->right;
|
330
|
+
far = n->left;
|
333
331
|
}
|
334
|
-
kdtree_nearestk0(kdtreep, near,
|
332
|
+
kdtree_nearestk0(kdtreep, near, x, y, k, depth + 1, k_list, k_len, k_dist);
|
335
333
|
if (ad * ad < *k_dist) {
|
336
334
|
kdtree_nearestk0(kdtreep, far, x, y, k, depth + 1, k_list, k_len, k_dist);
|
337
335
|
}
|
@@ -404,9 +402,7 @@ static void kdtree_nearestk0(struct kdtree_data *kdtreep, int i, float x, float
|
|
404
402
|
* # later, read the tree from disk
|
405
403
|
* kd2 = File.open("treefile") { |f| Kdtree.new(f) }
|
406
404
|
*/
|
407
|
-
static VALUE kdtree_persist(VALUE kdtree, VALUE io)
|
408
|
-
{
|
409
|
-
VALUE str;
|
405
|
+
static VALUE kdtree_persist(VALUE kdtree, VALUE io) {
|
410
406
|
KDTREEP;
|
411
407
|
|
412
408
|
if (!rb_respond_to(io, rb_intern("write"))) {
|
@@ -428,12 +424,11 @@ static VALUE kdtree_persist(VALUE kdtree, VALUE io)
|
|
428
424
|
*
|
429
425
|
* A string that tells you a bit about the tree.
|
430
426
|
*/
|
431
|
-
static VALUE kdtree_to_s(VALUE kdtree)
|
432
|
-
{
|
427
|
+
static VALUE kdtree_to_s(VALUE kdtree) {
|
433
428
|
char buf[256];
|
434
429
|
KDTREEP;
|
435
430
|
|
436
|
-
sprintf(buf, "#<%s:%p nodes=%d>", rb_obj_classname(kdtree), (void*)kdtree, kdtreep->len);
|
431
|
+
sprintf(buf, "#<%s:%p nodes=%d>", rb_obj_classname(kdtree), (void *)kdtree, kdtreep->len);
|
437
432
|
return rb_str_new(buf, strlen(buf));
|
438
433
|
}
|
439
434
|
|
@@ -441,8 +436,7 @@ static VALUE kdtree_to_s(VALUE kdtree)
|
|
441
436
|
// io helpers
|
442
437
|
//
|
443
438
|
|
444
|
-
static void read_all(VALUE io, void *buf, int len)
|
445
|
-
{
|
439
|
+
static void read_all(VALUE io, void *buf, int len) {
|
446
440
|
VALUE string = rb_funcall(io, id_read, 1, INT2NUM(len));
|
447
441
|
if (NIL_P(string) || RSTRING_LEN(string) != len) {
|
448
442
|
rb_raise(rb_eEOFError, "end of file reached");
|
@@ -450,8 +444,7 @@ static void read_all(VALUE io, void *buf, int len)
|
|
450
444
|
memcpy(buf, RSTRING_PTR(string), len);
|
451
445
|
}
|
452
446
|
|
453
|
-
static void write_all(VALUE io, const void *buf, int len)
|
454
|
-
{
|
447
|
+
static void write_all(VALUE io, const void *buf, int len) {
|
455
448
|
rb_funcall(io, id_write, 1, rb_str_new(buf, len));
|
456
449
|
}
|
457
450
|
|
@@ -487,8 +480,7 @@ static void write_all(VALUE io, const void *buf, int len)
|
|
487
480
|
*
|
488
481
|
* http://en.wikipedia.org/wiki/Kd-tree
|
489
482
|
*/
|
490
|
-
void Init_kdtree()
|
491
|
-
{
|
483
|
+
void Init_kdtree() {
|
492
484
|
static VALUE clazz;
|
493
485
|
|
494
486
|
clazz = rb_define_class("Kdtree", rb_cObject);
|
data/kdtree.gemspec
CHANGED
@@ -1,24 +1,33 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
|
-
s.name
|
3
|
-
s.version
|
2
|
+
s.name = "kdtree"
|
3
|
+
s.version = "0.5"
|
4
|
+
s.authors = ["Adam Doppelt"]
|
5
|
+
s.email = "amd@gurge.com"
|
4
6
|
|
5
|
-
s.
|
6
|
-
s.
|
7
|
-
s.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
s.summary = "Blazingly fast, native 2d kdtree."
|
8
|
+
s.homepage = "http://github.com/gurgeous/kdtree"
|
9
|
+
s.description = <<~EOF
|
10
|
+
A kdtree is a data structure that makes it possible to quickly solve
|
11
|
+
the nearest neighbor problem. This is a native 2d kdtree suitable for
|
12
|
+
production use with millions of points.
|
13
|
+
EOF
|
14
|
+
s.license = "MIT"
|
15
|
+
s.required_ruby_version = ">= 3.0.0"
|
16
|
+
s.metadata = {
|
17
|
+
"homepage_uri" => s.homepage,
|
18
|
+
"rubygems_mfa_required" => "true",
|
19
|
+
"source_code_uri" => s.homepage,
|
20
|
+
}
|
15
21
|
|
16
|
-
s.
|
17
|
-
|
18
|
-
|
22
|
+
s.files = %w[
|
23
|
+
ext/kdtree/extconf.rb
|
24
|
+
ext/kdtree/kdtree.c
|
25
|
+
kdtree.gemspec
|
26
|
+
lib/kdtree.rb
|
27
|
+
LICENSE
|
28
|
+
README.md
|
29
|
+
]
|
19
30
|
|
20
|
-
s.files = `git ls-files`.split("\n")
|
21
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
31
|
s.extensions = ["ext/kdtree/extconf.rb"]
|
23
32
|
s.require_paths = ["lib"]
|
24
33
|
end
|
metadata
CHANGED
@@ -1,70 +1,39 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kdtree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.5'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Doppelt
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: minitest
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '5.0'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '5.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake-compiler
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '1.0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '1.0'
|
11
|
+
date: 2025-05-01 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
41
13
|
description: |
|
42
14
|
A kdtree is a data structure that makes it possible to quickly solve
|
43
15
|
the nearest neighbor problem. This is a native 2d kdtree suitable for
|
44
16
|
production use with millions of points.
|
45
|
-
email:
|
46
|
-
- amd@gurge.com
|
17
|
+
email: amd@gurge.com
|
47
18
|
executables: []
|
48
19
|
extensions:
|
49
20
|
- ext/kdtree/extconf.rb
|
50
21
|
extra_rdoc_files: []
|
51
22
|
files:
|
52
|
-
- ".gitignore"
|
53
|
-
- ".travis.yml"
|
54
|
-
- Gemfile
|
55
23
|
- LICENSE
|
56
24
|
- README.md
|
57
|
-
- Rakefile
|
58
25
|
- ext/kdtree/extconf.rb
|
59
26
|
- ext/kdtree/kdtree.c
|
60
27
|
- kdtree.gemspec
|
61
28
|
- lib/kdtree.rb
|
62
|
-
- test/test_kdtree.rb
|
63
29
|
homepage: http://github.com/gurgeous/kdtree
|
64
30
|
licenses:
|
65
31
|
- MIT
|
66
|
-
metadata:
|
67
|
-
|
32
|
+
metadata:
|
33
|
+
homepage_uri: http://github.com/gurgeous/kdtree
|
34
|
+
rubygems_mfa_required: 'true'
|
35
|
+
source_code_uri: http://github.com/gurgeous/kdtree
|
36
|
+
post_install_message:
|
68
37
|
rdoc_options: []
|
69
38
|
require_paths:
|
70
39
|
- lib
|
@@ -72,17 +41,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
41
|
requirements:
|
73
42
|
- - ">="
|
74
43
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
44
|
+
version: 3.0.0
|
76
45
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
46
|
requirements:
|
78
47
|
- - ">="
|
79
48
|
- !ruby/object:Gem::Version
|
80
49
|
version: '0'
|
81
50
|
requirements: []
|
82
|
-
|
83
|
-
|
84
|
-
signing_key:
|
51
|
+
rubygems_version: 3.5.16
|
52
|
+
signing_key:
|
85
53
|
specification_version: 4
|
86
54
|
summary: Blazingly fast, native 2d kdtree.
|
87
|
-
test_files:
|
88
|
-
- test/test_kdtree.rb
|
55
|
+
test_files: []
|
data/.gitignore
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require "bundler/setup"
|
2
|
-
require "rake/extensiontask"
|
3
|
-
require "rake/testtask"
|
4
|
-
|
5
|
-
# load the spec, we use it below
|
6
|
-
spec = Gem::Specification.load("kdtree.gemspec")
|
7
|
-
|
8
|
-
#
|
9
|
-
# gem
|
10
|
-
#
|
11
|
-
|
12
|
-
task :build do
|
13
|
-
system "gem build --quiet kdtree.gemspec"
|
14
|
-
end
|
15
|
-
|
16
|
-
task install: :build do
|
17
|
-
system "sudo gem install --quiet kdtree-#{spec.version}.gem"
|
18
|
-
end
|
19
|
-
|
20
|
-
task release: :build do
|
21
|
-
system "git tag -a #{spec.version} -m 'Tagging #{spec.version}'"
|
22
|
-
system "git push --tags"
|
23
|
-
system "gem push kdtree-#{spec.version}.gem"
|
24
|
-
end
|
25
|
-
|
26
|
-
#
|
27
|
-
# rake-compiler
|
28
|
-
#
|
29
|
-
|
30
|
-
Rake::ExtensionTask.new("kdtree", spec)
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
# testing
|
35
|
-
#
|
36
|
-
|
37
|
-
Rake::TestTask.new(:test) do |test|
|
38
|
-
test.libs << "test"
|
39
|
-
end
|
40
|
-
task test: :compile
|
41
|
-
task default: :test
|
data/test/test_kdtree.rb
DELETED
@@ -1,148 +0,0 @@
|
|
1
|
-
require "benchmark"
|
2
|
-
require "kdtree"
|
3
|
-
require "tempfile"
|
4
|
-
require "minitest/autorun"
|
5
|
-
|
6
|
-
#
|
7
|
-
# create a tree
|
8
|
-
#
|
9
|
-
|
10
|
-
class KdtreeTest < Minitest::Test
|
11
|
-
TMP = "#{Dir.tmpdir}/kdtree_test"
|
12
|
-
|
13
|
-
def setup
|
14
|
-
@points = (0...1000).map { |i| [rand_coord, rand_coord, i] }
|
15
|
-
@kdtree = Kdtree.new(@points)
|
16
|
-
end
|
17
|
-
|
18
|
-
def teardown
|
19
|
-
File.unlink(TMP) if File.exist?(TMP)
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_nearest
|
23
|
-
100.times do
|
24
|
-
pt = [rand_coord, rand_coord]
|
25
|
-
|
26
|
-
# kdtree search
|
27
|
-
id = @kdtree.nearest(pt[0], pt[1])
|
28
|
-
kdpt = @points[id]
|
29
|
-
|
30
|
-
# slow search
|
31
|
-
sortpt = @points.sort_by { |i| distance(i, pt) }.first
|
32
|
-
|
33
|
-
# assert
|
34
|
-
kdd = distance(kdpt, pt)
|
35
|
-
sortd = distance(sortpt, pt)
|
36
|
-
assert((kdd - sortd).abs < 0.0000001, "kdtree didn't return the closest result")
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def test_nearestk
|
41
|
-
100.times do
|
42
|
-
pt = [rand_coord, rand_coord]
|
43
|
-
|
44
|
-
# kdtree search
|
45
|
-
list = @kdtree.nearestk(pt[0], pt[1], 5)
|
46
|
-
kdpt = @points[list.last]
|
47
|
-
|
48
|
-
# slow search
|
49
|
-
sortpt = @points.sort_by { |i| distance(i, pt) }[list.length - 1]
|
50
|
-
|
51
|
-
# assert
|
52
|
-
kdd = distance(kdpt, pt)
|
53
|
-
sortd = distance(sortpt, pt)
|
54
|
-
assert((kdd - sortd).abs < 0.0000001, "kdtree didn't return the closest result")
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def test_persist
|
59
|
-
# write
|
60
|
-
File.open(TMP, "w") { |f| @kdtree.persist(f) }
|
61
|
-
# read
|
62
|
-
kdtree2 = File.open(TMP, "r") { |f| Kdtree.new(f) }
|
63
|
-
|
64
|
-
# now test some random points
|
65
|
-
100.times do
|
66
|
-
pt = [rand_coord, rand_coord]
|
67
|
-
id1 = @kdtree.nearest(*pt)
|
68
|
-
id2 = kdtree2.nearest(*pt)
|
69
|
-
assert(id1 == id2, "kdtree2 differed from kdtree")
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def test_bad_magic
|
74
|
-
File.open(TMP, "w") { |f| f.puts "That ain't right" }
|
75
|
-
assert_raises RuntimeError do
|
76
|
-
File.open(TMP, "r") { |f| Kdtree.new(f) }
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def test_eof
|
81
|
-
File.open(TMP, "w") { |f| @kdtree.persist(f) }
|
82
|
-
bytes = File.read(TMP)
|
83
|
-
|
84
|
-
[2, 10, 100].each do |len|
|
85
|
-
File.open(TMP, "w") { |f| f.write(bytes[0, len]) }
|
86
|
-
assert_raises EOFError do
|
87
|
-
File.open(TMP, "r") { |f| Kdtree.new(f) }
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def dont_test_speed
|
93
|
-
sizes = [1, 100, 1000, 10000, 100000, 1000000]
|
94
|
-
ks = [1, 5, 50, 255]
|
95
|
-
sizes.each do |s|
|
96
|
-
points = (0...s).map { |i| [rand_coord, rand_coord, i] }
|
97
|
-
|
98
|
-
# build
|
99
|
-
Benchmark.bm(17) do |bm|
|
100
|
-
kdtree = nil
|
101
|
-
bm.report "build" do
|
102
|
-
kdtree = Kdtree.new(points)
|
103
|
-
end
|
104
|
-
bm.report "persist" do
|
105
|
-
File.open(TMP, "w") { |f| kdtree.persist(f) }
|
106
|
-
end
|
107
|
-
bm.report "read" do
|
108
|
-
File.open(TMP, "r") { |f| Kdtree.new(f) }
|
109
|
-
end
|
110
|
-
|
111
|
-
ks.each do |k|
|
112
|
-
bm.report "100 queries (#{k})" do
|
113
|
-
100.times do
|
114
|
-
if k == 1
|
115
|
-
kdtree.nearest(rand_coord, rand_coord)
|
116
|
-
else
|
117
|
-
kdtree.nearestk(rand_coord, rand_coord, k)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
puts
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
protected
|
128
|
-
|
129
|
-
def distance(a, b)
|
130
|
-
x, y = a[0] - b[0], a[1] - b[1]
|
131
|
-
x * x + y * y
|
132
|
-
end
|
133
|
-
|
134
|
-
def rand_coord
|
135
|
-
rand(0) * 10 - 5
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
# running dont_test_speed on my i5 2.8ghz:
|
140
|
-
#
|
141
|
-
# user system total real
|
142
|
-
# build 3.350000 0.020000 3.370000 ( 3.520528)
|
143
|
-
# persist 0.150000 0.020000 0.170000 ( 0.301963)
|
144
|
-
# read 0.280000 0.000000 0.280000 ( 0.432676)
|
145
|
-
# 100 queries (1) 0.000000 0.000000 0.000000 ( 0.000319)
|
146
|
-
# 100 queries (5) 0.000000 0.000000 0.000000 ( 0.000412)
|
147
|
-
# 100 queries (50) 0.000000 0.000000 0.000000 ( 0.001417)
|
148
|
-
# 100 queries (255) 0.000000 0.000000 0.000000 ( 0.006268)
|