File Coverage

blib/lib/Array/Heap/ModifiablePriorityQueue.pm
Criterion Covered Total %
statement 68 68 100.0
branch 14 16 87.5
condition n/a
subroutine 16 16 100.0
pod 12 12 100.0
total 110 112 98.2


line stmt bran cond sub pod time code
1             package Array::Heap::ModifiablePriorityQueue;
2 1     1   19721 use strict;
  1         2  
  1         36  
3 1     1   3 use warnings;
  1         1  
  1         28  
4 1     1   4 use vars qw( $VERSION );
  1         4  
  1         59  
5             $VERSION = '1.01';
6 1         882 use Array::Heap qw( adjust_heap_idx make_heap_idx pop_heap_idx push_heap_idx
7 1     1   3348 splice_heap_idx );
  1         595  
8              
9             =head1 NAME
10              
11             Array::Heap::ModifiablePriorityQueue - Modifiable priority queue
12              
13             =head1 SYNOPSIS
14              
15             use Array::Heap::ModifiablePriorityQueue;
16             my $pq = Array::Heap::ModifiablePriorityQueue->new();
17             $pq->add('fish', 42);
18             $pq->add('banana', 27);
19             print $pq->peek(), "\n"; # banana
20             $pq->remove('banana');
21             print $pq->get(), "\n"; # fish
22              
23             =head1 DESCRIPTION
24              
25             This module implements a priority queue, which is a data structure that can
26             efficiently locate the item with the lowest weight at any time. This is useful
27             for writing cost-minimizing and shortest-path algorithms.
28              
29             Why another priority queue module? First, unlike many similar modules, this one
30             allows you to modify the queue. Items can be removed from the queue or have
31             their weight changed after they are added.
32              
33             Second, it simple to use. Items in the queue don't have to implement any
34             specific interface. Just throw them in there along with a weight value and
35             the module will keep track of everything.
36              
37             Finally, it has good performance on large datasets. This is because it is
38             based on a partially-ordered heap data structure. Many other priority
39             queue modules are based on fully sorted lists (even ones that claim to be
40             heaps). Keeping the items only partially sorted saves time when there are
41             are a large number of them (several thousand or so).
42              
43             This module is a Perl wrapper around L, a lightweight and fast
44             heap management module implemented in XS.
45              
46             =head1 FUNCTIONS
47              
48             =over 4
49              
50             =item Array::Heap::ModifiablePriorityQueue->new()
51              
52             Create a new, empty priority queue.
53              
54             =cut
55              
56             sub new {
57 3     3 1 23 my ($class) = @_;
58 3         31 return bless { heap => [], items => {} } => $class;
59             }
60              
61             =item $pq->add($item, $weight)
62              
63             Add an item to the priority queue with the given weight. If the item is
64             already present in the queue, modify its weight. Weight must be numeric.
65              
66             =cut
67              
68             sub add {
69 6     6 1 28 my ($self, $item, $weight) = @_;
70 6 100       28 if (my $node = $self->{items}{$item}) {
71 1         4 $node->[0] = $weight;
72 1         3 adjust_heap_idx @{$self->{heap}}, $node->[1];
  1         10  
73             }
74             else {
75 5         14 $node = [ $weight, 0, $item ];
76 5         138 $self->{items}{$item} = $node;
77 5         9 push_heap_idx @{$self->{heap}}, $node;
  5         44  
78             }
79             }
80              
81             =item $pq->peek()
82              
83             Return the first (numerically lowest weight) item from the queue.
84             Does not modify the queue. Returns undef if the queue is empty.
85              
86             =cut
87              
88             sub peek {
89 1     1 1 3 my ($self) = @_;
90 1 50       7 my $node = $self->{heap}[0] or return;
91 1         7 return $node->[2];
92             }
93              
94             =item $pq->get()
95              
96             Removes the first item from the priority queue and returns it.
97             Returns undef if the queue is empty. If two items in the queue
98             have equal weight, this module makes no guarantee as to which
99             one will be returned first.
100              
101             =cut
102              
103             sub get {
104 3     3 1 7 my ($self) = @_;
105 3 50       6 my $node = pop_heap_idx @{$self->{heap}} or return;
  3         32  
106 3         8 my $item = $node->[2];
107 3         11 delete $self->{items}{$item};
108 3         17 return $item;
109             }
110              
111             =item $pq->remove($item)
112              
113             Removes the given item from the priority queue. If item is not present
114             in the queue, does nothing.
115              
116             =cut
117              
118             sub remove {
119 2     2 1 8 my ($self, $item) = @_;
120 2 100       12 my $node = delete $self->{items}{$item} or return;
121 1         3 splice_heap_idx @{$self->{heap}}, $node->[1];
  1         8  
122             }
123              
124             =item $pq->weight($item)
125              
126             Returns the weight of the item, or undef if it is not present.
127              
128             =cut
129              
130             sub weight {
131 3     3 1 9 my ($self, $item) = @_;
132 3 100       18 my $node = $self->{items}{$item} or return;
133 2         11 return $node->[0];
134             }
135              
136             =item $pq->size()
137              
138             Returns the number of items in the priority queue.
139              
140             =cut
141              
142             sub size {
143 12     12 1 612 my ($self) = @_;
144 12         17 return scalar @{$self->{heap}};
  12         73  
145             }
146              
147             =item $pq->items()
148              
149             Returns all items in the heap, in an arbitrary order.
150              
151             =cut
152              
153             sub items {
154 1     1 1 18 my ($self) = @_;
155 1         3 return map { $_->[2] } @{$self->{heap}};
  3         16  
  1         5  
156             }
157              
158             =item $pq->sorted_items()
159              
160             Returns all items in the heap, in weight order.
161              
162             =cut
163              
164             sub sorted_items {
165 1     1 1 3 my ($self) = @_;
166 1         3 return map { $_->[2] } sort { $a->[0] <=> $b->[0] } @{$self->{heap}};
  3         13  
  3         10  
  1         7  
167             }
168              
169             =item $pq->add_unordered($item, $weight)
170              
171             Add an item to the priority queue or change its weight, without updating
172             the heap structure. If you are adding a bunch of items at once, it may be
173             more efficient to use add_unordered, then call $pq->restore_order() once
174             you are done.
175              
176             =cut
177              
178             sub add_unordered {
179 5     5 1 38 my ($self, $item, $weight) = @_;
180 5 100       17 if (my $node = $self->{items}{$item}) {
181 1         5 $node->[0] = $weight;
182             }
183             else {
184 4         6 my $heap = $self->{heap};
185 4         10 $node = [ $weight, scalar(@$heap), $item ];
186 4         12 $self->{items}{$item} = $node;
187 4         11 push @$heap, $node;
188             }
189             }
190              
191             =item $pq->remove_unordered($item)
192              
193             Remove an item from the priority queue without updating the heap structure.
194             If item is not present in the queue, do nothing.
195              
196             =cut
197              
198             sub remove_unordered {
199 3     3 1 31 my ($self, $item) = @_;
200 3 100       19 my $node = delete $self->{items}{$item} or return;
201 2         3 my $heap = $self->{heap};
202 2         145 my $last = pop @$heap;
203 2 100       13 if ($last != $node) {
204 1         4 $heap->[$node->[1]] = $last;
205 1         5 $last->[1] = $node->[1];
206             }
207             }
208              
209             =item $pq->restore_order()
210              
211             Restore the heap structure after calling add_unordered or remove_unordered.
212             You need to do this before calling any of the ordered methods (add, remove,
213             peek, or get).
214              
215             =cut
216              
217             sub restore_order {
218 1     1 1 3 my ($self) = @_;
219 1         642 make_heap_idx @{$self->{heap}};
  1         12  
220             }
221              
222             =back
223              
224             =head1 PERFORMANCE
225              
226             The peek function runs in constant time, or O(1) in asymptotic notation.
227             The structure-modifying functions add, get, and remove run in O(log n) time.
228             Add_unordered and remove_unordered are O(1), but after a sequence of
229             unordered operations, you need to call restore_order, which is O(n).
230              
231             If you feel that you need maximum speed, go ahead and inline these
232             methods into your own code to avoid an extra method invocation. They
233             are all quite short and simple.
234              
235             =head1 LIMITATIONS
236              
237             Weight values must be numeric. This is a limitation of the underlying
238             Array::Heap module.
239              
240             Weights are sorted in increasing order only. If you want it the other way,
241             use the negative of the weights you have.
242              
243             Items are distinguished by their stringified values. This works fine if you
244             are storing scalars or plain references. If your items have a custom
245             stringifier that returns nonunique strings, or their stringified value can
246             change, you may need to use Array::Heap directly.
247              
248             =head1 SEE ALSO
249              
250             L for a different priority queue implementation.
251              
252             L is easy to use, but doesn't allow weights to be changed.
253              
254             L if you need more direct access to the data structure.
255              
256             =head1 AUTHOR
257              
258             Bob Mathews
259              
260             =head1 REPOSITORY
261              
262             L
263              
264             =head1 COPYRIGHT
265              
266             This program is free software; you can redistribute
267             it and/or modify it under the same terms as Perl itself.
268              
269             The full text of the license can be found in the
270             LICENSE file included with this module.
271              
272             =cut
273              
274             1 # end ModifiablePriorityQueue.pm