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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  module Tree
3
3
  class RedBlack
4
- VERSION = '0.4.2'
4
+ VERSION = '0.4.3'
5
5
  end
6
6
  end