File Coverage

blib/lib/Queue/Priority.pm
Criterion Covered Total %
statement 69 82 84.1
branch 20 26 76.9
condition 8 14 57.1
subroutine 13 16 81.2
pod 10 10 100.0
total 120 148 81.0


line stmt bran cond sub pod time code
1             package Queue::Priority;
2              
3             =head1 NAME
4              
5             Queue::Priority
6              
7             =head1 SYNOPSIS
8              
9             use Queue::Priority;
10             use List::Util qw( shuffle );
11              
12             my $queue = Queue::Priority->new( 10 );
13              
14             foreach my $i ( shuffle 1 .. 10 ) {
15             $queue->insert( $i );
16             }
17              
18             while (1) {
19             my $i = $queue->remove or last;
20             printf "%d * 2 = %d\n", $i, $i * 2;
21             }
22              
23             =head1 DESCRIPTION
24              
25             Priority queues automatically order their contents according to the inserted
26             item's priority. Calling code must ensure that their queue items are comparable
27             via this strategy (e.g. by overloading the <=> operator). This module is
28             implemented as an array heap.
29              
30             =cut
31              
32 1     1   791 use strict;
  1         2  
  1         25  
33 1     1   4 use warnings;
  1         2  
  1         28  
34 1     1   12 use Carp;
  1         2  
  1         57  
35 1     1   923 use Const::Fast;
  1         2777  
  1         6  
36 1     1   907 use POSIX qw(floor);
  1         7314  
  1         8  
37              
38             our $VERSION = 1.0;
39              
40             const my $SLOT_DATA => 0;
41             const my $SLOT_COUNT => 1;
42             const my $SLOT_MAX => 2;
43             const my $SLOT_DONE => 3;
44              
45             =head1 METHODS
46              
47             =head2 new
48              
49             Creates a new queue that can store C<$max> items.
50              
51             =cut
52              
53             sub new {
54 2     2 1 2498 my ( $class, $max ) = @_;
55              
56 2 50 33     16 croak 'expected positive int for $max'
57             unless defined $max
58             && $max > 0;
59              
60             # Pre-allocate array
61 2         3 my @arr;
62 2         6 $#arr = $max - 1;
63              
64 2         5 my $self = bless [], $class;
65 2         7 $self->[ $SLOT_DATA ] = \@arr;
66 2         4 $self->[ $SLOT_COUNT ] = 0;
67 2         2 $self->[ $SLOT_MAX ] = $max;
68 2         4 $self->[ $SLOT_DONE ] = 0;
69              
70 2         6 return $self;
71             }
72              
73             =head2 count
74              
75             Returns the number of items currently stored.
76              
77             =head2 is_empty
78              
79             Returns true if the queue is empty.
80              
81             =head2 is_full
82              
83             Returns true if the queue is full.
84              
85             =head2 peek
86              
87             Returns the first (highest priority) element in the queue without removing it
88             from the queue.
89              
90             =head2 is_shutdown
91              
92             Returns true if the queue has been shut down.
93              
94             =cut
95              
96 0     0 1 0 sub count { $_[0]->[ $SLOT_COUNT ] }
97 405     405 1 2444 sub is_empty { $_[0]->[ $SLOT_COUNT ] == 0 }
98 406     406 1 1416 sub is_full { $_[0]->[ $SLOT_COUNT ] >= $_[0]->[ $SLOT_MAX ] }
99 1   50 1 1 9 sub peek { $_[0]->[ $SLOT_DATA ][ $_[1] || 0 ] }
100 811     811 1 2743 sub is_shutdown { $_[0]->[ $SLOT_DONE ] };
101              
102             =head2 shutdown
103              
104             Shuts down the queue, after which no items may be inserted. Items already in
105             the queue can be pulled normally until empty, after which further calls to
106             C will return undefined.
107              
108             =cut
109              
110             sub shutdown {
111 1     1 1 480 my $self = shift;
112 1         4 $self->[ $SLOT_DONE ] = 1;
113             }
114              
115             =head2 insert
116              
117             Inserts an item into the queue. Dies if the queue is full, has been
118             shut down, or if the only argument is undefined.
119              
120             =cut
121              
122             sub insert {
123 407     407 1 153411 my ( $self, $item ) = @_;
124 407 100       1182 croak 'cannot insert undef' unless defined $item;
125 406 100       841 croak 'queue is shut down' if $self->is_shutdown;
126 405 100       886 croak 'queue is full' if $self->is_full;
127              
128 404         617 ++$self->[ $SLOT_COUNT ];
129              
130             # Place item at the bottom of the heap and sift up
131 404         532 my $arr = $self->[0];
132 404         571 my $idx = $self->[1] - 1;
133 404 100       1546 my $parent = $idx == 0 ? undef : floor( ( $idx - 1 ) / 2 );
134              
135 404         762 $self->[0][ $idx ] = $item;
136              
137 404   100     2083 while ( defined $parent && $arr->[ $idx ] < $arr->[ $parent ] ) {
138 339         766 @$arr[ $idx, $parent ] = @$arr[ $parent, $idx ];
139 339         481 $idx = $parent;
140 339 100       2013 $parent = $idx == 0 ? undef : floor( ( $idx - 1 ) / 2 );
141             }
142              
143 404         2090 return $self->[1];
144             }
145              
146             =head2 remove
147              
148             Removes and returns an item from the queue. If the queue is empty or shutdown,
149             returns undefined immediately.
150              
151             =cut
152              
153             sub remove {
154 404     404 1 185817 my $self = shift;
155              
156 404 50 33     992 return if $self->is_shutdown
157             || $self->is_empty;
158              
159 404         599 my $item = shift @{ $self->[0] };
  404         879  
160 404         931 --$self->[ $SLOT_COUNT ];
161              
162             # Move the last item to the root
163 404         417 unshift @{ $self->[0] }, pop @{ $self->[0] };
  404         660  
  404         2307  
164              
165             # Sift down
166 404         555 my $idx = 0;
167 404         577 my $last = $self->[1] - 1;
168 404         573 my $arr = $self->[0];
169              
170 404         415 while ( 1 ) {
171 1209         1542 my $l = $idx * 2 + 1;
172 1209         1460 my $r = $idx * 2 + 2;
173              
174 1209 100 66     3575 last if $l > $last && $r > $last;
175              
176 870         874 my $least;
177              
178 870 100       1347 if ( $r > $last ) {
179 60         79 $least = $l;
180             }
181             else {
182 810 100       1800 $least = $arr->[$l] <= $arr->[$r] ? $l : $r;
183             }
184              
185 870 100       1520 if ( $arr->[ $idx ] > $arr->[ $least ] ) {
186 805         1625 @$arr[ $idx, $least ] = @$arr[ $least, $idx ];
187 805         1195 $idx = $least;
188             }
189             else {
190 65         110 last;
191             }
192             }
193              
194 404         1989 return $item;
195             }
196              
197             =head1 DEBUG
198              
199             =head2 dump
200              
201             Prints an indented representation of the heap structure.
202              
203             =cut
204              
205             sub dump {
206 0     0 1   my $self = shift;
207 0           printf "Heap (%d/%d)\n", $self->[ $SLOT_COUNT ], $self->[ $SLOT_MAX ];
208 0           $self->_dump( 0, 0 );
209             }
210              
211             sub _dump {
212 0     0     my ( $self, $idx, $indent ) = @_;
213 0 0         return unless defined $self->peek( $idx );
214              
215 0 0         if ( $indent > 0 ) {
216 0           print ' ' for ( 1 .. $indent );
217             }
218              
219 0           printf "- %s\n", $self->peek( $idx );
220              
221 0           my $l = $idx * 2 + 1;
222 0           my $r = $idx * 2 + 2;
223 0           $self->_dump( $l, $indent + 1 );
224 0           $self->_dump( $r, $indent + 1 );
225             }
226              
227             =head1 AUTHOR
228              
229             Jeff Ober
230              
231             =head1 COPYRIGHT AND LICENSE
232              
233             This software is copyright (c) 2015 by Jeff Ober.
234              
235             This is free software; you can redistribute it and/or modify it under the same
236             terms as the Perl 5 programming language system itself.
237              
238             =cut
239              
240             1;