tree-red_black 0.4.2 → 0.4.3
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 +4 -4
- data/.gitignore +5 -0
- data/Gemfile +2 -0
- data/contrib/Graph Red-Black Tree.ipynb +163 -0
- data/contrib/Schedule Reservation.ipynb +561 -0
- data/contrib/graph_red-black_tree.rb +203 -0
- data/contrib/schedule_reservation.rb +242 -0
- data/lib/tree/red_black/version.rb +1 -1
- metadata +6 -4
- data/Gemfile.lock +0 -36
- data/contrib/iterate_dot_graphs.rb +0 -208
@@ -0,0 +1,203 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tree/red_black'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
OPEN = case RbConfig::CONFIG['target_os']
|
7
|
+
when /darwin/
|
8
|
+
'/usr/bin/open'
|
9
|
+
else
|
10
|
+
'/usr/bin/xdg-open'
|
11
|
+
end
|
12
|
+
|
13
|
+
def red_black_tree_paths(rbt)
|
14
|
+
paths = {}
|
15
|
+
rbt.pre_order do |node; path, count, ancestor|
|
16
|
+
if node.left.nil? || node.right.nil?
|
17
|
+
path = []
|
18
|
+
count = 0
|
19
|
+
ancestor = node
|
20
|
+
loop do
|
21
|
+
path.unshift ancestor.key
|
22
|
+
count += 1 if ancestor.color == :BLACK
|
23
|
+
break if (ancestor = ancestor.parent).nil?
|
24
|
+
end
|
25
|
+
paths[path] = count
|
26
|
+
end
|
27
|
+
end
|
28
|
+
paths
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# To graph duplicate values, generate a unique ID for each instance of
|
33
|
+
# a given value. For example, if there are multiple 10s, then the
|
34
|
+
# first instance is assigned ID 10_0, the second instance 10_1, etc.
|
35
|
+
#
|
36
|
+
# To calculate the index of a given value, note that during in-order
|
37
|
+
# traversal, a left child node is visited before its parent. So when a
|
38
|
+
# left child node is visited, its instance index must be incremented.
|
39
|
+
#
|
40
|
+
# On the other hand, since a graph is described as a sequence of arcs
|
41
|
+
# from parent nodes to their child nodes, e.g.,
|
42
|
+
#
|
43
|
+
# parent_1 -- left_child_of_parent_1
|
44
|
+
# parent_1 -- right_child_of_parent_2
|
45
|
+
# parent_2 -- left_child_of_parent_2
|
46
|
+
# parent_2 -- right_child_of_parent_2
|
47
|
+
# ...
|
48
|
+
#
|
49
|
+
# a right child node is referenced in an arc before it's visited via
|
50
|
+
# in-order traversal. So a right child node's instance index must be
|
51
|
+
# incremented when the right child is referenced in an arc.
|
52
|
+
#
|
53
|
+
# Finally, use the node itself (or perhaps a string containing its
|
54
|
+
# oject ID) as the key of a hash of instance values.
|
55
|
+
#
|
56
|
+
|
57
|
+
def graph_red_black_tree(rbt, filename, title)
|
58
|
+
count = {}
|
59
|
+
instance = {}
|
60
|
+
|
61
|
+
rbt.each { |node| count[node.key] ||= 0; count[node.key] += 1 }
|
62
|
+
|
63
|
+
File.open(filename, 'w') do |file|
|
64
|
+
file.write "graph \"\"\n{\n label=\"#{title}\"\n"
|
65
|
+
|
66
|
+
i = 0
|
67
|
+
rbt.in_order do |node|
|
68
|
+
parent = if count[node.key] == 1
|
69
|
+
node.key
|
70
|
+
else
|
71
|
+
|
72
|
+
# left child...
|
73
|
+
if node == node.parent&.left
|
74
|
+
|
75
|
+
# so increment node.key instance index.
|
76
|
+
instance[node.key] ||= -1
|
77
|
+
instance[node.key] += 1
|
78
|
+
instance[node] = instance[node.key]
|
79
|
+
end
|
80
|
+
"#{node.key}_#{instance[node]}"
|
81
|
+
end
|
82
|
+
|
83
|
+
left_child = if node.left
|
84
|
+
if count[node.left.key] > 1
|
85
|
+
"#{node.left.key}_#{instance[node.left]}"
|
86
|
+
else
|
87
|
+
node.left.key
|
88
|
+
end
|
89
|
+
else
|
90
|
+
'NIL' + (i += 1).to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
right_child = if node.right
|
94
|
+
|
95
|
+
# right child...
|
96
|
+
if count[node.right.key] > 1
|
97
|
+
|
98
|
+
# so increment node.right.key instance index.
|
99
|
+
instance[node.right.key] ||= -1
|
100
|
+
instance[node.right.key] += 1
|
101
|
+
instance[node.right] = instance[node.right.key]
|
102
|
+
"#{node.right.key}_#{instance[node.right]}"
|
103
|
+
else
|
104
|
+
node.right.key
|
105
|
+
end
|
106
|
+
else
|
107
|
+
'NIL' + (i += 1).to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
file.write(" \"#{parent}\" [style=filled,color=#{node.color.to_s.downcase},fontcolor=white,label=#{node.key}];\n")
|
111
|
+
file.write(" \"#{parent}\" -- \"#{left_child}\";\n")
|
112
|
+
file.write(" \"#{parent}\" -- \"#{right_child}\";\n")
|
113
|
+
end
|
114
|
+
|
115
|
+
i.times do |n|
|
116
|
+
file.write(" NIL#{n + 1} [fontsize=6,shape=box,width=0.2,height=0.2,style=filled,color=black,fontcolor=white,label=\"NIL\"];\n")
|
117
|
+
end
|
118
|
+
|
119
|
+
file.write("}\n")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete_keys_of_interest(rbt, deleted, log)
|
124
|
+
paths = red_black_tree_paths(rbt)
|
125
|
+
|
126
|
+
raise "Unbalanced tree after deleting: #{deleted}" if paths.values.uniq.size > 1
|
127
|
+
|
128
|
+
koi = rbt.select do |node|
|
129
|
+
node.color == :BLACK && node.left.nil? && node.right.nil?
|
130
|
+
end.map(&:key)
|
131
|
+
|
132
|
+
v = koi.size > 0 ? koi.shuffle.first : rbt.root&.key
|
133
|
+
|
134
|
+
return log if v.nil?
|
135
|
+
|
136
|
+
log << "key of interest: #{v}"
|
137
|
+
deleted << v
|
138
|
+
rbt.delete(v)
|
139
|
+
|
140
|
+
delete_keys_of_interest(rbt, deleted, log)
|
141
|
+
end
|
142
|
+
|
143
|
+
values = [*0..20].shuffle
|
144
|
+
# values = [2, 10, 4, 5, 6, 3, 1, 0, 9, 8, 7]
|
145
|
+
# values = [0, 2, 6, 4, 5, 3, 7, 1, 9, 8, 10]
|
146
|
+
puts "values: #{values.inspect}"
|
147
|
+
|
148
|
+
rbt = nil
|
149
|
+
Benchmark.bm do |benchmark|
|
150
|
+
benchmark.report("insert:") do
|
151
|
+
rbt = Tree::RedBlack.new(true)
|
152
|
+
rbt.insert(*values)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
puts "root.key: #{rbt.root.key}"
|
157
|
+
puts "tree size: #{rbt.size}"
|
158
|
+
puts "sorted values: #{rbt.map(&:key).inspect}"
|
159
|
+
paths = red_black_tree_paths(rbt)
|
160
|
+
puts "black-node counts: #{paths.values.uniq}"
|
161
|
+
graph_red_black_tree(rbt, 'dot.txt', 'Red-Black Tree')
|
162
|
+
system "dot -Tpng -odot.png dot.txt && #{OPEN} dot.png"
|
163
|
+
|
164
|
+
deleted = []
|
165
|
+
|
166
|
+
rbt_copy = rbt.dup
|
167
|
+
log = []
|
168
|
+
Benchmark.bm do |benchmark|
|
169
|
+
benchmark.report("koi:") do
|
170
|
+
log = delete_keys_of_interest(rbt_copy, deleted, log)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
puts log
|
175
|
+
|
176
|
+
trap("SIGINT") { print "\n"; exit! }
|
177
|
+
|
178
|
+
|
179
|
+
toggle = insert_n = delete_n = 0
|
180
|
+
while insert_n || delete_n
|
181
|
+
print "Integer to insert [Enter for none, Ctrl+C to exit]? "
|
182
|
+
ans = gets
|
183
|
+
insert_n = ans == "\n" ? nil : ans.to_i
|
184
|
+
print "Integer to delete [Enter for none, Ctrl+C to exit]? "
|
185
|
+
ans = gets
|
186
|
+
delete_n = ans == "\n" ? nil : ans.to_i
|
187
|
+
print "Inserting: #{insert_n}\n" if insert_n
|
188
|
+
print "Deleting: #{delete_n}\n" if delete_n
|
189
|
+
|
190
|
+
rbt.insert(insert_n) if insert_n
|
191
|
+
rbt.delete(delete_n) if delete_n
|
192
|
+
|
193
|
+
name = toggle == 0 ? 'dot2' : 'dot'
|
194
|
+
title = 'Red-Black Tree '
|
195
|
+
title += insert_n ? "+ #{insert_n} " : ''
|
196
|
+
title += delete_n ? "- #{delete_n} " : ''
|
197
|
+
graph_red_black_tree(rbt, name + '.txt', title)
|
198
|
+
|
199
|
+
system 'dot -Tpng -o' + name + '.png ' + name + '.txt'
|
200
|
+
system "#{OPEN} " + name + '.png'
|
201
|
+
|
202
|
+
toggle ^= 1
|
203
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'tree/red_black'
|
3
|
+
|
4
|
+
module IntegerTime
|
5
|
+
interval_in_seconds = {
|
6
|
+
second: 1,
|
7
|
+
minute: 60,
|
8
|
+
hour: 60 * 60,
|
9
|
+
day: 24 * 60 * 60,
|
10
|
+
week: 7 * 24 * 60 * 60,
|
11
|
+
year: 365 * 24 * 60 * 60
|
12
|
+
}
|
13
|
+
|
14
|
+
refine Integer do
|
15
|
+
interval_in_seconds.keys.each do |key|
|
16
|
+
define_method(key) { self * interval_in_seconds[key] }
|
17
|
+
define_method((key.to_s + 's').to_sym) { self * interval_in_seconds[key] }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class BusyFreeEvent
|
23
|
+
include Comparable
|
24
|
+
|
25
|
+
attr_reader :time, :owner
|
26
|
+
|
27
|
+
def initialize(time, owner)
|
28
|
+
@time = time
|
29
|
+
@owner = owner
|
30
|
+
end
|
31
|
+
|
32
|
+
def <=>(other)
|
33
|
+
time <=> other.time
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
{ time => owner }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Reservation
|
42
|
+
attr_reader :from, :to, :owner
|
43
|
+
|
44
|
+
def initialize(from, to, owner)
|
45
|
+
@from = from
|
46
|
+
@to = to
|
47
|
+
@owner = owner
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
{ [from, to] => owner }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Schedule
|
56
|
+
using IntegerTime
|
57
|
+
|
58
|
+
attr_accessor :events
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
@events = Tree::RedBlack.new(false)
|
62
|
+
end
|
63
|
+
|
64
|
+
def reserve(from:, to: from + 1.hour, owner:)
|
65
|
+
return nil if owner == :FREE
|
66
|
+
|
67
|
+
next_owner = :FREE
|
68
|
+
total_events = events.size
|
69
|
+
|
70
|
+
rsv_from = BusyFreeEvent.new(from, owner)
|
71
|
+
rsv_to = BusyFreeEvent.new(to, next_owner)
|
72
|
+
|
73
|
+
if total_events.zero?
|
74
|
+
events.insert(rsv_from, rsv_to)
|
75
|
+
else
|
76
|
+
succ_event = events.bsearch { |event| event.key > rsv_from }
|
77
|
+
if succ_event.nil?
|
78
|
+
pred_event = events.root.max
|
79
|
+
|
80
|
+
# Assert: pred_event.key.owner == :FREE
|
81
|
+
if pred_event.key == rsv_from
|
82
|
+
events.delete(pred_event.key)
|
83
|
+
total_events -= 1
|
84
|
+
end
|
85
|
+
events.insert(rsv_from, rsv_to)
|
86
|
+
elsif succ_event.key >= rsv_to
|
87
|
+
pred_event = succ_event.pred
|
88
|
+
if pred_event.nil? || pred_event.key.owner == :FREE
|
89
|
+
events.delete(pred_event.key) if pred_event&.key == rsv_from
|
90
|
+
events.insert(rsv_from)
|
91
|
+
if succ_event.key != rsv_to
|
92
|
+
|
93
|
+
# Assert: succ_event.key.owner != :FREE
|
94
|
+
events.insert(rsv_to)
|
95
|
+
else
|
96
|
+
next_owner = succ_event.key.owner
|
97
|
+
total_events -= 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Assert: inserts/deletes successful
|
104
|
+
events.size == total_events + 2 ? Reservation.new(from, to, owner) : nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def unreserve(reservation)
|
108
|
+
event = events.bsearch { |ev| ev.key.time >= reservation.from }
|
109
|
+
|
110
|
+
return false if (event.nil? || event.key.time != reservation.from || event.key.owner != reservation.owner)
|
111
|
+
|
112
|
+
# Assert: event.key.owner != :FREE
|
113
|
+
pred_event = event.pred
|
114
|
+
succ_event = event.succ
|
115
|
+
|
116
|
+
# Assert: ! succ_event.nil?
|
117
|
+
|
118
|
+
events.delete(event.key)
|
119
|
+
events.delete(succ_event.key) if succ_event.key.owner == :FREE
|
120
|
+
events.insert(BusyFreeEvent.new(reservation.from, :FREE)) if pred_event && pred_event.key.owner != :FREE
|
121
|
+
true
|
122
|
+
end
|
123
|
+
|
124
|
+
def is_consistent?
|
125
|
+
count = 0
|
126
|
+
prev = nil
|
127
|
+
event = events.root&.min
|
128
|
+
return false if event && event.key.owner == :FREE
|
129
|
+
while event
|
130
|
+
return false if prev&.key&.owner == :FREE && event.key.owner == :FREE
|
131
|
+
prev = event
|
132
|
+
event = event.succ
|
133
|
+
count += 1
|
134
|
+
end
|
135
|
+
return false if prev && prev.key.owner != :FREE
|
136
|
+
count == events.size && prev&.key&.owner == :FREE
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
if $0 == __FILE__
|
141
|
+
using IntegerTime
|
142
|
+
|
143
|
+
sched = Schedule.new
|
144
|
+
reserve_time = Time.now
|
145
|
+
|
146
|
+
puts "Before:"
|
147
|
+
sched.events.each do |event|
|
148
|
+
puts "key: #{event.key}"
|
149
|
+
end
|
150
|
+
|
151
|
+
puts "First reservation:"
|
152
|
+
reservation1 = sched.reserve(from: reserve_time, owner: :BUSY3)
|
153
|
+
puts "reservation: #{reservation1.inspect}"
|
154
|
+
|
155
|
+
puts "After first reservation:"
|
156
|
+
sched.events.each do |event|
|
157
|
+
puts "key: #{event.key.inspect}"
|
158
|
+
end
|
159
|
+
|
160
|
+
puts "Second reservation:"
|
161
|
+
reservation2 = sched.reserve(from: reserve_time - 1.hour, owner: :BUSY2)
|
162
|
+
puts "reservation: #{reservation2.inspect}"
|
163
|
+
|
164
|
+
puts "After second reservation:"
|
165
|
+
sched.events.each do |event|
|
166
|
+
puts "key: #{event.key.inspect}"
|
167
|
+
end
|
168
|
+
|
169
|
+
puts "Third reservation:"
|
170
|
+
reservation3 = sched.reserve(from: reserve_time + 1.hour, owner: :BUSY4)
|
171
|
+
puts "reservation: #{reservation3.inspect}"
|
172
|
+
|
173
|
+
puts "After third reservation:"
|
174
|
+
sched.events.each do |event|
|
175
|
+
puts "key: #{event.key.inspect}"
|
176
|
+
end
|
177
|
+
|
178
|
+
puts "Fourth reservation:"
|
179
|
+
reservation4 = sched.reserve(from: reserve_time - 4.hours, owner: :BUSY0)
|
180
|
+
puts "reservation: #{reservation4.inspect}"
|
181
|
+
|
182
|
+
puts "After fourth reservation:"
|
183
|
+
sched.events.each do |event|
|
184
|
+
puts "key: #{event.key.inspect}"
|
185
|
+
end
|
186
|
+
|
187
|
+
puts "Fifth reservation:"
|
188
|
+
reservation5 = sched.reserve(from: reserve_time - 2.hours, to: reserve_time - 90.minutes, owner: :BUSY1)
|
189
|
+
puts "reservation: #{reservation5.inspect}"
|
190
|
+
|
191
|
+
puts "After fifth reservation:"
|
192
|
+
sched.events.each do |event|
|
193
|
+
puts "key: #{event.key.inspect}"
|
194
|
+
end
|
195
|
+
|
196
|
+
puts "Schedule is consistent?: #{sched.is_consistent?}"
|
197
|
+
|
198
|
+
puts "Delete fifth reservation:"
|
199
|
+
result = sched.unreserve(reservation5)
|
200
|
+
puts "result: #{result}"
|
201
|
+
|
202
|
+
puts "After deleting fifth reservation:"
|
203
|
+
sched.events.each do |event|
|
204
|
+
puts "key: #{event.key.inspect}"
|
205
|
+
end
|
206
|
+
|
207
|
+
puts "Delete fourth reservation:"
|
208
|
+
result = sched.unreserve(reservation4)
|
209
|
+
puts "result: #{result}"
|
210
|
+
|
211
|
+
puts "After deleting fourth reservation:"
|
212
|
+
sched.events.each do |event|
|
213
|
+
puts "key: #{event.key.inspect}"
|
214
|
+
end
|
215
|
+
|
216
|
+
puts "Delete third reservation:"
|
217
|
+
result = sched.unreserve(reservation3)
|
218
|
+
puts "result: #{result}"
|
219
|
+
|
220
|
+
puts "After deleting third reservation:"
|
221
|
+
sched.events.each do |event|
|
222
|
+
puts "key: #{event.key.inspect}"
|
223
|
+
end
|
224
|
+
|
225
|
+
puts "Delete second reservation:"
|
226
|
+
result = sched.unreserve(reservation2)
|
227
|
+
puts "result: #{result}"
|
228
|
+
|
229
|
+
puts "After deleting second reservation:"
|
230
|
+
sched.events.each do |event|
|
231
|
+
puts "key: #{event.key.inspect}"
|
232
|
+
end
|
233
|
+
|
234
|
+
puts "Delete first reservation:"
|
235
|
+
result = sched.unreserve(reservation1)
|
236
|
+
puts "result: #{result}"
|
237
|
+
|
238
|
+
puts "After deleting first reservation:"
|
239
|
+
sched.events.each do |event|
|
240
|
+
puts "key: #{event.key.inspect}"
|
241
|
+
end
|
242
|
+
end
|