annoy-rb 0.1.0 → 0.3.0
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 +4 -4
- data/.github/workflows/build.yml +22 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +6 -4
- data/README.md +9 -4
- data/Rakefile +2 -1
- data/Steepfile +20 -0
- data/annoy-rb.gemspec +2 -1
- data/ext/annoy/{annoy.cpp → annoyext.cpp} +7 -7
- data/ext/annoy/{annoy.hpp → annoyext.hpp} +66 -34
- data/ext/annoy/extconf.rb +2 -2
- data/ext/annoy/src/annoylib.h +201 -56
- data/ext/annoy/src/mman.h +242 -0
- data/lib/annoy.rb +4 -3
- data/lib/annoy/version.rb +1 -1
- data/sig/annoy.rbs +114 -0
- metadata +14 -10
- data/.travis.yml +0 -12
@@ -0,0 +1,242 @@
|
|
1
|
+
|
2
|
+
// This is from https://code.google.com/p/mman-win32/
|
3
|
+
//
|
4
|
+
// Licensed under MIT
|
5
|
+
|
6
|
+
#ifndef _MMAN_WIN32_H
|
7
|
+
#define _MMAN_WIN32_H
|
8
|
+
|
9
|
+
#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
|
10
|
+
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows.
|
11
|
+
#endif
|
12
|
+
|
13
|
+
#include <sys/types.h>
|
14
|
+
#include <windows.h>
|
15
|
+
#include <errno.h>
|
16
|
+
#include <io.h>
|
17
|
+
|
18
|
+
#define PROT_NONE 0
|
19
|
+
#define PROT_READ 1
|
20
|
+
#define PROT_WRITE 2
|
21
|
+
#define PROT_EXEC 4
|
22
|
+
|
23
|
+
#define MAP_FILE 0
|
24
|
+
#define MAP_SHARED 1
|
25
|
+
#define MAP_PRIVATE 2
|
26
|
+
#define MAP_TYPE 0xf
|
27
|
+
#define MAP_FIXED 0x10
|
28
|
+
#define MAP_ANONYMOUS 0x20
|
29
|
+
#define MAP_ANON MAP_ANONYMOUS
|
30
|
+
|
31
|
+
#define MAP_FAILED ((void *)-1)
|
32
|
+
|
33
|
+
/* Flags for msync. */
|
34
|
+
#define MS_ASYNC 1
|
35
|
+
#define MS_SYNC 2
|
36
|
+
#define MS_INVALIDATE 4
|
37
|
+
|
38
|
+
#ifndef FILE_MAP_EXECUTE
|
39
|
+
#define FILE_MAP_EXECUTE 0x0020
|
40
|
+
#endif
|
41
|
+
|
42
|
+
static int __map_mman_error(const DWORD err, const int deferr)
|
43
|
+
{
|
44
|
+
if (err == 0)
|
45
|
+
return 0;
|
46
|
+
//TODO: implement
|
47
|
+
return err;
|
48
|
+
}
|
49
|
+
|
50
|
+
static DWORD __map_mmap_prot_page(const int prot)
|
51
|
+
{
|
52
|
+
DWORD protect = 0;
|
53
|
+
|
54
|
+
if (prot == PROT_NONE)
|
55
|
+
return protect;
|
56
|
+
|
57
|
+
if ((prot & PROT_EXEC) != 0)
|
58
|
+
{
|
59
|
+
protect = ((prot & PROT_WRITE) != 0) ?
|
60
|
+
PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
|
61
|
+
}
|
62
|
+
else
|
63
|
+
{
|
64
|
+
protect = ((prot & PROT_WRITE) != 0) ?
|
65
|
+
PAGE_READWRITE : PAGE_READONLY;
|
66
|
+
}
|
67
|
+
|
68
|
+
return protect;
|
69
|
+
}
|
70
|
+
|
71
|
+
static DWORD __map_mmap_prot_file(const int prot)
|
72
|
+
{
|
73
|
+
DWORD desiredAccess = 0;
|
74
|
+
|
75
|
+
if (prot == PROT_NONE)
|
76
|
+
return desiredAccess;
|
77
|
+
|
78
|
+
if ((prot & PROT_READ) != 0)
|
79
|
+
desiredAccess |= FILE_MAP_READ;
|
80
|
+
if ((prot & PROT_WRITE) != 0)
|
81
|
+
desiredAccess |= FILE_MAP_WRITE;
|
82
|
+
if ((prot & PROT_EXEC) != 0)
|
83
|
+
desiredAccess |= FILE_MAP_EXECUTE;
|
84
|
+
|
85
|
+
return desiredAccess;
|
86
|
+
}
|
87
|
+
|
88
|
+
inline void* mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
|
89
|
+
{
|
90
|
+
HANDLE fm, h;
|
91
|
+
|
92
|
+
void * map = MAP_FAILED;
|
93
|
+
|
94
|
+
#ifdef _MSC_VER
|
95
|
+
#pragma warning(push)
|
96
|
+
#pragma warning(disable: 4293)
|
97
|
+
#endif
|
98
|
+
|
99
|
+
const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ?
|
100
|
+
(DWORD)off : (DWORD)(off & 0xFFFFFFFFL);
|
101
|
+
const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ?
|
102
|
+
(DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL);
|
103
|
+
const DWORD protect = __map_mmap_prot_page(prot);
|
104
|
+
const DWORD desiredAccess = __map_mmap_prot_file(prot);
|
105
|
+
|
106
|
+
const off_t maxSize = off + (off_t)len;
|
107
|
+
|
108
|
+
const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ?
|
109
|
+
(DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL);
|
110
|
+
const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ?
|
111
|
+
(DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL);
|
112
|
+
|
113
|
+
#ifdef _MSC_VER
|
114
|
+
#pragma warning(pop)
|
115
|
+
#endif
|
116
|
+
|
117
|
+
errno = 0;
|
118
|
+
|
119
|
+
if (len == 0
|
120
|
+
/* Unsupported flag combinations */
|
121
|
+
|| (flags & MAP_FIXED) != 0
|
122
|
+
/* Usupported protection combinations */
|
123
|
+
|| prot == PROT_EXEC)
|
124
|
+
{
|
125
|
+
errno = EINVAL;
|
126
|
+
return MAP_FAILED;
|
127
|
+
}
|
128
|
+
|
129
|
+
h = ((flags & MAP_ANONYMOUS) == 0) ?
|
130
|
+
(HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE;
|
131
|
+
|
132
|
+
if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE)
|
133
|
+
{
|
134
|
+
errno = EBADF;
|
135
|
+
return MAP_FAILED;
|
136
|
+
}
|
137
|
+
|
138
|
+
fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL);
|
139
|
+
|
140
|
+
if (fm == NULL)
|
141
|
+
{
|
142
|
+
errno = __map_mman_error(GetLastError(), EPERM);
|
143
|
+
return MAP_FAILED;
|
144
|
+
}
|
145
|
+
|
146
|
+
map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len);
|
147
|
+
|
148
|
+
CloseHandle(fm);
|
149
|
+
|
150
|
+
if (map == NULL)
|
151
|
+
{
|
152
|
+
errno = __map_mman_error(GetLastError(), EPERM);
|
153
|
+
return MAP_FAILED;
|
154
|
+
}
|
155
|
+
|
156
|
+
return map;
|
157
|
+
}
|
158
|
+
|
159
|
+
inline int munmap(void *addr, size_t len)
|
160
|
+
{
|
161
|
+
if (UnmapViewOfFile(addr))
|
162
|
+
return 0;
|
163
|
+
|
164
|
+
errno = __map_mman_error(GetLastError(), EPERM);
|
165
|
+
|
166
|
+
return -1;
|
167
|
+
}
|
168
|
+
|
169
|
+
inline int mprotect(void *addr, size_t len, int prot)
|
170
|
+
{
|
171
|
+
DWORD newProtect = __map_mmap_prot_page(prot);
|
172
|
+
DWORD oldProtect = 0;
|
173
|
+
|
174
|
+
if (VirtualProtect(addr, len, newProtect, &oldProtect))
|
175
|
+
return 0;
|
176
|
+
|
177
|
+
errno = __map_mman_error(GetLastError(), EPERM);
|
178
|
+
|
179
|
+
return -1;
|
180
|
+
}
|
181
|
+
|
182
|
+
inline int msync(void *addr, size_t len, int flags)
|
183
|
+
{
|
184
|
+
if (FlushViewOfFile(addr, len))
|
185
|
+
return 0;
|
186
|
+
|
187
|
+
errno = __map_mman_error(GetLastError(), EPERM);
|
188
|
+
|
189
|
+
return -1;
|
190
|
+
}
|
191
|
+
|
192
|
+
inline int mlock(const void *addr, size_t len)
|
193
|
+
{
|
194
|
+
if (VirtualLock((LPVOID)addr, len))
|
195
|
+
return 0;
|
196
|
+
|
197
|
+
errno = __map_mman_error(GetLastError(), EPERM);
|
198
|
+
|
199
|
+
return -1;
|
200
|
+
}
|
201
|
+
|
202
|
+
inline int munlock(const void *addr, size_t len)
|
203
|
+
{
|
204
|
+
if (VirtualUnlock((LPVOID)addr, len))
|
205
|
+
return 0;
|
206
|
+
|
207
|
+
errno = __map_mman_error(GetLastError(), EPERM);
|
208
|
+
|
209
|
+
return -1;
|
210
|
+
}
|
211
|
+
|
212
|
+
#if !defined(__MINGW32__)
|
213
|
+
inline int ftruncate(const int fd, const int64_t size) {
|
214
|
+
if (fd < 0) {
|
215
|
+
errno = EBADF;
|
216
|
+
return -1;
|
217
|
+
}
|
218
|
+
|
219
|
+
HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
220
|
+
LARGE_INTEGER li_start, li_size;
|
221
|
+
li_start.QuadPart = static_cast<int64_t>(0);
|
222
|
+
li_size.QuadPart = size;
|
223
|
+
if (SetFilePointerEx(h, li_start, NULL, FILE_CURRENT) == ~0 ||
|
224
|
+
SetFilePointerEx(h, li_size, NULL, FILE_BEGIN) == ~0 ||
|
225
|
+
!SetEndOfFile(h)) {
|
226
|
+
unsigned long error = GetLastError();
|
227
|
+
fprintf(stderr, "I/O error while truncating: %lu\n", error);
|
228
|
+
switch (error) {
|
229
|
+
case ERROR_INVALID_HANDLE:
|
230
|
+
errno = EBADF;
|
231
|
+
break;
|
232
|
+
default:
|
233
|
+
errno = EIO;
|
234
|
+
break;
|
235
|
+
}
|
236
|
+
return -1;
|
237
|
+
}
|
238
|
+
return 0;
|
239
|
+
}
|
240
|
+
#endif
|
241
|
+
|
242
|
+
#endif
|
data/lib/annoy.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'annoy/version'
|
4
|
-
require 'annoy/
|
4
|
+
require 'annoy/annoyext'
|
5
5
|
|
6
6
|
module Annoy
|
7
7
|
# AnnoyIndex is a class that provides functions for k-nearest neighbors search.
|
@@ -68,9 +68,10 @@ module Annoy
|
|
68
68
|
# Build a forest of index trees. After building, no more items can be added.
|
69
69
|
#
|
70
70
|
# @param n_trees [Integer] The number of trees. More trees gives higher search precision.
|
71
|
+
# @param n_jobs [Integer] The number of threads used to build the trees. If -1 is given, uses all available CPU cores.
|
71
72
|
# @return [Boolean]
|
72
|
-
def build(n_trees)
|
73
|
-
@index.build(n_trees)
|
73
|
+
def build(n_trees, n_jobs: -1)
|
74
|
+
@index.build(n_trees, n_jobs)
|
74
75
|
end
|
75
76
|
|
76
77
|
# Save the search index to disk. After saving, no more items can be added.
|
data/lib/annoy/version.rb
CHANGED
data/sig/annoy.rbs
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module Annoy
|
2
|
+
VERSION: String
|
3
|
+
|
4
|
+
class AnnoyIndex
|
5
|
+
attr_reader n_features: Integer
|
6
|
+
attr_reader metric: String
|
7
|
+
|
8
|
+
def initialize: (n_features: Integer n_features, ?metric: String metric) -> void
|
9
|
+
def add_item: (Integer i, Array[Float | Integer] v) -> bool
|
10
|
+
def build: (Integer n_trees, ?n_jobs: Integer n_jobs) -> bool
|
11
|
+
def save: (String filename, ?prefault: bool prefault) -> bool
|
12
|
+
def load: (String filename, ?prefault: bool prefault) -> bool
|
13
|
+
def unload: () -> bool
|
14
|
+
def get_nns_by_item: (Integer i, Integer n, ?search_k: Integer search_k, ?include_distances: (true | false) include_distances) -> ([Array[Integer], Array[Float | Integer]] | Array[Integer])
|
15
|
+
def get_nns_by_vector: (Array[Float | Integer] v, Integer n, ?search_k: Integer search_k, ?include_distances: (true | false) include_distances) -> ([Array[Integer], Array[Float | Integer]] | Array[Integer])
|
16
|
+
def get_item: (Integer i) -> Array[Float | Integer]
|
17
|
+
def get_distance: (Integer i, Integer j) -> (Float | Integer)
|
18
|
+
def n_items: () -> Integer
|
19
|
+
def n_trees: () -> Integer
|
20
|
+
def on_disk_build: (String filename) -> bool
|
21
|
+
def verbose: (bool flag) -> nil
|
22
|
+
def seed: (Integer s) -> nil
|
23
|
+
end
|
24
|
+
|
25
|
+
class AnnoyIndexAngular
|
26
|
+
def initialize: (Integer n_features) -> void
|
27
|
+
def add_item: (Integer i, Array[Float] v) -> bool
|
28
|
+
def build: (Integer n_trees, Integer n_jobs) -> bool
|
29
|
+
def save: (String filename, bool prefault) -> bool
|
30
|
+
def load: (String filename, bool prefault) -> bool
|
31
|
+
def unload: () -> bool
|
32
|
+
def get_nns_by_item: (Integer i, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
33
|
+
def get_nns_by_vector: (Array[Float] v, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
34
|
+
def get_item: (Integer i) -> Array[Float]
|
35
|
+
def get_distance: (Integer i, Integer j) -> Float
|
36
|
+
def n_items: () -> Integer
|
37
|
+
def n_trees: () -> Integer
|
38
|
+
def on_disk_build: (String filename) -> bool
|
39
|
+
def verbose: (bool flag) -> nil
|
40
|
+
def seed: (Integer s) -> nil
|
41
|
+
end
|
42
|
+
|
43
|
+
class AnnoyIndexDotProduct
|
44
|
+
def initialize: (Integer n_features) -> void
|
45
|
+
def add_item: (Integer i, Array[Float] v) -> bool
|
46
|
+
def build: (Integer n_trees, Integer n_jobs) -> bool
|
47
|
+
def save: (String filename, bool prefault) -> bool
|
48
|
+
def load: (String filename, bool prefault) -> bool
|
49
|
+
def unload: () -> bool
|
50
|
+
def get_nns_by_item: (Integer i, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
51
|
+
def get_nns_by_vector: (Array[Float] v, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
52
|
+
def get_item: (Integer i) -> Array[Float]
|
53
|
+
def get_distance: (Integer i, Integer j) -> Float
|
54
|
+
def n_items: () -> Integer
|
55
|
+
def n_trees: () -> Integer
|
56
|
+
def on_disk_build: (String filename) -> bool
|
57
|
+
def verbose: (bool flag) -> nil
|
58
|
+
def seed: (Integer s) -> nil
|
59
|
+
end
|
60
|
+
|
61
|
+
class AnnoyIndexHamming
|
62
|
+
def initialize: (Integer n_features) -> void
|
63
|
+
def add_item: (Integer i, Array[Integer] v) -> bool
|
64
|
+
def build: (Integer n_trees, Integer n_jobs) -> bool
|
65
|
+
def save: (String filename, bool prefault) -> bool
|
66
|
+
def load: (String filename, bool prefault) -> bool
|
67
|
+
def unload: () -> bool
|
68
|
+
def get_nns_by_item: (Integer i, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Integer]] | Array[Integer])
|
69
|
+
def get_nns_by_vector: (Array[Integer] v, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Integer]] | Array[Integer])
|
70
|
+
def get_item: (Integer i) -> Array[Integer]
|
71
|
+
def get_distance: (Integer i, Integer j) -> Integer
|
72
|
+
def n_items: () -> Integer
|
73
|
+
def n_trees: () -> Integer
|
74
|
+
def on_disk_build: (String filename) -> bool
|
75
|
+
def verbose: (bool flag) -> nil
|
76
|
+
def seed: (Integer s) -> nil
|
77
|
+
end
|
78
|
+
|
79
|
+
class AnnoyIndexEuclidean
|
80
|
+
def initialize: (Integer n_features) -> void
|
81
|
+
def add_item: (Integer i, Array[Float] v) -> bool
|
82
|
+
def build: (Integer n_trees, Integer n_jobs) -> bool
|
83
|
+
def save: (String filename, bool prefault) -> bool
|
84
|
+
def load: (String filename, bool prefault) -> bool
|
85
|
+
def unload: () -> bool
|
86
|
+
def get_nns_by_item: (Integer i, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
87
|
+
def get_nns_by_vector: (Array[Float] v, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
88
|
+
def get_item: (Integer i) -> Array[Float]
|
89
|
+
def get_distance: (Integer i, Integer j) -> Float
|
90
|
+
def n_items: () -> Integer
|
91
|
+
def n_trees: () -> Integer
|
92
|
+
def on_disk_build: (String filename) -> bool
|
93
|
+
def verbose: (bool flag) -> nil
|
94
|
+
def seed: (Integer s) -> nil
|
95
|
+
end
|
96
|
+
|
97
|
+
class AnnoyIndexManhattan
|
98
|
+
def initialize: (Integer n_features) -> void
|
99
|
+
def add_item: (Integer i, Array[Float] v) -> bool
|
100
|
+
def build: (Integer n_trees, Integer n_jobs) -> bool
|
101
|
+
def save: (String filename, bool prefault) -> bool
|
102
|
+
def load: (String filename, bool prefault) -> bool
|
103
|
+
def unload: () -> bool
|
104
|
+
def get_nns_by_item: (Integer i, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
105
|
+
def get_nns_by_vector: (Array[Float] v, Integer n, Integer search_k, (true | false) include_distances) -> ([Array[Integer], Array[Float]] | Array[Integer])
|
106
|
+
def get_item: (Integer i) -> Array[Float]
|
107
|
+
def get_distance: (Integer i, Integer j) -> Float
|
108
|
+
def n_items: () -> Integer
|
109
|
+
def n_trees: () -> Integer
|
110
|
+
def on_disk_build: (String filename) -> bool
|
111
|
+
def verbose: (bool flag) -> nil
|
112
|
+
def seed: (Integer s) -> nil
|
113
|
+
end
|
114
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: annoy-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yoshoku
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Annoy.rb is a Ruby binding for the Annoy (Approximate Nearest Neighbors
|
14
14
|
Oh Yeah).
|
@@ -19,31 +19,35 @@ extensions:
|
|
19
19
|
- ext/annoy/extconf.rb
|
20
20
|
extra_rdoc_files: []
|
21
21
|
files:
|
22
|
+
- ".github/workflows/build.yml"
|
22
23
|
- ".gitignore"
|
23
24
|
- ".rspec"
|
24
|
-
- ".travis.yml"
|
25
25
|
- CHANGELOG.md
|
26
26
|
- CODE_OF_CONDUCT.md
|
27
27
|
- Gemfile
|
28
28
|
- LICENSE.txt
|
29
29
|
- README.md
|
30
30
|
- Rakefile
|
31
|
+
- Steepfile
|
31
32
|
- annoy-rb.gemspec
|
32
|
-
- ext/annoy/
|
33
|
-
- ext/annoy/
|
33
|
+
- ext/annoy/annoyext.cpp
|
34
|
+
- ext/annoy/annoyext.hpp
|
34
35
|
- ext/annoy/extconf.rb
|
35
36
|
- ext/annoy/src/annoylib.h
|
36
37
|
- ext/annoy/src/kissrandom.h
|
38
|
+
- ext/annoy/src/mman.h
|
37
39
|
- lib/annoy.rb
|
38
40
|
- lib/annoy/version.rb
|
41
|
+
- sig/annoy.rbs
|
39
42
|
homepage: https://github.com/yoshoku/annoy.rb
|
40
43
|
licenses:
|
41
44
|
- Apache-2.0
|
42
45
|
metadata:
|
43
46
|
homepage_uri: https://github.com/yoshoku/annoy.rb
|
44
47
|
source_code_uri: https://github.com/yoshoku/annoy.rb
|
45
|
-
changelog_uri: https://github.com/yoshoku/annoy.rb/blob/
|
46
|
-
|
48
|
+
changelog_uri: https://github.com/yoshoku/annoy.rb/blob/main/CHANGELOG.md
|
49
|
+
documentation_uri: https://yoshoku.github.io/annoy.rb/doc/
|
50
|
+
post_install_message:
|
47
51
|
rdoc_options: []
|
48
52
|
require_paths:
|
49
53
|
- lib
|
@@ -58,8 +62,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
62
|
- !ruby/object:Gem::Version
|
59
63
|
version: '0'
|
60
64
|
requirements: []
|
61
|
-
rubygems_version: 3.1.
|
62
|
-
signing_key:
|
65
|
+
rubygems_version: 3.1.6
|
66
|
+
signing_key:
|
63
67
|
specification_version: 4
|
64
68
|
summary: Ruby binding for the Annoy (Approximate Nearest Neighbors Oh Yeah).
|
65
69
|
test_files: []
|