├── .ameba.yml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── shard.yml ├── spec ├── heap_spec.cr ├── named_queue_spec.cr └── queue_spec.cr └── src ├── priority-queue.cr └── priority-queue ├── heap.cr ├── max_heap.cr └── named_queue.cr /.ameba.yml: -------------------------------------------------------------------------------- 1 | Lint/NotNil: 2 | Enabled: false 3 | 4 | Naming/BlockParameterName: 5 | Enabled: false 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: "0 6 * * 1" 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - {os: ubuntu-latest, crystal: latest} 15 | - {os: ubuntu-latest, crystal: nightly} 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - name: Install Crystal 19 | uses: crystal-lang/install-crystal@v1 20 | with: 21 | crystal: ${{matrix.crystal}} 22 | - uses: actions/checkout@v4 23 | - name: Install dependencies 24 | run: shards install --ignore-crystal-version 25 | - name: Lint 26 | run: ./bin/ameba 27 | - name: Format 28 | run: crystal tool format --check 29 | - name: Run tests 30 | run: crystal spec -v --error-trace 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | lib 3 | bin 4 | .crystal 5 | .shards 6 | app 7 | *.dwarf 8 | *.lock 9 | *.DS_Store 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) ACA Projects 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crystal Lang Priority Queue 2 | 3 | [![CI](https://github.com/spider-gazelle/priority-queue/actions/workflows/ci.yml/badge.svg)](https://github.com/spider-gazelle/priority-queue/actions/workflows/ci.yml) 4 | 5 | 6 | Usage 7 | ===== 8 | 9 | Simple priority queue. 10 | 11 | * Higher numbers `pop` first 12 | * lower numbers `shift` first 13 | * always insert using `push` or `<<` to maintain validity 14 | 15 | ```crystal 16 | 17 | require 'priority-queue' 18 | 19 | queue = Priority::Queue(String).new 20 | queue.push 10, "Insert 1" 21 | queue.push 20, "Insert 2" 22 | 23 | queue.pop.value # => "Insert 2" 24 | queue.pop.value # => "Insert 1" 25 | 26 | ``` 27 | 28 | A named priority queue where newer items with the same name replace existing items. The highest of the two priorities is inherited by the new item. 29 | 30 | ```crystal 31 | 32 | require 'priority-queue' 33 | 34 | queue = Priority::NamedQueue(String).new 35 | queue.push 20, "Insert 1", :named 36 | queue.push 20, "Insert 2" 37 | queue.push 10, "Insert 3", :named 38 | 39 | queue.size # => 2 40 | 41 | queue.pop.value # => "Insert 3" 42 | queue.pop.value # => "Insert 2" 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: priority-queue 2 | version: 1.1.2 3 | crystal: ">= 0.36.1" 4 | 5 | dependencies: 6 | bisect: 7 | github: spider-gazelle/bisect 8 | 9 | development_dependencies: 10 | ameba: 11 | github: veelenga/ameba 12 | -------------------------------------------------------------------------------- /spec/heap_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/priority-queue/max_heap" 3 | 4 | describe Priority::Heap do 5 | describe "(empty)" do 6 | it "should let you insert and remove one item" do 7 | heap = Priority::MaxHeap(Int32, Int32).new 8 | heap.size.should eq(0) 9 | 10 | heap.push(1) 11 | heap.size.should eq(1) 12 | 13 | heap.pop 14 | heap.size.should eq(0) 15 | end 16 | 17 | it "should merge another heap" do 18 | heap = Priority::MaxHeap(Int32, Int32).new 19 | heap2 = Priority::MaxHeap(Int32, Int32).new 20 | heap2.push(3) 21 | heap2.push(4) 22 | heap2.push(5) 23 | heap2.push(5) 24 | 25 | heap.merge! heap2 26 | heap2.size.should eq(0) 27 | heap.size.should eq(4) 28 | heap.pop.should eq(5) 29 | heap.pop.should eq(5) 30 | heap.pop.should eq(4) 31 | heap.pop.should eq(3) 32 | heap.size.should eq(0) 33 | end 34 | end 35 | 36 | describe "(non-empty)" do 37 | it "should display the correct size" do 38 | heap = Priority::MaxHeap(Int32, Int32).new 39 | random_array = [] of Int32 40 | num_items = 100 41 | num_items.times { random_array << rand(num_items) } 42 | random_array.each { |i| heap.push(i) } 43 | 44 | heap.size.should eq(num_items) 45 | 46 | ordered = [] of Int32 47 | while !heap.empty? 48 | ordered << heap.pop.not_nil! 49 | end 50 | ordered.should eq(random_array.sort.reverse!) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/named_queue_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/priority-queue" 3 | 4 | describe Priority::NamedQueue do 5 | it "should display the correct size and sort correctly" do 6 | queue = Priority::NamedQueue(Int32).new 7 | random_array = [] of Int32 8 | num_items = 100 9 | num_items.times { random_array << rand(num_items) } 10 | random_array.each { |i| queue.push(i, i) } 11 | 12 | queue.size.should eq(num_items) 13 | 14 | ordered = [] of Int32 15 | while !queue.empty? 16 | ordered << queue.pop.value 17 | end 18 | ordered.should eq(random_array.sort.reverse!) 19 | end 20 | 21 | it "should replace items of the same name" do 22 | queue = Priority::NamedQueue(String).new 23 | queue.push 11, "Test1", :named 24 | queue.push 11, "Test2" 25 | queue.push 10, "Test4", :named 26 | queue.push 11, "Test3" 27 | 28 | queue.size.should eq(3) 29 | queue.named_items.size.should eq(1) 30 | 31 | # Test 4 priority was upgraded 32 | queue.pop.value.should eq("Test4") 33 | queue.pop.value.should eq("Test2") 34 | queue.pop.value.should eq("Test3") 35 | 36 | queue.size.should eq(0) 37 | queue.named_items.size.should eq(0) 38 | 39 | queue.push 11, "Test1" 40 | queue.push 11, "Test2", :named 41 | queue.push 11, "Test3" 42 | queue.push 12, "Test4", :named 43 | 44 | queue.size.should eq(3) 45 | queue.named_items.size.should eq(1) 46 | 47 | # Test 2 was removed from the queue and test 4 replaced it 48 | queue.pop.value.should eq("Test4") 49 | queue.pop.value.should eq("Test1") 50 | queue.pop.value.should eq("Test3") 51 | 52 | queue.size.should eq(0) 53 | queue.named_items.size.should eq(0) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/queue_spec.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/priority-queue" 3 | 4 | describe Priority::Queue do 5 | it "should display the correct size and sort correctly" do 6 | queue = Priority::Queue(Int32).new 7 | random_array = [] of Int32 8 | num_items = 100 9 | num_items.times { random_array << rand(num_items) } 10 | random_array.each { |i| queue.push(i, i) } 11 | 12 | queue.size.should eq(num_items) 13 | 14 | ordered = [] of Int32 15 | while !queue.empty? 16 | ordered << queue.pop.value 17 | end 18 | ordered.should eq(random_array.sort.reverse!) 19 | end 20 | 21 | it "should add items of the same priority level in the correct order" do 22 | queue = Priority::Queue(String).new 23 | queue.push 11, "Test1" 24 | queue.push 11, "Test2" 25 | queue.push 10, "Test4" 26 | queue.push 11, "Test3" 27 | 28 | queue.size.should eq(4) 29 | 30 | queue.pop.value.should eq("Test1") 31 | queue.pop.value.should eq("Test2") 32 | queue.pop.value.should eq("Test3") 33 | queue.pop.value.should eq("Test4") 34 | 35 | queue.size.should eq(0) 36 | end 37 | 38 | it "should accept priority level as a floating-point number" do 39 | queue = Priority::Queue(String).new 40 | queue.push 0.11, "Test1" 41 | queue.push 0.12, "Test2" 42 | queue.push 0.13, "Test3" 43 | 44 | queue.size.should eq(3) 45 | 46 | queue.pop.value.should eq("Test3") 47 | queue.pop.value.should eq("Test2") 48 | queue.pop.value.should eq("Test1") 49 | 50 | queue.size.should eq(0) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /src/priority-queue.cr: -------------------------------------------------------------------------------- 1 | require "bisect" 2 | 3 | module Priority 4 | alias Value = Int8 | Int16 | Int32 | Int64 | Int128 | Float32 | Float64 5 | 6 | struct Item(V) 7 | include Comparable(Item(V)) 8 | 9 | def initialize(@priority : Value, @value : V, name = nil) 10 | @name = name.to_s if name 11 | end 12 | 13 | @name : String? 14 | getter :value, :name 15 | property :priority 16 | 17 | # required for comparable 18 | def <=>(other) 19 | @priority <=> other.priority 20 | end 21 | end 22 | 23 | class Queue(V) 24 | def initialize 25 | @array = [] of Item(V) 26 | end 27 | 28 | def push(priority : Value, value : V, name = nil) 29 | item = Item(V).new(priority, value, name) 30 | push(item) 31 | end 32 | 33 | def push(item : Item(V)) 34 | Bisect.insort_left(@array, item) 35 | end 36 | 37 | def push(*items : Item(V)) 38 | items.each { |item| push(item) } 39 | end 40 | 41 | def <<(item : Item(V)) 42 | push(item) 43 | end 44 | 45 | delegate empty?, first, last, first?, last?, size, shift, pop, clear, to: @array 46 | forward_missing_to @array 47 | end 48 | end 49 | 50 | require "./priority-queue/named_queue" 51 | -------------------------------------------------------------------------------- /src/priority-queue/heap.cr: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/kanwei/algorithms/blob/master/lib/containers/heap.rb 2 | # NOTE:: methods such as delete don't work, here or in the ruby version 3 | # Most other functionality works, left here mainly for anyone who wants to use 4 | # this data structure. This code is not used in the priority queue implementation 5 | 6 | class Priority::Heap(K, V) 7 | # call-seq: 8 | # size -> int 9 | # 10 | # Return the number of elements in the heap. 11 | def size : Int32 12 | @size 13 | end 14 | 15 | getter :stored 16 | 17 | def next_node 18 | @next 19 | end 20 | 21 | # call-seq: 22 | # Heap.new(optional_array) { |x, y| optional_comparison_fn } -> new_heap 23 | # 24 | # If an optional array is passed, the entries in the array are inserted into the heap with 25 | # equal key and value fields. Also, an optional block can be passed to define the function 26 | # that maintains heap property. For example, a min-heap can be created with: 27 | # 28 | # minheap = Heap.new { |x, y| (x <=> y) == -1 } 29 | # minheap.push(6) 30 | # minheap.push(10) 31 | # minheap.pop #=> 6 32 | # 33 | # Thus, smaller elements will be parent nodes. The heap defaults to a min-heap if no block 34 | # is given. 35 | def initialize 36 | @compare_fn = ->(x : K, y : K) { (x <=> y) == -1 } 37 | @next = nil 38 | @size = 0 39 | @stored = {} of K => Array(Node(K, V)) 40 | end 41 | 42 | def initialize(&@compare_fn : Proc(K, K, Bool)) 43 | @next = nil 44 | @size = 0 45 | @stored = {} of K => Array(Node(K, V)) 46 | end 47 | 48 | @next : Node(K, V)? 49 | @stored : Hash(K, Array(Node(K, V))) 50 | 51 | # call-seq: 52 | # push(key, value) -> value 53 | # push(value) -> value 54 | # 55 | # Inserts an item with a given key into the heap. If only one parameter is given, 56 | # the key is set to the value. 57 | # 58 | # Complexity: O(1) 59 | # 60 | # heap = MinHeap.new 61 | # heap.push(1, "Cat") 62 | # heap.push(2) 63 | # heap.pop #=> "Cat" 64 | # heap.pop #=> 2 65 | def push(key : K, value : V = key) 66 | raise "Heap keys must not be nil." unless key 67 | node = Node.new(key, value) 68 | # Add new node to the left of the @next node 69 | if lnext = @next 70 | node.right = lnext 71 | node.left = lnext.left 72 | node.left.right = node 73 | lnext.left = node 74 | if @compare_fn.call key, lnext.key 75 | @next = node 76 | end 77 | else 78 | @next = node 79 | end 80 | @size += 1 81 | 82 | array = @stored[key]? || (@stored[key] = [] of Node(K, V)) 83 | array << node 84 | value 85 | end 86 | 87 | def <<(key) 88 | push(key) 89 | end 90 | 91 | # call-seq: 92 | # key?(key) -> true or false 93 | # 94 | # Returns true if heap contains the key. 95 | # 96 | # Complexity: O(1) 97 | # 98 | # minheap = MinHeap.new([1, 2]) 99 | # minheap.key?(2) #=> true 100 | # minheap.key?(4) #=> false 101 | def key?(key) 102 | store = @stored[key]? 103 | store && !store.empty? 104 | end 105 | 106 | # call-seq: 107 | # next -> value 108 | # next -> nil 109 | # 110 | # Returns the value of the next item in heap order, but does not remove it. 111 | # 112 | # Complexity: O(1) 113 | # 114 | # minheap = MinHeap.new([1, 2]) 115 | # minheap.next #=> 1 116 | # minheap.size #=> 2 117 | def next 118 | lnext = @next 119 | lnext && lnext.value 120 | end 121 | 122 | # call-seq: 123 | # next_key -> key 124 | # next_key -> nil 125 | # 126 | # Returns the key associated with the next item in heap order, but does not remove the value. 127 | # 128 | # Complexity: O(1) 129 | # 130 | # minheap = MinHeap.new 131 | # minheap.push(1, :a) 132 | # minheap.next_key #=> 1 133 | # 134 | def next_key 135 | @next && @next.key 136 | end 137 | 138 | # call-seq: 139 | # clear -> nil 140 | # 141 | # Removes all elements from the heap, destructively. 142 | # 143 | # Complexity: O(1) 144 | # 145 | def clear 146 | @next = nil 147 | @size = 0 148 | @stored = {} of K => Array(Node(K, V)) 149 | nil 150 | end 151 | 152 | # call-seq: 153 | # empty? -> true or false 154 | # 155 | # Returns true if the heap is empty, false otherwise. 156 | def empty? 157 | @next.nil? 158 | end 159 | 160 | # call-seq: 161 | # merge!(otherheap) -> merged_heap 162 | # 163 | # Does a shallow merge of all the nodes in the other heap and clears the other 164 | # heap 165 | # 166 | # Complexity: O(1) 167 | # 168 | # heap = MinHeap.new([5, 6, 7, 8]) 169 | # otherheap = MinHeap.new([1, 2, 3, 4]) 170 | # heap.merge!(otherheap) 171 | # heap.size #=> 8 172 | # heap.pop #=> 1 173 | def merge!(otherheap) 174 | other_root = otherheap.next_node 175 | if other_root 176 | @stored = @stored.merge(otherheap.stored) { |_key, a, b| a + b } 177 | 178 | # Insert othernode's @next node to the left of current @next 179 | if lnext = @next 180 | left = lnext.left 181 | left.right = other_root if left 182 | left = other_root.left 183 | other_root.left = lnext.left 184 | if left 185 | left.right = lnext 186 | lnext.left = left 187 | end 188 | @next = other_root if @compare_fn.call(other_root.key, lnext.key) 189 | else 190 | @next = other_root 191 | end 192 | end 193 | @size += otherheap.size 194 | otherheap.clear 195 | @size 196 | end 197 | 198 | # call-seq: 199 | # pop -> value 200 | # pop -> nil 201 | # 202 | # Returns the value of the next item in heap order and removes it from the heap. 203 | # 204 | # Complexity: O(1) 205 | # 206 | # minheap = MinHeap.new([1, 2]) 207 | # minheap.pop #=> 1 208 | # minheap.size #=> 1 209 | def pop 210 | return nil unless @next 211 | lnext = @next.not_nil! 212 | popped = lnext 213 | if @size == 1 && popped 214 | clear 215 | return popped.value 216 | end 217 | # Merge the popped's children into root node 218 | 219 | if child = lnext.child 220 | child.parent = nil 221 | 222 | # get rid of parent 223 | sibling = child.right 224 | while sibling != lnext.child 225 | sibling.parent = nil 226 | sibling = sibling.right 227 | end 228 | 229 | # Merge the children into the root. If @next is the only root node, make its child the @next node 230 | if lnext.right == lnext 231 | lnext = @next = lnext.child 232 | else 233 | next_right = lnext.right.not_nil! 234 | next_left = lnext.left.not_nil! 235 | current_child = lnext.child.not_nil! 236 | next_right.left = current_child 237 | next_left.right = current_child.right 238 | current_child.right.left = next_left 239 | current_child.right = next_right 240 | @next = lnext.right 241 | end 242 | else 243 | lnext.left.right = lnext.right 244 | lnext.right.left = lnext.left 245 | @next = lnext.right 246 | end 247 | consolidate 248 | 249 | store = @stored[popped.key] 250 | result = store.delete(popped) 251 | raise "Couldn't delete node from stored nodes hash\n#{@stored.inspect}\n#{popped.inspect}" unless result 252 | 253 | @size -= 1 254 | popped.value 255 | end 256 | 257 | def next! 258 | pop 259 | end 260 | 261 | # call-seq: 262 | # change_key(key, new_key) -> [new_key, value] 263 | # change_key(key, new_key) -> nil 264 | # 265 | # Changes the key from one to another. Doing so must not violate the heap property or 266 | # an exception will be raised. If the key is found, an array containing the new key and 267 | # value pair is returned, otherwise nil is returned. 268 | # 269 | # In the case of duplicate keys, an arbitrary key is changed. This will be investigated 270 | # more in the future. 271 | # 272 | # Complexity: amortized O(1) 273 | # 274 | # minheap = MinHeap.new([1, 2]) 275 | # minheap.change_key(2, 3) #=> raise error since we can't increase the value in a min-heap 276 | # minheap.change_key(2, 0) #=> [0, 2] 277 | # minheap.pop #=> 2 278 | # minheap.pop #=> 1 279 | # ameba:disable Metrics/CyclomaticComplexity 280 | def change_key(key, new_key, delete = false) 281 | store = @stored[key]? 282 | return unless store 283 | return if store.empty? || (key == new_key) 284 | 285 | # Must maintain heap property 286 | raise "Changing this key would not maintain heap property!" unless delete || new_key && @compare_fn.call(new_key, key) 287 | node = store.shift 288 | if node 289 | if new_key 290 | node.key = new_key 291 | new_store = @stored[new_key]? || (@stored[new_key] = [] of Node(K, V)) 292 | new_store << node 293 | @stored[new_key] = new_store 294 | end 295 | parent = node.parent 296 | if parent 297 | # if heap property is violated 298 | if delete || new_key && @compare_fn.call(new_key, parent.key) 299 | cut(node, parent) 300 | cascading_cut(parent) 301 | end 302 | end 303 | lnext = @next 304 | @next = node if delete || lnext && @compare_fn.call(node.key, lnext.key) 305 | return [node.key, node.value] 306 | end 307 | nil 308 | end 309 | 310 | # call-seq: 311 | # delete(key) -> value 312 | # delete(key) -> nil 313 | # 314 | # Deletes the item with associated key and returns it. nil is returned if the key 315 | # is not found. In the case of nodes with duplicate keys, an arbitrary one is deleted. 316 | # 317 | # Complexity: amortized O(log n) 318 | # 319 | # minheap = MinHeap.new([1, 2]) 320 | # minheap.delete(1) #=> 1 321 | # minheap.size #=> 1 322 | def delete(key) 323 | pop if change_key(key, nil, true) 324 | end 325 | 326 | # Node class used internally 327 | class Node(K, V) # :nodoc: 328 | property :parent, :child, :key, :value, :degree, :marked 329 | setter :left, :right 330 | 331 | @right : Node(K, V)? 332 | @left : Node(K, V)? 333 | @child : Node(K, V)? 334 | @parent : Node(K, V)? 335 | 336 | def initialize(@key : K, @value : V) 337 | @degree = 0 338 | @marked = false 339 | @right = self 340 | @left = self 341 | end 342 | 343 | def marked? 344 | @marked == true 345 | end 346 | 347 | def left : Node(K, V) 348 | l = @left 349 | l.not_nil! 350 | end 351 | 352 | def right : Node(K, V) 353 | r = @right 354 | r.not_nil! 355 | end 356 | end 357 | 358 | # make node a child of a parent node 359 | private def link_nodes(child, parent) 360 | # link the child's siblings 361 | child.left.right = child.right 362 | child.right.left = child.left 363 | 364 | child.parent = parent 365 | 366 | # if parent doesn't have children, make new child its only child 367 | if parent.child.nil? 368 | parent.child = child.right = child.left = child 369 | else # otherwise insert new child into parent's children list 370 | current_child = parent.child.not_nil! 371 | child.left = current_child 372 | child.right = current_child.right 373 | current_child.right.left = child 374 | current_child.right = child 375 | end 376 | parent.degree += 1 377 | child.marked = false 378 | end 379 | 380 | # Makes sure the structure does not contain nodes in the root list with equal degrees 381 | private def consolidate 382 | roots = [] of Node(K, V) 383 | lnext = @next.not_nil! 384 | min = lnext 385 | # find the nodes in the list 386 | loop do 387 | roots << lnext 388 | lnext = lnext.right 389 | break if lnext == @next 390 | end 391 | degrees = [] of Node(K, V)? 392 | roots.each do |root| 393 | min = root if @compare_fn.call(root.key, min.key) 394 | # check if we need to merge 395 | if !degrees[root.degree]? # no other node with the same degree 396 | size = degrees.size 397 | while size <= root.degree 398 | size += 1 399 | degrees << nil 400 | end 401 | degrees[root.degree] = root 402 | next 403 | else # there is another node with the same degree, consolidate them 404 | degree = root.degree 405 | while degrees[degree]? 406 | other_root_with_degree = degrees[degree].not_nil! 407 | if @compare_fn.call(root.key, other_root_with_degree.key) # determine which node is the parent, which one is the child 408 | smaller, larger = root, other_root_with_degree 409 | else 410 | smaller, larger = other_root_with_degree, root 411 | end 412 | link_nodes(larger, smaller) 413 | size = degrees.size 414 | while size <= degree 415 | size += 1 416 | degrees << nil 417 | end 418 | degrees[degree] = nil 419 | root = smaller 420 | degree += 1 421 | end 422 | size = degrees.size 423 | while size <= root.degree 424 | size += 1 425 | degrees << nil 426 | end 427 | degrees[degree] = root 428 | min = root if min.key == root.key # this fixes a bug with duplicate keys not being in the right order 429 | end 430 | end 431 | @next = min 432 | end 433 | 434 | private def cascading_cut(node) 435 | p = node.parent 436 | if p 437 | if node.marked? 438 | cut(node, p) 439 | cascading_cut(p) 440 | else 441 | node.marked = true 442 | end 443 | end 444 | end 445 | 446 | # remove x from y's children and add x to the root list 447 | private def cut(x : Node(K, V), y : Node(K, V)) 448 | x.left.right = x.right 449 | x.right.left = x.left 450 | y.degree -= 1 451 | if y.degree == 0 452 | y.child = nil 453 | elsif y.child == x 454 | y.child = x.right 455 | end 456 | lnext = @next.not_nil! 457 | x.right = lnext 458 | x.left = lnext.left 459 | lnext.left = x 460 | x.left.right = x 461 | x.parent = nil 462 | x.marked = false 463 | end 464 | end 465 | -------------------------------------------------------------------------------- /src/priority-queue/max_heap.cr: -------------------------------------------------------------------------------- 1 | require "./heap" 2 | 3 | # A MaxHeap is a heap where the items are returned in descending order of key value. 4 | class Priority::MaxHeap(K, V) < Priority::Heap(K, V) 5 | # call-seq: 6 | # MaxHeap.new(ary) -> new_heap 7 | # 8 | # Creates a new MaxHeap with an optional array parameter of items to insert into the heap. 9 | # A MaxHeap is created by calling Heap.new { |x, y| (x <=> y) == 1 }, so this is a convenience class. 10 | # 11 | # maxheap = MaxHeap.new([1, 2, 3, 4]) 12 | # maxheap.pop #=> 4 13 | # maxheap.pop #=> 3 14 | def initialize 15 | super { |x, y| (x <=> y) == 1 } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /src/priority-queue/named_queue.cr: -------------------------------------------------------------------------------- 1 | require "../priority-queue" 2 | 3 | class Priority::NamedQueue(V) < Priority::Queue(V) 4 | @named_items = {} of String => Item(V) 5 | getter :named_items 6 | 7 | def push(item : Item(V)) 8 | if name = item.name 9 | if exists = @named_items[name]? 10 | index = bsearch_index { |existing, _index| existing.name == item.name } 11 | if index 12 | if item.priority > exists.priority 13 | delete_at(index) 14 | Bisect.insort_left(@array, item) 15 | else 16 | item.priority = exists.priority 17 | self[index] = item 18 | end 19 | else 20 | Bisect.insort_left(@array, item) 21 | end 22 | else 23 | Bisect.insort_left(@array, item) 24 | end 25 | @named_items[name] = item 26 | else 27 | Bisect.insort_left(@array, item) 28 | end 29 | end 30 | 31 | def pop 32 | item = @array.pop 33 | @named_items.delete(item.name) if item.name 34 | item 35 | end 36 | 37 | def pop(n : Int) 38 | items = @array.pop(n) 39 | items.each { |item| @named_items.delete(item.name) if item.name } 40 | items 41 | end 42 | 43 | def shift 44 | item = @array.shift 45 | @named_items.delete(item.name) if item.name 46 | item 47 | end 48 | 49 | def shift(n : Int) 50 | items = @array.shift(n) 51 | items.each { |item| @named_items.delete(item.name) if item.name } 52 | items 53 | end 54 | end 55 | --------------------------------------------------------------------------------