rbtree-ruby 0.1.2 → 0.1.4
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/CHANGELOG.md +21 -0
- data/README.md +37 -2
- data/lib/rbtree/version.rb +1 -1
- data/lib/rbtree.rb +47 -15
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d5898bb2e8aaea4617747c5f4024ab023a1afeabba3abafeaf2b86bb6afa986
|
|
4
|
+
data.tar.gz: c6651a33b8e66ee3528dc8ab3abf2359f3e2647fc27491e8bd5083ef08844d77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7d1619486160150a91842631953842f3bddcb90213d415ec9aaca846184220ada9494cdae1eadf05ae5d523c5646b1ce19590d0b60c9df8ddd0c0f01a40ede7
|
|
7
|
+
data.tar.gz: ed2f20ab619e64c6daf16f4484fc52b0bc724e3b9b0f8b08c3a61921cbde5077f99beace823afbf3133c74ab3dfd838b48c5b09392bcaaee4fc0a930eb507589
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.4] - 2026-01-13
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Memory Pool**: Implemented internal node recycling mechanism
|
|
12
|
+
- Reuse `RBTree::Node` objects instead of creating new ones for every insertion
|
|
13
|
+
- Significantly reduces GC pressure during frequent insert/delete operations
|
|
14
|
+
- Automatically manages pool size (grows on delete, shrinks on insert)
|
|
15
|
+
- Fully transparent to the user
|
|
16
|
+
|
|
17
|
+
## [0.1.3] - 2026-01-13
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Hybrid Hash Index**: Added internal `@hash_index` for O(1) key lookup
|
|
21
|
+
- `get(key)` and `has_key?(key)` now use hash lookup instead of tree traversal
|
|
22
|
+
- Search performance now matches Hash (within 10-20% overhead)
|
|
23
|
+
- Benchmark: 10M elements, 1M lookups - RBTree 290ms vs Hash 271ms
|
|
24
|
+
- Both RBTree and MultiRBTree benefit from this optimization
|
|
25
|
+
- Memory trade-off: ~1.5x due to hash index storage
|
|
26
|
+
|
|
8
27
|
## [0.1.2] - 2026-01-13
|
|
9
28
|
|
|
10
29
|
### Changed
|
|
@@ -49,4 +68,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
49
68
|
- ASCII diagrams for tree rotation operations
|
|
50
69
|
- MIT License (Copyright © 2026 Masahito Suzuki)
|
|
51
70
|
|
|
71
|
+
[0.1.4]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.4
|
|
72
|
+
[0.1.3]: https://github.com/firelzrd/rbtree-ruby/releases/tag/v0.1.3
|
|
52
73
|
[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(
|
|
137
|
-
- `
|
|
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:
|
data/lib/rbtree/version.rb
CHANGED
data/lib/rbtree.rb
CHANGED
|
@@ -92,6 +92,8 @@ class RBTree
|
|
|
92
92
|
@nil_node.right = @nil_node
|
|
93
93
|
@root = @nil_node
|
|
94
94
|
@min_node = @nil_node
|
|
95
|
+
@hash_index = {} # Hash index for O(1) key lookup
|
|
96
|
+
@node_pool = [] # Memory pool for recycling nodes
|
|
95
97
|
@size = 0
|
|
96
98
|
|
|
97
99
|
if args.any?
|
|
@@ -139,7 +141,7 @@ class RBTree
|
|
|
139
141
|
# tree.has_key?(1) # => true
|
|
140
142
|
# tree.has_key?(3) # => false
|
|
141
143
|
def has_key?(key)
|
|
142
|
-
|
|
144
|
+
@hash_index.key?(key)
|
|
143
145
|
end
|
|
144
146
|
|
|
145
147
|
# Retrieves the value associated with the given key.
|
|
@@ -152,8 +154,7 @@ class RBTree
|
|
|
152
154
|
# tree[2] # => "two"
|
|
153
155
|
# tree[3] # => nil
|
|
154
156
|
def get(key)
|
|
155
|
-
|
|
156
|
-
n == @nil_node ? nil : n.value
|
|
157
|
+
@hash_index[key]&.value
|
|
157
158
|
end
|
|
158
159
|
alias_method :[], :get
|
|
159
160
|
|
|
@@ -188,7 +189,7 @@ class RBTree
|
|
|
188
189
|
x = x.right
|
|
189
190
|
end
|
|
190
191
|
end
|
|
191
|
-
z =
|
|
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
|
|
@@ -207,6 +208,7 @@ class RBTree
|
|
|
207
208
|
@min_node = z
|
|
208
209
|
end
|
|
209
210
|
|
|
211
|
+
@hash_index[key] = z # Add to hash index
|
|
210
212
|
true
|
|
211
213
|
end
|
|
212
214
|
alias_method :[]=, :insert
|
|
@@ -232,6 +234,7 @@ class RBTree
|
|
|
232
234
|
def clear
|
|
233
235
|
@root = @nil_node
|
|
234
236
|
@min_node = @nil_node
|
|
237
|
+
@hash_index.clear
|
|
235
238
|
@size = 0
|
|
236
239
|
self
|
|
237
240
|
end
|
|
@@ -622,8 +625,8 @@ class RBTree
|
|
|
622
625
|
# @param key [Object] the key to delete
|
|
623
626
|
# @return [Object, nil] the value of the deleted node, or nil if not found
|
|
624
627
|
def delete_node(key)
|
|
625
|
-
z =
|
|
626
|
-
return nil
|
|
628
|
+
z = @hash_index.delete(key) # O(1) lookup and remove from index
|
|
629
|
+
return nil unless z
|
|
627
630
|
remove_node(z)
|
|
628
631
|
end
|
|
629
632
|
|
|
@@ -678,7 +681,9 @@ class RBTree
|
|
|
678
681
|
@min_node = next_min_node
|
|
679
682
|
end
|
|
680
683
|
|
|
681
|
-
z.value
|
|
684
|
+
value = z.value
|
|
685
|
+
release_node(z)
|
|
686
|
+
value
|
|
682
687
|
end
|
|
683
688
|
|
|
684
689
|
# Restores red-black tree properties after deletion.
|
|
@@ -898,6 +903,32 @@ class RBTree
|
|
|
898
903
|
y.parent = x
|
|
899
904
|
end
|
|
900
905
|
|
|
906
|
+
# Allocates a new node or recycles one from the pool.
|
|
907
|
+
# @return [Node]
|
|
908
|
+
def allocate_node(key, value, color, left, right, parent)
|
|
909
|
+
if (node = @node_pool.pop)
|
|
910
|
+
node.key = key
|
|
911
|
+
node.value = value
|
|
912
|
+
node.color = color
|
|
913
|
+
node.left = left
|
|
914
|
+
node.right = right
|
|
915
|
+
node.parent = parent
|
|
916
|
+
node
|
|
917
|
+
else
|
|
918
|
+
Node.new(key, value, color, left, right, parent)
|
|
919
|
+
end
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
# Releases a node back to the pool.
|
|
923
|
+
# @param node [Node] the node to release
|
|
924
|
+
def release_node(node)
|
|
925
|
+
node.left = nil
|
|
926
|
+
node.right = nil
|
|
927
|
+
node.parent = nil
|
|
928
|
+
node.value = nil # Help GC
|
|
929
|
+
@node_pool << node
|
|
930
|
+
end
|
|
931
|
+
|
|
901
932
|
# Recursively checks black height consistency.
|
|
902
933
|
#
|
|
903
934
|
# Verifies that:
|
|
@@ -936,12 +967,12 @@ end
|
|
|
936
967
|
# A Multi Red-Black Tree that allows duplicate keys.
|
|
937
968
|
#
|
|
938
969
|
# MultiRBTree extends RBTree to support multiple values per key. Each key maps to
|
|
939
|
-
#
|
|
970
|
+
# an array of values rather than a single value. The size reflects the total
|
|
940
971
|
# number of key-value pairs (not unique keys).
|
|
941
972
|
#
|
|
942
973
|
# == Features
|
|
943
974
|
#
|
|
944
|
-
# * Multiple values per key using
|
|
975
|
+
# * Multiple values per key using arrays
|
|
945
976
|
# * Separate methods for single deletion (`delete_one`) vs. all deletions (`delete`)
|
|
946
977
|
# * Values for each key maintain insertion order
|
|
947
978
|
# * min/max return first/last values respectively
|
|
@@ -1022,7 +1053,7 @@ class MultiRBTree < RBTree
|
|
|
1022
1053
|
x = x.right
|
|
1023
1054
|
end
|
|
1024
1055
|
end
|
|
1025
|
-
z =
|
|
1056
|
+
z = allocate_node(key, [value], Node::RED, @nil_node, @nil_node, @nil_node)
|
|
1026
1057
|
z.parent = y
|
|
1027
1058
|
if y == @nil_node
|
|
1028
1059
|
@root = z
|
|
@@ -1041,6 +1072,7 @@ class MultiRBTree < RBTree
|
|
|
1041
1072
|
@min_node = z
|
|
1042
1073
|
end
|
|
1043
1074
|
|
|
1075
|
+
@hash_index[key] = z # Add to hash index
|
|
1044
1076
|
true
|
|
1045
1077
|
end
|
|
1046
1078
|
|
|
@@ -1058,12 +1090,13 @@ class MultiRBTree < RBTree
|
|
|
1058
1090
|
# tree.delete_one(1) # => "first"
|
|
1059
1091
|
# tree.get(1) # => "second"
|
|
1060
1092
|
def delete_one(key)
|
|
1061
|
-
z =
|
|
1062
|
-
return nil
|
|
1093
|
+
z = @hash_index[key] # O(1) lookup
|
|
1094
|
+
return nil unless z
|
|
1063
1095
|
|
|
1064
1096
|
value = z.value.shift
|
|
1065
1097
|
@size -= 1
|
|
1066
1098
|
if z.value.empty?
|
|
1099
|
+
@hash_index.delete(key) # Remove from index when node removed
|
|
1067
1100
|
remove_node(z)
|
|
1068
1101
|
end
|
|
1069
1102
|
value
|
|
@@ -1082,8 +1115,8 @@ class MultiRBTree < RBTree
|
|
|
1082
1115
|
# vals = tree.delete(1) # removes both values
|
|
1083
1116
|
# vals.size # => 2
|
|
1084
1117
|
def delete(key)
|
|
1085
|
-
z =
|
|
1086
|
-
return nil
|
|
1118
|
+
z = @hash_index.delete(key) # O(1) lookup and remove from index
|
|
1119
|
+
return nil unless z
|
|
1087
1120
|
|
|
1088
1121
|
count = z.value.size
|
|
1089
1122
|
remove_node(z)
|
|
@@ -1257,4 +1290,3 @@ class RBTree::Node
|
|
|
1257
1290
|
# @return [Boolean] true if black, false otherwise
|
|
1258
1291
|
def black? = @color == BLACK
|
|
1259
1292
|
end
|
|
1260
|
-
|