pairing_heap 0.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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