tree_map 1.0.4
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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/tree_map/bounded_map.rb +380 -0
- data/lib/tree_map/tree_map.rb +671 -0
- data/lib/tree_map/version.rb +3 -0
- data/lib/tree_map.rb +3 -0
- data/tree_map.gemspec +34 -0
- metadata +103 -0
@@ -0,0 +1,671 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
# TreeMap is a Ruby port of https://android.googlesource.com/platform/libcore.git/+/android-6.0.1_r32/luni/src/main/java/java/util/TreeMap.java
|
5
|
+
# This is an AVL tree based implementation of Java's java.util.TreeMap structure.
|
6
|
+
# It implements Java's java.util.NavigableMap interface.
|
7
|
+
# Warning: Not all of the reference implementation has been ported.
|
8
|
+
class TreeMap
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
module Relation
|
12
|
+
LOWER = 1
|
13
|
+
FLOOR = 2
|
14
|
+
EQUAL = 3
|
15
|
+
CREATE = 4
|
16
|
+
CEILING = 5
|
17
|
+
HIGHER = 6
|
18
|
+
|
19
|
+
def self.for_order(relation, ascending)
|
20
|
+
if ascending
|
21
|
+
relation
|
22
|
+
else
|
23
|
+
case relation
|
24
|
+
when LOWER
|
25
|
+
HIGHER
|
26
|
+
when FLOOR
|
27
|
+
CEILING
|
28
|
+
when EQUAL
|
29
|
+
EQUAL
|
30
|
+
when CEILING
|
31
|
+
FLOOR
|
32
|
+
when HIGHER
|
33
|
+
LOWER
|
34
|
+
else
|
35
|
+
raise "Unknown relation: #{relation.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Node
|
42
|
+
attr_accessor :parent, :left, :right, :key, :value, :height
|
43
|
+
|
44
|
+
def initialize(parent, key)
|
45
|
+
@parent = parent
|
46
|
+
@left = nil
|
47
|
+
@right = nil
|
48
|
+
@key = key
|
49
|
+
@value = nil
|
50
|
+
@height = 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def copy(parent)
|
54
|
+
result = Node.new(@parent, @key)
|
55
|
+
if @left
|
56
|
+
result.left = @left.copy(result)
|
57
|
+
end
|
58
|
+
if @right
|
59
|
+
result.right = @right.copy(result)
|
60
|
+
end
|
61
|
+
result.value = @value
|
62
|
+
result.height = @height
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_value(new_value)
|
67
|
+
old_value = @value
|
68
|
+
@value = new_value
|
69
|
+
old_value
|
70
|
+
end
|
71
|
+
|
72
|
+
def ==(other)
|
73
|
+
if other.is_a?(Node)
|
74
|
+
@key == other.key && @value == other.value
|
75
|
+
else
|
76
|
+
false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
alias eql? ==
|
81
|
+
|
82
|
+
def hash
|
83
|
+
(key.nil? ? 0 : key.hash) ^ (value.nil? ? 0 : value.hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
"#{@key}=#{@value}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the next node in an inorder traversal, or null if this is the last node in the tree.
|
91
|
+
def next_node
|
92
|
+
return @right.first if @right
|
93
|
+
|
94
|
+
node = self
|
95
|
+
parent = node.parent
|
96
|
+
while parent
|
97
|
+
if parent.left == node
|
98
|
+
return parent
|
99
|
+
end
|
100
|
+
node = parent
|
101
|
+
parent = node.parent
|
102
|
+
end
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the previous node in an inorder traversal, or null if this is the first node in the tree.
|
107
|
+
def prev_node
|
108
|
+
return @left.last if @left
|
109
|
+
|
110
|
+
node = self
|
111
|
+
parent = node.parent
|
112
|
+
while parent
|
113
|
+
if parent.right == node
|
114
|
+
return parent
|
115
|
+
end
|
116
|
+
node = parent
|
117
|
+
parent = node.parent
|
118
|
+
end
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns the first node in this subtree.
|
123
|
+
def first
|
124
|
+
node = self
|
125
|
+
child = node.left
|
126
|
+
while child
|
127
|
+
node = child
|
128
|
+
child = node.left
|
129
|
+
end
|
130
|
+
node
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the last node in this subtree.
|
134
|
+
def last
|
135
|
+
node = self
|
136
|
+
child = node.right
|
137
|
+
while child
|
138
|
+
node = child
|
139
|
+
child = node.right
|
140
|
+
end
|
141
|
+
node
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
NaturalOrder = ->(this, that) { this <=> that }
|
146
|
+
|
147
|
+
attr_accessor :comparator, :root, :size
|
148
|
+
|
149
|
+
# comparator is a function of the form: (this, that) -> int ; where int is -1 if this < that, 0 if this == that, and 1 if this > that
|
150
|
+
def initialize(comparator = NaturalOrder)
|
151
|
+
@comparator = comparator
|
152
|
+
@root = nil
|
153
|
+
@size = 0
|
154
|
+
@mod_count = 0
|
155
|
+
end
|
156
|
+
|
157
|
+
def empty?
|
158
|
+
@size == 0
|
159
|
+
end
|
160
|
+
|
161
|
+
def get(key)
|
162
|
+
entry = find_by_object(key)
|
163
|
+
entry.value if entry
|
164
|
+
end
|
165
|
+
|
166
|
+
alias [] get
|
167
|
+
|
168
|
+
def contains_key?(key)
|
169
|
+
find_by_object(key)
|
170
|
+
end
|
171
|
+
|
172
|
+
def put(key, value)
|
173
|
+
put_internal(key, value)
|
174
|
+
end
|
175
|
+
|
176
|
+
def clear
|
177
|
+
@root = nil
|
178
|
+
@size = 0
|
179
|
+
@mod_count += 1
|
180
|
+
end
|
181
|
+
|
182
|
+
def remove(key)
|
183
|
+
node = remove_internal_by_key(key)
|
184
|
+
node.value if node
|
185
|
+
end
|
186
|
+
|
187
|
+
def put_internal(key, value)
|
188
|
+
created = find(key, Relation::CREATE)
|
189
|
+
result = created.value
|
190
|
+
created.value = value
|
191
|
+
result
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns the node at or adjacent to the given key, creating it if requested.
|
195
|
+
def find(key, relation)
|
196
|
+
if @root.nil?
|
197
|
+
if relation == Relation::CREATE
|
198
|
+
@root = Node.new(nil, key)
|
199
|
+
@size = 1
|
200
|
+
@mod_count += 1
|
201
|
+
return @root
|
202
|
+
else
|
203
|
+
return nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
nearest = @root
|
208
|
+
while true
|
209
|
+
comparison = @comparator.call(key, nearest.key)
|
210
|
+
|
211
|
+
# we found the requested key
|
212
|
+
if comparison == 0
|
213
|
+
case relation
|
214
|
+
when Relation::LOWER
|
215
|
+
return nearest.prev_node
|
216
|
+
when Relation::FLOOR, Relation::EQUAL, Relation::CREATE, Relation::CEILING
|
217
|
+
return nearest
|
218
|
+
when Relation::HIGHER
|
219
|
+
return nearest.next_node
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
child = (comparison < 0) ? nearest.left : nearest.right
|
224
|
+
if child
|
225
|
+
nearest = child
|
226
|
+
next # continue
|
227
|
+
end
|
228
|
+
|
229
|
+
# We found a nearest node. Every key not in the tree has up to two nearest nodes, one lower and one higher.
|
230
|
+
if comparison < 0 # nearest.key is higher
|
231
|
+
case relation
|
232
|
+
when Relation::LOWER, Relation::FLOOR
|
233
|
+
return nearest.prev_node
|
234
|
+
when Relation::CEILING, Relation::HIGHER
|
235
|
+
return nearest
|
236
|
+
when Relation::EQUAL
|
237
|
+
return nil
|
238
|
+
when Relation::CREATE
|
239
|
+
created = Node.new(nearest, key)
|
240
|
+
nearest.left = created
|
241
|
+
@size += 1
|
242
|
+
@mod_count += 1
|
243
|
+
rebalance(nearest, true)
|
244
|
+
return created
|
245
|
+
end
|
246
|
+
else # comparison > 0 ; nearest.key is lower
|
247
|
+
case relation
|
248
|
+
when Relation::LOWER, Relation::FLOOR
|
249
|
+
return nearest
|
250
|
+
when Relation::CEILING, Relation::HIGHER
|
251
|
+
return nearest.next_node
|
252
|
+
when Relation::EQUAL
|
253
|
+
return nil
|
254
|
+
when Relation::CREATE
|
255
|
+
created = Node.new(nearest, key)
|
256
|
+
nearest.right = created
|
257
|
+
@size += 1
|
258
|
+
@mod_count += 1
|
259
|
+
rebalance(nearest, true)
|
260
|
+
return created
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# returns a Node
|
267
|
+
def find_by_object(key)
|
268
|
+
find(key, Relation::EQUAL)
|
269
|
+
end
|
270
|
+
|
271
|
+
# entry is a key-value pair in an array: [key, value]
|
272
|
+
# Returns this map's entry that has the same key and value as <entry>, or null if this map has no such entry.
|
273
|
+
#
|
274
|
+
# This method uses the comparator for key equality rather than <equals>. If this map's comparator isn't consistent with equals,
|
275
|
+
# then {@code remove()} and {@code contains()} will violate the collections API.
|
276
|
+
#
|
277
|
+
# returns a Node
|
278
|
+
def find_by_entry(key, value)
|
279
|
+
key, value = *entry
|
280
|
+
mine = find_by_object(key)
|
281
|
+
mine if mine && mine.value == value
|
282
|
+
end
|
283
|
+
|
284
|
+
# Removes {@code node} from this tree, rearranging the tree's structure as necessary.
|
285
|
+
# return value not meaningful
|
286
|
+
def remove_internal(node)
|
287
|
+
left = node.left
|
288
|
+
right = node.right
|
289
|
+
original_parent = node.parent
|
290
|
+
|
291
|
+
if left && right
|
292
|
+
# To remove a node with both left and right subtrees, move an adjacent node from one of those subtrees into this node's place.
|
293
|
+
# Removing the adjacent node may change this node's subtrees. This node may no longer have two subtrees once the adjacent node is gone!
|
294
|
+
|
295
|
+
adjacent = left.height > right.height ? left.last : right.first
|
296
|
+
remove_internal(adjacent) # takes care of rebalance and size--
|
297
|
+
|
298
|
+
left_height = 0
|
299
|
+
left = node.left
|
300
|
+
if left
|
301
|
+
left_height = left.height
|
302
|
+
adjacent.left = left
|
303
|
+
left.parent = adjacent
|
304
|
+
node.left = nil
|
305
|
+
end
|
306
|
+
right_height = 0
|
307
|
+
right = node.right
|
308
|
+
if right
|
309
|
+
right_height = right.height
|
310
|
+
adjacent.right = right
|
311
|
+
right.parent = adjacent
|
312
|
+
node.right = nil
|
313
|
+
end
|
314
|
+
adjacent.height = [left_height, right_height].max + 1
|
315
|
+
replace_in_parent(node, adjacent)
|
316
|
+
return
|
317
|
+
elsif left
|
318
|
+
replace_in_parent(node, left)
|
319
|
+
node.left = nil
|
320
|
+
elsif right
|
321
|
+
replace_in_parent(node, right)
|
322
|
+
node.right = nil
|
323
|
+
else
|
324
|
+
replace_in_parent(node, nil)
|
325
|
+
end
|
326
|
+
|
327
|
+
rebalance(original_parent, false)
|
328
|
+
@size -= 1
|
329
|
+
@mod_count -= 1
|
330
|
+
end
|
331
|
+
|
332
|
+
def remove_internal_by_key(key)
|
333
|
+
node = find_by_object(key)
|
334
|
+
if node
|
335
|
+
remove_internal(node)
|
336
|
+
end
|
337
|
+
node
|
338
|
+
end
|
339
|
+
|
340
|
+
def replace_in_parent(node, replacement)
|
341
|
+
parent = node.parent
|
342
|
+
node.parent = nil
|
343
|
+
if replacement
|
344
|
+
replacement.parent = parent
|
345
|
+
end
|
346
|
+
|
347
|
+
if parent
|
348
|
+
if parent.left == node
|
349
|
+
parent.left = replacement
|
350
|
+
else
|
351
|
+
# assert (parent.right == node)
|
352
|
+
parent.right = replacement
|
353
|
+
end
|
354
|
+
else
|
355
|
+
@root = replacement
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# Rebalances the tree by making any AVL rotations necessary between the newly-unbalanced node and the tree's root.
|
360
|
+
#
|
361
|
+
# @param insert true if the node was unbalanced by an insert; false if it was by a removal.
|
362
|
+
def rebalance(unbalanced, insert)
|
363
|
+
node = unbalanced
|
364
|
+
while node
|
365
|
+
left = node.left
|
366
|
+
right = node.right
|
367
|
+
left_height = left ? left.height : 0
|
368
|
+
right_height = right ? right.height : 0
|
369
|
+
|
370
|
+
delta = left_height - right_height
|
371
|
+
if delta == -2
|
372
|
+
right_left = right.left
|
373
|
+
right_right = right.right
|
374
|
+
right_right_height = right_right ? right_right.height : 0
|
375
|
+
right_left_height = right_left ? right_left.height : 0
|
376
|
+
|
377
|
+
right_delta = right_left_height - right_right_height
|
378
|
+
if right_delta == -1 || (right_delta == 0 && !insert)
|
379
|
+
rotate_left(node)
|
380
|
+
else
|
381
|
+
# assert (right_delta == 1)
|
382
|
+
rotate_right(right) # AVL right left
|
383
|
+
rotate_left(node)
|
384
|
+
end
|
385
|
+
break if insert # no further rotations will be necessary
|
386
|
+
elsif delta == 2
|
387
|
+
left_left = left.left
|
388
|
+
left_right = left.right
|
389
|
+
left_right_height = left_right ? left_right.height : 0
|
390
|
+
left_left_height = left_left ? left_left.height : 0
|
391
|
+
|
392
|
+
left_delta = left_left_height - left_right_height
|
393
|
+
if left_delta == 1 || (left_delta == 0 && !insert)
|
394
|
+
rotate_right(node) # AVL left left
|
395
|
+
else
|
396
|
+
# assert (left_delta == -1)
|
397
|
+
rotate_left(left) # AVL left right
|
398
|
+
rotate_right(node)
|
399
|
+
end
|
400
|
+
break if insert
|
401
|
+
elsif delta == 0
|
402
|
+
node.height = left_height + 1 # left_height == right_height
|
403
|
+
break if insert
|
404
|
+
else
|
405
|
+
# assert (delta == -1 || delta == 1)
|
406
|
+
node.height = [left_height, right_height].max + 1
|
407
|
+
break unless insert # the height hasn't changed, so rebalancing is done!
|
408
|
+
end
|
409
|
+
|
410
|
+
node = node.parent
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Rotates the subtree so that its root's right child is the new root
|
415
|
+
def rotate_left(root)
|
416
|
+
left = root.left
|
417
|
+
pivot = root.right
|
418
|
+
pivot_left = pivot.left
|
419
|
+
pivot_right = pivot.right
|
420
|
+
|
421
|
+
# move the pivot's left child to the root's right
|
422
|
+
root.right = pivot_left
|
423
|
+
if pivot_left
|
424
|
+
pivot_left.parent = root
|
425
|
+
end
|
426
|
+
|
427
|
+
replace_in_parent(root, pivot)
|
428
|
+
|
429
|
+
# move the root to the pivot's left
|
430
|
+
pivot.left = root
|
431
|
+
root.parent = pivot
|
432
|
+
|
433
|
+
# fix heights
|
434
|
+
root.height = [left ? left.height : 0, pivot_left ? pivot_left.height : 0].max + 1
|
435
|
+
pivot.height = [root.height, pivot_right ? pivot_right.height : 0].max + 1
|
436
|
+
end
|
437
|
+
|
438
|
+
# Rotates the subtree so that its root's left child is the new root
|
439
|
+
def rotate_right(root)
|
440
|
+
pivot = root.left
|
441
|
+
right = root.right
|
442
|
+
pivot_left = pivot.left
|
443
|
+
pivot_right = pivot.right
|
444
|
+
|
445
|
+
# move the pivot's right child to the root's left
|
446
|
+
root.left = pivot_right
|
447
|
+
if pivot_right
|
448
|
+
pivot_right.parent = root
|
449
|
+
end
|
450
|
+
|
451
|
+
replace_in_parent(root, pivot)
|
452
|
+
|
453
|
+
# move the root to the pivot's right
|
454
|
+
pivot.right = root
|
455
|
+
root.parent = pivot
|
456
|
+
|
457
|
+
# fix heights
|
458
|
+
root.height = [right ? right.height : 0, pivot_right ? pivot_right.height : 0].max + 1
|
459
|
+
pivot.height = [root.height, pivot_left ? pivot_left.height : 0].max + 1
|
460
|
+
end
|
461
|
+
|
462
|
+
# Navigable Methods
|
463
|
+
|
464
|
+
# Returns a key-value mapping associated with the least key in this map, or null if the map is empty.
|
465
|
+
def first_entry
|
466
|
+
root.first if root
|
467
|
+
end
|
468
|
+
|
469
|
+
def internal_poll_first_entry
|
470
|
+
if root
|
471
|
+
result = root.first
|
472
|
+
remove_internal(result)
|
473
|
+
result
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def poll_first_entry
|
478
|
+
internal_poll_first_entry
|
479
|
+
end
|
480
|
+
|
481
|
+
def first_key
|
482
|
+
raise "No such element." unless root
|
483
|
+
root.first.key
|
484
|
+
end
|
485
|
+
|
486
|
+
# Returns a key-value mapping associated with the greatest key in this map, or null if the map is empty.
|
487
|
+
def last_entry
|
488
|
+
root.last if root
|
489
|
+
end
|
490
|
+
|
491
|
+
def internal_poll_last_entry
|
492
|
+
if root
|
493
|
+
result = root.last
|
494
|
+
remove_internal(result)
|
495
|
+
result
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def poll_last_entry
|
500
|
+
internal_poll_last_entry
|
501
|
+
end
|
502
|
+
|
503
|
+
def last_key
|
504
|
+
raise "No such element." unless root
|
505
|
+
root.last.key
|
506
|
+
end
|
507
|
+
|
508
|
+
# Returns a key-value mapping associated with the greatest key strictly less than the given key, or null if there is no such key.
|
509
|
+
def lower_entry(key)
|
510
|
+
find(key, Relation::LOWER)
|
511
|
+
end
|
512
|
+
|
513
|
+
# Returns the greatest key strictly less than the given key, or null if there is no such key.
|
514
|
+
def lower_key(key)
|
515
|
+
entry = find(key, Relation::LOWER)
|
516
|
+
entry.key if entry
|
517
|
+
end
|
518
|
+
|
519
|
+
# Returns a key-value mapping associated with the greatest key less than or equal to the given key, or null if there is no such key.
|
520
|
+
def floor_entry(key)
|
521
|
+
find(key, Relation::FLOOR)
|
522
|
+
end
|
523
|
+
|
524
|
+
# Returns the greatest key less than or equal to the given key, or null if there is no such key.
|
525
|
+
def floor_key(key)
|
526
|
+
entry = find(key, Relation::FLOOR)
|
527
|
+
entry.key if entry
|
528
|
+
end
|
529
|
+
|
530
|
+
# Returns a key-value mapping associated with the least key greater than or equal to the given key, or null if there is no such key.
|
531
|
+
def ceiling_entry(key)
|
532
|
+
find(key, Relation::CEILING)
|
533
|
+
end
|
534
|
+
|
535
|
+
# Returns the least key greater than or equal to the given key, or null if there is no such key.
|
536
|
+
def ceiling_key(key)
|
537
|
+
entry = find(key, Relation::CEILING)
|
538
|
+
entry.key if entry
|
539
|
+
end
|
540
|
+
|
541
|
+
# Returns a key-value mapping associated with the least key strictly greater than the given key, or null if there is no such key.
|
542
|
+
def higher_entry(key)
|
543
|
+
find(key, Relation::HIGHER)
|
544
|
+
end
|
545
|
+
|
546
|
+
# Returns the least key strictly greater than the given key, or null if there is no such key.
|
547
|
+
def higher_key(key)
|
548
|
+
entry = find(key, Relation::HIGHER)
|
549
|
+
entry.key if entry
|
550
|
+
end
|
551
|
+
|
552
|
+
# View factory methods
|
553
|
+
|
554
|
+
def entry_set
|
555
|
+
Set.new(each_node.to_a)
|
556
|
+
end
|
557
|
+
|
558
|
+
def key_set
|
559
|
+
Set.new(each_node.map(&:key))
|
560
|
+
end
|
561
|
+
|
562
|
+
alias keys key_set
|
563
|
+
|
564
|
+
def values
|
565
|
+
each_node.map(&:value)
|
566
|
+
end
|
567
|
+
|
568
|
+
# This can be called in 1 of 2 ways:
|
569
|
+
# sub_map(from_inclusive, to_exclusive)
|
570
|
+
# OR
|
571
|
+
# sub_map(from, from_inclusive, to, to_inclusive)
|
572
|
+
def sub_map(*args)
|
573
|
+
case args.count
|
574
|
+
when 2
|
575
|
+
from_inclusive, to_exclusive = *args
|
576
|
+
BoundedMap.new(self, true, from_inclusive, Bound::INCLUSIVE, to_exclusive, Bound::EXCLUSIVE)
|
577
|
+
when 4
|
578
|
+
from, from_inclusive, to, to_inclusive = *args
|
579
|
+
from_bound = from_inclusive ? Bound::INCLUSIVE : Bound::EXCLUSIVE
|
580
|
+
to_bound = to_inclusive ? Bound::INCLUSIVE : Bound::EXCLUSIVE
|
581
|
+
BoundedMap.new(self, true, from, from_bound, to, to_bound);
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
# This can be called in 1 of 2 ways:
|
586
|
+
# head_map(to_exclusive)
|
587
|
+
# OR
|
588
|
+
# head_map(to, inclusive)
|
589
|
+
def head_map(*args)
|
590
|
+
case args.count
|
591
|
+
when 1
|
592
|
+
to_exclusive = args.first
|
593
|
+
BoundedMap.new(self, true, nil, Bound::NO_BOUND, to_exclusive, Bound::EXCLUSIVE)
|
594
|
+
when 2
|
595
|
+
to, inclusive = *args
|
596
|
+
to_bound = inclusive ? Bound::INCLUSIVE : Bound::EXCLUSIVE
|
597
|
+
BoundedMap.new(self, true, nil, Bound::NO_BOUND, to, to_bound)
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
# This can be called in 1 of 2 ways:
|
602
|
+
# tail_map(from_inclusive)
|
603
|
+
# OR
|
604
|
+
# tail_map(from, inclusive)
|
605
|
+
def tail_map(*args)
|
606
|
+
case args.count
|
607
|
+
when 1
|
608
|
+
from_inclusive = args.first
|
609
|
+
BoundedMap.new(self, true, from_inclusive, Bound::INCLUSIVE, nil, Bound::NO_BOUND)
|
610
|
+
when 2
|
611
|
+
from, inclusive = *args
|
612
|
+
from_bound = inclusive ? Bound::INCLUSIVE : Bound::EXCLUSIVE
|
613
|
+
BoundedMap.new(self, true, from, from_bound, nil, Bound::NO_BOUND)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
def descending_map
|
618
|
+
BoundedMap.new(self, false, nil, Bound::NO_BOUND, nil, Bound::NO_BOUND)
|
619
|
+
end
|
620
|
+
|
621
|
+
# Tree traversal methods
|
622
|
+
|
623
|
+
# in-order traversal of nodes in tree
|
624
|
+
class NodeIterator
|
625
|
+
def initialize(next_node)
|
626
|
+
@next_node = next_node
|
627
|
+
@last_node = nil
|
628
|
+
end
|
629
|
+
|
630
|
+
def has_next?
|
631
|
+
!!@next_node
|
632
|
+
end
|
633
|
+
|
634
|
+
def step_forward
|
635
|
+
if @next_node
|
636
|
+
@last_node = @next_node
|
637
|
+
@next_node = @next_node.next_node
|
638
|
+
@last_node
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def step_backward
|
643
|
+
if @next_node
|
644
|
+
@last_node = @next_node
|
645
|
+
@next_node = @next_node.prev_node
|
646
|
+
@last_node
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# each {|k,v| puts "#{k}->#{v}"}
|
652
|
+
def each(&blk)
|
653
|
+
if block_given?
|
654
|
+
each_node {|node| blk.call(node.key, node.value) }
|
655
|
+
else
|
656
|
+
enum_for(:each)
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# each_node {|node| puts "#{node.key}->#{node.value}"}
|
661
|
+
def each_node
|
662
|
+
if block_given?
|
663
|
+
iter = NodeIterator.new(@root.first)
|
664
|
+
while iter.has_next?
|
665
|
+
yield iter.step_forward()
|
666
|
+
end
|
667
|
+
else
|
668
|
+
enum_for(:each_node)
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
data/lib/tree_map.rb
ADDED
data/tree_map.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "tree_map/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "tree_map"
|
7
|
+
spec.version = TreeMap::VERSION
|
8
|
+
spec.authors = ["tianl677"]
|
9
|
+
spec.email = ["tianlu1677@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{avl tree }
|
12
|
+
spec.description = %q{avl tree .}
|
13
|
+
spec.homepage = "https://github.com/tianlu1677/tree_map"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# spec.metadata["allowed_push_host"] = "https://github.com/tianlu1677/tree_map"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/tianlu1677/tree_map."
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/tianlu1677/tree_map."
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
34
|
+
end
|