pairing_heap 0.3.0 → 2.0.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.
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
+ require "standard/rake"
5
6
 
6
7
  Rake::TestTask.new(:test) do |t|
7
8
  t.libs << "test"
@@ -9,4 +10,4 @@ Rake::TestTask.new(:test) do |t|
9
10
  t.test_files = FileList["test/**/*_test.rb"]
10
11
  end
11
12
 
12
- task default: %i[test]
13
+ task default: %i[test standard]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PairingHeap
4
- VERSION = "0.3.0"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/pairing_heap.rb CHANGED
@@ -8,18 +8,21 @@ module PairingHeap
8
8
  return heaps if heaps.next_sibling.nil?
9
9
 
10
10
  # [H1, H2, H3, H4, H5, H6, H7] => [H1H2, H3H4, H5H6, H7]
11
- stack = []
12
- current = heaps
13
- while current
14
- prev = current
15
- current = current.next_sibling
16
- unless current
17
- stack << prev
11
+ pairs = nil
12
+ left = heaps
13
+ while left
14
+ right = left.next_sibling
15
+ unless right
16
+ left.next_sibling = pairs
17
+ pairs = left
18
18
  break
19
19
  end
20
- next_val = current.next_sibling
21
- stack << meld(prev, current)
22
- current = next_val
20
+ next_val = right.next_sibling
21
+ right = meld(left, right)
22
+ right.next_sibling = pairs
23
+ pairs = right
24
+
25
+ left = next_val
23
26
  end
24
27
 
25
28
  # [H1H2, H3H4, H5H6, H7]
@@ -27,13 +30,14 @@ module PairingHeap
27
30
  # [H1H2, H3H45H67]
28
31
  # [H1H2H3H45H67]
29
32
  # return H1H2H3H45H67
30
- while true
31
- right = stack.pop
32
- return right if stack.empty?
33
-
34
- left = stack.pop
35
- stack << meld(left, right)
33
+ left = pairs
34
+ right = pairs.next_sibling
35
+ while right
36
+ next_val = right.next_sibling
37
+ left = meld(left, right)
38
+ right = next_val
36
39
  end
40
+ left
37
41
  end
38
42
  end
39
43
  private_constant :MergePairs
@@ -43,22 +47,22 @@ module PairingHeap
43
47
  class PairingHeap
44
48
  class Node
45
49
  attr_accessor :elem, :priority, :subheaps, :parent, :prev_sibling, :next_sibling
46
- def initialize(elem, priority, subheaps, parent, prev_sibling, next_sibling)
50
+ def initialize(elem, priority)
47
51
  @elem = elem
48
52
  @priority = priority
49
- @subheaps = subheaps
50
- @parent = parent
51
- @prev_sibling = prev_sibling
52
- @next_sibling = next_sibling
53
+ @subheaps = nil
54
+ @parent = nil
55
+ @prev_sibling = nil
56
+ @next_sibling = nil
53
57
  end
54
58
 
55
59
  def remove_from_parents_list!
56
- if self.prev_sibling
57
- self.prev_sibling.next_sibling = self.next_sibling
58
- self.next_sibling.prev_sibling = self.prev_sibling if self.next_sibling
59
- elsif self.parent.subheaps.equal?(self)
60
- self.parent.subheaps = self.next_sibling
61
- self.next_sibling.prev_sibling = nil if self.next_sibling
60
+ if prev_sibling
61
+ prev_sibling.next_sibling = next_sibling
62
+ next_sibling.prev_sibling = prev_sibling if next_sibling
63
+ else # parent.subheaps must equal self
64
+ parent.subheaps = next_sibling
65
+ next_sibling.prev_sibling = nil if next_sibling
62
66
  end
63
67
  self.prev_sibling = nil
64
68
  self.next_sibling = nil
@@ -66,7 +70,8 @@ module PairingHeap
66
70
  end
67
71
  private_constant :Node
68
72
 
69
- # @param &block Optional heap property priority comparator. `<:=.to_proc` by default
73
+ # @yield [l_priority, r_priority] Optional heap property priority comparator. `<:=.to_proc` by default
74
+ # @yieldreturn [boolean] if `l_priority` is more prioritary than `r_priority`, or the priorities are equal
70
75
  def initialize(&block)
71
76
  @root = nil
72
77
  @nodes = {}
@@ -79,15 +84,20 @@ module PairingHeap
79
84
  # @param priority Priority of the element
80
85
  # @raise [ArgumentError] if the element is already in the heap
81
86
  # @return [PairingHeap]
82
- def push(elem, priority)
87
+ def push(elem, priority = elem)
83
88
  raise ArgumentError, "Element already in the heap" if @nodes.key?(elem)
84
89
 
85
- node = Node.new(elem, priority, nil, nil, nil, nil)
90
+ node = Node.new(elem, priority)
86
91
  @nodes[elem] = node
87
- @root = meld(@root, node)
92
+ @root = if @root
93
+ meld(@root, node)
94
+ else
95
+ node
96
+ end
88
97
  self
89
98
  end
90
- alias enqueue push
99
+ alias_method :enqueue, :push
100
+ alias_method :offer, :push
91
101
 
92
102
  # Returns the element at the top of the heap
93
103
  # Time Complexity: O(1)
@@ -95,7 +105,13 @@ module PairingHeap
95
105
  @root&.elem
96
106
  end
97
107
 
108
+ # @return [Object]
98
109
  def peek_priority
110
+ @root&.priority
111
+ end
112
+
113
+ # @return [Array(Object, Object)]
114
+ def peek_with_priority
99
115
  [@root&.elem, @root&.priority]
100
116
  end
101
117
 
@@ -116,13 +132,12 @@ module PairingHeap
116
132
  def size
117
133
  @nodes.size
118
134
  end
119
- alias length size
135
+ alias_method :length, :size
120
136
 
121
- # Removes element from the top of the heap
137
+ # Removes element from the top of the heap and returns it
122
138
  # Time Complexity: O(N)
123
139
  # Amortized time Complexity: O(log(N))
124
- # @raise [ArgumEntError] if the heap is empty
125
- # @return [PairingHeap]
140
+ # @raise [ArgumentError] if the heap is empty
126
141
  def pop
127
142
  raise ArgumentError, "Cannot remove from an empty heap" if @root.nil?
128
143
 
@@ -136,9 +151,17 @@ module PairingHeap
136
151
  end
137
152
  elem
138
153
  end
139
- alias dequeue pop
154
+ alias_method :dequeue, :pop
140
155
 
156
+ # @return [Object]
141
157
  def pop_priority
158
+ node = @root
159
+ pop
160
+ node.priority
161
+ end
162
+
163
+ # @return [Array(Object, Object)]
164
+ def pop_with_priority
142
165
  node = @root
143
166
  pop
144
167
  [node.elem, node.priority]
@@ -149,7 +172,7 @@ module PairingHeap
149
172
  # Amortized Time Complexity: o(log(N))
150
173
  # @param elem Element
151
174
  # @param priority New priority
152
- # @raise [ArgumentError] if the element heap is not in heap or the new priority is less prioritary
175
+ # @raise [ArgumentError] if the element is not in the heap or the new priority is less prioritary
153
176
  # @return [PairingHeap]
154
177
  def change_priority(elem, priority)
155
178
  node = @nodes[elem]
@@ -171,7 +194,7 @@ module PairingHeap
171
194
  # Removes element from the heap
172
195
  # Time Complexity: O(N)
173
196
  # Amortized Time Complexity: O(log(N))
174
- # @raise [ArgumentError] if the element heap is not in heap
197
+ # @raise [ArgumentError] if the element is not in the heap
175
198
  # @return [PairingHeap]
176
199
  def delete(elem)
177
200
  node = @nodes[elem]
@@ -180,26 +203,57 @@ module PairingHeap
180
203
  @nodes.delete(elem)
181
204
  if node.parent.nil?
182
205
  @root = merge_pairs(node.subheaps)
206
+ if @root
207
+ @root.parent = nil
208
+ @root.prev_sibling = nil
209
+ @root.next_sibling = nil
210
+ end
183
211
  else
184
212
  node.remove_from_parents_list!
185
213
  new_heap = merge_pairs(node.subheaps)
186
214
  if new_heap
187
- new_heap.prev_sibling = nil
188
- new_heap.next_sibling = nil
215
+ @root = meld(new_heap, @root)
216
+ @root.parent = nil
217
+ @root.prev_sibling = nil
218
+ @root.next_sibling = nil
189
219
  end
190
- @root = meld(new_heap, @root)
191
220
  end
192
- @root&.parent = nil
193
221
  self
194
222
  end
195
223
 
224
+ # Returns priority of the provided element
225
+ # Time Complexity: O(1)
226
+ # @raise [ArgumentError] if the element is not in the heap
227
+ # @return [Object]
228
+ def get_priority(elem)
229
+ node = @nodes[elem]
230
+ raise ArgumentError, "Provided element is not in heap" if node.nil?
231
+ node.priority
232
+ end
233
+
234
+ # Returns a pair where first element is success flag, and second element is priority
235
+ # Time Complexity: O(1)
236
+ # @return [Array(false, nil)] if the element is not in heap
237
+ # @return [Array(true, Object)] if the element is in heap;
238
+ # second element of returned tuple is the priority
239
+ def get_priority_if_exists(elem)
240
+ node = @nodes[elem]
241
+ return [false, nil] if node.nil?
242
+ [true, node.priority]
243
+ end
244
+
245
+ # Returns enumerator of elements. No order guarantees are provided.
246
+ # @return [Enumerator]
247
+ def each
248
+ return to_enum(__method__) { size } unless block_given?
249
+ NodeVisitor.visit_node(@root) { |x| yield x.elem }
250
+ end
251
+
196
252
  private
253
+
197
254
  include MergePairs
198
255
 
199
256
  def meld(left, right)
200
- return right if left.nil?
201
- return left if right.nil?
202
-
203
257
  if @order[left.priority, right.priority]
204
258
  parent = left
205
259
  child = right
@@ -219,16 +273,17 @@ module PairingHeap
219
273
  class SimplePairingHeap
220
274
  class Node
221
275
  attr_accessor :elem, :priority, :subheaps, :next_sibling
222
- def initialize(elem, priority, subheaps, next_sibling)
276
+ def initialize(elem, priority)
223
277
  @elem = elem
224
278
  @priority = priority
225
- @subheaps = subheaps
226
- @next_sibling = next_sibling
279
+ @subheaps = nil
280
+ @next_sibling = nil
227
281
  end
228
282
  end
229
283
  private_constant :Node
230
284
 
231
- # @param &block Optional heap property priority comparator. `<:=.to_proc` by default
285
+ # @yield [l_priority, r_priority] Optional heap property priority comparator. `<:=.to_proc` by default
286
+ # @yieldreturn [boolean] if `l_priority` is more prioritary than `r_priority`, or the priorities are equal
232
287
  def initialize(&block)
233
288
  @root = nil
234
289
  @order = block || :<=.to_proc
@@ -239,15 +294,19 @@ module PairingHeap
239
294
  # Time Complexity: O(1)
240
295
  # @param elem Element to be pushed
241
296
  # @param priority Priority of the element
242
- # @raise [ArgumentError] if the element is already in the heap
243
297
  # @return [PairingHeap]
244
- def push(elem, priority)
245
- node = Node.new(elem, priority, nil, nil)
246
- @root = meld(@root, node)
298
+ def push(elem, priority = elem)
299
+ node = Node.new(elem, priority)
300
+ @root = if @root
301
+ meld(@root, node)
302
+ else
303
+ node
304
+ end
247
305
  @size += 1
248
306
  self
249
307
  end
250
- alias enqueue push
308
+ alias_method :enqueue, :push
309
+ alias_method :offer, :push
251
310
 
252
311
  # Returns the element at the top of the heap
253
312
  # Time Complexity: O(1)
@@ -255,7 +314,13 @@ module PairingHeap
255
314
  @root&.elem
256
315
  end
257
316
 
317
+ # @return [Object]
258
318
  def peek_priority
319
+ @root&.priority
320
+ end
321
+
322
+ # @return [Array(Object, Object)]
323
+ def peek_with_priority
259
324
  [@root&.elem, @root&.priority]
260
325
  end
261
326
 
@@ -273,42 +338,51 @@ module PairingHeap
273
338
 
274
339
  # Time Complexity: O(1)
275
340
  # @return [Integer]
276
- def size
277
- @size
278
- end
279
- alias length size
341
+ attr_reader :size
342
+ alias_method :length, :size
280
343
 
281
- # Removes element from the top of the heap
344
+ # Removes an element from the top of the heap and returns it
282
345
  # Time Complexity: O(N)
283
346
  # Amortized time Complexity: O(log(N))
284
- # @raise [ArgumEntError] if the heap is empty
285
- # @return [PairingHeap]
347
+ # @raise [ArgumentError] if the heap is empty
286
348
  def pop
287
349
  raise ArgumentError, "Cannot remove from an empty heap" if @root.nil?
288
350
  @size -= 1
289
351
 
290
352
  elem = @root.elem
291
353
  @root = merge_pairs(@root.subheaps)
292
- if @root
293
- @root.next_sibling = nil
294
- end
354
+ @root&.next_sibling = nil
355
+
295
356
  elem
296
357
  end
297
- alias dequeue pop
358
+ alias_method :dequeue, :pop
298
359
 
360
+ # @return [Object]
299
361
  def pop_priority
362
+ node = @root
363
+ pop
364
+ node.priority
365
+ end
366
+
367
+ # @return [Array(Object, Object)]
368
+ def pop_with_priority
300
369
  node = @root
301
370
  pop
302
371
  [node.elem, node.priority]
303
372
  end
304
373
 
374
+ # Returns enumerator of elements. No order guarantees are provided.
375
+ # @return [Enumerator]
376
+ def each
377
+ return to_enum(__method__) { size } unless block_given?
378
+ NodeVisitor.visit_node(@root) { |x| yield x.elem }
379
+ end
380
+
305
381
  private
382
+
306
383
  include MergePairs
307
384
 
308
385
  def meld(left, right)
309
- return right if left.nil?
310
- return left if right.nil?
311
-
312
386
  if @order[left.priority, right.priority]
313
387
  parent = left
314
388
  child = right
@@ -322,16 +396,15 @@ module PairingHeap
322
396
  end
323
397
  end
324
398
 
325
-
326
399
  # Priority queue where the smallest priority is the most prioritary
327
400
  class MinPriorityQueue < PairingHeap
328
401
  def initialize
329
402
  super(&:<=)
330
403
  end
331
404
 
332
- alias decrease_key change_priority
333
- alias min peek
334
- alias extract_min dequeue
405
+ alias_method :decrease_key, :change_priority
406
+ alias_method :min, :peek
407
+ alias_method :extract_min, :dequeue
335
408
  end
336
409
 
337
410
  # Priority queue where the highest priority is the most prioritary
@@ -340,17 +413,17 @@ module PairingHeap
340
413
  super(&:>=)
341
414
  end
342
415
 
343
- alias increase_key change_priority
344
- alias max peek
345
- alias extract_max dequeue
416
+ alias_method :increase_key, :change_priority
417
+ alias_method :max, :peek
418
+ alias_method :extract_max, :dequeue
346
419
  end
347
420
 
348
421
  # Priority queue with change_priority, that accepts changing to a less prioritary priority
349
422
  class SafeChangePriorityQueue < PairingHeap
350
- # Changes a priority of the element to a more prioritary one
423
+ # Changes a priority of the element
351
424
  # Time Complexity: O(N)
352
425
  # Amortized Time Complexity: O(log(N))
353
- # @raise [ArgumentError] if the element heap is not in the heap
426
+ # @raise [ArgumentError] if the element is not in the heap
354
427
  # @return [PairingHeap]
355
428
  def change_priority(elem, priority)
356
429
  raise ArgumentError, "Provided element is not in heap" unless @nodes.key?(elem)
@@ -362,4 +435,22 @@ module PairingHeap
362
435
  end
363
436
  end
364
437
  end
438
+
439
+ module NodeVisitor
440
+ extend self
441
+
442
+ def visit_node(node, &block)
443
+ return unless node
444
+
445
+ block.call(node)
446
+
447
+ if node.subheaps
448
+ visit_node(node.subheaps, &block)
449
+ end
450
+ if node.next_sibling
451
+ visit_node(node.next_sibling, &block)
452
+ end
453
+ end
454
+ end
455
+ private_constant :NodeVisitor
365
456
  end
data/pairing_heap.gemspec CHANGED
@@ -3,19 +3,19 @@
3
3
  require_relative "lib/pairing_heap/version"
4
4
 
5
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"
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
15
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
16
16
 
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = spec.homepage
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
19
  spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/pairing_heap"
20
20
 
21
21
  # Specify which files should be added to the gem when it is released.
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
24
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
25
  end
26
- spec.bindir = "exe"
27
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
30
  # Uncomment to register a new dependency of your gem
@@ -32,6 +32,8 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.add_development_dependency "minitest", "~> 5.0"
34
34
  spec.add_development_dependency "rake", "~> 13.0"
35
+ spec.add_development_dependency "codecov", "0.6.0"
36
+ spec.add_development_dependency "standard", "~> 1.20"
35
37
 
36
38
  # For more information and examples about making a new gem, checkout our
37
39
  # guide at: https://bundler.io/guides/creating_gem.html
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pairing_heap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcin Henryk Bartkowiak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-08 00:00:00.000000000 Z
11
+ date: 2022-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.6.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.6.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: standard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.20'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.20'
41
69
  description: Performant priority queue in pure ruby with support for changing priority
42
70
  using pairing heap data structure
43
71
  email:
@@ -48,7 +76,7 @@ extra_rdoc_files: []
48
76
  files:
49
77
  - ".github/workflows/main.yml"
50
78
  - ".gitignore"
51
- - ".rubocop.yml"
79
+ - ".standard.yml"
52
80
  - Gemfile
53
81
  - Gemfile.lock
54
82
  - LICENSE.txt
@@ -81,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
109
  - !ruby/object:Gem::Version
82
110
  version: '0'
83
111
  requirements: []
84
- rubygems_version: 3.3.3
112
+ rubygems_version: 3.4.1
85
113
  signing_key:
86
114
  specification_version: 4
87
115
  summary: Performant priority queue in pure ruby with support for changing priority
data/.rubocop.yml DELETED
@@ -1,17 +0,0 @@
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