ffi-radix_tree 0.1.5 → 0.5.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 +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
|