tree-red_black 0.4.2

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,12 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require 'rdoc/task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ RDoc::Task.new do |rdoc|
10
+ rdoc.main = "README.md"
11
+ rdoc.rdoc_files.include("lib/ *.rb")
12
+ end
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tree/red_black'
4
+ require 'benchmark'
5
+
6
+ values = [*0..10]
7
+ # values = [2, 10, 4, 5, 6, 3, 1, 0, 9, 8, 7]
8
+ # values = [0, 2, 6, 4, 5, 3, 7, 1, 9, 8, 10]
9
+ puts "values.size: #{values.size}"
10
+
11
+ # rbt = Tree::RedBlack.new
12
+ # values.each do |v|
13
+ # # rbt = rbt.insert_red_black(v)
14
+ # rbt.insert(v)
15
+ # end
16
+
17
+
18
+ rbt = rbt_copy = nil
19
+ Benchmark.bm do |benchmark|
20
+ benchmark.report("insert:") do
21
+ # rbt = values.reduce(Tree::RedBlackNode.new) do |acc, v|
22
+ # acc.insert_red_black(v)
23
+ # end
24
+ rbt = values.reduce(Tree::RedBlack.new) do |acc, v|
25
+ acc.insert(v)
26
+ end
27
+ end
28
+
29
+ # benchmark.report("dup:") do
30
+ # rbt_copy = rbt.dup
31
+ # end
32
+ end
33
+
34
+
35
+ # rbt = rbt.insert_red_black(values.size)
36
+ # rbt_copy = rbt_copy.insert_red_black(values.size).delete_red_black(rbt_copy.key)
37
+ # rbt.insert(values.size)
38
+ # rbt_copy.insert(values.size)
39
+
40
+
41
+ File.open('dot.txt', 'w') do |file|
42
+ file.write "graph \"\"\n{\n label=\"Red-Black Tree\"\n"
43
+
44
+ i = 0
45
+ rbt.each do |node|
46
+ file.write(" #{node.key} [style=filled,color=#{node.color.to_s.downcase},fontcolor=white];\n")
47
+ file.write(" #{node.key} -- %s;\n" % [node.left ? "#{node.left.key}" : 'NULL' + (i += 1).to_s])
48
+ file.write(" #{node.key} -- %s;\n" % [node.right ? "#{node.right.key}" : 'NULL' + (i += 1).to_s])
49
+ end
50
+ i.times do |n|
51
+ file.write(" NULL#{n + 1} [fontsize=6,shape=box,width=0.2,height=0.2,style=filled,color=gray,label=\"NULL\"];\n")
52
+ end
53
+ file.write("}\n")
54
+ end
55
+
56
+ system 'dot -Tpng -odot.png dot.txt'
57
+ system 'open dot.png'
58
+
59
+ # puts "root: #{rbt_copy.key}"
60
+ puts "root: #{rbt.root.key}"
61
+ puts "size: #{rbt.size}"
62
+
63
+ p rbt.each.map(&:key)
64
+
65
+ paths = {}
66
+ rbt.pre_order do |node; path, count, ancestor|
67
+ if node.left.nil? || node.right.nil?
68
+ path = []
69
+ count = 0
70
+ ancestor = node
71
+ loop do
72
+ path.unshift ancestor.key
73
+ count += 1 if ancestor.color == :BLACK
74
+ break if (ancestor = ancestor.parent).nil?
75
+ end
76
+ paths[path] = count
77
+ end
78
+ end
79
+
80
+ # puts "paths from root (#{paths.size}):"
81
+ # paths.each do |path, count|
82
+ # p path
83
+ # end
84
+
85
+ puts "black-node counts: #{paths.values.uniq}"
86
+
87
+
88
+ def delete_keys_of_interest(rbt, deleted)
89
+ # puts "root: #{rbt.root.key}"
90
+ # puts "size: #{rbt.size}"
91
+
92
+ paths = {}
93
+ rbt.pre_order do |node; path, count, ancestor|
94
+ if node.left.nil? || node.right.nil?
95
+ path = []
96
+ count = 0
97
+ ancestor = node
98
+ loop do
99
+ path.unshift ancestor.key
100
+ count += 1 if ancestor.color == :BLACK
101
+ break if (ancestor = ancestor.parent).nil?
102
+ end
103
+ paths[path] = count
104
+ end
105
+ end
106
+
107
+ # puts "paths from root (#{paths.size}):"
108
+ # paths.each do |path, count|
109
+ # p path
110
+ # end
111
+
112
+ # puts "black-node counts: #{paths.values.uniq}"
113
+
114
+ raise "Unbalanced tree after deleting: #{deleted}" if paths.values.uniq.size > 1
115
+
116
+ koi = rbt.each.select do |node|
117
+ node.color == :BLACK && node.left.nil? && node.right.nil?
118
+ end.map(&:key)
119
+ # puts "keys of interest: #{koi}"
120
+
121
+ v = koi.size > 0 ? koi.shuffle.first : rbt.root&.key
122
+
123
+ return if v.nil?
124
+
125
+ deleted << v
126
+ # rbt = rbt.delete_red_black(v)
127
+ rbt.delete(v)
128
+
129
+ delete_keys_of_interest(rbt, deleted)
130
+ end
131
+
132
+ # deleted = []
133
+
134
+ # Benchmark.bm do |benchmark|
135
+ # # benchmark.report("koi:") do
136
+ # # delete_keys_of_interest(rbt_copy, deleted)
137
+ # # end
138
+ # # rbt_copy = rbt.dup
139
+ # benchmark.report("delete:") do
140
+ # # values.each { |v| rbt_copy = rbt_copy.delete_red_black(v) }
141
+ # values.each { |v| rbt.delete(v) }
142
+ # end
143
+ # end
144
+
145
+ # # p "deleted: #{deleted}"
146
+ # p rbt.inspect
147
+
148
+ print "Value to delete? "
149
+ n = gets.to_i
150
+
151
+ toggle = 0
152
+ while n >= 0
153
+ # rbt_copy = rbt.delete_red_black(n)
154
+ rbt.delete(n)
155
+
156
+ name = toggle == 0 ? 'dot2' : 'dot'
157
+ File.open(name + '.txt', 'w') do |file|
158
+ file.write "graph \"\"\n{\n label=\"Red-Black Tree - #{n}\"\n"
159
+ # file.write " labelfontsize=8.0\n"
160
+
161
+ i = 0
162
+ rbt.each do |node|
163
+ file.write(" #{node.key} [style=filled,color=#{node.color.to_s.downcase},fontcolor=white];\n")
164
+ file.write(" #{node.key} -- %s;\n" % [node.left ? "#{node.left.key}" : 'NULL' + (i += 1).to_s])
165
+ file.write(" #{node.key} -- %s;\n" % [node.right ? "#{node.right.key}" : 'NULL' + (i += 1).to_s])
166
+ end
167
+ i.times do |n|
168
+ file.write(" NULL#{n + 1} [fontsize=6,shape=box,width=0.2,height=0.2,style=filled,color=gray,label=\"NULL\"];\n")
169
+ end
170
+ file.write("}\n")
171
+ end
172
+
173
+ system 'dot -Tpng -o' + name + '.png ' + name + '.txt'
174
+ system 'open ' + name + '.png'
175
+
176
+ print "Value to delete? "
177
+ n = gets.to_i
178
+ toggle ^= 1
179
+ end
180
+
181
+ # values.delete(n)
182
+ # values.each do |v|
183
+ # puts "deleting #{v}"
184
+ # rbt = rbt.delete_red_black(v)
185
+ # end
186
+
187
+ # puts "empty rbt: #{rbt.inspect}"
188
+
189
+
190
+ # name = 'dot2'
191
+ # File.open(name + '.txt', 'w') do |file|
192
+ # file.write "graph \"\"\n{\n label=\"Red-Black Tree Copy\"\n"
193
+ # # file.write " labelfontsize=8.0\n"
194
+
195
+ # i = 0
196
+ # rbt_copy.each do |node|
197
+ # file.write(" #{node.key} [style=filled,color=#{node.color.to_s.downcase},fontcolor=white];\n")
198
+ # file.write(" #{node.key} -- %s;\n" % [node.left ? "#{node.left.key}" : 'NULL' + (i += 1).to_s])
199
+ # file.write(" #{node.key} -- %s;\n" % [node.right ? "#{node.right.key}" : 'NULL' + (i += 1).to_s])
200
+ # end
201
+ # i.times do |n|
202
+ # file.write(" NULL#{n + 1} [fontsize=6,shape=box,width=0.2,height=0.2,style=filled,color=gray,label=\"NULL\"];\n")
203
+ # end
204
+ # file.write("}\n")
205
+ # end
206
+
207
+ # system 'dot -Tpng -o' + name + '.png ' + name + '.txt'
208
+ # system 'open ' + name + '.png'
@@ -0,0 +1 @@
1
+ require 'tree/red_black'
@@ -0,0 +1,243 @@
1
+ # -*- coding: utf-8 -*-
2
+ # coding: UTF-8
3
+
4
+ require 'tree/red_black/red_black_node'
5
+
6
+ module Tree
7
+
8
+ ##
9
+ # Tree::RedBlack is a pure-Ruby implementation of a
10
+ # {Red-Black tree}[https://en.wikipedia.org/wiki/Red–black_tree] --
11
+ # i.e., a self-balancing binary search tree with
12
+ # {O(log n)}[https://en.wikipedia.org/wiki/Big-O_notation]
13
+ # search, insert and delete operations. It is appropriate for
14
+ # maintaining an ordered collection where insertion and deletion
15
+ # are desired at arbitrary positions.
16
+ #
17
+ # The implementation differs slightly from the Wikipedia description
18
+ # referenced above. In particular, leaf nodes are +nil+, which
19
+ # affects the details of node deletion.
20
+
21
+ class RedBlack
22
+ include Enumerable
23
+
24
+ attr_accessor :root, :size, :allow_duplicates
25
+
26
+ ##
27
+ # Returns a new, empty Red-Black tree. If option +allow_duplicates+ is
28
+ # +false+, then only unique values are inserted in a Red-Black tree.
29
+ #
30
+ # The +root+ attribute references the root node of the tree.
31
+ # The +size+ attribute indicates the number of nodes in the tree.
32
+ # When +size+ is +0+, +root+ is always +nil+.
33
+ #
34
+ # === Example
35
+ #
36
+ # require 'tree/red_black'
37
+ #
38
+ # rbt = Tree::RedBlack.new
39
+ # p rbt.root #=> nil
40
+ # p rbt.size #=> 0
41
+ # p rbt.allow_duplicates? #=> true
42
+
43
+ def initialize(allow_duplicates = true)
44
+ @root = nil
45
+ @size = 0
46
+ @allow_duplicates = allow_duplicates
47
+ end
48
+
49
+ def allow_duplicates?
50
+ @allow_duplicates
51
+ end
52
+
53
+ ##
54
+ # Inserts a value or sequence of values in a Red-Black tree and
55
+ # increments the +size+ attribute by the number of values
56
+ # inserted.
57
+ #
58
+ # Since a Red-Black tree maintains an ordered, Enumerable
59
+ # collection, every value inserted must be comparable with every
60
+ # other value. Methods +each+, +map+, +select+, +find+, +sort+,
61
+ # etc., can be applied directly to the tree.
62
+ #
63
+ # The individual nodes yielded by enumeration respond to method
64
+ # +key+ to retrieve the value stored in that node. Method +each+,
65
+ # in particular, is aliased to +in_order+, so that nodes are
66
+ # sorted in ascending order by +key+ value. Nodes can also be
67
+ # traversed by method +pre_order+, e.g., to generate paths in the
68
+ # tree.
69
+ #
70
+ # === Example
71
+ #
72
+ # require 'tree/red_black'
73
+ #
74
+ # rbt = Tree::RedBlack.new
75
+ # rbt.insert(*1..10) #=> #<Tree::RedBlack:0x00...>
76
+ # p rbt.size #=> 10
77
+ # rbt.map(&:key) #=> [1, 2, ..., 10]
78
+ # rbt.select { |node| node.key % 2 == 0 }.map(&:key)
79
+ # #=> [2, 4, ..., 10]
80
+
81
+ def insert(*values)
82
+ values.each do |value; new_root|
83
+ new_root = (root.nil? ? RedBlackNode.new(value, :BLACK) :
84
+ root.insert_red_black(value, @allow_duplicates))
85
+ unless new_root.nil?
86
+ @root = new_root
87
+ @size += 1
88
+ end
89
+ end
90
+ self
91
+ end
92
+
93
+ ##
94
+ # Deletes a value or sequence of values from a Red-Black tree.
95
+ #
96
+ # === Example
97
+ #
98
+ # require 'tree/red_black'
99
+ #
100
+ # rbt = Tree::RedBlack.new
101
+ # rbt.insert(*1..10) #=> #<Tree::RedBlack:0x00...>
102
+ # p rbt.size #=> 10
103
+ # rbt.delete(*4..8) #=> #<Tree::RedBlack:0x00...>
104
+ # p rbt.size #=> 5
105
+ # rbt.map(&:key) #=> [1, 2, 3, 9, 10]
106
+
107
+ def delete(*values)
108
+ values.each do |value; new_root|
109
+ new_root = root.nil? ? nil : root.delete_red_black(value)
110
+ unless new_root.nil?
111
+ @root = new_root
112
+ @size -= 1
113
+ end
114
+ @root = nil if size == 0
115
+ end
116
+ self
117
+ end
118
+
119
+ ##
120
+ # Returns a Red-Black tree node whose +key+ matches +value+ by
121
+ # binary search. If no match is found, calls non-nil +ifnone+,
122
+ # otherwise returns +nil+.
123
+ #
124
+ # === Example
125
+ # require 'tree/red_black'
126
+ #
127
+ # shuffled_values = [*1..10].shuffle
128
+ # rbt = shuffled_values.reduce(Tree::RedBlack.new) do |acc, v|
129
+ # acc.insert(v)
130
+ # end
131
+ # rbt.search(7) #=> <Tree::RedBlackNode:0x00..., @key=7, ...>
132
+
133
+ def search(value, ifnone = nil)
134
+ root ? root.search(value, ifnone) : nil
135
+ end
136
+
137
+ ##
138
+ # Returns a Red-Black tree node satisfying a criterion defined in
139
+ # +block+ by binary search.
140
+ #
141
+ # If +block+ evaluates to +true+ or +false+, returns the first node
142
+ # for which the +block+ evaluates to +true+. In this case, the
143
+ # criterion is expected to return +false+ for nodes preceding
144
+ # the matching node and +true+ for subsequent nodes.
145
+ #
146
+ # === Example
147
+ # require 'tree/red_black'
148
+ #
149
+ # shuffled_values = [*1..10].shuffle
150
+ # rbt = shuffled_values.reduce(Tree::RedBlack.new) do |acc, v|
151
+ # acc.insert(v)
152
+ # end
153
+ # rbt.bsearch { |node| node.key >= 7 }
154
+ # #=> <Tree::RedBlackNode:0x00... @key=7 ...>
155
+ #
156
+ # If +block+ evaluates to <tt><0</tt>, +0+ or <tt>>0</tt>, returns
157
+ # first node for which +block+ evaluates to +0+. Otherwise returns
158
+ # +nil+. In this case, the criterion is expected to return
159
+ # <tt><0</tt> for nodes preceding the matching node, +0+ for some
160
+ # subsequent nodes and <tt>>0</tt> for nodes beyond that.
161
+ #
162
+ # === Example
163
+ # require 'tree/red_black'
164
+ #
165
+ # shuffled_values = [*1..10].shuffle
166
+ # rbt = shuffled_values.reduce(Tree::RedBlack.new) do |acc, v|
167
+ # acc.insert(v)
168
+ # end
169
+ # rbt.bsearch { |node| 7 <=> node.key }
170
+ # #=> <Tree::RedBlackNode:0x00... @key=7 ...>
171
+ #
172
+ # If +block++ is not given, returns an enumerator.
173
+
174
+ def bsearch(&block)
175
+ return enum_for(:bsearch) unless block_given?
176
+
177
+ root ? root.bsearch(&block) : nil
178
+ end
179
+
180
+ ##
181
+ # Returns an enumerator for nodes in a Red-Black tree by pre-order
182
+ # traversal.
183
+ #
184
+ # === Example
185
+ # require 'tree/red_black'
186
+ #
187
+ # rbt = Tree::RedBlack.new
188
+ # rbt.insert(*1..10) #=> #<Tree::RedBlack:0x00...>
189
+ # rbt.pre_order.map(&:key) #=> [4, 2, 1, 3, 6, 5, 8, 7, 9, 10]
190
+
191
+ def pre_order(&block)
192
+ return enum_for(:pre_order) unless block_given?
193
+ return if root.nil?
194
+
195
+ root.pre_order(&block)
196
+ end
197
+
198
+ ##
199
+ # Returns an enumerator for nodes in a Red-Black tree by in-order
200
+ # traversal. The +each+ method is aliased to +in_order+
201
+ #
202
+ # === Example
203
+ # require 'tree/red_black'
204
+ #
205
+ # rbt = Tree::RedBlack.new
206
+ # shuffled_values = [*1..10].shuffle
207
+ # rbt.insert(*shuffled_values)
208
+ # rbt.in_order.map(&:key) #=> [1, 2, ..., 10]
209
+
210
+ def in_order(&block)
211
+ return enum_for(:in_order) unless block_given?
212
+ return if root.nil?
213
+
214
+ root.in_order(&block)
215
+ end
216
+
217
+ ##
218
+ # Returns a deep copy of a Red-Black tree, provided that the
219
+ # +dup+ method for values in the tree is also a deep copy.
220
+ #
221
+ # === Example
222
+ #
223
+ # require 'tree/red_black'
224
+ #
225
+ # rbt = Tree::RedBlack.new
226
+ # rbt.insert({a: 1, b: 2})
227
+ # rbt_copy = rbt.dup
228
+ # p rbt.root.key #=> {:a=>1, :b=>2}
229
+ # p rbt.root.key.delete(:a) #=> 1
230
+ # p rbt.root.key #=> {:b=>2}
231
+ # p rbt_copy.root.key #=> {:a=>1, :b=>2}
232
+
233
+ def dup
234
+ copy = RedBlack.new
235
+ copy.size = size
236
+ copy.allow_duplicates = allow_duplicates
237
+ copy.root = root.dup
238
+ copy
239
+ end
240
+
241
+ alias each in_order
242
+ end
243
+ end