ffi-radix_tree 0.1.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +35 -1
- data/Rakefile +67 -5
- data/ffi-radix_tree.gemspec +4 -1
- data/lib/ffi/radix_tree.rb +138 -32
- data/lib/ffi/radix_tree/version.rb +1 -1
- data/vendor/radixtree/ffi_radix_tree.cpp +94 -3
- data/vendor/radixtree/radix_tree.hpp +61 -0
- metadata +49 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 273613cad863a6795457a41a30cac700b0f1a1a0be3e53359ab6dff4c405cc00
|
4
|
+
data.tar.gz: 3533ae343c7fbf6d4a5cee15ad33f69bd7c63f0df3a1e239d684a9605215268d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b1cfcf6c1121b84d4b0f51f76dff086e76e854ca2de8eb9d4c3d70446566c4524572194f5490d7c8625e60e61701ce02383d168d47462732f4aee423685789d
|
7
|
+
data.tar.gz: f1c75e06b8af34d66d463c3b2702854ce711a08ae33eb4e6d69d58d72954e01d2e7777b80bcdd1f95d6bdf40fedafb4f11f7fbea1d39df1af0ee25354f7fc2a2
|
data/README.md
CHANGED
@@ -22,7 +22,41 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
25
|
+
```ruby
|
26
|
+
# create a new tree
|
27
|
+
rtree = ::FFI::RadixTree::Tree.new
|
28
|
+
|
29
|
+
# add key/value pairs to the tree
|
30
|
+
rtree.push("key1", "value1")
|
31
|
+
rtree.push("key2", ["value2", "value3"])
|
32
|
+
rtree.push("key3", { :attr1 => "value4", :attr2 => "value5" })
|
33
|
+
|
34
|
+
# work with the collection
|
35
|
+
if rtree.has_key?("key1")
|
36
|
+
val = rtree.get("key2")
|
37
|
+
rtree.set("key3", "value6")
|
38
|
+
end
|
39
|
+
|
40
|
+
# search the tree using the keys
|
41
|
+
|
42
|
+
# keys based off the root word: "manage"
|
43
|
+
rtree.push("manage", "base verb")
|
44
|
+
rtree.push("managed", "past tense")
|
45
|
+
rtree.push("manager", "noun")
|
46
|
+
rtree.push("managers", "plural noun")
|
47
|
+
rtree.push("managing", "present tense")
|
48
|
+
|
49
|
+
# Find the key that matches the _most_ of the beggining of the search term
|
50
|
+
rtree.longest_prefix("managerial") # returns "manager"
|
51
|
+
rtree.longest_prefix_and_value("managerial") # returns ["manager", "noun"]
|
52
|
+
rtree.longest_prefix_value("managerial") # returns "noun"
|
53
|
+
|
54
|
+
# Find all values whose keys match the _most_ of the beginning of the search term
|
55
|
+
rtree.greedy_match("managerial") # returns ["noun", "plural noun"]
|
56
|
+
|
57
|
+
# Find all values whose keys are included _anywhere_ in the search term
|
58
|
+
rtree.greedy_substring_match("I managed to jump") # returns ["base verb", "past tense"]
|
59
|
+
```
|
26
60
|
|
27
61
|
## Development
|
28
62
|
|
data/Rakefile
CHANGED
@@ -7,13 +7,75 @@ namespace :radixtree do
|
|
7
7
|
task :compile do
|
8
8
|
Rake::Task[:compile_radixtree].invoke
|
9
9
|
end
|
10
|
+
|
11
|
+
desc "run benchmarks for radixtree lib"
|
12
|
+
task :benchmark do
|
13
|
+
require "benchmark/ips"
|
14
|
+
require "./lib/ffi/radix_tree"
|
15
|
+
|
16
|
+
radix_tree = ::FFI::RadixTree::Tree.new
|
17
|
+
|
18
|
+
(1..1000).each do |number|
|
19
|
+
radix_tree.push(number.to_s, "DERP#{number}" * (number % 10))
|
20
|
+
radix_tree.push(number.to_s * 100, "DERP#{number}" * (number % 10))
|
21
|
+
radix_tree.push(number.to_s * 1000, "DERP#{number}" * (number % 10))
|
22
|
+
end
|
23
|
+
|
24
|
+
::Benchmark.ips do |x|
|
25
|
+
x.config(:warmup => 10)
|
26
|
+
|
27
|
+
x.report("get") do
|
28
|
+
radix_tree.get(rand(1000).to_s * [1, 100, 100].sample)
|
29
|
+
end
|
30
|
+
|
31
|
+
x.report("longest prefix") do
|
32
|
+
radix_tree.longest_prefix(rand(1000).to_s * [1, 100, 100].sample)
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report("get then value") do
|
36
|
+
val = rand(1000).to_s * [1, 100, 100].sample
|
37
|
+
|
38
|
+
radix_tree.longest_prefix(val)
|
39
|
+
radix_tree.longest_prefix_value(val)
|
40
|
+
end
|
41
|
+
|
42
|
+
x.report("prefix and value (combined)") do
|
43
|
+
radix_tree.longest_prefix_and_value(rand(1000).to_s * [1, 100, 100].sample)
|
44
|
+
end
|
45
|
+
|
46
|
+
x.report("longest prefix (miss)") do
|
47
|
+
radix_tree.longest_prefix("DERP DERPIE")
|
48
|
+
end
|
49
|
+
|
50
|
+
x.report("prefix and value (combined/miss)") do
|
51
|
+
radix_tree.longest_prefix_and_value("DERP DERPIE")
|
52
|
+
end
|
53
|
+
|
54
|
+
x.report("greedy match") do
|
55
|
+
radix_tree.greedy_match(rand(1000).to_s * [1, 100, 100].sample)
|
56
|
+
end
|
57
|
+
|
58
|
+
x.report("greedy match (miss)") do
|
59
|
+
radix_tree.greedy_match("DERP DERPIE")
|
60
|
+
end
|
61
|
+
|
62
|
+
x.report("greedy substring match") do
|
63
|
+
radix_tree.greedy_substring_match(rand(1000).to_s * [1, 100, 100].sample)
|
64
|
+
end
|
65
|
+
|
66
|
+
x.report("greedy substring match (miss)") do
|
67
|
+
radix_tree.greedy_substring_match("DERP DERPIE")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
10
71
|
end
|
11
72
|
|
12
|
-
Rake::TestTask.new(:
|
13
|
-
t.libs << "
|
73
|
+
Rake::TestTask.new(:spec) do |t|
|
74
|
+
t.libs << "spec"
|
14
75
|
t.libs << "lib"
|
15
|
-
t.
|
76
|
+
t.pattern = "spec/**/*_spec.rb"
|
77
|
+
t.verbose = true
|
16
78
|
end
|
17
|
-
Rake::Task[:
|
79
|
+
Rake::Task[:spec].prerequisites << "radixtree:compile"
|
18
80
|
|
19
|
-
task :default => :
|
81
|
+
task :default => :spec
|
data/ffi-radix_tree.gemspec
CHANGED
@@ -34,7 +34,10 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency "msgpack"
|
35
35
|
spec.add_dependency "ffi"
|
36
36
|
|
37
|
-
spec.add_development_dependency "
|
37
|
+
spec.add_development_dependency "benchmark-ips"
|
38
|
+
spec.add_development_dependency "bundler"
|
38
39
|
spec.add_development_dependency "rake", "~> 10.0"
|
40
|
+
spec.add_development_dependency "mocha"
|
41
|
+
spec.add_development_dependency "pry"
|
39
42
|
spec.add_development_dependency "minitest", "~> 5.0"
|
40
43
|
end
|
data/lib/ffi/radix_tree.rb
CHANGED
@@ -74,75 +74,181 @@ module FFI
|
|
74
74
|
|
75
75
|
attach_function :create, [], :pointer
|
76
76
|
attach_function :destroy, [:pointer], :void
|
77
|
-
attach_function :erase, [:string], :void
|
77
|
+
attach_function :erase, [:pointer, :string], :void
|
78
78
|
attach_function :fetch, [:pointer, :string, :pointer], :pointer
|
79
79
|
attach_function :insert, [:pointer, :string, :pointer, :size_t], :void
|
80
|
+
attach_function :update, [:pointer, :string, :pointer, :size_t], :bool
|
80
81
|
attach_function :longest_prefix, [:pointer, :string], :string
|
82
|
+
attach_function :longest_prefix_and_value, [:pointer, :string, :pointer, :pointer], :pointer
|
81
83
|
attach_function :longest_prefix_value, [:pointer, :string, :pointer], :pointer
|
84
|
+
attach_function :greedy_match, [:pointer, :string, :pointer, :pointer], :int
|
85
|
+
attach_function :greedy_substring_match, [:pointer, :string, :pointer, :pointer], :int
|
82
86
|
attach_function :match_free, [:pointer], :void
|
87
|
+
attach_function :multi_match_free, [:pointer, :int], :void
|
83
88
|
attach_function :has_key, [:pointer, :string], :bool
|
84
89
|
|
85
90
|
class Tree
|
86
|
-
DESTROY_METHOD = ::FFI::RadixTree.method(:destroy)
|
87
|
-
FREE_METHOD = ::FFI::RadixTree.method(:match_free)
|
88
|
-
|
89
|
-
def self.destroy!(tree)
|
90
|
-
tree.destroy! unless tree.nil?
|
91
|
-
end
|
92
|
-
|
93
91
|
def initialize
|
94
|
-
@ptr = ::FFI::AutoPointer.new(::FFI::RadixTree.create,
|
92
|
+
@ptr = ::FFI::AutoPointer.new(::FFI::RadixTree.create, ::FFI::RadixTree.method(:destroy))
|
95
93
|
@first_character_present = {}
|
96
94
|
end
|
97
95
|
|
98
|
-
def destroy!
|
99
|
-
::FFI::RadixTree.destroy(@ptr) unless @ptr.nil?
|
100
|
-
@ptr = nil
|
101
|
-
end
|
102
|
-
|
103
96
|
def has_key?(key)
|
104
97
|
::FFI::RadixTree.has_key(@ptr, key)
|
105
98
|
end
|
106
99
|
|
107
100
|
def push(key, value)
|
101
|
+
push_response = nil
|
102
|
+
@first_character_present[key[0]] = true
|
103
|
+
storage_data = ::MessagePack.pack(value)
|
104
|
+
bytesize = storage_data.bytesize
|
105
|
+
|
106
|
+
::FFI::MemoryPointer.new(:char, bytesize, true) do |memory_buffer|
|
107
|
+
memory_buffer.put_bytes(0, storage_data)
|
108
|
+
push_response = ::FFI::RadixTree.insert(@ptr, key, memory_buffer, bytesize)
|
109
|
+
end
|
110
|
+
|
111
|
+
push_response
|
112
|
+
end
|
113
|
+
|
114
|
+
def push_or_update(key, value)
|
115
|
+
response = nil
|
108
116
|
@first_character_present[key[0]] = true
|
109
117
|
storage_data = ::MessagePack.pack(value)
|
110
118
|
bytesize = storage_data.bytesize
|
111
|
-
|
112
|
-
|
113
|
-
|
119
|
+
|
120
|
+
::FFI::MemoryPointer.new(:char, bytesize, true) do |memory_buffer|
|
121
|
+
memory_buffer.put_bytes(0, storage_data)
|
122
|
+
response = ::FFI::RadixTree.update(@ptr, key, memory_buffer, bytesize)
|
123
|
+
response ||= ::FFI::RadixTree.insert(@ptr, key, memory_buffer, bytesize)
|
124
|
+
end
|
125
|
+
|
126
|
+
response
|
127
|
+
end
|
128
|
+
|
129
|
+
def erase(key)
|
130
|
+
::FFI::RadixTree.erase(@ptr, key)
|
114
131
|
end
|
115
132
|
|
116
133
|
def get(key)
|
117
134
|
return nil unless @first_character_present[key[0]]
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
135
|
+
byte_pointer = get_response = nil
|
136
|
+
|
137
|
+
::FFI::MemoryPointer.new(:int) do |byte_length|
|
138
|
+
byte_pointer = ::FFI::RadixTree.fetch(@ptr, key, byte_length)
|
139
|
+
bytesize = byte_length.read_int
|
140
|
+
|
141
|
+
if bytesize && bytesize > 0
|
142
|
+
bytes = byte_pointer.get_bytes(0, bytesize)
|
143
|
+
get_response = ::MessagePack.unpack(bytes)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
get_response
|
123
148
|
ensure
|
124
|
-
::FFI::RadixTree.match_free(byte_pointer)
|
149
|
+
::FFI::RadixTree.match_free(byte_pointer) if byte_pointer
|
125
150
|
end
|
126
151
|
|
127
152
|
def longest_prefix(string)
|
128
153
|
return nil unless @first_character_present[string[0]]
|
129
154
|
value, p_out = ::FFI::RadixTree.longest_prefix(@ptr, string)
|
130
|
-
|
131
|
-
value.force_encoding("UTF-8") unless value.nil?
|
155
|
+
value.force_encoding("UTF-8") if value
|
132
156
|
value
|
133
157
|
ensure
|
134
|
-
::FFI::RadixTree.match_free(p_out)
|
158
|
+
::FFI::RadixTree.match_free(p_out) if p_out
|
159
|
+
end
|
160
|
+
|
161
|
+
def longest_prefix_and_value(string)
|
162
|
+
return [nil, nil] unless @first_character_present[string[0]]
|
163
|
+
byte_pointer = prefix_response = get_response = nil
|
164
|
+
|
165
|
+
::FFI::MemoryPointer.new(:int) do |byte_length|
|
166
|
+
::FFI::MemoryPointer.new(:int) do |prefix_length|
|
167
|
+
byte_pointer = ::FFI::RadixTree.longest_prefix_and_value(@ptr, string, byte_length, prefix_length)
|
168
|
+
bytesize = byte_length.read_int
|
169
|
+
|
170
|
+
if bytesize && bytesize > 0
|
171
|
+
prefix_size = prefix_length.read_int
|
172
|
+
get_response = byte_pointer.get_bytes(0, bytesize)
|
173
|
+
prefix_response = get_response[0..(prefix_size - 1)]
|
174
|
+
get_response = ::MessagePack.unpack(get_response[prefix_size..-1])
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
[prefix_response, get_response]
|
180
|
+
ensure
|
181
|
+
::FFI::RadixTree.match_free(byte_pointer) if byte_pointer
|
135
182
|
end
|
136
183
|
|
137
184
|
def longest_prefix_value(string)
|
138
185
|
return nil unless @first_character_present[string[0]]
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
186
|
+
byte_pointer = get_response = nil
|
187
|
+
|
188
|
+
::FFI::MemoryPointer.new(:int) do |byte_length|
|
189
|
+
byte_pointer = ::FFI::RadixTree.longest_prefix_value(@ptr, string, byte_length)
|
190
|
+
bytesize = byte_length.read_int
|
191
|
+
get_response = ::MessagePack.unpack(byte_pointer.get_bytes(0, bytesize)) if bytesize && bytesize > 0
|
192
|
+
end
|
193
|
+
|
194
|
+
get_response
|
195
|
+
ensure
|
196
|
+
::FFI::RadixTree.match_free(byte_pointer) if byte_pointer
|
197
|
+
end
|
198
|
+
|
199
|
+
def greedy_match(string)
|
200
|
+
return [] unless @first_character_present[string[0]]
|
201
|
+
array_pointer = nil
|
202
|
+
match_sizes_pointer = nil
|
203
|
+
array_size = 0
|
204
|
+
get_response = []
|
205
|
+
|
206
|
+
::FFI::MemoryPointer.new(:pointer) do |match_array|
|
207
|
+
::FFI::MemoryPointer.new(:pointer) do |match_sizes_array|
|
208
|
+
array_size = ::FFI::RadixTree.greedy_match(@ptr, string, match_array, match_sizes_array)
|
209
|
+
if array_size > 0
|
210
|
+
array_sizes_pointer = match_sizes_array.read_pointer
|
211
|
+
match_sizes = array_sizes_pointer.get_array_of_int(0, array_size)
|
212
|
+
array_pointer = match_array.read_pointer
|
213
|
+
char_arrays = array_pointer.get_array_of_pointer(0, array_size)
|
214
|
+
char_arrays.each_with_index do |ptr, index|
|
215
|
+
get_response << ::MessagePack.unpack(ptr.get_bytes(0, match_sizes[index]))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
get_response
|
222
|
+
ensure
|
223
|
+
::FFI::RadixTree.multi_match_free(array_pointer, array_size) if array_pointer
|
224
|
+
::FFI::RadixTree.match_free(match_sizes_pointer) if match_sizes_pointer
|
225
|
+
end
|
226
|
+
|
227
|
+
def greedy_substring_match(string)
|
228
|
+
array_pointer = nil
|
229
|
+
match_sizes_pointer = nil
|
230
|
+
array_size = 0
|
231
|
+
get_response = []
|
232
|
+
|
233
|
+
::FFI::MemoryPointer.new(:pointer) do |match_array|
|
234
|
+
::FFI::MemoryPointer.new(:pointer) do |match_sizes_array|
|
235
|
+
array_size = ::FFI::RadixTree.greedy_substring_match(@ptr, string, match_array, match_sizes_array)
|
236
|
+
if array_size > 0
|
237
|
+
array_sizes_pointer = match_sizes_array.read_pointer
|
238
|
+
match_sizes = array_sizes_pointer.get_array_of_int(0, array_size)
|
239
|
+
array_pointer = match_array.read_pointer
|
240
|
+
char_arrays = array_pointer.get_array_of_pointer(0, array_size)
|
241
|
+
char_arrays.each_with_index do |ptr, index|
|
242
|
+
get_response << ::MessagePack.unpack(ptr.get_bytes(0, match_sizes[index]))
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
get_response
|
144
249
|
ensure
|
145
|
-
::FFI::RadixTree.
|
250
|
+
::FFI::RadixTree.multi_match_free(array_pointer, array_size) if array_pointer
|
251
|
+
::FFI::RadixTree.match_free(match_sizes_pointer) if match_sizes_pointer
|
146
252
|
end
|
147
253
|
end
|
148
254
|
end
|
@@ -38,13 +38,23 @@ void match_free(const char* match) {
|
|
38
38
|
}
|
39
39
|
}
|
40
40
|
|
41
|
+
void multi_match_free(const char** match, int length) {
|
42
|
+
if (match != NULL) {
|
43
|
+
for (int i=0; i<length; ++i) {
|
44
|
+
delete[] match[i];
|
45
|
+
match[i] = NULL;
|
46
|
+
}
|
47
|
+
delete[] match;
|
48
|
+
match = NULL;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
41
52
|
const char* longest_prefix(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key) {
|
42
53
|
std::string string_key(key);
|
43
54
|
auto iter = map_pointer->longest_match(string_key);
|
44
55
|
|
45
56
|
if (iter != map_pointer->end()) {
|
46
57
|
char *val = new char[iter->first.size() + 1]{0};
|
47
|
-
val[iter->first.size()] = '\0';
|
48
58
|
memcpy(val, iter->first.c_str(), iter->first.size());
|
49
59
|
|
50
60
|
return val;
|
@@ -53,6 +63,33 @@ const char* longest_prefix(radix_tree<std::string, std::vector<char>>* map_point
|
|
53
63
|
return NULL;
|
54
64
|
}
|
55
65
|
|
66
|
+
const char* longest_prefix_and_value(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, int* read_size, int* prefix_size) {
|
67
|
+
std::string string_key(key);
|
68
|
+
auto iter = map_pointer->longest_match(string_key);
|
69
|
+
|
70
|
+
if (iter != map_pointer->end()) {
|
71
|
+
long counter = 0;
|
72
|
+
int size_of_response = iter->second.size() + iter->first.size();
|
73
|
+
char *return_val = new char[size_of_response]{0};
|
74
|
+
|
75
|
+
strncpy(return_val, iter->first.c_str(), iter->first.size());
|
76
|
+
counter = iter->first.size();
|
77
|
+
|
78
|
+
for( auto& val3 : iter->second) {
|
79
|
+
return_val[counter] = val3;
|
80
|
+
counter++;
|
81
|
+
}
|
82
|
+
|
83
|
+
*prefix_size = iter->first.size();
|
84
|
+
*read_size = size_of_response;
|
85
|
+
return return_val;
|
86
|
+
}
|
87
|
+
|
88
|
+
*prefix_size = 0;
|
89
|
+
*read_size = 0;
|
90
|
+
return NULL;
|
91
|
+
}
|
92
|
+
|
56
93
|
const char* longest_prefix_value(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, int* read_size) {
|
57
94
|
std::string string_key(key);
|
58
95
|
auto iter = map_pointer->longest_match(string_key);
|
@@ -73,12 +110,12 @@ const char* longest_prefix_value(radix_tree<std::string, std::vector<char>>* map
|
|
73
110
|
return NULL;
|
74
111
|
}
|
75
112
|
|
76
|
-
|
113
|
+
unsigned char* fetch(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, int* read_size) {
|
77
114
|
auto iter = map_pointer->find(std::string(key));
|
78
115
|
long counter = 0;
|
79
116
|
|
80
117
|
if (iter != map_pointer->end()) {
|
81
|
-
char *return_val = new char[iter->second.size()]{0};
|
118
|
+
unsigned char *return_val = new unsigned char[iter->second.size()]{0};
|
82
119
|
for( auto& val : iter->second ) {
|
83
120
|
return_val[counter] = val;
|
84
121
|
counter++;
|
@@ -92,10 +129,64 @@ const char* fetch(radix_tree<std::string, std::vector<char>>* map_pointer, const
|
|
92
129
|
return NULL;
|
93
130
|
}
|
94
131
|
|
132
|
+
int greedy_match(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, const char*** matches, int** match_sizes) {
|
133
|
+
std::string string_key(key);
|
134
|
+
typedef radix_tree<std::string, std::vector<char>>::iterator iterator;
|
135
|
+
std::vector<iterator> vec;
|
136
|
+
map_pointer->greedy_match(string_key, vec);
|
137
|
+
long counter = 0;
|
138
|
+
|
139
|
+
if (vec.size() > 0) {
|
140
|
+
*matches = new const char* [vec.size()]{nullptr};
|
141
|
+
*match_sizes = new int [vec.size()]{0};
|
142
|
+
for (auto& iter : vec) {
|
143
|
+
auto ret_str = new char[iter->second.size()];
|
144
|
+
long char_index = 0;
|
145
|
+
for (auto& val : iter->second) {
|
146
|
+
ret_str[char_index] = val;
|
147
|
+
++char_index;
|
148
|
+
}
|
149
|
+
(*matches)[counter] = ret_str;
|
150
|
+
(*match_sizes)[counter] = iter->second.size();
|
151
|
+
++counter;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
return counter;
|
155
|
+
}
|
156
|
+
|
157
|
+
int greedy_substring_match(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, const char*** matches, int** match_sizes) {
|
158
|
+
std::string string_key(key);
|
159
|
+
typedef radix_tree<std::string, std::vector<char>>::iterator iterator;
|
160
|
+
std::vector<iterator> vec;
|
161
|
+
map_pointer->greedy_substring_match(string_key, vec);
|
162
|
+
long counter = 0;
|
163
|
+
|
164
|
+
if (vec.size() > 0) {
|
165
|
+
*matches = new const char* [vec.size()]{nullptr};
|
166
|
+
*match_sizes = new int [vec.size()]{0};
|
167
|
+
for (auto& iter : vec) {
|
168
|
+
auto ret_str = new char[iter->second.size()];
|
169
|
+
long char_index = 0;
|
170
|
+
for (auto& val : iter->second) {
|
171
|
+
ret_str[char_index] = val;
|
172
|
+
++char_index;
|
173
|
+
}
|
174
|
+
(*matches)[counter] = ret_str;
|
175
|
+
(*match_sizes)[counter] = iter->second.size();
|
176
|
+
++counter;
|
177
|
+
}
|
178
|
+
}
|
179
|
+
return counter;
|
180
|
+
}
|
181
|
+
|
95
182
|
void insert(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, char* value, size_t size) {
|
96
183
|
map_pointer->insert({std::string(key), std::vector<char>(value, value + size)});
|
97
184
|
}
|
98
185
|
|
186
|
+
bool update(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, char* value, size_t size) {
|
187
|
+
return map_pointer->update({std::string(key), std::vector<char>(value, value + size)});
|
188
|
+
}
|
189
|
+
|
99
190
|
void destroy(radix_tree<std::string, std::vector<char>>* map_pointer) {
|
100
191
|
delete map_pointer;
|
101
192
|
map_pointer = NULL;
|
@@ -67,10 +67,12 @@ public:
|
|
67
67
|
iterator end();
|
68
68
|
|
69
69
|
std::pair<iterator, bool> insert(const value_type &val);
|
70
|
+
bool update(const value_type &val);
|
70
71
|
bool erase(const K &key);
|
71
72
|
void erase(iterator it);
|
72
73
|
void prefix_match(const K &key, std::vector<iterator> &vec);
|
73
74
|
void greedy_match(const K &key, std::vector<iterator> &vec);
|
75
|
+
void greedy_substring_match(const K &key, std::vector<iterator> &vec);
|
74
76
|
iterator longest_match(const K &key);
|
75
77
|
|
76
78
|
T& operator[] (const K &lhs);
|
@@ -81,9 +83,11 @@ private:
|
|
81
83
|
|
82
84
|
radix_tree_node<K, T>* begin(radix_tree_node<K, T> *node);
|
83
85
|
radix_tree_node<K, T>* find_node(const K &key, radix_tree_node<K, T> *node, int depth);
|
86
|
+
std::vector<radix_tree_node<K, T>*> find_leaf_nodes_with_substr(const K &key, radix_tree_node<K, T> *node, const K &ancestor_key);
|
84
87
|
radix_tree_node<K, T>* append(radix_tree_node<K, T> *parent, const value_type &val);
|
85
88
|
radix_tree_node<K, T>* prepend(radix_tree_node<K, T> *node, const value_type &val);
|
86
89
|
void greedy_match(radix_tree_node<K, T> *node, std::vector<iterator> &vec);
|
90
|
+
void greedy_substring_match(radix_tree_node<K, T> *node, std::vector<iterator> &vec);
|
87
91
|
|
88
92
|
radix_tree(const radix_tree& other); // delete
|
89
93
|
radix_tree& operator =(const radix_tree other); // delete
|
@@ -232,6 +236,23 @@ void radix_tree<K, T>::greedy_match(radix_tree_node<K, T> *node, std::vector<ite
|
|
232
236
|
}
|
233
237
|
}
|
234
238
|
|
239
|
+
template <typename K, typename T>
|
240
|
+
void radix_tree<K, T>::greedy_substring_match(const K &key, std::vector<iterator> &vec)
|
241
|
+
{
|
242
|
+
std::vector<radix_tree_node<K, T>*> nodes;
|
243
|
+
|
244
|
+
vec.clear();
|
245
|
+
|
246
|
+
if (m_root == NULL)
|
247
|
+
return;
|
248
|
+
|
249
|
+
nodes = find_leaf_nodes_with_substr(key, m_root, "");
|
250
|
+
|
251
|
+
for (auto& node : nodes) {
|
252
|
+
vec.push_back(node);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
235
256
|
template <typename K, typename T>
|
236
257
|
void radix_tree<K, T>::erase(iterator it)
|
237
258
|
{
|
@@ -448,6 +469,22 @@ std::pair<typename radix_tree<K, T>::iterator, bool> radix_tree<K, T>::insert(co
|
|
448
469
|
}
|
449
470
|
}
|
450
471
|
|
472
|
+
template <typename K, typename T>
|
473
|
+
bool radix_tree<K, T>::update(const value_type &val)
|
474
|
+
{
|
475
|
+
if (m_root == NULL)
|
476
|
+
return false;
|
477
|
+
|
478
|
+
radix_tree_node<K, T> *node = find_node(val.first, m_root, 0);
|
479
|
+
|
480
|
+
// if the node is a internal node, return false
|
481
|
+
if (! node->m_is_leaf)
|
482
|
+
return false;
|
483
|
+
|
484
|
+
node->m_value = new value_type(val);
|
485
|
+
return true;
|
486
|
+
}
|
487
|
+
|
451
488
|
template <typename K, typename T>
|
452
489
|
typename radix_tree<K, T>::iterator radix_tree<K, T>::find(const K &key)
|
453
490
|
{
|
@@ -495,6 +532,30 @@ radix_tree_node<K, T>* radix_tree<K, T>::find_node(const K &key, radix_tree_node
|
|
495
532
|
return node;
|
496
533
|
}
|
497
534
|
|
535
|
+
template <typename K, typename T>
|
536
|
+
std::vector<radix_tree_node<K, T>*> radix_tree<K, T>::find_leaf_nodes_with_substr(const K &key, radix_tree_node<K, T> *node, const K &ancestor_key)
|
537
|
+
{
|
538
|
+
std::vector<radix_tree_node<K, T>*> nodes;
|
539
|
+
|
540
|
+
if (node->m_children.empty())
|
541
|
+
return nodes;
|
542
|
+
|
543
|
+
typename radix_tree_node<K, T>::it_child it;
|
544
|
+
for (it = node->m_children.begin(); it != node->m_children.end(); ++it) {
|
545
|
+
if (it->second->m_is_leaf) {
|
546
|
+
nodes.push_back(it->second);
|
547
|
+
} else {
|
548
|
+
auto child_key = ancestor_key + it->first;
|
549
|
+
if (key.find(child_key) != std::string::npos ) {
|
550
|
+
auto found_nodes = find_leaf_nodes_with_substr(key, it->second, child_key);
|
551
|
+
nodes.insert(nodes.end(), found_nodes.begin(), found_nodes.end());
|
552
|
+
}
|
553
|
+
}
|
554
|
+
}
|
555
|
+
|
556
|
+
return nodes;
|
557
|
+
}
|
558
|
+
|
498
559
|
/*
|
499
560
|
|
500
561
|
(root)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffi-radix_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Dewitt
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -38,20 +38,34 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: benchmark-ips
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
|
-
- - "
|
59
|
+
- - ">="
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
61
|
+
version: '0'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
|
-
- - "
|
66
|
+
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rake
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,34 @@ dependencies:
|
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mocha
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
112
|
name: minitest
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
167
|
- !ruby/object:Gem::Version
|
126
168
|
version: '0'
|
127
169
|
requirements: []
|
128
|
-
|
129
|
-
rubygems_version: 2.5.1
|
170
|
+
rubygems_version: 3.0.3
|
130
171
|
signing_key:
|
131
172
|
specification_version: 4
|
132
173
|
summary: radix tree implementation in c++ with FFI bindings
|