salgo 1.0.1
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.
- data/lib/salgo/btree.rb +529 -0
- data/tests/btree_test.rb +238 -0
- metadata +86 -0
data/lib/salgo/btree.rb
ADDED
@@ -0,0 +1,529 @@
|
|
1
|
+
#
|
2
|
+
# The MIT License
|
3
|
+
#
|
4
|
+
# Copyright (c) 2010 Samuel R. Baskinger
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
module Salgo
|
26
|
+
|
27
|
+
class Btree
|
28
|
+
|
29
|
+
class Node
|
30
|
+
attr_reader :keys, :nodes
|
31
|
+
attr_writer :keys, :nodes
|
32
|
+
|
33
|
+
def initialize()
|
34
|
+
@keys = []
|
35
|
+
@nodes = []
|
36
|
+
end
|
37
|
+
|
38
|
+
# Assume we have enough space to insert in the node.
|
39
|
+
# If two sub-trees are specified, it is assumed that they are replacing the subtree
|
40
|
+
# that the new_key was in. That sub-tree is replaced with the left_node
|
41
|
+
# and the right_node is inserted after the left_node's index.
|
42
|
+
def insert(new_key, left_node=nil, right_node=nil)
|
43
|
+
|
44
|
+
# Caution code.
|
45
|
+
throw Exception.new(
|
46
|
+
"Both right and left nodes must be nil or defined. One is not: #{right_node} #{left_node}") if (
|
47
|
+
right_node.nil? ^ left_node.nil? )
|
48
|
+
|
49
|
+
insertion_point = 0
|
50
|
+
|
51
|
+
catch(:foundI) do
|
52
|
+
@keys.each_with_index do |node_key, index|
|
53
|
+
if ( new_key < node_key )
|
54
|
+
insertion_point = index
|
55
|
+
throw :foundI
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
insertion_point = @keys.size
|
60
|
+
end
|
61
|
+
|
62
|
+
@keys.insert(insertion_point, new_key)
|
63
|
+
@nodes[insertion_point] = left_node if left_node
|
64
|
+
@nodes.insert(insertion_point+1, right_node) if right_node
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# This node will split itself and return a list of 3 items, [key, left_node, right_node ].
|
69
|
+
def split()
|
70
|
+
|
71
|
+
node_partition = @nodes.size / 2
|
72
|
+
key_partition = @keys.size / 2
|
73
|
+
|
74
|
+
left_node = Node.new()
|
75
|
+
right_node = Node.new()
|
76
|
+
|
77
|
+
left_node.nodes = @nodes[0...node_partition]
|
78
|
+
right_node.nodes = @nodes[node_partition..-1]
|
79
|
+
|
80
|
+
left_node.keys = @keys[0...key_partition]
|
81
|
+
right_node.keys = @keys[key_partition+1..-1]
|
82
|
+
|
83
|
+
[ @keys[key_partition], left_node, right_node ]
|
84
|
+
end
|
85
|
+
|
86
|
+
def leaf?()
|
87
|
+
@nodes.size == 0
|
88
|
+
end
|
89
|
+
|
90
|
+
# Similar to find_node_containing_key, but
|
91
|
+
# considers keys as they node is iterated through.
|
92
|
+
# An array of Salgo::Btree::Node or Salgo::Btree::Key object will be returned with the second element
|
93
|
+
# set to the index of the node.
|
94
|
+
#
|
95
|
+
# If it is a key, then the key holds a match. If a node, then the node
|
96
|
+
# subtree that should be expanded and searched.
|
97
|
+
def find_node_or_key_containing_key(key)
|
98
|
+
|
99
|
+
@keys.each_with_index do |node_key, i|
|
100
|
+
if ( key == node_key )
|
101
|
+
return [ node_key, i ]
|
102
|
+
elsif ( key < node_key )
|
103
|
+
return [ @nodes[i], i ]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
return [ @nodes[-1], @nodes.size - 1 ]
|
108
|
+
end
|
109
|
+
|
110
|
+
def find_node_containing_key(key)
|
111
|
+
@keys.each_with_index do |node_key, i|
|
112
|
+
if ( key < node_key )
|
113
|
+
return @nodes[i]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# If no throw, we assign the last node because key is bigger (or equal to) all our keys.
|
118
|
+
return @nodes[-1]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return an array of the min-key and min-node from this node.
|
122
|
+
# There may or may not be a min-node.
|
123
|
+
def take_min()
|
124
|
+
[ @keys.shift, @nodes.shift ]
|
125
|
+
end
|
126
|
+
|
127
|
+
def take(index)
|
128
|
+
[ @keys.delete_at(index), @nodes.delete_at(index) ]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return an array of the max-key and the max-node from this node.
|
132
|
+
# There may or may not be a max-node.
|
133
|
+
def take_max()
|
134
|
+
[ @keys.pop, @nodes.pop ]
|
135
|
+
end
|
136
|
+
|
137
|
+
def put_max(key, node)
|
138
|
+
@keys.push(key)
|
139
|
+
@nodes.push(node) if node
|
140
|
+
end
|
141
|
+
|
142
|
+
def put(index, key, node)
|
143
|
+
@keys.insert(index, key)
|
144
|
+
@nodes.insert(index, node) if node
|
145
|
+
end
|
146
|
+
|
147
|
+
def put_min(key, node)
|
148
|
+
@keys.unshift(key)
|
149
|
+
@nodes.unshift(node) if node
|
150
|
+
end
|
151
|
+
|
152
|
+
def last_node?(node)
|
153
|
+
@nodes[-1].equal?(node)
|
154
|
+
end
|
155
|
+
|
156
|
+
def first_node?(node)
|
157
|
+
@nodes[0].equal?(node)
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
# The key value should support >, < and ==.
|
163
|
+
class Key
|
164
|
+
attr_reader :val, :key
|
165
|
+
attr_writer :val, :key
|
166
|
+
|
167
|
+
def initialize(key, val=true)
|
168
|
+
@key = key
|
169
|
+
@val = val
|
170
|
+
end
|
171
|
+
|
172
|
+
def < (k)
|
173
|
+
@key < k.key
|
174
|
+
end
|
175
|
+
|
176
|
+
def > (k)
|
177
|
+
@key > k.key
|
178
|
+
end
|
179
|
+
|
180
|
+
def == (k)
|
181
|
+
@key == k.key
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
attr_reader :size
|
187
|
+
|
188
|
+
def initialize(minnodes=2)
|
189
|
+
@minnodes = ( minnodes < 2 )? 2 : minnodes
|
190
|
+
@maxnodes = 2 * minnodes
|
191
|
+
@root = Node.new()
|
192
|
+
@size = 0
|
193
|
+
end
|
194
|
+
|
195
|
+
def root?(node)
|
196
|
+
@root.equal? node
|
197
|
+
end
|
198
|
+
|
199
|
+
def full?(node)
|
200
|
+
node.keys.size == @maxnodes-1
|
201
|
+
end
|
202
|
+
|
203
|
+
# Does the given node have enough keys (and perhaps nodes) to merge with another node?
|
204
|
+
def mergable?(node)
|
205
|
+
node.keys.size < @minnodes
|
206
|
+
end
|
207
|
+
|
208
|
+
def has_minimum_keys?(node)
|
209
|
+
node.keys.size < @minnodes
|
210
|
+
end
|
211
|
+
|
212
|
+
# Is there an extra key to take, should we need it.
|
213
|
+
def has_extra_keys?(node)
|
214
|
+
node.keys.size >= @minnodes
|
215
|
+
end
|
216
|
+
|
217
|
+
def split_root
|
218
|
+
node = Node.new()
|
219
|
+
|
220
|
+
node.insert(*@root.split())
|
221
|
+
|
222
|
+
@root = node
|
223
|
+
end
|
224
|
+
|
225
|
+
# Insert a new value at the given key. Duplicate values are allowed in this data structure
|
226
|
+
# and insert does not prevent them. The []= method will replace values.
|
227
|
+
def insert(key, val)
|
228
|
+
key = Key.new(key, val)
|
229
|
+
|
230
|
+
# Always make sure our special friend "root" is OK and has room for an insert.
|
231
|
+
split_root if full?(@root)
|
232
|
+
|
233
|
+
parent_node = nil
|
234
|
+
node = @root
|
235
|
+
|
236
|
+
not_inserted = true
|
237
|
+
|
238
|
+
while(not_inserted)
|
239
|
+
if ( full? node )
|
240
|
+
|
241
|
+
median_key, lnode, rnode = node.split()
|
242
|
+
|
243
|
+
# NOTE: Because we always split full root nodes, we will never enter here with parent_node=nil
|
244
|
+
# Oh good, we can do a normal split and insert the result in the parent.
|
245
|
+
parent_node.insert(median_key, lnode, rnode)
|
246
|
+
|
247
|
+
if ( key < median_key )
|
248
|
+
node = lnode
|
249
|
+
else
|
250
|
+
node = rnode
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Can we insert?
|
255
|
+
if ( node.leaf? )
|
256
|
+
node.insert(key)
|
257
|
+
@size += 1
|
258
|
+
not_inserted = false
|
259
|
+
else
|
260
|
+
# which node to examine?
|
261
|
+
parent_node = node
|
262
|
+
node = node.find_node_containing_key(key)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def find_key(key)
|
268
|
+
|
269
|
+
node = @root
|
270
|
+
|
271
|
+
candidate, candidate_idx = node.find_node_or_key_containing_key(key)
|
272
|
+
|
273
|
+
while( ! candidate.nil?)
|
274
|
+
if ( candidate.is_a?(Key) )
|
275
|
+
|
276
|
+
return candidate
|
277
|
+
end
|
278
|
+
|
279
|
+
node = candidate
|
280
|
+
candidate, candidate_idx = node.find_node_or_key_containing_key(key)
|
281
|
+
end
|
282
|
+
|
283
|
+
return nil
|
284
|
+
end
|
285
|
+
|
286
|
+
def find(key)
|
287
|
+
k = find_key(Key.new(key, nil))
|
288
|
+
|
289
|
+
(k.nil?) ? nil : k.val
|
290
|
+
end
|
291
|
+
|
292
|
+
# Given the parent node and the child_index of a child node,
|
293
|
+
# this method will merge with the sibling to the left of the
|
294
|
+
# child. The resulting "unknown" tree will be placed on
|
295
|
+
# the left and the known-filled node will be placed in the child
|
296
|
+
# node's current spot.
|
297
|
+
# The new target node is returned.
|
298
|
+
def merge_with_left(parent, child_index)
|
299
|
+
|
300
|
+
child = parent.nodes[child_index]
|
301
|
+
sibling = parent.nodes[child_index-1]
|
302
|
+
node = Node.new()
|
303
|
+
|
304
|
+
node.nodes = sibling.nodes + child.nodes
|
305
|
+
|
306
|
+
node.keys = sibling.keys + [ parent.keys[child_index-1] ] + child.keys
|
307
|
+
parent.take(child_index-1)
|
308
|
+
parent.nodes[child_index-1] = node
|
309
|
+
node
|
310
|
+
end
|
311
|
+
|
312
|
+
# Same as merge_with_right, but the roles are reversed as
|
313
|
+
# are the locations of the resulting subtrees.
|
314
|
+
# The new target node is returned.
|
315
|
+
def merge_with_right(parent, child_index)
|
316
|
+
child = parent.nodes[child_index]
|
317
|
+
sibling = parent.nodes[child_index+1]
|
318
|
+
node = Node.new()
|
319
|
+
|
320
|
+
node.nodes = child.nodes + sibling.nodes
|
321
|
+
|
322
|
+
node.keys = child.keys + [ parent.keys[child_index] ] + sibling.keys
|
323
|
+
parent.take(child_index)
|
324
|
+
parent.nodes[child_index] = node
|
325
|
+
node
|
326
|
+
end
|
327
|
+
|
328
|
+
# Delete from a subtree. We assume the node can withstand delete when called.
|
329
|
+
# They key object is returned.
|
330
|
+
def delete_max_key(node=@root)
|
331
|
+
|
332
|
+
return nil if @size == 0
|
333
|
+
|
334
|
+
if root?(node) and @root.keys.size == 0 and @root.nodes.size == 1
|
335
|
+
@root = node = @root.nodes[0]
|
336
|
+
end
|
337
|
+
|
338
|
+
while(true) do
|
339
|
+
if ( node.leaf? )
|
340
|
+
@size -= 1
|
341
|
+
return node.take_max()[0]
|
342
|
+
else
|
343
|
+
|
344
|
+
# Fix up the node before deleting from it.
|
345
|
+
if has_minimum_keys?(node.nodes[-1])
|
346
|
+
if has_minimum_keys?(node.nodes[-2])
|
347
|
+
|
348
|
+
node = merge_with_left(node, node.nodes.size-1)
|
349
|
+
|
350
|
+
else
|
351
|
+
|
352
|
+
# Pull the max key and node from our "left" sibling.
|
353
|
+
# Make the left key be our parent and put the node
|
354
|
+
# in the minimum of the right tree node.
|
355
|
+
another_key, another_node = node.nodes[-2].take_max
|
356
|
+
|
357
|
+
node.nodes[-1].put_min(node.keys[-1], another_node)
|
358
|
+
node.keys[-1] = another_key
|
359
|
+
|
360
|
+
node = node.nodes[-1]
|
361
|
+
|
362
|
+
end
|
363
|
+
else
|
364
|
+
node = node.nodes[-1]
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Delete from a subtree. We assume the node can withstand delete when called.
|
371
|
+
# The key object is returned.
|
372
|
+
def delete_min_key(node=@root)
|
373
|
+
|
374
|
+
return nil if @size == 0
|
375
|
+
|
376
|
+
if root?(node) and @root.keys.size == 0 and @root.nodes.size == 1
|
377
|
+
@root = node = @root.nodes[0]
|
378
|
+
end
|
379
|
+
|
380
|
+
while(true) do
|
381
|
+
if ( node.leaf? )
|
382
|
+
@size -= 1
|
383
|
+
return node.take_min()[0]
|
384
|
+
else
|
385
|
+
# Fix up the node before deleting from it.
|
386
|
+
if has_minimum_keys?(node.nodes[0])
|
387
|
+
if has_minimum_keys?(node.nodes[1])
|
388
|
+
node = merge_with_right(node, 0)
|
389
|
+
|
390
|
+
else
|
391
|
+
|
392
|
+
# Pull the min key and node from our "right" sibling.
|
393
|
+
# Make the right key be our parent and put the node
|
394
|
+
# in the minimum of the right tree node.
|
395
|
+
another_key, another_node = node.nodes[1].take_min
|
396
|
+
|
397
|
+
node.nodes[0].put_max(node.keys[0], another_node)
|
398
|
+
node.keys[0] = another_key
|
399
|
+
|
400
|
+
node = node.nodes[0]
|
401
|
+
end
|
402
|
+
else
|
403
|
+
node = node.nodes[0]
|
404
|
+
end
|
405
|
+
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def delete_key(key, node=@root)
|
411
|
+
|
412
|
+
candidate, candidate_idx = node.find_node_or_key_containing_key(key)
|
413
|
+
|
414
|
+
return nil if candidate.nil?
|
415
|
+
|
416
|
+
# Delete from this node.
|
417
|
+
if ( candidate.is_a?(Key) )
|
418
|
+
|
419
|
+
# If it's a simple delete...
|
420
|
+
if node.leaf?
|
421
|
+
node.keys.delete_at(candidate_idx)
|
422
|
+
@size -= 1
|
423
|
+
return candidate
|
424
|
+
elsif has_extra_keys?(node.nodes[candidate_idx])
|
425
|
+
node.keys[candidate_idx] = delete_max_key(node.nodes[candidate_idx])
|
426
|
+
return candidate
|
427
|
+
elsif has_extra_keys?(node.nodes[candidate_idx+1])
|
428
|
+
node.keys[candidate_idx] = delete_min_key(node.nodes[candidate_idx+1])
|
429
|
+
return candidate
|
430
|
+
else
|
431
|
+
node = merge_with_right(node, candidate_idx)
|
432
|
+
|
433
|
+
# The merge_with_right call left the root with no keys and 1 child node.
|
434
|
+
# Replace the root and delete from the root.
|
435
|
+
@root = @root.nodes[0] if ( @root.nodes.size == 1 )
|
436
|
+
|
437
|
+
return delete_key(key, node)
|
438
|
+
end
|
439
|
+
|
440
|
+
elsif candidate.is_a?(Node)
|
441
|
+
|
442
|
+
# Ensure that the node can sustain a delete BEFORE entering it...
|
443
|
+
unless has_extra_keys?(candidate)
|
444
|
+
|
445
|
+
if ( node.first_node?(candidate) )
|
446
|
+
if ( has_extra_keys?(node.nodes[1]))
|
447
|
+
another_key, another_node = node.nodes[1].take_min
|
448
|
+
candidate.put_max(node.keys[0], another_node)
|
449
|
+
node.keys[0] = another_key
|
450
|
+
else
|
451
|
+
merge_with_right(node, candidate_idx)
|
452
|
+
end
|
453
|
+
#elsif ( node.last_node?(candidate) )
|
454
|
+
else
|
455
|
+
if ( has_extra_keys?(node.nodes[candidate_idx-1]) )
|
456
|
+
another_key, another_node = node.nodes[candidate_idx-1].take_max
|
457
|
+
candidate.put_min(node.keys[candidate_idx-1], another_node)
|
458
|
+
node.keys[candidate_idx-1] = another_key
|
459
|
+
else
|
460
|
+
merge_with_left(node, candidate_idx)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# If one of the above merges removed all keys from the root, then there is only 1 node.
|
466
|
+
# Promote that node as the root.
|
467
|
+
candidate = @root = @root.nodes[0] if ( @root.nodes.size == 1 )
|
468
|
+
|
469
|
+
delete_key(key, candidate)
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def delete(key)
|
474
|
+
|
475
|
+
key = delete_key(Key.new(key))
|
476
|
+
|
477
|
+
(key.nil?)? nil : key.val
|
478
|
+
end
|
479
|
+
|
480
|
+
alias [] find
|
481
|
+
|
482
|
+
# Set the key in this tree to the given value.
|
483
|
+
# If there is already a value at the given key, it is replaced and the old value is returned.
|
484
|
+
# Nil is returned otherwise.
|
485
|
+
def []=(key, val)
|
486
|
+
|
487
|
+
k = find_key(Key.new(key))
|
488
|
+
|
489
|
+
if ( k.nil? )
|
490
|
+
insert(key, val)
|
491
|
+
nil
|
492
|
+
else
|
493
|
+
v = k.val
|
494
|
+
k.val = val
|
495
|
+
v
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def each(node=@root, &call)
|
500
|
+
|
501
|
+
proc_child = ( node.leaf?() )? lambda { |x| } : lambda { |child_node| each(child_node, &call) }
|
502
|
+
|
503
|
+
index = 0
|
504
|
+
|
505
|
+
|
506
|
+
node.keys.each do |key|
|
507
|
+
proc_child.call(node.nodes[index])
|
508
|
+
|
509
|
+
call.call(key.key, key.val)
|
510
|
+
|
511
|
+
index+=1
|
512
|
+
|
513
|
+
end
|
514
|
+
|
515
|
+
proc_child.call(node.nodes[index])
|
516
|
+
end
|
517
|
+
|
518
|
+
def has_key?(key)
|
519
|
+
! find_key(Key.new(key)).nil?
|
520
|
+
end
|
521
|
+
|
522
|
+
alias member? has_key?
|
523
|
+
alias include? has_key?
|
524
|
+
alias key? has_key?
|
525
|
+
|
526
|
+
alias store []=
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
data/tests/btree_test.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'salgo/btree'
|
4
|
+
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
class BTreeTest < Test::Unit::TestCase
|
8
|
+
include Salgo
|
9
|
+
|
10
|
+
def test_nodeinsert()
|
11
|
+
assert(true, "OK")
|
12
|
+
|
13
|
+
n = Btree::Node.new()
|
14
|
+
|
15
|
+
begin
|
16
|
+
n.insert(1, 2)
|
17
|
+
assert(false, "Failed to report undefined right or left subtree.")
|
18
|
+
rescue Exception => e
|
19
|
+
assert(true, "Caught expected exception.")
|
20
|
+
end
|
21
|
+
|
22
|
+
n.insert(1, 2, 3)
|
23
|
+
assert(n.nodes[0] == 2)
|
24
|
+
assert(n.nodes[1] == 3)
|
25
|
+
assert(! n.nodes[0].nil?)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_nodesplit()
|
29
|
+
n = Btree::Node.new()
|
30
|
+
n.nodes = [1,1,3,3]
|
31
|
+
n.keys = [1,2,3]
|
32
|
+
key, lnode, rnode = n.split()
|
33
|
+
assert(key == 2, "Key was #{key}")
|
34
|
+
assert(lnode.keys==[1])
|
35
|
+
assert(rnode.keys==[3])
|
36
|
+
assert(lnode.nodes==[1,1])
|
37
|
+
assert(rnode.nodes==[3,3])
|
38
|
+
|
39
|
+
n2 = Btree::Node.new()
|
40
|
+
|
41
|
+
n2.insert(*n.split())
|
42
|
+
|
43
|
+
assert(n2.keys[0]==2)
|
44
|
+
assert(n2.nodes[0].keys==[1])
|
45
|
+
assert(n2.nodes[1].keys==[3])
|
46
|
+
assert(n2.nodes[0].nodes==[1,1])
|
47
|
+
assert(n2.nodes[1].nodes==[3,3])
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_find_node_containing_key()
|
51
|
+
n = Btree::Node.new()
|
52
|
+
|
53
|
+
n.keys = [Btree::Key.new(1,1), Btree::Key.new(2,2), Btree::Key.new(3,3)]
|
54
|
+
n.nodes = [ Btree::Node.new(), Btree::Node.new(), Btree::Node.new(), Btree::Node.new() ]
|
55
|
+
|
56
|
+
assert(n.find_node_containing_key(Btree::Key.new(0)).equal? n.nodes[0])
|
57
|
+
assert(n.find_node_containing_key(Btree::Key.new(1)).equal? n.nodes[1])
|
58
|
+
assert(n.find_node_containing_key(Btree::Key.new(2)).equal? n.nodes[2])
|
59
|
+
assert(n.find_node_containing_key(Btree::Key.new(3)).equal? n.nodes[3])
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_split()
|
63
|
+
bt = Btree.new()
|
64
|
+
bt.insert(1, 1)
|
65
|
+
bt.insert(2, 2)
|
66
|
+
bt.insert(3, 3)
|
67
|
+
rt = bt.instance_variable_get("@root")
|
68
|
+
|
69
|
+
key, lnode, rnode = rt.split()
|
70
|
+
|
71
|
+
assert(key.val == 2)
|
72
|
+
assert(lnode.keys[0].val == 1)
|
73
|
+
assert(rnode.keys[0].val == 3)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_insert()
|
77
|
+
bt = Btree.new()
|
78
|
+
bt.insert(1, 'a')
|
79
|
+
bt.insert(2, 'b')
|
80
|
+
bt.insert(3, 'c')
|
81
|
+
bt.insert(4, 'd')
|
82
|
+
bt.insert(5, 'e')
|
83
|
+
bt.insert(6, 'f')
|
84
|
+
|
85
|
+
assert(bt.size == 6, "Size was not 5 but #{bt.size}")
|
86
|
+
|
87
|
+
rt = bt.instance_variable_get("@root")
|
88
|
+
|
89
|
+
assert(rt.nodes[0].keys[0].key == 1)
|
90
|
+
assert(rt.keys[0].key == 2)
|
91
|
+
assert(rt.nodes[1].keys[0].key == 3)
|
92
|
+
assert(rt.keys[1].key == 4)
|
93
|
+
assert(rt.nodes[2].keys[0].key == 5)
|
94
|
+
assert(rt.nodes[2].keys[1].key == 6)
|
95
|
+
|
96
|
+
assert(bt.find(1) == 'a')
|
97
|
+
assert(bt.find(2) == 'b')
|
98
|
+
assert(bt.find(3) == 'c')
|
99
|
+
assert(bt.find(4) == 'd')
|
100
|
+
assert(bt.find(5) == 'e')
|
101
|
+
assert(bt.find(6) == 'f')
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_delete_max_key()
|
105
|
+
bt = Btree.new()
|
106
|
+
bt.insert(1, 'a')
|
107
|
+
bt.insert(2, 'b')
|
108
|
+
bt.insert(3, 'c')
|
109
|
+
bt.insert(4, 'd')
|
110
|
+
bt.insert(5, 'e')
|
111
|
+
bt.insert(6, 'f')
|
112
|
+
|
113
|
+
assert(bt.size == 6, "Size was not 5 but #{bt.size}")
|
114
|
+
|
115
|
+
assert(bt.delete_max_key().val == 'f')
|
116
|
+
assert(bt.delete_max_key().val == 'e')
|
117
|
+
assert(bt.delete_max_key().val == 'd')
|
118
|
+
assert(bt.delete_max_key().val == 'c')
|
119
|
+
assert(bt.delete_max_key().val == 'b')
|
120
|
+
assert(bt.delete_max_key().val == 'a')
|
121
|
+
assert(bt.size == 0)
|
122
|
+
assert(bt.delete_max_key() == nil)
|
123
|
+
assert(bt.size == 0)
|
124
|
+
|
125
|
+
rt = bt.instance_variable_get("@root")
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_delete_min_key()
|
130
|
+
bt = Btree.new()
|
131
|
+
bt.insert(1, 'a')
|
132
|
+
bt.insert(2, 'b')
|
133
|
+
bt.insert(3, 'c')
|
134
|
+
bt.insert(4, 'd')
|
135
|
+
bt.insert(5, 'e')
|
136
|
+
bt.insert(6, 'f')
|
137
|
+
|
138
|
+
assert(bt.size == 6, "Size was not 5 but #{bt.size}")
|
139
|
+
assert(bt.delete_min_key().val == 'a')
|
140
|
+
assert(bt.delete_min_key().val == 'b')
|
141
|
+
assert(bt.delete_min_key().val == 'c')
|
142
|
+
assert(bt.delete_min_key().val == 'd')
|
143
|
+
assert(bt.delete_min_key().val == 'e')
|
144
|
+
assert(bt.delete_min_key().val == 'f')
|
145
|
+
assert(bt.size == 0)
|
146
|
+
assert(bt.delete_min_key() == nil)
|
147
|
+
assert(bt.size == 0)
|
148
|
+
|
149
|
+
rt = bt.instance_variable_get("@root")
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_delete_from_root()
|
154
|
+
bt = Btree.new()
|
155
|
+
bt.insert(1, 'a')
|
156
|
+
bt.insert(2, 'b')
|
157
|
+
bt.insert(3, 'c')
|
158
|
+
bt.insert(4, 'd')
|
159
|
+
bt.insert(5, 'e')
|
160
|
+
bt.insert(6, 'f')
|
161
|
+
|
162
|
+
assert(bt.delete(2)=='b')
|
163
|
+
assert(bt.delete(3)=='c')
|
164
|
+
assert(bt.delete(1)=='a')
|
165
|
+
assert(bt.delete(4)=='d')
|
166
|
+
|
167
|
+
rt = bt.instance_variable_get("@root")
|
168
|
+
assert(bt.size == 2)
|
169
|
+
assert(rt.nodes.size == 0)
|
170
|
+
assert(rt.keys.size == 2)
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_list_add_remove()
|
174
|
+
bt = Btree.new()
|
175
|
+
|
176
|
+
added_items = [975, 801, 916, 648, 259, 103, 212, 230, 336, 371]
|
177
|
+
|
178
|
+
added_items.each do |r|
|
179
|
+
bt.insert(r, r)
|
180
|
+
end
|
181
|
+
|
182
|
+
assert(bt.size == added_items.size)
|
183
|
+
|
184
|
+
added_items.each do |k|
|
185
|
+
prev_size = bt.size
|
186
|
+
assert(bt.delete(k) == k)
|
187
|
+
assert(bt.size == prev_size -1)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_random_add_remove()
|
192
|
+
bt = Btree.new()
|
193
|
+
|
194
|
+
added_items = []
|
195
|
+
|
196
|
+
sz = 10
|
197
|
+
sz.times do
|
198
|
+
r = (rand * 1000).to_i
|
199
|
+
added_items << r
|
200
|
+
bt.insert(r, r)
|
201
|
+
end
|
202
|
+
|
203
|
+
assert(bt.size == sz)
|
204
|
+
|
205
|
+
added_items.each do |k|
|
206
|
+
prev_size = bt.size
|
207
|
+
assert(bt.delete(k) == k)
|
208
|
+
assert(bt.size == prev_size -1)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_adds()
|
213
|
+
bt = Btree.new()
|
214
|
+
|
215
|
+
bt[1] = 1
|
216
|
+
bt[1] = 'a'
|
217
|
+
|
218
|
+
assert(bt[1] == 'a')
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_each()
|
222
|
+
|
223
|
+
i = 0
|
224
|
+
|
225
|
+
bt = Btree.new()
|
226
|
+
bt.insert(1, 'a')
|
227
|
+
bt.insert(2, 'b')
|
228
|
+
bt.insert(3, 'c')
|
229
|
+
bt.insert(4, 'd')
|
230
|
+
bt.insert(5, 'e')
|
231
|
+
bt.insert(6, 'f')
|
232
|
+
|
233
|
+
bt.each { |k,v| i+=1 }
|
234
|
+
|
235
|
+
assert(i == bt.size)
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: salgo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 21
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sam Baskinger
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-02 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: log4r
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 29
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 5
|
34
|
+
version: 1.0.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: A collection of algorithms and datastructure for ruby written in ruby.
|
38
|
+
email: basking2@rubyforge.org.com
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- lib/salgo/btree.rb
|
47
|
+
- tests/btree_test.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://salgo.rubyforge.org
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 31
|
63
|
+
segments:
|
64
|
+
- 1
|
65
|
+
- 6
|
66
|
+
- 8
|
67
|
+
version: 1.6.8
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project: http://salgo.rubyforge.org/
|
80
|
+
rubygems_version: 1.3.7
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: simply algorithms
|
84
|
+
test_files:
|
85
|
+
- tests/btree_test.rb
|
86
|
+
- tests/btree_test.rb
|