queap 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +156 -0
- data/Rakefile +14 -0
- data/benchmark/queap_bench.rb +30 -0
- data/lib/queap/queue.rb +100 -0
- data/lib/queap/tree.rb +225 -0
- data/lib/queap/version.rb +5 -0
- data/lib/queap.rb +13 -0
- data/sig/queap.rbs +4 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e7e14026a37ca2d8ef67eb8e7310b30333604b5b49f583406117c1a6b2beea94
|
4
|
+
data.tar.gz: 37e1be46c16584da9d292270c6ecba8117a58e8ace1ef33e52d1fb89f1974894
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d627a6b824ca2b00639024cba91b0d717a5c3a8b0babef20f748e8e80d46ab8cbb4b7b0e85000885244f0160b9e8cb9b1ebfa9a5685ddef55765ff7b92d2799c
|
7
|
+
data.tar.gz: e518d807fad71698cc230d134952379f7e844cccc1cb16d96e6028fbe908a8255f08232a94d4fa7dcfa34f2b3735c692c39ec5966d798f8edae72867dd05a716
|
data/.rspec
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Jeffrey Crowell
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# Queap
|
2
|
+
|
3
|
+
[](https://rubygems.org/gems/queap)
|
4
|
+
[](https://github.com/crowell/queap/actions/workflows/ci.yml)
|
5
|
+
[](LICENSE.txt)
|
6
|
+
|
7
|
+
**Queap** is a pure‑Ruby implementation of the *queap* data structure
|
8
|
+
a meld of queue and heap that gives
|
9
|
+
|
10
|
+
| Operation | Amortised | Worst‑case |
|
11
|
+
|------------------|-----------|-----------|
|
12
|
+
| `insert(x)` | **O(1)** | O(k)\* |
|
13
|
+
| `minimum` | **O(1)** | O(1) |
|
14
|
+
| `delete_min` | **O(log n)** | O(log n) |
|
15
|
+
| `delete(elem)` | **O(log n)** | O(log n) |
|
16
|
+
|
17
|
+
\*`k` is the number of items flushed from the buffer list into the tree—
|
18
|
+
in steady state this cost is spread out, so the amortised cost per insert is constant.
|
19
|
+
|
20
|
+
Under the hood Queap combines:
|
21
|
+
|
22
|
+
* A simple buffer list for **O(1) inserts**.
|
23
|
+
* A left‑leaning Red‑Black tree for **log‑time deletions** (isomorphic to a 2‑4 tree).
|
24
|
+
* A cached pointer to the list‑minimum so `minimum` never touches the tree.
|
25
|
+
|
26
|
+
---
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Add to your Gemfile:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem "queap", "~> 0.2"
|
34
|
+
```
|
35
|
+
|
36
|
+
or install globally:
|
37
|
+
|
38
|
+
```console
|
39
|
+
$ gem install queap
|
40
|
+
```
|
41
|
+
|
42
|
+
Supported Rubies: MRI 3.1 – 3.3, TruffleRuby, JRuby.
|
43
|
+
|
44
|
+
---
|
45
|
+
|
46
|
+
## Quick start
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
require "queap"
|
50
|
+
|
51
|
+
q = Queap.new # same as Queap::Queue.new
|
52
|
+
[5, 3, 9, 1].each { |k| q.insert(Queap::Queue::Element.new(key: k)) }
|
53
|
+
|
54
|
+
q.minimum.key # => 1
|
55
|
+
q.delete_min.key # => 1
|
56
|
+
q.minimum.key # => 3
|
57
|
+
```
|
58
|
+
|
59
|
+
---
|
60
|
+
|
61
|
+
## API
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
q = Queap.new
|
65
|
+
elem = q.insert(Queap::Queue::Element.new(key: 42))
|
66
|
+
q.size # => 1
|
67
|
+
|
68
|
+
q.delete(elem) # arbitrary deletion
|
69
|
+
q.empty? # => true
|
70
|
+
```
|
71
|
+
|
72
|
+
**Element** is a tiny struct with a single mandatory field:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
Element.new(key: <comparable>)
|
76
|
+
```
|
77
|
+
|
78
|
+
Anything that implements `<=>` works—numbers, strings, structs, etc.
|
79
|
+
|
80
|
+
---
|
81
|
+
|
82
|
+
## Performance
|
83
|
+
|
84
|
+
A micro‑benchmark on Ruby 3.3 (M1 Pro macOS 15):
|
85
|
+
```console
|
86
|
+
Benchmarking with n = 1000
|
87
|
+
ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin24]
|
88
|
+
Warming up --------------------------------------
|
89
|
+
insert 69.000 i/100ms
|
90
|
+
minimum 324.493k i/100ms
|
91
|
+
delete_min 1.113M i/100ms
|
92
|
+
Calculating -------------------------------------
|
93
|
+
insert 687.073 (± 2.3%) i/s (1.46 ms/i) - 3.450k in 5.024233s
|
94
|
+
minimum 3.242M (± 4.4%) i/s (308.42 ns/i) - 16.225M in 5.015994s
|
95
|
+
delete_min 10.982M (± 0.8%) i/s (91.06 ns/i) - 55.651M in 5.067814s
|
96
|
+
|
97
|
+
Comparison:
|
98
|
+
delete_min: 10981987.0 i/s
|
99
|
+
minimum: 3242293.1 i/s - 3.39x slower
|
100
|
+
insert: 687.1 i/s - 15983.72x slower
|
101
|
+
```
|
102
|
+
|
103
|
+
Such numbers are competitive with `SortedSet`/`RBTree` gems while giving strictly better
|
104
|
+
insertion complexity.
|
105
|
+
|
106
|
+
Run your own benchmarks:
|
107
|
+
|
108
|
+
```console
|
109
|
+
$ rake benchmark
|
110
|
+
```
|
111
|
+
|
112
|
+
---
|
113
|
+
|
114
|
+
## Algorithm background
|
115
|
+
|
116
|
+
In essence, it keeps a *buffered queue* of recent inserts and a *balanced tree* of older
|
117
|
+
items. Deletions that hit the buffer trigger a one‑off flush; otherwise they hit the tree.
|
118
|
+
|
119
|
+
---
|
120
|
+
|
121
|
+
## Development
|
122
|
+
|
123
|
+
```console
|
124
|
+
$ git clone https://github.com/crowell/queap.git
|
125
|
+
$ cd queap
|
126
|
+
$ bundle install
|
127
|
+
$ bundle exec rspec # run test suite
|
128
|
+
```
|
129
|
+
|
130
|
+
RuboCop and RSpec run automatically on every push via GitHub Actions.
|
131
|
+
|
132
|
+
---
|
133
|
+
|
134
|
+
## Contributing
|
135
|
+
|
136
|
+
Bug reports and pull requests are welcome on GitHub at
|
137
|
+
<https://github.com/crowell/queap>.
|
138
|
+
|
139
|
+
1. Fork the project
|
140
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
141
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
142
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
143
|
+
5. Open a pull request
|
144
|
+
|
145
|
+
Please run `bundle exec rspec && rubocop` before submitting.
|
146
|
+
|
147
|
+
---
|
148
|
+
|
149
|
+
## License
|
150
|
+
|
151
|
+
The gem is available as open source under the terms of the
|
152
|
+
[MIT License](LICENSE.txt).
|
153
|
+
|
154
|
+
---
|
155
|
+
|
156
|
+
© 2025 Jeffrey Crowell
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task default: :spec
|
9
|
+
|
10
|
+
desc "Run micro-benchmarks (default n = 1_000)"
|
11
|
+
task :benchmark, [:n] do |_, args|
|
12
|
+
n = args[:n] || 1_000
|
13
|
+
exec "bundle exec ruby benchmark/queap_bench.rb #{n}"
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Run with:
|
4
|
+
# bundle exec ruby benchmark/queap_bench.rb [n]
|
5
|
+
#
|
6
|
+
# n defaults to 1_000. Use 1_000_000 for a heavier run.
|
7
|
+
|
8
|
+
require "benchmark/ips"
|
9
|
+
require "queap"
|
10
|
+
|
11
|
+
N = (ARGV.shift || 1_000).to_i
|
12
|
+
STDERR.puts "Benchmarking with n = #{N}"
|
13
|
+
|
14
|
+
Benchmark.ips do |x|
|
15
|
+
# ------------------------------------------------------------------
|
16
|
+
x.report("insert") do
|
17
|
+
q = Queap.new
|
18
|
+
N.times { |i| q.insert(Queap::Queue::Element.new(key: i)) }
|
19
|
+
end
|
20
|
+
|
21
|
+
q_for_min = Queap.new
|
22
|
+
N.times { |i| q_for_min.insert(Queap::Queue::Element.new(key: i)) }
|
23
|
+
x.report("minimum") { q_for_min.minimum }
|
24
|
+
|
25
|
+
q_for_del = Queap.new
|
26
|
+
N.times { |i| q_for_del.insert(Queap::Queue::Element.new(key: i)) }
|
27
|
+
x.report("delete_min") { q_for_del.delete_min }
|
28
|
+
|
29
|
+
x.compare!
|
30
|
+
end
|
data/lib/queap/queue.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "tree"
|
4
|
+
|
5
|
+
module Queap
|
6
|
+
# q = Queap::Queue.new
|
7
|
+
# elem = q.insert(42)
|
8
|
+
# q.minimum # => elem
|
9
|
+
# q.delete_min # => elem
|
10
|
+
#
|
11
|
+
# Elements are small structs that remember whether they currently sit in the
|
12
|
+
# buffer list (O(1) insertion area) or inside the balanced search tree.
|
13
|
+
class Queue
|
14
|
+
Element = Struct.new(:key, :in_list, :node, keyword_init: true) do
|
15
|
+
include Comparable
|
16
|
+
def <=>(other) = key <=> other.key
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :size
|
20
|
+
|
21
|
+
BUFFER_FLUSH_FACTOR = 2 # flush when list grows to n / factor
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@list = [] # simple dynamic array works fine; we always scan O(k)
|
25
|
+
@min_l = nil
|
26
|
+
@tree = RBTree.new
|
27
|
+
@size = 0
|
28
|
+
end
|
29
|
+
|
30
|
+
# O(1) amortised
|
31
|
+
def insert(x)
|
32
|
+
raise ArgumentError, "Element must respond to #<=>" unless x.respond_to?(:<=>)
|
33
|
+
|
34
|
+
x.in_list = true
|
35
|
+
@list << x
|
36
|
+
@min_l = x if @min_l.nil? || x < @min_l
|
37
|
+
@size += 1
|
38
|
+
maybe_flush
|
39
|
+
x
|
40
|
+
end
|
41
|
+
|
42
|
+
# O(1)
|
43
|
+
def minimum
|
44
|
+
tmin = @tree.min&.key
|
45
|
+
return @min_l if tmin.nil? || (@min_l && @min_l < tmin)
|
46
|
+
|
47
|
+
tmin
|
48
|
+
end
|
49
|
+
|
50
|
+
# O(log n)
|
51
|
+
def delete_min
|
52
|
+
m = minimum
|
53
|
+
return nil unless m
|
54
|
+
|
55
|
+
delete(m)
|
56
|
+
m
|
57
|
+
end
|
58
|
+
|
59
|
+
# O(log n) if element in tree, otherwise amortised O(k) for the one-off flush
|
60
|
+
def delete(element)
|
61
|
+
if element.in_list
|
62
|
+
flush_buffer!
|
63
|
+
# element’s node was set during insert_all
|
64
|
+
end
|
65
|
+
@tree.delete(element.node)
|
66
|
+
@size -= 1
|
67
|
+
recompute_min_l if element.equal?(@min_l)
|
68
|
+
element
|
69
|
+
end
|
70
|
+
|
71
|
+
def empty? = @size.zero?
|
72
|
+
|
73
|
+
# ----------------------------------------------------------------------
|
74
|
+
private
|
75
|
+
|
76
|
+
def flush_threshold
|
77
|
+
# k ≤ n; we flush when buffer length > n / F.
|
78
|
+
(@size / BUFFER_FLUSH_FACTOR).clamp(1, @size)
|
79
|
+
end
|
80
|
+
|
81
|
+
def maybe_flush
|
82
|
+
flush_buffer! if @list.length >= flush_threshold
|
83
|
+
end
|
84
|
+
|
85
|
+
def flush_buffer!
|
86
|
+
@list.each do |elem|
|
87
|
+
next unless elem.in_list
|
88
|
+
|
89
|
+
elem.in_list = false
|
90
|
+
elem.node = @tree.insert(elem)
|
91
|
+
end
|
92
|
+
@list.clear
|
93
|
+
@min_l = nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def recompute_min_l
|
97
|
+
@min_l = @list.min
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/queap/tree.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Queap
|
4
|
+
# Internal balanced BST. A classic left-leaning Red–Black tree gives the
|
5
|
+
# same asymptotics as a 2-4 tree but is ~40 % less code and copies well to
|
6
|
+
# Ruby. Public surface is deliberately tiny: insert, delete(node) and min.
|
7
|
+
class RBTree
|
8
|
+
Node = Struct.new(:key, :left, :right, :parent, :red, keyword_init: true)
|
9
|
+
attr_reader :root
|
10
|
+
|
11
|
+
# ---- helpers ----------------------------------------------------------
|
12
|
+
|
13
|
+
def min(node = root)
|
14
|
+
return nil unless node
|
15
|
+
|
16
|
+
node = node.left while node.left
|
17
|
+
node
|
18
|
+
end
|
19
|
+
|
20
|
+
def insert(key)
|
21
|
+
n = Node.new(key:, red: true)
|
22
|
+
y = nil
|
23
|
+
x = @root
|
24
|
+
while x
|
25
|
+
y = x
|
26
|
+
x = key < x.key ? x.left : x.right
|
27
|
+
end
|
28
|
+
n.parent = y
|
29
|
+
if y.nil?
|
30
|
+
@root = n
|
31
|
+
elsif key < y.key
|
32
|
+
y.left = n
|
33
|
+
else
|
34
|
+
y.right = n
|
35
|
+
end
|
36
|
+
insert_fixup(n)
|
37
|
+
n
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(node)
|
41
|
+
return unless node
|
42
|
+
|
43
|
+
y = node
|
44
|
+
y_orig = y.red
|
45
|
+
if node.left.nil?
|
46
|
+
x = node.right
|
47
|
+
transplant(node, node.right)
|
48
|
+
elsif node.right.nil?
|
49
|
+
x = node.left
|
50
|
+
transplant(node, node.left)
|
51
|
+
else
|
52
|
+
y = min(node.right)
|
53
|
+
y_orig = y.red
|
54
|
+
x = y.right
|
55
|
+
if y.parent.equal?(node)
|
56
|
+
x&.parent = y
|
57
|
+
else
|
58
|
+
transplant(y, y.right)
|
59
|
+
y.right = node.right
|
60
|
+
y.right.parent = y
|
61
|
+
end
|
62
|
+
transplant(node, y)
|
63
|
+
y.left = node.left
|
64
|
+
y.left.parent = y
|
65
|
+
y.red = node.red
|
66
|
+
end
|
67
|
+
delete_fixup(x) unless y_orig
|
68
|
+
x
|
69
|
+
end
|
70
|
+
|
71
|
+
# ---- private ----------------------------------------------------------
|
72
|
+
private
|
73
|
+
|
74
|
+
def red?(n) = n&.red
|
75
|
+
def black?(n) = !red?(n)
|
76
|
+
|
77
|
+
def left_rotate(x)
|
78
|
+
y = x.right
|
79
|
+
x.right = y.left
|
80
|
+
y.left&.parent = x
|
81
|
+
y.parent = x.parent
|
82
|
+
if x.parent.nil?
|
83
|
+
@root = y
|
84
|
+
elsif x.equal?(x.parent.left)
|
85
|
+
x.parent.left = y
|
86
|
+
else
|
87
|
+
x.parent.right = y
|
88
|
+
end
|
89
|
+
y.left = x
|
90
|
+
x.parent = y
|
91
|
+
end
|
92
|
+
|
93
|
+
def right_rotate(x)
|
94
|
+
y = x.left
|
95
|
+
x.left = y.right
|
96
|
+
y.right&.parent = x
|
97
|
+
y.parent = x.parent
|
98
|
+
if x.parent.nil?
|
99
|
+
@root = y
|
100
|
+
elsif x.equal?(x.parent.right)
|
101
|
+
x.parent.right = y
|
102
|
+
else
|
103
|
+
x.parent.left = y
|
104
|
+
end
|
105
|
+
y.right = x
|
106
|
+
x.parent = y
|
107
|
+
end
|
108
|
+
|
109
|
+
def insert_fixup(z)
|
110
|
+
while red?(z.parent)
|
111
|
+
if z.parent.equal?(z.parent.parent.left)
|
112
|
+
y = z.parent.parent.right
|
113
|
+
if red?(y)
|
114
|
+
z.parent.red = y.red = false
|
115
|
+
z.parent.parent.red = true
|
116
|
+
z = z.parent.parent
|
117
|
+
else
|
118
|
+
if z.equal?(z.parent.right)
|
119
|
+
z = z.parent
|
120
|
+
left_rotate(z)
|
121
|
+
end
|
122
|
+
z.parent.red = false
|
123
|
+
z.parent.parent.red = true
|
124
|
+
right_rotate(z.parent.parent)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
y = z.parent.parent.left
|
128
|
+
if red?(y)
|
129
|
+
z.parent.red = y.red = false
|
130
|
+
z.parent.parent.red = true
|
131
|
+
z = z.parent.parent
|
132
|
+
else
|
133
|
+
if z.equal?(z.parent.left)
|
134
|
+
z = z.parent
|
135
|
+
right_rotate(z)
|
136
|
+
end
|
137
|
+
z.parent.red = false
|
138
|
+
z.parent.parent.red = true
|
139
|
+
left_rotate(z.parent.parent)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
@root.red = false
|
144
|
+
end
|
145
|
+
|
146
|
+
def delete_fixup(x)
|
147
|
+
# keep going while x is a *real* node, not the root, and black
|
148
|
+
while x && x != @root && black?(x)
|
149
|
+
if x.equal?(x&.parent&.left)
|
150
|
+
w = x&.parent&.right
|
151
|
+
unless w # ← new: sibling is nil
|
152
|
+
x = x.parent
|
153
|
+
next
|
154
|
+
end
|
155
|
+
|
156
|
+
if red?(w)
|
157
|
+
w.red = false
|
158
|
+
x.parent.red = true
|
159
|
+
left_rotate(x.parent)
|
160
|
+
w = x.parent.right
|
161
|
+
end
|
162
|
+
|
163
|
+
if black?(w&.left) && black?(w&.right) # ← safe-nav
|
164
|
+
w.red = true
|
165
|
+
x = x.parent
|
166
|
+
else
|
167
|
+
if black?(w&.right)
|
168
|
+
w.left.red = false if w.left
|
169
|
+
w.red = true
|
170
|
+
right_rotate(w)
|
171
|
+
w = x.parent.right
|
172
|
+
end
|
173
|
+
w.red = x.parent.red
|
174
|
+
x.parent.red = false
|
175
|
+
w.right.red = false if w.right
|
176
|
+
left_rotate(x.parent)
|
177
|
+
x = @root
|
178
|
+
end
|
179
|
+
else
|
180
|
+
w = x&.parent&.left
|
181
|
+
unless w # ← new: sibling is nil
|
182
|
+
x = x.parent
|
183
|
+
next
|
184
|
+
end
|
185
|
+
|
186
|
+
if red?(w)
|
187
|
+
w.red = false
|
188
|
+
x.parent.red = true
|
189
|
+
right_rotate(x.parent)
|
190
|
+
w = x.parent.left
|
191
|
+
end
|
192
|
+
|
193
|
+
if black?(w&.right) && black?(w&.left) # ← safe-nav
|
194
|
+
w.red = true
|
195
|
+
x = x.parent
|
196
|
+
else
|
197
|
+
if black?(w&.left)
|
198
|
+
w.right.red = false if w.right
|
199
|
+
w.red = true
|
200
|
+
left_rotate(w)
|
201
|
+
w = x.parent.left
|
202
|
+
end
|
203
|
+
w.red = x.parent.red
|
204
|
+
x.parent.red = false
|
205
|
+
w.left.red = false if w.left
|
206
|
+
right_rotate(x.parent)
|
207
|
+
x = @root
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
x&.red = false
|
212
|
+
end
|
213
|
+
|
214
|
+
def transplant(u, v)
|
215
|
+
if u.parent.nil?
|
216
|
+
@root = v
|
217
|
+
elsif u.equal?(u.parent.left)
|
218
|
+
u.parent.left = v
|
219
|
+
else
|
220
|
+
u.parent.right = v
|
221
|
+
end
|
222
|
+
v&.parent = u.parent
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
data/lib/queap.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "queap/version"
|
4
|
+
require_relative "queap/tree"
|
5
|
+
require_relative "queap/queue"
|
6
|
+
|
7
|
+
module Queap
|
8
|
+
# you can expose convenience aliases here if you like:
|
9
|
+
#
|
10
|
+
# Queap.new is nicer than Queap::Queue.new
|
11
|
+
#
|
12
|
+
def self.new(*args, **kw) = Queue.new(*args, **kw)
|
13
|
+
end
|
data/sig/queap.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: queap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeffrey Crowell
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-05-02 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Ruby implementation of the 'queap' data structure
|
14
|
+
email:
|
15
|
+
- jeff@crowell.biz
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".rspec"
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- Rakefile
|
24
|
+
- benchmark/queap_bench.rb
|
25
|
+
- lib/queap.rb
|
26
|
+
- lib/queap/queue.rb
|
27
|
+
- lib/queap/tree.rb
|
28
|
+
- lib/queap/version.rb
|
29
|
+
- sig/queap.rbs
|
30
|
+
homepage: https://github.com/crowell/queap
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata:
|
34
|
+
homepage_uri: https://github.com/crowell/queap
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.1.0
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubygems_version: 3.5.16
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: A doubly-ended priority queue (Queap) with O(1) amortised insert.
|
54
|
+
test_files: []
|