philiprehberger-priority_queue 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96eac91320e11af568cae20213fb6ea3bc6a16562ec2f8359539f69ce8d551f1
4
- data.tar.gz: 9757f9de2dae44612d2fbc90abe916dbfa21b74657b8e19ca708ba43f5b90e39
3
+ metadata.gz: effc38578d3d02aa350f639420123b99a26e99de790207eb8babb13668540f92
4
+ data.tar.gz: a303a127bd33cae057fd6c20d5198e55d2dc104f5d22ae4c693ad3515b52b1b0
5
5
  SHA512:
6
- metadata.gz: b8043b9740dacab51fe67334cc21d06614eb7c0351181fd0ff8558f4cc10cda92cbfe74b68db3c40bdc104916cb2f9df26151065e7d022e684a389d1f608365b
7
- data.tar.gz: 00a4ebadb39f16879e25b81fae93f307a00337791271a5ca9b393726a24c32d5d41cb12181a16e5a84fd08d1c2a11e54980de39322162d23d5f14b2c57c5060c
6
+ metadata.gz: 5d41bad688a95b632280e32d3352034ab2b14fb8ce8a75653a0632d0a0db090dd7b7afee08602bd0a12f9e24c7f6018e83c56a4eb6903453b66890b5b02ce3a2
7
+ data.tar.gz: 2a1e8ea6ad147e6070a12d503d0543b95776a156152f50c67b2732ac0c63e1226c606eaa9f5abba785ae4c3eb15a94d9a79a89e1e00f6023e9aaaa9bce0d8407
data/CHANGELOG.md CHANGED
@@ -1,20 +1,20 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this gem will be documented in this file.
3
+ All notable changes to this project will be documented in this file.
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
9
-
10
- ## [0.1.0] - 2026-03-21
8
+ ## [0.1.0] - 2026-03-22
11
9
 
12
10
  ### Added
13
- - Initial release
14
- - Binary heap `Queue` with min-heap (default) and max-heap modes
15
- - `push(item, priority:)` and `pop` with O(log n) performance
16
- - `peek` to view the highest-priority item without removal
17
- - `change_priority(item, new_priority)` to update priorities in-place
18
- - `to_a` for sorted array extraction
19
- - `merge(other)` to combine two priority queues
11
+
12
+ - Binary heap priority queue with min-heap and max-heap modes
20
13
  - Custom comparator support via block
14
+ - `push`, `pop`, `peek` operations with O(log n) performance
15
+ - `<<` operator for hash-based push syntax
16
+ - `change_priority` to update item priority with re-heapification
17
+ - `merge` to combine two queues into a new queue
18
+ - `to_a` to return items sorted by priority
19
+ - `include?`, `clear`, `size`, `empty?` utility methods
20
+ - FIFO tie-breaking for equal priorities
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # philiprehberger-priority_queue
2
2
 
3
- [![Tests](https://github.com/philiprehberger/rb-priority-queue/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-priority-queue/actions/workflows/ci.yml)
4
- [![Gem Version](https://badge.fury.io/rb/philiprehberger-priority_queue.svg)](https://rubygems.org/gems/philiprehberger-priority_queue)
5
- [![License](https://img.shields.io/github/license/philiprehberger/rb-priority-queue)](LICENSE)
3
+ [![Gem Version](https://badge.fury.io/rb/philiprehberger-priority_queue.svg)](https://badge.fury.io/rb/philiprehberger-priority_queue)
4
+ [![CI](https://github.com/philiprehberger/rb-priority-queue/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-priority-queue/actions/workflows/ci.yml)
6
5
 
7
- Binary heap priority queue with min/max modes and custom comparators
6
+ Binary heap priority queue with min/max modes and custom comparators. Features O(log n) push/pop, priority updates, merge operations, and FIFO tie-breaking for equal priorities.
8
7
 
9
8
  ## Requirements
10
9
 
@@ -12,16 +11,14 @@ Binary heap priority queue with min/max modes and custom comparators
12
11
 
13
12
  ## Installation
14
13
 
15
- Add to your Gemfile:
16
-
17
- ```ruby
18
- gem 'philiprehberger-priority_queue'
14
+ ```sh
15
+ gem install philiprehberger-priority_queue
19
16
  ```
20
17
 
21
- Or install directly:
18
+ Or add to your Gemfile:
22
19
 
23
- ```bash
24
- gem install philiprehberger-priority_queue
20
+ ```ruby
21
+ gem 'philiprehberger-priority_queue'
25
22
  ```
26
23
 
27
24
  ## Usage
@@ -29,80 +26,99 @@ gem install philiprehberger-priority_queue
29
26
  ```ruby
30
27
  require 'philiprehberger/priority_queue'
31
28
 
32
- pq = Philiprehberger::PriorityQueue::Queue.new
33
- pq.push('low', priority: 1)
34
- pq.push('high', priority: 10)
35
- pq.push('mid', priority: 5)
36
-
37
- pq.pop # => "low"
38
- pq.pop # => "mid"
39
- pq.pop # => "high"
29
+ # Min-heap (default) - lowest priority first
30
+ queue = Philiprehberger::PriorityQueue::Queue.new
31
+ queue.push('low', priority: 1)
32
+ queue.push('high', priority: 10)
33
+ queue.push('mid', priority: 5)
34
+
35
+ queue.pop # => "low"
36
+ queue.pop # => "mid"
37
+ queue.pop # => "high"
38
+
39
+ # Max-heap - highest priority first
40
+ queue = Philiprehberger::PriorityQueue::Queue.new(mode: :max)
41
+ queue.push('task_a', priority: 3)
42
+ queue.push('task_b', priority: 7)
43
+ queue.pop # => "task_b"
44
+
45
+ # Custom comparator
46
+ queue = Philiprehberger::PriorityQueue::Queue.new { |a, b| a.length <=> b.length }
47
+ queue.push('short', priority: 'short')
48
+ queue.push('very long', priority: 'very long')
49
+ queue.pop # => "short"
50
+
51
+ # Peek without removing
52
+ queue = Philiprehberger::PriorityQueue::Queue.new
53
+ queue.push('item', priority: 1)
54
+ queue.peek # => "item"
55
+ queue.size # => 1
56
+
57
+ # Change priority
58
+ queue = Philiprehberger::PriorityQueue::Queue.new
59
+ queue.push('task', priority: 10)
60
+ queue.change_priority('task', 1) # now highest priority in min-heap
61
+
62
+ # Merge queues
63
+ merged = queue1.merge(queue2)
64
+
65
+ # Other operations
66
+ queue.include?('task') # => true
67
+ queue.to_a # => items sorted by priority
68
+ queue.empty? # => false
69
+ queue.clear # removes all items
40
70
  ```
41
71
 
42
- ### Max-Heap
72
+ ## API
43
73
 
44
- ```ruby
45
- pq = Philiprehberger::PriorityQueue::Queue.new(mode: :max)
46
- pq.push('low', priority: 1)
47
- pq.push('high', priority: 10)
48
- pq.pop # => "high"
49
- ```
74
+ ### `Queue.new(mode: :min, &comparator)`
50
75
 
51
- ### Custom Comparator
76
+ Creates a new priority queue. Mode can be `:min` (default) or `:max`. An optional block provides a custom comparator.
52
77
 
53
- ```ruby
54
- pq = Philiprehberger::PriorityQueue::Queue.new { |a, b| a[:priority] <=> b[:priority] }
55
- pq.push('task', priority: 5)
56
- ```
78
+ ### `#push(item, priority:)` / `#<<`
57
79
 
58
- ### Updating Priorities
80
+ Adds an item with the given priority. Returns self. The `<<` operator accepts a hash: `queue << { item: 'x', priority: 1 }`.
59
81
 
60
- ```ruby
61
- pq = Philiprehberger::PriorityQueue::Queue.new
62
- pq.push('task_a', priority: 10)
63
- pq.push('task_b', priority: 5)
64
- pq.change_priority('task_a', 1)
65
- pq.pop # => "task_a" (now has lowest priority)
66
- ```
82
+ ### `#pop`
67
83
 
68
- ### Merging Queues
84
+ Removes and returns the highest-priority item. Returns `nil` if empty.
69
85
 
70
- ```ruby
71
- pq1 = Philiprehberger::PriorityQueue::Queue.new
72
- pq1.push('a', priority: 1)
86
+ ### `#peek`
73
87
 
74
- pq2 = Philiprehberger::PriorityQueue::Queue.new
75
- pq2.push('b', priority: 2)
88
+ Returns the highest-priority item without removing it. Returns `nil` if empty.
76
89
 
77
- pq1.merge(pq2)
78
- pq1.size # => 2
79
- ```
90
+ ### `#size` / `#empty?`
80
91
 
81
- ## API
92
+ Returns the number of items or whether the queue is empty.
93
+
94
+ ### `#change_priority(item, new_priority)`
95
+
96
+ Updates the priority of an existing item and re-heapifies. Raises `ArgumentError` if the item is not found.
97
+
98
+ ### `#to_a`
99
+
100
+ Returns all items sorted by priority.
101
+
102
+ ### `#include?(item)`
103
+
104
+ Returns `true` if the item is in the queue.
105
+
106
+ ### `#clear`
107
+
108
+ Removes all items from the queue.
109
+
110
+ ### `#merge(other)`
82
111
 
83
- ### `Philiprehberger::PriorityQueue::Queue`
84
-
85
- | Method | Description |
86
- |--------|-------------|
87
- | `.new(mode: :min)` | Create a min-heap (default) or max-heap |
88
- | `.new { \|a, b\| ... }` | Create with a custom comparator block |
89
- | `#push(item, priority:)` | Add an item with a priority |
90
- | `#pop` | Remove and return the highest-priority item |
91
- | `#peek` | View the highest-priority item without removing it |
92
- | `#size` | Number of items in the queue |
93
- | `#empty?` | Whether the queue is empty |
94
- | `#change_priority(item, new_priority)` | Update an item's priority |
95
- | `#to_a` | Return all items in priority order |
96
- | `#merge(other)` | Merge another queue into this one |
112
+ Returns a new queue containing items from both queues. Does not modify the originals.
97
113
 
98
114
  ## Development
99
115
 
100
- ```bash
116
+ ```sh
101
117
  bundle install
102
- bundle exec rspec # Run tests
103
- bundle exec rubocop # Check code style
118
+ bundle exec rspec
119
+ bundle exec rubocop
104
120
  ```
105
121
 
106
122
  ## License
107
123
 
108
- MIT
124
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module PriorityQueue
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
  end
7
7
  end
@@ -4,164 +4,142 @@ require_relative 'priority_queue/version'
4
4
 
5
5
  module Philiprehberger
6
6
  module PriorityQueue
7
- class Error < StandardError; end
8
-
9
- # A binary heap priority queue supporting min-heap, max-heap, and custom comparators
10
- #
11
- # @example Min-heap (default)
12
- # pq = Queue.new
13
- # pq.push('low', priority: 1)
14
- # pq.push('high', priority: 10)
15
- # pq.pop # => "low"
16
- #
17
- # @example Max-heap
18
- # pq = Queue.new(mode: :max)
19
- # pq.push('low', priority: 1)
20
- # pq.push('high', priority: 10)
21
- # pq.pop # => "high"
22
7
  class Queue
23
- # Create a new priority queue
24
- #
25
- # @param mode [Symbol] :min (default) or :max
26
- # @yield [a, b] optional custom comparator block
27
- # @raise [Error] if mode is invalid
28
- def initialize(mode: :min, &comparator)
29
- unless %i[min max].include?(mode)
30
- raise Error, "Invalid mode: #{mode}. Use :min or :max"
31
- end
8
+ attr_reader :size
32
9
 
10
+ def initialize(mode: :min, &comparator)
33
11
  @heap = []
34
- @comparator = comparator || default_comparator(mode)
12
+ @size = 0
13
+ @insertion_counter = 0
14
+ @item_index = {}
15
+
16
+ @comparator = if comparator
17
+ comparator
18
+ elsif mode == :max
19
+ ->(a, b) { b <=> a }
20
+ else
21
+ ->(a, b) { a <=> b }
22
+ end
35
23
  end
36
24
 
37
- # Push an item onto the queue with a given priority
38
- #
39
- # @param item [Object] the item to enqueue
40
- # @param priority [Numeric] the priority value
41
- # @return [self]
42
- def push(item, priority: 0)
43
- entry = { item: item, priority: priority }
25
+ def push(item, priority:)
26
+ entry = [priority, @insertion_counter, item]
27
+ @insertion_counter += 1
44
28
  @heap << entry
45
- bubble_up(@heap.length - 1)
29
+ @item_index[item] = @size
30
+ @size += 1
31
+ bubble_up(@size - 1)
46
32
  self
47
33
  end
48
34
 
49
- # Remove and return the highest-priority item
50
- #
51
- # @return [Object, nil] the item, or nil if empty
35
+ def <<(item_with_priority)
36
+ raise ArgumentError, 'Expected a hash with :item and :priority keys' unless item_with_priority.is_a?(Hash)
37
+
38
+ push(item_with_priority[:item], priority: item_with_priority[:priority])
39
+ end
40
+
52
41
  def pop
53
- return nil if @heap.empty?
42
+ return nil if empty?
54
43
 
55
- swap(0, @heap.length - 1)
44
+ swap(0, @size - 1)
56
45
  entry = @heap.pop
57
- bubble_down(0) unless @heap.empty?
58
- entry[:item]
46
+ @size -= 1
47
+ @item_index.delete(entry[2])
48
+ bubble_down(0) unless empty?
49
+ entry[2]
59
50
  end
60
51
 
61
- # Return the highest-priority item without removing it
62
- #
63
- # @return [Object, nil] the item, or nil if empty
64
52
  def peek
65
- return nil if @heap.empty?
66
-
67
- @heap[0][:item]
68
- end
53
+ return nil if empty?
69
54
 
70
- # Return the number of items in the queue
71
- #
72
- # @return [Integer]
73
- def size
74
- @heap.length
55
+ @heap[0][2]
75
56
  end
76
57
 
77
- # Check if the queue is empty
78
- #
79
- # @return [Boolean]
80
58
  def empty?
81
- @heap.empty?
59
+ @size.zero?
82
60
  end
83
61
 
84
- # Update the priority of an existing item
85
- #
86
- # @param item [Object] the item to update
87
- # @param new_priority [Numeric] the new priority
88
- # @return [self]
89
- # @raise [Error] if the item is not found
90
62
  def change_priority(item, new_priority)
91
- index = @heap.index { |e| e[:item] == item }
92
- raise Error, 'Item not found in queue' if index.nil?
63
+ idx = @item_index[item]
64
+ raise ArgumentError, "Item not found in queue: #{item.inspect}" if idx.nil?
65
+
66
+ old_priority = @heap[idx][0]
67
+ @heap[idx] = [new_priority, @heap[idx][1], item]
93
68
 
94
- @heap[index][:priority] = new_priority
95
- bubble_up(index)
96
- bubble_down(index)
69
+ cmp = @comparator.call(new_priority, old_priority)
70
+ if cmp.negative?
71
+ bubble_up(idx)
72
+ elsif cmp.positive?
73
+ bubble_down(idx)
74
+ end
97
75
  self
98
76
  end
99
77
 
100
- # Return all items as a sorted array
101
- #
102
- # @return [Array] items in priority order
103
78
  def to_a
104
- clone = Queue.new(&@comparator)
105
- @heap.each { |entry| clone.push(entry[:item], priority: entry[:priority]) }
79
+ @heap.sort { |a, b| compare_entries(a, b) }.map { |entry| entry[2] }
80
+ end
106
81
 
107
- result = []
108
- result << clone.pop until clone.empty?
109
- result
82
+ def include?(item)
83
+ @item_index.key?(item)
110
84
  end
111
85
 
112
- # Merge another priority queue into this one
113
- #
114
- # @param other [Queue] the queue to merge
115
- # @return [self]
116
- def merge(other)
117
- other.each_entry { |item, priority| push(item, priority: priority) }
86
+ def clear
87
+ @heap.clear
88
+ @item_index.clear
89
+ @size = 0
118
90
  self
119
91
  end
120
92
 
121
- # @api private
122
- def each_entry
123
- @heap.each { |entry| yield entry[:item], entry[:priority] }
93
+ def merge(other)
94
+ merged = self.class.new(&@comparator)
95
+ @heap.each { |entry| merged.push(entry[2], priority: entry[0]) }
96
+ other.each_entry { |entry| merged.push(entry[2], priority: entry[0]) }
97
+ merged
98
+ end
99
+
100
+ protected
101
+
102
+ def each_entry(&)
103
+ @heap.each(&)
124
104
  end
125
105
 
126
106
  private
127
107
 
128
- def default_comparator(mode)
129
- case mode
130
- when :min then ->(a, b) { a[:priority] <=> b[:priority] }
131
- when :max then ->(a, b) { b[:priority] <=> a[:priority] }
132
- end
108
+ def compare_entries(a, b)
109
+ cmp = @comparator.call(a[0], b[0])
110
+ cmp.zero? ? a[1] <=> b[1] : cmp
133
111
  end
134
112
 
135
- def bubble_up(index)
136
- while index.positive?
137
- parent = (index - 1) / 2
138
- break unless @comparator.call(@heap[index], @heap[parent]).negative?
113
+ def bubble_up(idx)
114
+ while idx.positive?
115
+ parent = (idx - 1) / 2
116
+ break unless compare_entries(@heap[idx], @heap[parent]).negative?
139
117
 
140
- swap(index, parent)
141
- index = parent
118
+ swap(idx, parent)
119
+ idx = parent
142
120
  end
143
121
  end
144
122
 
145
- def bubble_down(index)
146
- size = @heap.length
147
-
123
+ def bubble_down(idx)
148
124
  loop do
149
- smallest = index
150
- left = 2 * index + 1
151
- right = 2 * index + 2
125
+ smallest = idx
126
+ left = (2 * idx) + 1
127
+ right = (2 * idx) + 2
152
128
 
153
- smallest = left if left < size && @comparator.call(@heap[left], @heap[smallest]).negative?
154
- smallest = right if right < size && @comparator.call(@heap[right], @heap[smallest]).negative?
129
+ smallest = left if left < @size && compare_entries(@heap[left], @heap[smallest]).negative?
130
+ smallest = right if right < @size && compare_entries(@heap[right], @heap[smallest]).negative?
155
131
 
156
- break if smallest == index
132
+ break if smallest == idx
157
133
 
158
- swap(index, smallest)
159
- index = smallest
134
+ swap(idx, smallest)
135
+ idx = smallest
160
136
  end
161
137
  end
162
138
 
163
139
  def swap(i, j)
164
140
  @heap[i], @heap[j] = @heap[j], @heap[i]
141
+ @item_index[@heap[i][2]] = i
142
+ @item_index[@heap[j][2]] = j
165
143
  end
166
144
  end
167
145
  end
metadata CHANGED
@@ -1,19 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-priority_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
- - Philip Rehberger
7
+ - philiprehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2026-03-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: An efficient priority queue using a binary heap. Supports min-heap and
14
- max-heap modes, custom comparators, priority updates, and merge operations.
13
+ description: A binary heap-based priority queue supporting min-heap, max-heap, and
14
+ custom comparator modes. Features O(log n) push/pop, priority changes, merge operations,
15
+ and FIFO tie-breaking.
15
16
  email:
16
- - me@philiprehberger.com
17
+ - philiprehberger@users.noreply.github.com
17
18
  executables: []
18
19
  extensions: []
19
20
  extra_rdoc_files: []
@@ -30,7 +31,6 @@ metadata:
30
31
  homepage_uri: https://github.com/philiprehberger/rb-priority-queue
31
32
  source_code_uri: https://github.com/philiprehberger/rb-priority-queue
32
33
  changelog_uri: https://github.com/philiprehberger/rb-priority-queue/blob/main/CHANGELOG.md
33
- bug_tracker_uri: https://github.com/philiprehberger/rb-priority-queue/issues
34
34
  rubygems_mfa_required: 'true'
35
35
  post_install_message:
36
36
  rdoc_options: []
@@ -40,7 +40,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 3.1.0
43
+ version: '3.1'
44
44
  required_rubygems_version: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - ">="