red-black-tree 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e609f03254a0ff34b07adf5445e91803cf4f50ffc5ba91b6288c31003218b87a
4
- data.tar.gz: e1289ab4cefa28f5ae2eca05be45d5a7dbd2fa13a8a5256c81ac21511bb8e0fb
3
+ metadata.gz: 3e772062ad3ed9928eff044fa4e40bbd80dd0ab121a0c9cb2982969159bcf1ef
4
+ data.tar.gz: ad53b47275b4c5ad04a80faf5e05a06e5e7576cb33f7d232e502c30b1a4455c8
5
5
  SHA512:
6
- metadata.gz: 514eee78de1f75f5caaa55d285109d5e64a431a95732b3e6cabbee23e523ddd93b8b1e5e996c4ebcebce86e0ac54a15e0b88299a295515ce592254706f905e1a
7
- data.tar.gz: '092aaa352db1d9d8d1082393bfff927c35dbb349ffc980720835379c27297f7d47f817edb2eb29a22103bc4beda53c683150b05756444d75b823e673161358e1'
6
+ metadata.gz: 856164178d9887beec334f8a2fea1a51f60cf3f822a427a2a232847720a9c016250404e2af244962fe4033dbfaecfb447c3e746c3ebbe6861c77dab3ed6b3216
7
+ data.tar.gz: c1a13b5313f3b31c9a0aab24e708e556ac9b707ea53ed539f7aaaa7fa6264eb17ca2205be6efdc1690277acb3d8ea09536590d1b5864e133fa9d649740d1fc51
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.4] - 2024-10-27
4
+
5
+ - Fix incorrect `super` fallback in `RedBlackTree::DataDelegation#respond_to_missing?`
6
+
7
+ ## [0.1.3] - 2024-10-21
8
+
9
+ - Make `RedBlackTree#left_most_node` public
10
+ - Add `RedBlackTree#traverse_pre_order`
11
+ - Add `RedBlackTree#traverse_in_order`
12
+ - Add `RedBlackTree#traverse_post_order`
13
+ - Add `RedBlackTree#traverse_level_order`
14
+ - Add `RedBlackTree#traverse`, alias of `RedBlackTree#traverse_in_order`
15
+ - Extend `RedBlackTree#search` to accept a block
16
+ - Delegate missing `RedBlackTree::Node` methods to its `#data`
17
+
3
18
  ## [0.1.2] - 2024-09-08
4
19
 
5
20
  - Fix a bunch of issues in `RedBlackTree#insert!` and `RedBlackTree#delete!` algorithms
data/README.md CHANGED
@@ -37,13 +37,154 @@ until tree.empty?
37
37
  end
38
38
  ```
39
39
 
40
+ ## Performance
41
+
42
+ > [!NOTE]
43
+ > Red-black trees are designed for specific use cases and are not intended as a general-purpose data structure. The
44
+ comparisons below are provided merely to illustrate the performance characteristics of the gem. However, it is important
45
+ to note that the benchmarks do not take into account the self-balancing nature of red-black trees.
46
+
47
+ ### Sort and iterate 10,000 elements
48
+
49
+ ```ruby
50
+ require 'benchmark/ips'
51
+
52
+ Work = Struct.new :min_latency, keyword_init: true
53
+
54
+ class WorkNode < RedBlackTree::Node
55
+ def <=> other
56
+ self.data.min_latency <=> other.data.min_latency
57
+ end
58
+ end
59
+
60
+ sample_data = 10_000.times.map { Work.new(min_latency: rand(1_000)) }
61
+
62
+ Benchmark.ips do |x|
63
+ x.report("RedBlackTree") do
64
+ tree = RedBlackTree.new
65
+ sample_data.each { |work| tree << WorkNode.new(work); }
66
+ tree.shift until tree.empty?
67
+ end
68
+
69
+ # 1:1 comparison
70
+ x.report("Array (gradual sort)") do
71
+ array = []
72
+ sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
73
+ array.shift until array.empty?
74
+ end
75
+
76
+ x.report("Array (single sort)") do
77
+ array = []
78
+ sample_data.each { |work| array << work; }
79
+ array.sort_by!(&:min_latency)
80
+ array.shift until array.empty?
81
+ end
82
+
83
+ x.compare!
84
+ end
85
+
86
+ #=> ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin24]
87
+ #=> Warming up --------------------------------------
88
+ #=> RedBlackTree 1.000 i/100ms
89
+ #=> Array (gradual sort) 1.000 i/100ms
90
+ #=> Array (single sort) 78.000 i/100ms
91
+ #=> Calculating -------------------------------------
92
+ #=> RedBlackTree 5.417 (± 0.0%) i/s (184.61 ms/i) - 28.000 in 5.172532s
93
+ #=> Array (gradual sort) 0.268 (± 0.0%) i/s (3.74 s/i) - 2.000 in 7.473005s
94
+ #=> Array (single sort) 768.691 (± 2.2%) i/s (1.30 ms/i) - 3.900k in 5.076337s
95
+ #=>
96
+ #=> Comparison:
97
+ #=> Array (single sort): 768.7 i/s
98
+ #=> RedBlackTree: 5.4 i/s - 141.91x slower
99
+ #=> Array (gradual sort): 0.3 i/s - 2872.03x slower
100
+ ```
101
+
102
+ ### Sort and search 10,000 elements
103
+
104
+ ```ruby
105
+ require 'benchmark/ips'
106
+
107
+ Work = Struct.new :min_latency, keyword_init: true
108
+
109
+ class WorkNode < RedBlackTree::Node
110
+ def <=> other
111
+ self.data.min_latency <=> other.data.min_latency
112
+ end
113
+ end
114
+
115
+ sample_data = 10_000.times.map { Work.new(min_latency: rand(1_000)) }
116
+ search_sample = sample_data.sample
117
+
118
+ Benchmark.ips do |x|
119
+ x.report("RedBlackTree#search") do
120
+ tree = RedBlackTree.new
121
+ sample_data.each { |work| tree << WorkNode.new(work); }
122
+ raise unless tree.search search_sample
123
+ end
124
+
125
+ # 1:1 comparison
126
+ x.report("Array#find (gradual sort)") do
127
+ array = []
128
+ sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
129
+ raise unless array.find { |work| work.min_latency == search_sample.min_latency }
130
+ end
131
+
132
+ x.report("Array#find (single sort)") do
133
+ array = []
134
+ sample_data.each { |work| array << work; }
135
+ array.sort_by!(&:min_latency)
136
+ raise unless array.find { |work| work.min_latency == search_sample.min_latency }
137
+ end
138
+
139
+ # 1:1 comparison
140
+ x.report("Array#bsearch (gradual sort)") do
141
+ array = []
142
+ sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
143
+ raise unless array.bsearch { |work| search_sample.min_latency <= work.min_latency }
144
+ end
145
+
146
+ x.report("Array#bsearch (single sort)") do
147
+ array = []
148
+ sample_data.each { |work| array << work; }
149
+ array.sort_by!(&:min_latency)
150
+ raise unless array.bsearch { |work| search_sample.min_latency <= work.min_latency }
151
+ end
152
+
153
+ x.compare!
154
+ end
155
+
156
+ #=> ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin24]
157
+ #=> Warming up --------------------------------------
158
+ #=> RedBlackTree#search 1.000 i/100ms
159
+ #=> Array#find (gradual sort)
160
+ #=> 1.000 i/100ms
161
+ #=> Array#find (single sort)
162
+ #=> 69.000 i/100ms
163
+ #=> Array#bsearch (gradual sort)
164
+ #=> 1.000 i/100ms
165
+ #=> Array#bsearch (single sort)
166
+ #=> 89.000 i/100ms
167
+ #=> Calculating -------------------------------------
168
+ #=> RedBlackTree#search 12.926 (± 0.0%) i/s (77.36 ms/i) - 65.000 in 5.030736s
169
+ #=> Array#find (gradual sort)
170
+ #=> 0.262 (± 0.0%) i/s (3.81 s/i) - 2.000 in 7.623953s
171
+ #=> Array#find (single sort)
172
+ #=> 690.631 (± 1.0%) i/s (1.45 ms/i) - 3.519k in 5.095823s
173
+ #=> Array#bsearch (gradual sort)
174
+ #=> 0.267 (± 0.0%) i/s (3.75 s/i) - 2.000 in 7.492482s
175
+ #=> Array#bsearch (single sort)
176
+ #=> 895.413 (± 1.7%) i/s (1.12 ms/i) - 4.539k in 5.070590s
177
+ #=>
178
+ #=> Comparison:
179
+ #=> Array#bsearch (single sort): 895.4 i/s
180
+ #=> Array#find (single sort): 690.6 i/s - 1.30x slower
181
+ #=> RedBlackTree#search: 12.9 i/s - 69.27x slower
182
+ #=> Array#bsearch (gradual sort): 0.3 i/s - 3354.39x slower
183
+ #=> Array#find (gradual sort): 0.3 i/s - 3412.57x slower
184
+ ```
185
+
40
186
  ## WIP Features
41
187
 
42
- - `RedBlackTree#traverse_in_order`
43
- - `RedBlackTree#traverse_pre_order`
44
- - `RedBlackTree#traverse_post_order`
45
- - `RedBlackTree#traverse_level_order`
46
- - `RedBlackTree#traverse` (default in-order)
47
188
  - `RedBlackTree#max`
48
189
  - `RedBlackTree#height`
49
190
  - `RedBlackTree#clear`
@@ -14,7 +14,6 @@ class RedBlackTree
14
14
  # @return [RedBlackTree::Node, nil] the root node
15
15
  attr_reader :root
16
16
 
17
- # @private
18
17
  # @return [RedBlackTree::Node, nil] the left most node
19
18
  attr_reader :left_most_node
20
19
  alias_method :min, :left_most_node
@@ -78,8 +77,8 @@ class RedBlackTree
78
77
  #
79
78
  # @private ideally this is only used internally e.g. in #<< which has context on the ideal location for the node
80
79
  # @param node [RedBlackTree::Node] the node to be inserted
81
- # @param target_parent [RedBlackTree::Node, nil] the parent under which the node should be inserted
82
- # @param direction ["left", "right", nil] the direction of the node relative to the parent
80
+ # @param target_parent [RedBlackTree::Node] the parent under which the node should be inserted
81
+ # @param direction ["left", "right"] the direction of the node relative to the parent
83
82
  # @return [RedBlackTree] self
84
83
  def insert! node, target_parent = nil, direction = nil
85
84
  raise ArgumentError, "cannot insert leaf node" if node.instance_of? LeafNode
@@ -221,11 +220,18 @@ class RedBlackTree
221
220
  # Searches for a node which matches the given data/value.
222
221
  #
223
222
  # @param data [any] the data to search for
223
+ # @yield [RedBlackTree::Node] the block to be used for comparison
224
224
  # @return [RedBlackTree::Node, nil] the matching node
225
- def search data
226
- raise ArgumentError, "data must be provided for search" unless data
225
+ def search data = nil, &block
226
+ if block_given?
227
+ raise ArgumentError, "provide either data or block, not both" if data
228
+
229
+ _search_by_block block, @root
230
+ else
231
+ raise ArgumentError, "data must be provided for search" unless data
227
232
 
228
- _search data, @root
233
+ _search_by_data data, @root
234
+ end
229
235
  end
230
236
 
231
237
  # Returns true if there is a node which matches the given data/value, and false if there is not.
@@ -235,6 +241,64 @@ class RedBlackTree
235
241
  !!search(data)
236
242
  end
237
243
 
244
+ # Traverses the tree in pre-order and yields each node.
245
+ #
246
+ # @param node [RedBlackTree::Node] the node to start the traversal from
247
+ # @yield [RedBlackTree::Node] the block to be executed for each node
248
+ # @return [void]
249
+ def traverse_pre_order node = @root, &block
250
+ return if node.nil? || node.leaf?
251
+
252
+ block.call node
253
+ traverse_pre_order node.left, &block
254
+ traverse_pre_order node.right, &block
255
+ end
256
+
257
+ # Traverses the tree in in-order and yields each node.
258
+ #
259
+ # @param node [RedBlackTree::Node] the node to start the traversal from
260
+ # @yield [RedBlackTree::Node] the block to be executed for each node
261
+ # @return [void]
262
+ def traverse_in_order node = @root, &block
263
+ return if node.nil? || node.leaf?
264
+
265
+ traverse_in_order node.left, &block
266
+ block.call node
267
+ traverse_in_order node.right, &block
268
+ end
269
+ alias_method :traverse, :traverse_in_order
270
+
271
+ # Traverses the tree in post-order and yields each node.
272
+ #
273
+ # @param node [RedBlackTree::Node] the node to start the traversal from
274
+ # @yield [RedBlackTree::Node] the block to be executed for each node
275
+ # @return [void]
276
+ def traverse_post_order node = @root, &block
277
+ return if node.nil? || node.leaf?
278
+
279
+ traverse_post_order node.left, &block
280
+ traverse_post_order node.right, &block
281
+ block.call node
282
+ end
283
+
284
+ # Traverses the tree in level-order and yields each node.
285
+ #
286
+ # @yield [RedBlackTree::Node] the block to be executed for each node
287
+ # @return [void]
288
+ def traverse_level_order &block
289
+ return if @root.nil?
290
+
291
+ queue = [@root]
292
+ until queue.empty?
293
+ node = queue.shift
294
+ next if node.nil? || node.leaf?
295
+
296
+ block.call node
297
+ queue << node.left
298
+ queue << node.right
299
+ end
300
+ end
301
+
238
302
  private
239
303
 
240
304
  # Rotates a (sub-)tree starting from the given node in the given direction.
@@ -263,15 +327,23 @@ class RedBlackTree
263
327
  opp_direction_child
264
328
  end
265
329
 
266
- def _search data, current_node
267
- return if current_node.nil? || current_node.leaf?
268
- return current_node if data == current_node.data
330
+ def _search_by_block block, node
331
+ traverse node do |current|
332
+ next if current.leaf?
333
+
334
+ return current if block.call current
335
+ end
336
+ end
337
+
338
+ def _search_by_data data, node
339
+ return if node.nil? || node.leaf?
340
+ return node if data == node.data
269
341
 
270
- mock_node = current_node.class.new data
271
- if mock_node >= current_node
272
- _search data, current_node.right
342
+ mock_node = node.class.new data
343
+ if mock_node >= node
344
+ _search_by_data data, node.right
273
345
  else
274
- _search data, current_node.left
346
+ _search_by_data data, node.left
275
347
  end
276
348
  end
277
349
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedBlackTree
4
+ module DataDelegation
5
+ def method_missing method_name, *args, &block
6
+ if @data.respond_to? method_name
7
+ @data.public_send method_name, *args, &block
8
+ else
9
+ super
10
+ end
11
+ end
12
+
13
+ def respond_to_missing? method_name, include_private = false
14
+ (@data.respond_to? method_name, include_private) || super
15
+ end
16
+ end
17
+ end
@@ -7,7 +7,7 @@ class RedBlackTree
7
7
  case direction
8
8
  when Node::LEFT then @left
9
9
  when Node::RIGHT then @right
10
- else raise ArgumentError, "Direction must be one of #{DIRECTIONS}"
10
+ else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}"
11
11
  end
12
12
  end
13
13
 
@@ -15,7 +15,7 @@ class RedBlackTree
15
15
  case direction
16
16
  when Node::LEFT then @left = node
17
17
  when Node::RIGHT then @right = node
18
- else raise ArgumentError, "Direction must be one of #{DIRECTIONS}"
18
+ else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}"
19
19
  end
20
20
  end
21
21
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "utils"
4
4
  require_relative "node/leaf_node_comparable"
5
+ require_relative "node/data_delegation"
5
6
  require_relative "node/left_right_element_referencers"
6
7
 
7
8
  class RedBlackTree
@@ -13,6 +14,7 @@ class RedBlackTree
13
14
  end
14
15
 
15
16
  include Comparable
17
+ include DataDelegation
16
18
 
17
19
  # @return [any] the data/value representing the node
18
20
  attr_reader :data
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedBlackTree
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: red-black-tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-14 00:00:00.000000000 Z
11
+ date: 2024-10-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -23,6 +23,7 @@ files:
23
23
  - README.md
24
24
  - lib/red-black-tree.rb
25
25
  - lib/red_black_tree/node.rb
26
+ - lib/red_black_tree/node/data_delegation.rb
26
27
  - lib/red_black_tree/node/leaf_node.rb
27
28
  - lib/red_black_tree/node/leaf_node_comparable.rb
28
29
  - lib/red_black_tree/node/left_right_element_referencers.rb
@@ -51,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
52
  - !ruby/object:Gem::Version
52
53
  version: '0'
53
54
  requirements: []
54
- rubygems_version: 3.5.15
55
+ rubygems_version: 3.5.16
55
56
  signing_key:
56
57
  specification_version: 4
57
58
  summary: Red-Black Tree Data Structure for Ruby