pairing_heap 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/.github/workflows/main.yml +18 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +17 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +426 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/pairing_heap.rb +246 -0
- data/lib/pairing_heap/version.rb +5 -0
- data/pairing_heap.gemspec +37 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 06be98427116306a2d0968edb9757188dde8d9ee97226ab03edf7fe4aba8d9d6
|
4
|
+
data.tar.gz: f91175ec148649d8a1987e8af3935b6f96740c0b21b6bf2e34dc154b355c35a8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5441d35660f0fec2c66f4e1ea5c4b89499d9e4d97f96e01b39f5fada5c462fafe75196562a611a2ae85e95bceb006a4b9fbd1252d9135a5859e4cb165b57ab74
|
7
|
+
data.tar.gz: 01b5f44ca3f608ca26091c5fd983965e95591fd097f9a82f87ef87e28bb01c8f66f0f6bf21bdbad2f9dcfc1f0b70fe34243a277d6a743b24c4d03f6377b419af
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.0
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler -v 2.2.3
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- 'test/fib.rb'
|
4
|
+
|
5
|
+
Style/InfiniteLoop:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
Style/StringLiterals:
|
9
|
+
Enabled: false
|
10
|
+
EnforcedStyle: double_quotes
|
11
|
+
|
12
|
+
Style/StringLiteralsInInterpolation:
|
13
|
+
Enabled: true
|
14
|
+
EnforcedStyle: double_quotes
|
15
|
+
|
16
|
+
Layout/LineLength:
|
17
|
+
Max: 120
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pairing_heap (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.14.3)
|
10
|
+
rake (13.0.3)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
x86_64-darwin-20
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
minitest (~> 5.0)
|
17
|
+
pairing_heap!
|
18
|
+
rake (~> 13.0)
|
19
|
+
|
20
|
+
BUNDLED WITH
|
21
|
+
2.2.3
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Marcin Henryk Bartkowiak
|
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,426 @@
|
|
1
|
+
# PairingHeap
|
2
|
+
|
3
|
+
PairingHeap is a pure Ruby priority queue implementation using a pairing heap as the underlying data structure. While a pairing heap is asymptotically less efficient than the Fibonacci heap, it is usually faster in practice. This makes it a popular choice for Prim's MST or Dijkstra's algorithm implementations.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'pairing_heap'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install pairing_heap
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
```ruby
|
23
|
+
require 'pairing_heap'
|
24
|
+
|
25
|
+
# Min priority queue
|
26
|
+
best_defenses = PairingHeap::MinPriorityQueue.new
|
27
|
+
best_defenses.push('Chelsea', 24)
|
28
|
+
best_defenses.push('City', 30)
|
29
|
+
best_defenses.push('Tottenham', 25)
|
30
|
+
best_defenses.any? # => true
|
31
|
+
best_defenses.size # => 3
|
32
|
+
best_defenses.decrease_key('City', 15)
|
33
|
+
best_defenses.min # => 'City'
|
34
|
+
best_defenses.pop # => 'City'
|
35
|
+
best_defenses.extract_min # => 'Chelsea'
|
36
|
+
best_defenses.extract_min # => 'Tottenham'
|
37
|
+
best_defenses.any? # => false
|
38
|
+
|
39
|
+
# Max priority queue
|
40
|
+
best_teams = PairingHeap::MaxPriorityQueue.new
|
41
|
+
best_teams.push('City', 56)
|
42
|
+
best_teams.push('United', 46)
|
43
|
+
best_teams.push('Leicester', 46)
|
44
|
+
best_teams.increase_key('Leicester', 47)
|
45
|
+
best_teams.max # => 'City'
|
46
|
+
best_teams.pop # => 'City'
|
47
|
+
best_teams.extract_max # => 'Leicester'
|
48
|
+
|
49
|
+
# Custom comparator(it defaults to :<=.to_proc)
|
50
|
+
compare_by_length = PairingHeap::PairingHeap.new { |l, r| l.length <= r.length }
|
51
|
+
compare_by_length.push(:a, '11')
|
52
|
+
compare_by_length.push(:b, '1')
|
53
|
+
compare_by_length.push(:c, '11')
|
54
|
+
compare_by_length.change_priority(:c, '')
|
55
|
+
compare_by_length.peek # => :c
|
56
|
+
compare_by_length.pop # => :c
|
57
|
+
compare_by_length.pop # => :b
|
58
|
+
compare_by_length.pop # => :a
|
59
|
+
|
60
|
+
# SafeChangePriortyQueue
|
61
|
+
queue = PairingHeap::SafeChangePriorityQueue.new
|
62
|
+
queue.push(:a, 1)
|
63
|
+
queue.push(:b, 2)
|
64
|
+
queue.change_priority(:a, 3) # This works and does not throw an exception
|
65
|
+
queue.peek # => :b
|
66
|
+
```
|
67
|
+
See also [test/performance_dijkstra.rb](./test/performance_dijkstra.rb) for a Dijkstra algorithm implementation.
|
68
|
+
### Changes from lazy_priority_queue
|
69
|
+
This API is a drop-in replacement of [lazy_priority_queue](https://github.com/matiasbattocchia/lazy_priority_queue) with the following differences:
|
70
|
+
|
71
|
+
* Custom comparator provided to constructur, compares weights, not internal nodes
|
72
|
+
* `change_priority` returns `self` instead of the first argument
|
73
|
+
* `enqueue` returns `self` instead of the first argument
|
74
|
+
* Queue classes are in the `PairingHeap` namespace, so `require 'pairing_heap` does not load `MinPriorityQueue` to the global scope
|
75
|
+
* `top_condidition` constructor argument is removed
|
76
|
+
|
77
|
+
## Time Complexity
|
78
|
+
| Operation | Time complexity | Amortized time complexity |
|
79
|
+
| --------------- | --------------- | ------------------------- |
|
80
|
+
| enqueue | O(1) | O(1) |
|
81
|
+
| peek | O(1) | O(1) |
|
82
|
+
| change_priority | O(1) | o(log n) |
|
83
|
+
| dequeue | O(n) | O(log n) |
|
84
|
+
| delete | O(n) | O(log n) |
|
85
|
+
|
86
|
+
## Benchmarks
|
87
|
+
I picked the two fastest pure Ruby priority queue implementations I was aware of for the comparison:
|
88
|
+
|
89
|
+
* [lazy_priority_queue](https://github.com/matiasbattocchia/lazy_priority_queue) that uses a lazy binomial heap. This is probably the most popular option, used for example in [RGL](https://github.com/monora/rgl/)
|
90
|
+
* Pure Ruby implementation of Fibonacci Heap from [priority-queue](https://github.com/supertinou/priority-queue) ([link to source](https://github.com/supertinou/priority-queue/blob/master/lib/priority_queue/ruby_priority_queue.rb))
|
91
|
+
|
92
|
+
All tests except for the third one were executed by [benchmark-ips](https://github.com/evanphx/benchmark-ips) with parameters `time = 180` and `warmup = 30`, on an `Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz`.
|
93
|
+
### Stress test without changing priority test(N = 1000) [source code](./test/performance.rb)
|
94
|
+
Original performance test from [lazy_priority_queue](https://github.com/matiasbattocchia/lazy_priority_queue)
|
95
|
+
> A stress test of 1,000,000 operations: starting with 1,000 pushes/0 pops, following 999 pushes/1 pop, and so on till 0 pushes/1000 pops.
|
96
|
+
<table>
|
97
|
+
<tr>
|
98
|
+
<th colspan="4">ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]</th>
|
99
|
+
</tr>
|
100
|
+
<tr>
|
101
|
+
<th>Library</th>
|
102
|
+
<th>Iterations</th>
|
103
|
+
<th>Seconds</th>
|
104
|
+
<th>Iterations per second</th>
|
105
|
+
</tr>
|
106
|
+
<tr>
|
107
|
+
<td>pairing_heap</td>
|
108
|
+
<td>14</td>
|
109
|
+
<td>60.564595</td>
|
110
|
+
<td>0.231</td>
|
111
|
+
</tr>
|
112
|
+
<tr>
|
113
|
+
<td>lazy_priority_queue</td>
|
114
|
+
<td>8</td>
|
115
|
+
<td>62.489819</td>
|
116
|
+
<td>0.128(1.81x slower)</td>
|
117
|
+
</tr>
|
118
|
+
<tr>
|
119
|
+
<td>Fibonacci</td>
|
120
|
+
<td>8</td>
|
121
|
+
<td>68.719194</td>
|
122
|
+
<td>0.116(1.99x slower)</td>
|
123
|
+
</tr>
|
124
|
+
<tr>
|
125
|
+
<th colspan="4">jruby 9.2.14.0 (2.5.7) 2020-12-08 ebe64bafb9 OpenJDK 64-Bit Server VM 15.0.2+7 on 15.0.2+7 +jit [darwin-x86_64]</th>
|
126
|
+
</tr>
|
127
|
+
<tr>
|
128
|
+
<th>Library</th>
|
129
|
+
<th>Iterations</th>
|
130
|
+
<th>Seconds</th>
|
131
|
+
<th>Iterations per second</th>
|
132
|
+
</tr>
|
133
|
+
<tr>
|
134
|
+
<td>pairing_heap</td>
|
135
|
+
<td>17</td>
|
136
|
+
<td>61.195794</td>
|
137
|
+
<td>0.278</td>
|
138
|
+
</tr>
|
139
|
+
<tr>
|
140
|
+
<td>lazy_priority_queue</td>
|
141
|
+
<td>14</td>
|
142
|
+
<td>64.375927</td>
|
143
|
+
<td>0.218(1.28x slower)</td>
|
144
|
+
</tr>
|
145
|
+
<tr>
|
146
|
+
<td>Fibonacci</td>
|
147
|
+
<td>9</td>
|
148
|
+
<td>67.415358</td>
|
149
|
+
<td>0.134(2.08x slower)</td>
|
150
|
+
</tr>
|
151
|
+
</table>
|
152
|
+
|
153
|
+
### Stress test with changing priority(N = 1000) [source code](./test/performance_with_change_priority.rb)
|
154
|
+
A stress test of 2,000,000 operations: starting with 1,000 pushes/1000 change_priorities/0 pops, following 999 pushes/999 change_priorities/1 pop, and so on till 0 pushes/0 change_priorities/1000 pops.
|
155
|
+
<table>
|
156
|
+
<tr>
|
157
|
+
<th colspan="4">ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]</th>
|
158
|
+
</tr>
|
159
|
+
<tr>
|
160
|
+
<th>Library</th>
|
161
|
+
<th>Iterations</th>
|
162
|
+
<th>Seconds</th>
|
163
|
+
<th>Iterations per second</th>
|
164
|
+
</tr>
|
165
|
+
<tr>
|
166
|
+
<td>pairing_heap</td>
|
167
|
+
<td>13</td>
|
168
|
+
<td>60.280165</td>
|
169
|
+
<td>0.216</td>
|
170
|
+
</tr>
|
171
|
+
<tr>
|
172
|
+
<td>lazy_priority_queue</td>
|
173
|
+
<td>8</td>
|
174
|
+
<td>67.414861s</td>
|
175
|
+
<td>0.119(1.82x slower)</td>
|
176
|
+
</tr>
|
177
|
+
<tr>
|
178
|
+
<td>Fibonacci</td>
|
179
|
+
<td>7</td>
|
180
|
+
<td>61.067436</td>
|
181
|
+
<td>0.115(1.88x slower)</td>
|
182
|
+
</tr>
|
183
|
+
<tr>
|
184
|
+
<th colspan="4">jruby 9.2.14.0 (2.5.7) 2020-12-08 ebe64bafb9 OpenJDK 64-Bit Server VM 15.0.2+7 on 15.0.2+7 +jit [darwin-x86_64]</th>
|
185
|
+
</tr>
|
186
|
+
<tr>
|
187
|
+
<th>Library</th>
|
188
|
+
<th>Iterations</th>
|
189
|
+
<th>Seconds</th>
|
190
|
+
<th>Iterations per second</th>
|
191
|
+
</tr>
|
192
|
+
<tr>
|
193
|
+
<td>pairing_heap</td>
|
194
|
+
<td>16</td>
|
195
|
+
<td>62.519677</td>
|
196
|
+
<td>0.256</td>
|
197
|
+
</tr>
|
198
|
+
<tr>
|
199
|
+
<td>lazy_priority_queue</td>
|
200
|
+
<td>13</td>
|
201
|
+
<td>63.832733</td>
|
202
|
+
<td>0.204(1.26x slower)</td>
|
203
|
+
</tr>
|
204
|
+
<tr>
|
205
|
+
<td>Fibonacci</td>
|
206
|
+
<td>8</td>
|
207
|
+
<td>60.250658</td>
|
208
|
+
<td>0.133(1.93x slower)</td>
|
209
|
+
</tr>
|
210
|
+
</table>
|
211
|
+
|
212
|
+
### Stress test with changing priority(N = 10) [source code](./test/performance_with_change_priority.rb)
|
213
|
+
A stress test of 200 operations: starting with 10 pushes/10 change_priorities/0 pops, following 9 pushes/9 change_priorities/1 pop, and so on till 0 pushes/0 change_priorities/10 pops.
|
214
|
+
<table>
|
215
|
+
<tr>
|
216
|
+
<th colspan="4">ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]</th>
|
217
|
+
</tr>
|
218
|
+
<tr>
|
219
|
+
<th>Library</th>
|
220
|
+
<th>Iterations per second</th>
|
221
|
+
</tr>
|
222
|
+
<tr>
|
223
|
+
<td>pairing_heap</td>
|
224
|
+
<td>5991.2</td>
|
225
|
+
</tr>
|
226
|
+
<tr>
|
227
|
+
<td>Fibonacci</td>
|
228
|
+
<td>3803.5(1.58x slower)</td>
|
229
|
+
</tr>
|
230
|
+
<tr>
|
231
|
+
<td>lazy_priority_queue</td>
|
232
|
+
<td>3681.9(1.64x slower)</td>
|
233
|
+
</tr>
|
234
|
+
<tr>
|
235
|
+
<th colspan="4">jruby 9.2.14.0 (2.5.7) 2020-12-08 ebe64bafb9 OpenJDK 64-Bit Server VM 15.0.2+7 on 15.0.2+7 +jit [darwin-x86_64]</th>
|
236
|
+
</tr>
|
237
|
+
<tr>
|
238
|
+
<th>Library</th>
|
239
|
+
<th>Iterations per second</th>
|
240
|
+
</tr>
|
241
|
+
<tr>
|
242
|
+
<td>pairing_heap</td>
|
243
|
+
<td>6784.3</td>
|
244
|
+
</tr>
|
245
|
+
<tr>
|
246
|
+
<td>lazy_priority_queue</td>
|
247
|
+
<td>6044.5(1.12x slower)</td>
|
248
|
+
</tr>
|
249
|
+
<tr>
|
250
|
+
<td>Fibonacci</td>
|
251
|
+
<td>4070.5(1.67x slower)</td>
|
252
|
+
</tr>
|
253
|
+
</table>
|
254
|
+
|
255
|
+
### Dijkstra's algorithm with RGL [source code](./test/performance_rgl.rb)
|
256
|
+
<table>
|
257
|
+
<tr>
|
258
|
+
<th colspan="4">ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]</th>
|
259
|
+
</tr>
|
260
|
+
<tr>
|
261
|
+
<th>Library</th>
|
262
|
+
<th>Iterations</th>
|
263
|
+
<th>Seconds</th>
|
264
|
+
<th>Iterations per second</th>
|
265
|
+
</tr>
|
266
|
+
<tr>
|
267
|
+
<td>pairing_heap</td>
|
268
|
+
<td>7</td>
|
269
|
+
<td>64.768526</td>
|
270
|
+
<td>0.108</td>
|
271
|
+
</tr>
|
272
|
+
<tr>
|
273
|
+
<td>lazy_priority_queue</td>
|
274
|
+
<td>6</td>
|
275
|
+
<td>63.278091</td>
|
276
|
+
<td>0.095(1.14x slower)</td>
|
277
|
+
</tr>
|
278
|
+
<tr>
|
279
|
+
<td>Fibonacci</td>
|
280
|
+
<td>6</td>
|
281
|
+
<td>65.898081</td>
|
282
|
+
<td>0.091(1.19x slower)</td>
|
283
|
+
</tr>
|
284
|
+
<tr>
|
285
|
+
<th colspan="4">jruby 9.2.14.0 (2.5.7) 2020-12-08 ebe64bafb9 OpenJDK 64-Bit Server VM 15.0.2+7 on 15.0.2+7 +jit [darwin-x86_64]</th>
|
286
|
+
</tr>
|
287
|
+
<tr>
|
288
|
+
<th>Library</th>
|
289
|
+
<th>Iterations</th>
|
290
|
+
<th>Seconds</th>
|
291
|
+
<th>Iterations per second</th>
|
292
|
+
</tr>
|
293
|
+
<tr>
|
294
|
+
<td>pairing_heap</td>
|
295
|
+
<td>12</td>
|
296
|
+
<td>60.277567</td>
|
297
|
+
<td>0.199</td>
|
298
|
+
</tr>
|
299
|
+
<tr>
|
300
|
+
<td>lazy_priority_queue</td>
|
301
|
+
<td>12</td>
|
302
|
+
<td>61.238395</td>
|
303
|
+
<td>0.196(1.02x slower)</td>
|
304
|
+
</tr>
|
305
|
+
<tr>
|
306
|
+
<td>Fibonacci</td>
|
307
|
+
<td>10</td>
|
308
|
+
<td>62.687378</td>
|
309
|
+
<td>0.160(1.25x slower)</td>
|
310
|
+
</tr>
|
311
|
+
</table>
|
312
|
+
|
313
|
+
### Simple Dijkstra's algorithm implementation [source code](./test/performance_dijkstra.rb)
|
314
|
+
<table>
|
315
|
+
<tr>
|
316
|
+
<th colspan="4">ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]</th>
|
317
|
+
</tr>
|
318
|
+
<tr>
|
319
|
+
<th>Library</th>
|
320
|
+
<th>Iterations</th>
|
321
|
+
<th>Seconds</th>
|
322
|
+
<th>Iterations per second</th>
|
323
|
+
</tr>
|
324
|
+
<tr>
|
325
|
+
<td>pairing_heap</td>
|
326
|
+
<td>20</td>
|
327
|
+
<td>60.028380</td>
|
328
|
+
<td>0.334</td>
|
329
|
+
</tr>
|
330
|
+
<tr>
|
331
|
+
<td>Fibonacci</td>
|
332
|
+
<td>10</td>
|
333
|
+
<td>64.471303</td>
|
334
|
+
<td>0.155(2.14x slower)</td>
|
335
|
+
</tr>
|
336
|
+
<tr>
|
337
|
+
<td>lazy_priority_queue</td>
|
338
|
+
<td>9</td>
|
339
|
+
<td>65.986618</td>
|
340
|
+
<td>0.136(2.45x slower)</td>
|
341
|
+
</tr>
|
342
|
+
<tr>
|
343
|
+
<th colspan="4">jruby 9.2.14.0 (2.5.7) 2020-12-08 ebe64bafb9 OpenJDK 64-Bit Server VM 15.0.2+7 on 15.0.2+7 +jit [darwin-x86_64]</th>
|
344
|
+
</tr>
|
345
|
+
<tr>
|
346
|
+
<th>Library</th>
|
347
|
+
<th>Iterations</th>
|
348
|
+
<th>Seconds</th>
|
349
|
+
<th>Iterations per second</th>
|
350
|
+
</tr>
|
351
|
+
<tr>
|
352
|
+
<td>pairing_heap</td>
|
353
|
+
<td>21</td>
|
354
|
+
<td>61.727259</td>
|
355
|
+
<td>0.340</td>
|
356
|
+
</tr>
|
357
|
+
<tr>
|
358
|
+
<td>lazy_priority_queue</td>
|
359
|
+
<td>14</td>
|
360
|
+
<td>63.436863</td>
|
361
|
+
<td>0.221(1.54x slower)</td>
|
362
|
+
</tr>
|
363
|
+
<tr>
|
364
|
+
<td>Fibonacci</td>
|
365
|
+
<td>10</td>
|
366
|
+
<td>62.447662</td>
|
367
|
+
<td>0.160(2.12x slower)</td>
|
368
|
+
</tr>
|
369
|
+
</table>
|
370
|
+
|
371
|
+
### Summary
|
372
|
+
<table>
|
373
|
+
<tr>
|
374
|
+
<th colspan="4">ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]</th>
|
375
|
+
</tr>
|
376
|
+
<tr>
|
377
|
+
<th>Library</th>
|
378
|
+
<th>Slower geometric mean</th>
|
379
|
+
</tr>
|
380
|
+
<tr>
|
381
|
+
<td>pairing_heap</td>
|
382
|
+
<td>1</td>
|
383
|
+
</tr>
|
384
|
+
<tr>
|
385
|
+
<td>Fibonacci</td>
|
386
|
+
<td>1.720x slower</td>
|
387
|
+
</tr>
|
388
|
+
<tr>
|
389
|
+
<td>lazy_priority_queue</td>
|
390
|
+
<td>1.721x slower</td>
|
391
|
+
</tr>
|
392
|
+
<tr>
|
393
|
+
<th colspan="4">jruby 9.2.14.0 (2.5.7) 2020-12-08 ebe64bafb9 OpenJDK 64-Bit Server VM 15.0.2+7 on 15.0.2+7 +jit [darwin-x86_64]</th>
|
394
|
+
</tr>
|
395
|
+
<tr>
|
396
|
+
<th>Library</th>
|
397
|
+
<th>Slower geometric mean</th>
|
398
|
+
</tr>
|
399
|
+
<tr>
|
400
|
+
<td>pairing_heap</td>
|
401
|
+
<td>1</td>
|
402
|
+
</tr>
|
403
|
+
<tr>
|
404
|
+
<td>lazy_priority_queue</td>
|
405
|
+
<td>1.23x slower</td>
|
406
|
+
|
407
|
+
</tr>
|
408
|
+
<tr>
|
409
|
+
<td>Fibonacci</td>
|
410
|
+
<td>1.78x slower</td>
|
411
|
+
</tr>
|
412
|
+
</table>
|
413
|
+
|
414
|
+
## Development
|
415
|
+
|
416
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
417
|
+
|
418
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
419
|
+
|
420
|
+
## Contributing
|
421
|
+
|
422
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mhib/pairing_heap.
|
423
|
+
|
424
|
+
## License
|
425
|
+
|
426
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "pairing_heap"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/pairing_heap.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PairingHeap
|
4
|
+
# Pairing heap data structure implementation
|
5
|
+
# @see https://en.wikipedia.org/wiki/Pairing_heap
|
6
|
+
class PairingHeap
|
7
|
+
class Node
|
8
|
+
attr_accessor :elem, :priority, :subheaps, :parent, :prev_sibling, :next_sibling
|
9
|
+
def initialize(elem, priority, subheaps, parent, prev_sibling, next_sibling)
|
10
|
+
@elem = elem
|
11
|
+
@priority = priority
|
12
|
+
@subheaps = subheaps
|
13
|
+
@parent = parent
|
14
|
+
@prev_sibling = prev_sibling
|
15
|
+
@next_sibling = next_sibling
|
16
|
+
end
|
17
|
+
end
|
18
|
+
private_constant :Node
|
19
|
+
|
20
|
+
# @param &block Optional heap property priority comparator. `<:=.to_proc` by default
|
21
|
+
def initialize(&block)
|
22
|
+
@root = nil
|
23
|
+
@nodes = {}
|
24
|
+
@order = block || :<=.to_proc
|
25
|
+
end
|
26
|
+
|
27
|
+
# Pushes element to the heap.
|
28
|
+
# Time Complexity: O(1)
|
29
|
+
# @param elem Element to be pushed
|
30
|
+
# @param priority Priority of the element
|
31
|
+
# @raise [ArgumentError] if the element is already in the heap
|
32
|
+
# @return [PairingHeap]
|
33
|
+
def push(elem, priority)
|
34
|
+
raise ArgumentError, "Element already in the heap" if @nodes.key?(elem)
|
35
|
+
|
36
|
+
node = Node.new(elem, priority, nil, nil, nil, nil)
|
37
|
+
@nodes[elem] = node
|
38
|
+
@root = meld(@root, node)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
alias enqueue push
|
42
|
+
|
43
|
+
# Returns the element at the top of the heap
|
44
|
+
# Time Complexity: O(1)
|
45
|
+
def peek
|
46
|
+
@root&.elem
|
47
|
+
end
|
48
|
+
|
49
|
+
# Time Complexity: O(1)
|
50
|
+
# @return [Boolean]
|
51
|
+
def empty?
|
52
|
+
@root.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Time Complexity: O(1)
|
56
|
+
# @return [Boolean]
|
57
|
+
def any?
|
58
|
+
!@root.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
# Time Complexity: O(1)
|
62
|
+
# @return [Integer]
|
63
|
+
def size
|
64
|
+
@nodes.size
|
65
|
+
end
|
66
|
+
alias length size
|
67
|
+
|
68
|
+
# Removes element from the top of the heap
|
69
|
+
# Time Complexity: O(N)
|
70
|
+
# Amortized time Complexity: O(log(N))
|
71
|
+
# @raise [ArgumEntError] if the heap is empty
|
72
|
+
# @return [PairingHeap]
|
73
|
+
def pop
|
74
|
+
raise ArgumentError, "Cannot remove from an empty heap" if @root.nil?
|
75
|
+
|
76
|
+
elem = @root.elem
|
77
|
+
@nodes.delete(elem)
|
78
|
+
@root = merge_pairs(@root.subheaps)
|
79
|
+
if @root
|
80
|
+
@root.parent = nil
|
81
|
+
@root.next_sibling = nil
|
82
|
+
@root.prev_sibling = nil
|
83
|
+
end
|
84
|
+
elem
|
85
|
+
end
|
86
|
+
alias dequeue pop
|
87
|
+
|
88
|
+
# Changes a priority of element to a more prioritary one
|
89
|
+
# Time Complexity: O(1)
|
90
|
+
# Amortized Time Complexity: o(log(N))
|
91
|
+
# @param elem Element
|
92
|
+
# @param priority New priority
|
93
|
+
# @raise [ArgumentError] if the element heap is not in heap or the new priority is less prioritary
|
94
|
+
# @return [PairingHeap]
|
95
|
+
def change_priority(elem, priority)
|
96
|
+
node = @nodes[elem]
|
97
|
+
raise ArgumentError, "Provided element is not in heap" if node.nil?
|
98
|
+
unless @order[priority, node.priority]
|
99
|
+
raise ArgumentError, "Priority cannot be changed to a less prioritary value."
|
100
|
+
end
|
101
|
+
|
102
|
+
node.priority = priority
|
103
|
+
return if node.parent.nil?
|
104
|
+
return if @order[node.parent.priority, node.priority]
|
105
|
+
|
106
|
+
remove_from_parents_list(node)
|
107
|
+
@root = meld(node, @root)
|
108
|
+
@root.parent = nil
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Removes element from the top of the heap
|
113
|
+
# Time Complexity: O(N)
|
114
|
+
# Amortized Time Complexity: O(log(N))
|
115
|
+
# @raise [ArgumentError] if the element heap is not in heap
|
116
|
+
# @return [PairingHeap]
|
117
|
+
def delete(elem)
|
118
|
+
node = @nodes[elem]
|
119
|
+
raise ArgumentError, "Provided element is not in heap" if node.nil?
|
120
|
+
|
121
|
+
@nodes.delete(elem)
|
122
|
+
if node.parent.nil?
|
123
|
+
@root = merge_pairs(node.subheaps)
|
124
|
+
else
|
125
|
+
remove_from_parents_list(node)
|
126
|
+
new_heap = merge_pairs(node.subheaps)
|
127
|
+
if new_heap
|
128
|
+
new_heap.prev_sibling = nil
|
129
|
+
new_heap.next_sibling = nil
|
130
|
+
end
|
131
|
+
@root = meld(new_heap, @root)
|
132
|
+
end
|
133
|
+
@root&.parent = nil
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def remove_from_parents_list(node)
|
140
|
+
if node.prev_sibling
|
141
|
+
node.prev_sibling.next_sibling = node.next_sibling
|
142
|
+
node.next_sibling.prev_sibling = node.prev_sibling if node.next_sibling
|
143
|
+
elsif node.parent.subheaps.equal?(node)
|
144
|
+
node.parent.subheaps = node.next_sibling
|
145
|
+
node.next_sibling.prev_sibling = nil if node.next_sibling
|
146
|
+
elsif node.next_sibling
|
147
|
+
node.next_sibling.prev_sibling = nil
|
148
|
+
end
|
149
|
+
node.prev_sibling = nil
|
150
|
+
node.next_sibling = nil
|
151
|
+
end
|
152
|
+
|
153
|
+
def meld(left, right)
|
154
|
+
return right if left.nil?
|
155
|
+
return left if right.nil?
|
156
|
+
|
157
|
+
if @order[left.priority, right.priority]
|
158
|
+
parent = left
|
159
|
+
child = right
|
160
|
+
else
|
161
|
+
parent = right
|
162
|
+
child = left
|
163
|
+
end
|
164
|
+
child.next_sibling = parent.subheaps
|
165
|
+
parent.subheaps = child
|
166
|
+
child.next_sibling.prev_sibling = child if child.next_sibling
|
167
|
+
child.prev_sibling = nil
|
168
|
+
child.parent = parent
|
169
|
+
parent
|
170
|
+
end
|
171
|
+
|
172
|
+
# Non-recursive implementation of method described in https://en.wikipedia.org/wiki/Pairing_heap#delete-min
|
173
|
+
def merge_pairs(heaps)
|
174
|
+
return nil if heaps.nil?
|
175
|
+
return heaps if heaps.next_sibling.nil?
|
176
|
+
|
177
|
+
# [H1, H2, H3, H4, H5, H6, H7] => [H1H2, H3H4, H5H6, H7]
|
178
|
+
stack = []
|
179
|
+
current = heaps
|
180
|
+
while current
|
181
|
+
prev = current
|
182
|
+
current = current.next_sibling
|
183
|
+
unless current
|
184
|
+
stack << prev
|
185
|
+
break
|
186
|
+
end
|
187
|
+
next_val = current.next_sibling
|
188
|
+
stack << meld(prev, current)
|
189
|
+
current = next_val
|
190
|
+
end
|
191
|
+
|
192
|
+
# [H1H2, H3H4, H5H6, H7]
|
193
|
+
# [H1H2, H3H4, H5H67]
|
194
|
+
# [H1H2, H3H45H67]
|
195
|
+
# [H1H2H3H45H67]
|
196
|
+
# return H1H2H3H45H67
|
197
|
+
while true
|
198
|
+
right = stack.pop
|
199
|
+
return right if stack.empty?
|
200
|
+
|
201
|
+
left = stack.pop
|
202
|
+
stack << meld(left, right)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Priority queue where the smallest priority is the most prioritary
|
208
|
+
class MinPriorityQueue < PairingHeap
|
209
|
+
def initialize
|
210
|
+
super(&:<=)
|
211
|
+
end
|
212
|
+
|
213
|
+
alias decrease_key change_priority
|
214
|
+
alias min peek
|
215
|
+
alias extract_min dequeue
|
216
|
+
end
|
217
|
+
|
218
|
+
# Priority queue where the highest priority is the most prioritary
|
219
|
+
class MaxPriorityQueue < PairingHeap
|
220
|
+
def initialize
|
221
|
+
super(&:>=)
|
222
|
+
end
|
223
|
+
|
224
|
+
alias increase_key change_priority
|
225
|
+
alias max peek
|
226
|
+
alias extract_max dequeue
|
227
|
+
end
|
228
|
+
|
229
|
+
# Priority queue with change_priority, that accepts changing to a less prioritary priority
|
230
|
+
class SafeChangePriorityQueue < PairingHeap
|
231
|
+
# Changes a priority of the element to a more prioritary one
|
232
|
+
# Time Complexity: O(N)
|
233
|
+
# Amortized Time Complexity: O(log(N))
|
234
|
+
# @raise [ArgumentError] if the element heap is not in the heap
|
235
|
+
# @return [PairingHeap]
|
236
|
+
def change_priority(elem, priority)
|
237
|
+
raise ArgumentError, "Provided element is not in heap" unless @nodes.key?(elem)
|
238
|
+
if !@order[priority, @nodes[elem].priority]
|
239
|
+
delete(elem)
|
240
|
+
push(elem, priority)
|
241
|
+
else
|
242
|
+
super(elem, priority)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/pairing_heap/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "pairing_heap"
|
7
|
+
spec.version = PairingHeap::VERSION
|
8
|
+
spec.authors = ["Marcin Henryk Bartkowiak"]
|
9
|
+
spec.email = ["mhbartkowiak@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Performant priority queue in pure Ruby with support for changing priority"
|
12
|
+
spec.description = "Performant priority queue in pure Ruby with support for changing priority using pairing heap data structure"
|
13
|
+
spec.homepage = "https://github.com/mhib/pairing_heap"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
# Uncomment to register a new dependency of your gem
|
30
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
31
|
+
|
32
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
33
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
34
|
+
|
35
|
+
# For more information and examples about making a new gem, checkout our
|
36
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pairing_heap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marcin Henryk Bartkowiak
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
description: Performant priority queue in pure Ruby with support for changing priority
|
42
|
+
using pairing heap data structure
|
43
|
+
email:
|
44
|
+
- mhbartkowiak@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".github/workflows/main.yml"
|
50
|
+
- ".gitignore"
|
51
|
+
- ".rubocop.yml"
|
52
|
+
- Gemfile
|
53
|
+
- Gemfile.lock
|
54
|
+
- LICENSE.txt
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- bin/console
|
58
|
+
- bin/setup
|
59
|
+
- lib/pairing_heap.rb
|
60
|
+
- lib/pairing_heap/version.rb
|
61
|
+
- pairing_heap.gemspec
|
62
|
+
homepage: https://github.com/mhib/pairing_heap
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata:
|
66
|
+
homepage_uri: https://github.com/mhib/pairing_heap
|
67
|
+
source_code_uri: https://github.com/mhib/pairing_heap
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.3.0
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubygems_version: 3.2.3
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Performant priority queue in pure Ruby with support for changing priority
|
87
|
+
test_files: []
|