pairing_heap 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PairingHeap
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/pairing_heap.rb CHANGED
@@ -1,18 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PairingHeap
4
+ module MergePairs
5
+ # Non-recursive implementation of method described in https://en.wikipedia.org/wiki/Pairing_heap#delete-min
6
+ def merge_pairs(heaps)
7
+ return nil if heaps.nil?
8
+ return heaps if heaps.next_sibling.nil?
9
+
10
+ # [H1, H2, H3, H4, H5, H6, H7] => [H1H2, H3H4, H5H6, H7]
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
+ break
19
+ end
20
+ next_val = right.next_sibling
21
+ right = meld(left, right)
22
+ right.next_sibling = pairs
23
+ pairs = right
24
+
25
+ left = next_val
26
+ end
27
+
28
+ # [H1H2, H3H4, H5H6, H7]
29
+ # [H1H2, H3H4, H5H67]
30
+ # [H1H2, H3H45H67]
31
+ # [H1H2H3H45H67]
32
+ # return H1H2H3H45H67
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
39
+ end
40
+ left
41
+ end
42
+ end
43
+ private_constant :MergePairs
44
+
4
45
  # Pairing heap data structure implementation
5
46
  # @see https://en.wikipedia.org/wiki/Pairing_heap
6
47
  class PairingHeap
7
48
  class Node
8
49
  attr_accessor :elem, :priority, :subheaps, :parent, :prev_sibling, :next_sibling
9
- def initialize(elem, priority, subheaps, parent, prev_sibling, next_sibling)
50
+ def initialize(elem, priority)
10
51
  @elem = elem
11
52
  @priority = priority
12
- @subheaps = subheaps
13
- @parent = parent
14
- @prev_sibling = prev_sibling
15
- @next_sibling = next_sibling
53
+ @subheaps = nil
54
+ @parent = nil
55
+ @prev_sibling = nil
56
+ @next_sibling = nil
57
+ end
58
+
59
+ def remove_from_parents_list!
60
+ if self.prev_sibling
61
+ self.prev_sibling.next_sibling = self.next_sibling
62
+ self.next_sibling.prev_sibling = self.prev_sibling if self.next_sibling
63
+ elsif self.parent.subheaps.equal?(self)
64
+ self.parent.subheaps = self.next_sibling
65
+ self.next_sibling.prev_sibling = nil if self.next_sibling
66
+ end
67
+ self.prev_sibling = nil
68
+ self.next_sibling = nil
16
69
  end
17
70
  end
18
71
  private_constant :Node
@@ -33,12 +86,17 @@ module PairingHeap
33
86
  def push(elem, priority)
34
87
  raise ArgumentError, "Element already in the heap" if @nodes.key?(elem)
35
88
 
36
- node = Node.new(elem, priority, nil, nil, nil, nil)
89
+ node = Node.new(elem, priority)
37
90
  @nodes[elem] = node
38
- @root = meld(@root, node)
91
+ if @root
92
+ @root = meld(@root, node)
93
+ else
94
+ @root = node
95
+ end
39
96
  self
40
97
  end
41
98
  alias enqueue push
99
+ alias offer push
42
100
 
43
101
  # Returns the element at the top of the heap
44
102
  # Time Complexity: O(1)
@@ -46,6 +104,14 @@ module PairingHeap
46
104
  @root&.elem
47
105
  end
48
106
 
107
+ def peek_priority
108
+ @root&.priority
109
+ end
110
+
111
+ def peek_with_priority
112
+ [@root&.elem, @root&.priority]
113
+ end
114
+
49
115
  # Time Complexity: O(1)
50
116
  # @return [Boolean]
51
117
  def empty?
@@ -65,11 +131,10 @@ module PairingHeap
65
131
  end
66
132
  alias length size
67
133
 
68
- # Removes element from the top of the heap
134
+ # Removes element from the top of the heap and returns it
69
135
  # Time Complexity: O(N)
70
136
  # Amortized time Complexity: O(log(N))
71
- # @raise [ArgumEntError] if the heap is empty
72
- # @return [PairingHeap]
137
+ # @raise [ArgumentError] if the heap is empty
73
138
  def pop
74
139
  raise ArgumentError, "Cannot remove from an empty heap" if @root.nil?
75
140
 
@@ -85,12 +150,18 @@ module PairingHeap
85
150
  end
86
151
  alias dequeue pop
87
152
 
153
+ def pop_priority
154
+ node = @root
155
+ pop
156
+ [node.elem, node.priority]
157
+ end
158
+
88
159
  # Changes a priority of element to a more prioritary one
89
160
  # Time Complexity: O(1)
90
161
  # Amortized Time Complexity: o(log(N))
91
162
  # @param elem Element
92
163
  # @param priority New priority
93
- # @raise [ArgumentError] if the element heap is not in heap or the new priority is less prioritary
164
+ # @raise [ArgumentError] if the element is not in the heap or the new priority is less prioritary
94
165
  # @return [PairingHeap]
95
166
  def change_priority(elem, priority)
96
167
  node = @nodes[elem]
@@ -103,16 +174,16 @@ module PairingHeap
103
174
  return if node.parent.nil?
104
175
  return if @order[node.parent.priority, node.priority]
105
176
 
106
- remove_from_parents_list(node)
177
+ node.remove_from_parents_list!
107
178
  @root = meld(node, @root)
108
179
  @root.parent = nil
109
180
  self
110
181
  end
111
182
 
112
- # Removes element from the top of the heap
183
+ # Removes element from the heap
113
184
  # Time Complexity: O(N)
114
185
  # Amortized Time Complexity: O(log(N))
115
- # @raise [ArgumentError] if the element heap is not in heap
186
+ # @raise [ArgumentError] if the element is not in the heap
116
187
  # @return [PairingHeap]
117
188
  def delete(elem)
118
189
  node = @nodes[elem]
@@ -121,39 +192,37 @@ module PairingHeap
121
192
  @nodes.delete(elem)
122
193
  if node.parent.nil?
123
194
  @root = merge_pairs(node.subheaps)
195
+ if @root
196
+ @root.parent = nil
197
+ @root.prev_sibling = nil
198
+ @root.next_sibling = nil
199
+ end
124
200
  else
125
- remove_from_parents_list(node)
201
+ node.remove_from_parents_list!
126
202
  new_heap = merge_pairs(node.subheaps)
127
203
  if new_heap
128
- new_heap.prev_sibling = nil
129
- new_heap.next_sibling = nil
204
+ @root = meld(new_heap, @root)
205
+ @root.parent = nil
206
+ @root.prev_sibling = nil
207
+ @root.next_sibling = nil
130
208
  end
131
- @root = meld(new_heap, @root)
132
209
  end
133
- @root&.parent = nil
134
210
  self
135
211
  end
136
212
 
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
213
+ # Returns priority of the provided element
214
+ # Time Complexity: O(1)
215
+ # @raise [ArgumentError] if the element is not in the heap
216
+ def get_priority(elem)
217
+ node = @nodes[elem]
218
+ raise ArgumentError, "Provided element is not in heap" if node.nil?
219
+ node.priority
151
220
  end
152
221
 
153
- def meld(left, right)
154
- return right if left.nil?
155
- return left if right.nil?
222
+ private
223
+ include MergePairs
156
224
 
225
+ def meld(left, right)
157
226
  if @order[left.priority, right.priority]
158
227
  parent = left
159
228
  child = right
@@ -168,42 +237,124 @@ module PairingHeap
168
237
  child.parent = parent
169
238
  parent
170
239
  end
240
+ end
171
241
 
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?
242
+ class SimplePairingHeap
243
+ class Node
244
+ attr_accessor :elem, :priority, :subheaps, :next_sibling
245
+ def initialize(elem, priority)
246
+ @elem = elem
247
+ @priority = priority
248
+ @subheaps = nil
249
+ @next_sibling = nil
250
+ end
251
+ end
252
+ private_constant :Node
176
253
 
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
254
+ # @param &block Optional heap property priority comparator. `<:=.to_proc` by default
255
+ def initialize(&block)
256
+ @root = nil
257
+ @order = block || :<=.to_proc
258
+ @size = 0
259
+ end
260
+
261
+ # Pushes element to the heap.
262
+ # Time Complexity: O(1)
263
+ # @param elem Element to be pushed
264
+ # @param priority Priority of the element
265
+ # @return [PairingHeap]
266
+ def push(elem, priority)
267
+ node = Node.new(elem, priority)
268
+ if @root
269
+ @root = meld(@root, node)
270
+ else
271
+ @root = node
190
272
  end
273
+ @size += 1
274
+ self
275
+ end
276
+ alias enqueue push
277
+ alias offer push
191
278
 
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?
279
+ # Returns the element at the top of the heap
280
+ # Time Complexity: O(1)
281
+ def peek
282
+ @root&.elem
283
+ end
284
+
285
+ def peek_priority
286
+ @root&.priority
287
+ end
288
+
289
+ def peek_with_priority
290
+ [@root&.elem, @root&.priority]
291
+ end
292
+
293
+ # Time Complexity: O(1)
294
+ # @return [Boolean]
295
+ def empty?
296
+ @root.nil?
297
+ end
298
+
299
+ # Time Complexity: O(1)
300
+ # @return [Boolean]
301
+ def any?
302
+ !@root.nil?
303
+ end
304
+
305
+ # Time Complexity: O(1)
306
+ # @return [Integer]
307
+ def size
308
+ @size
309
+ end
310
+ alias length size
311
+
312
+ # Removes element from the top of the heap and returns it
313
+ # Time Complexity: O(N)
314
+ # Amortized time Complexity: O(log(N))
315
+ # @raise [ArgumEntError] if the heap is empty
316
+ def pop
317
+ raise ArgumentError, "Cannot remove from an empty heap" if @root.nil?
318
+ @size -= 1
319
+
320
+ elem = @root.elem
321
+ @root = merge_pairs(@root.subheaps)
322
+ @root&.next_sibling = nil
323
+
324
+ elem
325
+ end
326
+ alias dequeue pop
200
327
 
201
- left = stack.pop
202
- stack << meld(left, right)
328
+ def pop_priority
329
+ node = @root
330
+ pop
331
+ node.priority
332
+ end
333
+
334
+ def pop_with_priority
335
+ node = @root
336
+ pop
337
+ [node.elem, node.priority]
338
+ end
339
+
340
+ private
341
+ include MergePairs
342
+
343
+ def meld(left, right)
344
+ if @order[left.priority, right.priority]
345
+ parent = left
346
+ child = right
347
+ else
348
+ parent = right
349
+ child = left
203
350
  end
351
+ child.next_sibling = parent.subheaps
352
+ parent.subheaps = child
353
+ parent
204
354
  end
205
355
  end
206
356
 
357
+
207
358
  # Priority queue where the smallest priority is the most prioritary
208
359
  class MinPriorityQueue < PairingHeap
209
360
  def initialize
@@ -231,7 +382,7 @@ module PairingHeap
231
382
  # Changes a priority of the element to a more prioritary one
232
383
  # Time Complexity: O(N)
233
384
  # Amortized Time Complexity: O(log(N))
234
- # @raise [ArgumentError] if the element heap is not in the heap
385
+ # @raise [ArgumentError] if the element is not in the heap
235
386
  # @return [PairingHeap]
236
387
  def change_priority(elem, priority)
237
388
  raise ArgumentError, "Provided element is not in heap" unless @nodes.key?(elem)
data/pairing_heap.gemspec CHANGED
@@ -8,14 +8,15 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Marcin Henryk Bartkowiak"]
9
9
  spec.email = ["mhbartkowiak@gmail.com"]
10
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"
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
13
  spec.homepage = "https://github.com/mhib/pairing_heap"
14
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
+ spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/pairing_heap"
19
20
 
20
21
  # Specify which files should be added to the gem when it is released.
21
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
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.1.0
4
+ version: 1.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: 2021-02-19 00:00:00.000000000 Z
11
+ date: 2022-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '13.0'
41
- description: Performant priority queue in pure Ruby with support for changing priority
41
+ description: Performant priority queue in pure ruby with support for changing priority
42
42
  using pairing heap data structure
43
43
  email:
44
44
  - mhbartkowiak@gmail.com
@@ -65,6 +65,7 @@ licenses:
65
65
  metadata:
66
66
  homepage_uri: https://github.com/mhib/pairing_heap
67
67
  source_code_uri: https://github.com/mhib/pairing_heap
68
+ documentation_uri: https://rubydoc.info/gems/pairing_heap
68
69
  post_install_message:
69
70
  rdoc_options: []
70
71
  require_paths:
@@ -80,8 +81,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
81
  - !ruby/object:Gem::Version
81
82
  version: '0'
82
83
  requirements: []
83
- rubygems_version: 3.2.3
84
+ rubygems_version: 3.3.3
84
85
  signing_key:
85
86
  specification_version: 4
86
- summary: Performant priority queue in pure Ruby with support for changing priority
87
+ summary: Performant priority queue in pure ruby with support for changing priority
87
88
  test_files: []