ffi-radix_tree 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2547ed89d9cbedb9bd340a86785aa16f0becd3c
4
- data.tar.gz: 737b4483c593146c8c9fef84330f9ed5eac149ee
3
+ metadata.gz: b44935a7f81323aa80f656cab493c5637d3029a0
4
+ data.tar.gz: f2854b861c6c3507110328ce5e291f533938f97c
5
5
  SHA512:
6
- metadata.gz: 79474879d50035f089b2a04b60db2dee547e9aabf4aeffa43c33b430156bac7004e55da050e35bb71cda97eede7bcda077c9ca4a53dbc66809d040a06c04d277
7
- data.tar.gz: becb72bae362661101c75aed79d53c42f9d698c19116593b0180971026ba8e59eebcb712d8c65ea7e4e5ffbd71a9de6ef6dcf46b2a22141cc2e64288fdafddb9
6
+ metadata.gz: 45f6f4eb51181c92fedeb6fbfa577b67a73a921196edfc4ce937c251dd9a660d9ff6888c1f17f2afe67cd5d2858997ce947946588448e4d2e87810138bae4764
7
+ data.tar.gz: ecb205e7db9b41cf866fce8709d894eebf1b21472fd5546b2fb529dea605bd9a06494c66d83d5d1401e00b601861d72e1108dd34243320d1059595e5b1d4fcb8
data/README.md CHANGED
@@ -22,7 +22,44 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- TODO: Write usage instructions here
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
+
60
+ # cleanup
61
+ rtree.destroy!
62
+ ```
26
63
 
27
64
  ## Development
28
65
 
data/Rakefile CHANGED
@@ -7,6 +7,67 @@ 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
73
  Rake::TestTask.new(:spec) do |t|
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "msgpack"
35
35
  spec.add_dependency "ffi"
36
36
 
37
+ spec.add_development_dependency "benchmark-ips"
37
38
  spec.add_development_dependency "bundler", "~> 1.15"
38
39
  spec.add_development_dependency "rake", "~> 10.0"
39
40
  spec.add_development_dependency "mocha"
@@ -77,9 +77,14 @@ module FFI
77
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
@@ -115,6 +120,21 @@ module FFI
115
120
  push_response
116
121
  end
117
122
 
123
+ def push_or_update(key, value)
124
+ response = nil
125
+ @first_character_present[key[0]] = true
126
+ storage_data = ::MessagePack.pack(value)
127
+ bytesize = storage_data.bytesize
128
+
129
+ ::FFI::MemoryPointer.new(:char, bytesize, true) do |memory_buffer|
130
+ memory_buffer.put_bytes(0, storage_data)
131
+ response = ::FFI::RadixTree.update(@ptr, key, memory_buffer, bytesize)
132
+ response ||= ::FFI::RadixTree.insert(@ptr, key, memory_buffer, bytesize)
133
+ end
134
+
135
+ response
136
+ end
137
+
118
138
  def get(key)
119
139
  return nil unless @first_character_present[key[0]]
120
140
  byte_pointer = get_response = nil
@@ -122,7 +142,11 @@ module FFI
122
142
  ::FFI::MemoryPointer.new(:int) do |byte_length|
123
143
  byte_pointer = ::FFI::RadixTree.fetch(@ptr, key, byte_length)
124
144
  bytesize = byte_length.read_int
125
- get_response = ::MessagePack.unpack(byte_pointer.get_bytes(0, bytesize)) if bytesize && bytesize > 0
145
+
146
+ if bytesize && bytesize > 0
147
+ bytes = byte_pointer.get_bytes(0, bytesize)
148
+ get_response = ::MessagePack.unpack(bytes)
149
+ end
126
150
  end
127
151
 
128
152
  get_response
@@ -139,6 +163,29 @@ module FFI
139
163
  ::FFI::RadixTree.match_free(p_out) if p_out
140
164
  end
141
165
 
166
+ def longest_prefix_and_value(string)
167
+ return [nil, nil] unless @first_character_present[string[0]]
168
+ byte_pointer = prefix_response = get_response = nil
169
+
170
+ ::FFI::MemoryPointer.new(:int) do |byte_length|
171
+ ::FFI::MemoryPointer.new(:int) do |prefix_length|
172
+ byte_pointer = ::FFI::RadixTree.longest_prefix_and_value(@ptr, string, byte_length, prefix_length)
173
+ bytesize = byte_length.read_int
174
+
175
+ if bytesize && bytesize > 0
176
+ prefix_size = prefix_length.read_int
177
+ get_response = byte_pointer.get_bytes(0, bytesize)
178
+ prefix_response = get_response[0..(prefix_size - 1)]
179
+ get_response = ::MessagePack.unpack(get_response[prefix_size..-1])
180
+ end
181
+ end
182
+ end
183
+
184
+ [prefix_response, get_response]
185
+ ensure
186
+ ::FFI::RadixTree.match_free(byte_pointer) if byte_pointer
187
+ end
188
+
142
189
  def longest_prefix_value(string)
143
190
  return nil unless @first_character_present[string[0]]
144
191
  byte_pointer = get_response = nil
@@ -153,6 +200,61 @@ module FFI
153
200
  ensure
154
201
  ::FFI::RadixTree.match_free(byte_pointer) if byte_pointer
155
202
  end
203
+
204
+ def greedy_match(string)
205
+ return [] unless @first_character_present[string[0]]
206
+ array_pointer = nil
207
+ match_sizes_pointer = nil
208
+ array_size = 0
209
+ get_response = []
210
+
211
+ ::FFI::MemoryPointer.new(:pointer) do |match_array|
212
+ ::FFI::MemoryPointer.new(:pointer) do |match_sizes_array|
213
+ array_size = ::FFI::RadixTree.greedy_match(@ptr, string, match_array, match_sizes_array)
214
+ if array_size > 0
215
+ array_sizes_pointer = match_sizes_array.read_pointer
216
+ match_sizes = array_sizes_pointer.get_array_of_int(0, array_size)
217
+ array_pointer = match_array.read_pointer
218
+ char_arrays = array_pointer.get_array_of_pointer(0, array_size)
219
+ char_arrays.each_with_index do |ptr, index|
220
+ get_response << ::MessagePack.unpack(ptr.get_bytes(0, match_sizes[index]))
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ get_response
227
+ ensure
228
+ ::FFI::RadixTree.multi_match_free(array_pointer, array_size) if array_pointer
229
+ ::FFI::RadixTree.match_free(match_sizes_pointer) if match_sizes_pointer
230
+ end
231
+
232
+ def greedy_substring_match(string)
233
+ array_pointer = nil
234
+ match_sizes_pointer = nil
235
+ array_size = 0
236
+ get_response = []
237
+
238
+ ::FFI::MemoryPointer.new(:pointer) do |match_array|
239
+ ::FFI::MemoryPointer.new(:pointer) do |match_sizes_array|
240
+ array_size = ::FFI::RadixTree.greedy_substring_match(@ptr, string, match_array, match_sizes_array)
241
+ if array_size > 0
242
+ array_sizes_pointer = match_sizes_array.read_pointer
243
+ match_sizes = array_sizes_pointer.get_array_of_int(0, array_size)
244
+ array_pointer = match_array.read_pointer
245
+ char_arrays = array_pointer.get_array_of_pointer(0, array_size)
246
+ char_arrays.each_with_index do |ptr, index|
247
+ get_response << ::MessagePack.unpack(ptr.get_bytes(0, match_sizes[index]))
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ get_response
254
+ ensure
255
+ ::FFI::RadixTree.multi_match_free(array_pointer, array_size) if array_pointer
256
+ ::FFI::RadixTree.match_free(match_sizes_pointer) if match_sizes_pointer
257
+ end
156
258
  end
157
259
  end
158
260
  end
@@ -1,5 +1,5 @@
1
1
  module FFI
2
2
  module RadixTree
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  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
- const char* fetch(radix_tree<std::string, std::vector<char>>* map_pointer, const char* key, int* read_size) {
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.2.0
4
+ version: 0.3.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: 2018-11-23 00:00:00.000000000 Z
11
+ date: 2018-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -38,6 +38,20 @@ 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