rbtree-ruby 0.1.3 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc2846050227a83e7ae7bb63d1e6208ed39acc54de759c98e08d994685d1b067
4
- data.tar.gz: 2a28105ee3667fb794c1f5d32c396b21542f45bceb3c858c160d7389f6d2ae3b
3
+ metadata.gz: 56ddd66545f9e423b96066b12deea8e22fc86b35b7a97be056ebf91f80543e77
4
+ data.tar.gz: 10a3ec88b0a5f5cb199ac96267d7665bfee99ab4fb9cf89488790fe798120b05
5
5
  SHA512:
6
- metadata.gz: 22239d22c7b771a93bbb1811407d6ea6364ae21b22008d53d44e102188991b7299b6dacdcbc28a206dbfd21658d8081f964bf855b7c69c3d19d3312be49ffe3d
7
- data.tar.gz: 8f98487332a3cb77f478f745b775b148aff862ab80fc020d064cade6a84692a4e498854cce864b4d49806a760ee7c6d9c38e4e9ac3e5c7598444ead625e91eb7
6
+ metadata.gz: 1d521a30299f8c3db327d1b671d84113cf3c9e5adc90a78747e76b7480ef02ceb95d813a90eeebadfd32e4b8360b2d43fffd1ed1ac4d9314d8368cbe137ce7aa
7
+ data.tar.gz: 445a6cf0a99efdd0b5b4dc41dd86ec219e784cc3a2ecf150b4467e2619bcb08bd9b47d119d1c5310f43fdecdd7dc19941c3bfa4ae9e7b68d2b359819336f0a97
data/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.5] - 2026-01-13
9
+
10
+ ### Changed
11
+ - **Iterative Traversal**: Replaced recursive tree traversal with iterative approach
12
+ - `each`, `reverse_each`, and other traversal methods now use an explicit stack
13
+ - Prevents `SystemStackError` (stack overflow) on extremely deep trees
14
+ - Slightly improves iteration performance by removing recursion overhead
15
+ - Applies to both `RBTree` and `MultiRBTree`
16
+
17
+ ## [0.1.4] - 2026-01-13
18
+
19
+ ### Changed
20
+ - **Memory Pool**: Implemented internal node recycling mechanism
21
+ - Reuse `RBTree::Node` objects instead of creating new ones for every insertion
22
+ - Significantly reduces GC pressure during frequent insert/delete operations
23
+ - Automatically manages pool size (grows on delete, shrinks on insert)
24
+ - Fully transparent to the user
25
+
8
26
  ## [0.1.3] - 2026-01-13
9
27
 
10
28
  ### Changed
@@ -59,5 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
59
77
  - ASCII diagrams for tree rotation operations
60
78
  - MIT License (Copyright © 2026 Masahito Suzuki)
61
79
 
80
+ [0.1.5]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.5
81
+ [0.1.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.4
62
82
  [0.1.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.3
63
83
  [0.1.2]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.2
data/README.md CHANGED
@@ -133,12 +133,47 @@ All major operations run in **O(log n)** time:
133
133
 
134
134
  - `insert(key, value)` - O(log n)
135
135
  - `delete(key)` - O(log n)
136
- - `get(key)` / `[]` - O(log n)
137
- - `min` / `max` - O(1) for min, O(log n) for max
136
+ - `get(key)` / `[]` - **O(1)** (hybrid hash index)
137
+ - `has_key?` - **O(1)** (hybrid hash index)
138
+ - `min` - **O(1)**
139
+ - `max` - O(log n)
138
140
  - `shift` / `pop` - O(log n)
139
141
 
140
142
  Iteration over all elements takes O(n) time.
141
143
 
144
+ ### Memory Efficiency
145
+
146
+ RBTree uses an internal **Memory Pool** to recycle node objects.
147
+ - Significantly reduces Garbage Collection (GC) pressure during frequent insertions and deletions (e.g., in high-throughput queues).
148
+ - In benchmarks with 100,000 cyclic operations, **GC time was 0.0s** compared to significant pauses without pooling.
149
+
150
+ ### RBTree vs Hash vs Array
151
+
152
+ RBTree provides significant advantages for ordered operations:
153
+
154
+ | Operation | RBTree | Hash | Speedup |
155
+ |-----------|--------|------|---------|
156
+ | `min` / `max` | O(1) / O(log n) | O(n) | **~1000x faster** |
157
+ | Range queries (`between`, `lt`, `gt`) | O(log n + k) | O(n) | **10-100x faster** |
158
+ | Nearest key search | O(log n) | O(n) | **100x+ faster** |
159
+ | Ordered iteration | O(n), always sorted | Requires `sort` O(n log n) | **Free sorting** |
160
+ | Key lookup | O(1) | O(1) | Equal |
161
+
162
+ ### When to Use RBTree
163
+
164
+ ✅ **Use RBTree when you need:**
165
+ - Ordered iteration by key
166
+ - Fast min/max retrieval
167
+ - Range queries (`between`, `lt`, `gt`, `lte`, `gte`)
168
+ - Nearest key search
169
+ - Priority queue behavior (shift/pop by key order)
170
+
171
+ ✅ **Use Hash when you only need:**
172
+ - Fast key-value lookup (RBTree is now equally fast!)
173
+ - No ordering requirements
174
+
175
+ Run `ruby demo.rb` for a full benchmark demonstration.
176
+
142
177
  ## API Documentation
143
178
 
144
179
  Full RDoc documentation is available. Generate it locally with:
@@ -2,5 +2,5 @@
2
2
 
3
3
  class RBTree
4
4
  # The version of the rbtree-ruby gem
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.5"
6
6
  end
data/lib/rbtree.rb CHANGED
@@ -93,6 +93,7 @@ class RBTree
93
93
  @root = @nil_node
94
94
  @min_node = @nil_node
95
95
  @hash_index = {} # Hash index for O(1) key lookup
96
+ @node_pool = [] # Memory pool for recycling nodes
96
97
  @size = 0
97
98
 
98
99
  if args.any?
@@ -188,7 +189,7 @@ class RBTree
188
189
  x = x.right
189
190
  end
190
191
  end
191
- z = Node.new(key, value, Node::RED, @nil_node, @nil_node, @nil_node)
192
+ z = allocate_node(key, value, Node::RED, @nil_node, @nil_node, @nil_node)
192
193
  z.parent = y
193
194
  if y == @nil_node
194
195
  @root = z
@@ -464,10 +465,17 @@ class RBTree
464
465
  # @yield [key, value] each key-value pair in ascending order
465
466
  # @return [void]
466
467
  def traverse_asc(node, &block)
467
- return if node == @nil_node
468
- traverse_asc(node.left, &block)
469
- yield node.key, node.value
470
- traverse_asc(node.right, &block)
468
+ stack = []
469
+ current = node
470
+ while current != @nil_node || !stack.empty?
471
+ while current != @nil_node
472
+ stack << current
473
+ current = current.left
474
+ end
475
+ current = stack.pop
476
+ yield current.key, current.value
477
+ current = current.right
478
+ end
471
479
  end
472
480
 
473
481
  # Traverses the tree in descending order (reverse in-order traversal).
@@ -476,10 +484,17 @@ class RBTree
476
484
  # @yield [key, value] each key-value pair in descending order
477
485
  # @return [void]
478
486
  def traverse_desc(node, &block)
479
- return if node == @nil_node
480
- traverse_desc(node.right, &block)
481
- yield node.key, node.value
482
- traverse_desc(node.left, &block)
487
+ stack = []
488
+ current = node
489
+ while current != @nil_node || !stack.empty?
490
+ while current != @nil_node
491
+ stack << current
492
+ current = current.right
493
+ end
494
+ current = stack.pop
495
+ yield current.key, current.value
496
+ current = current.left
497
+ end
483
498
  end
484
499
 
485
500
  # Traverses nodes with keys less than the specified key.
@@ -680,7 +695,9 @@ class RBTree
680
695
  @min_node = next_min_node
681
696
  end
682
697
 
683
- z.value
698
+ value = z.value
699
+ release_node(z)
700
+ value
684
701
  end
685
702
 
686
703
  # Restores red-black tree properties after deletion.
@@ -900,6 +917,32 @@ class RBTree
900
917
  y.parent = x
901
918
  end
902
919
 
920
+ # Allocates a new node or recycles one from the pool.
921
+ # @return [Node]
922
+ def allocate_node(key, value, color, left, right, parent)
923
+ if (node = @node_pool.pop)
924
+ node.key = key
925
+ node.value = value
926
+ node.color = color
927
+ node.left = left
928
+ node.right = right
929
+ node.parent = parent
930
+ node
931
+ else
932
+ Node.new(key, value, color, left, right, parent)
933
+ end
934
+ end
935
+
936
+ # Releases a node back to the pool.
937
+ # @param node [Node] the node to release
938
+ def release_node(node)
939
+ node.left = nil
940
+ node.right = nil
941
+ node.parent = nil
942
+ node.value = nil # Help GC
943
+ @node_pool << node
944
+ end
945
+
903
946
  # Recursively checks black height consistency.
904
947
  #
905
948
  # Verifies that:
@@ -1024,7 +1067,7 @@ class MultiRBTree < RBTree
1024
1067
  x = x.right
1025
1068
  end
1026
1069
  end
1027
- z = Node.new(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
1070
+ z = allocate_node(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
1028
1071
  z.parent = y
1029
1072
  if y == @nil_node
1030
1073
  @root = z
@@ -1137,17 +1180,31 @@ class MultiRBTree < RBTree
1137
1180
  private
1138
1181
 
1139
1182
  def traverse_asc(node, &block)
1140
- return if node == @nil_node
1141
- traverse_asc(node.left, &block)
1142
- node.value.each { |v| yield node.key, v }
1143
- traverse_asc(node.right, &block)
1183
+ stack = []
1184
+ current = node
1185
+ while current != @nil_node || !stack.empty?
1186
+ while current != @nil_node
1187
+ stack << current
1188
+ current = current.left
1189
+ end
1190
+ current = stack.pop
1191
+ current.value.each { |v| yield current.key, v }
1192
+ current = current.right
1193
+ end
1144
1194
  end
1145
1195
 
1146
1196
  def traverse_desc(node, &block)
1147
- return if node == @nil_node
1148
- traverse_desc(node.right, &block)
1149
- node.value.reverse_each { |v| yield node.key, v }
1150
- traverse_desc(node.left, &block)
1197
+ stack = []
1198
+ current = node
1199
+ while current != @nil_node || !stack.empty?
1200
+ while current != @nil_node
1201
+ stack << current
1202
+ current = current.right
1203
+ end
1204
+ current = stack.pop
1205
+ current.value.reverse_each { |v| yield current.key, v }
1206
+ current = current.left
1207
+ end
1151
1208
  end
1152
1209
 
1153
1210
  def traverse_lt(node, key, &block)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbtree-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahito Suzuki