File Coverage

blib/lib/List/PriorityQueue.pm
Criterion Covered Total %
statement 83 85 97.6
branch 26 28 92.8
condition n/a
subroutine 9 9 100.0
pod 6 6 100.0
total 124 128 96.8


line stmt bran cond sub pod time code
1             package List::PriorityQueue;
2              
3             our $VERSION = '0.01';
4              
5 7     7   171514 use strict;
  7         18  
  7         306  
6 7     7   36 use warnings;
  7         13  
  7         7111  
7              
8             sub new {
9 6     6 1 97 return bless {
10             queue => [],
11             prios => {}, # by payload
12             }, shift();
13             }
14              
15             sub pop {
16 103     103 1 1548 my ($self) = @_;
17 103 100       116 if (@{$self->{queue}} == 0) {
  103         282  
18 13         61 return undef;
19             }
20 90         205 delete($self->{prios}->{$self->{queue}->[0]});
21 90         99 return shift(@{$self->{queue}});
  90         300  
22             }
23              
24             sub unchecked_insert {
25 96     96 1 1387 my ($self, $payload, $priority, $lower, $upper) = @_;
26 96 100       211 $lower = 0 unless defined($lower);
27 96 100       179 $upper = scalar(@{$self->{queue}}) - 1 unless defined($upper);
  93         193  
28              
29             # first of all, map the payload to the desired priority
30             # run an update if the element already exists
31 96         237 $self->{prios}->{$payload} = $priority;
32              
33             # And register the payload in the queue. There are a lot of special
34             # cases that can be exploited to save us from doing the relatively
35             # expensive binary search.
36              
37             # Special case: No items in the queue. The queue IS the item.
38 96 100       127 if (@{$self->{queue}} == 0) {
  96         247  
39 12         23 push(@{$self->{queue}}, $payload);
  12         31  
40 12         39 return;
41             }
42              
43             # Special case: The new item belongs at the end of the queue.
44 84 100       241 if ($priority >= $self->{prios}->{$self->{queue}->[-1]}) {
45 29         34 push(@{$self->{queue}}, $payload);
  29         86  
46 29         69 return;
47             }
48              
49             # Special case: The new item belongs at the head of the queue.
50 55 100       145 if ($priority < $self->{prios}->{$self->{queue}->[0]}) {
51 14         17 unshift(@{$self->{queue}}, $payload);
  14         38  
52 14         34 return;
53             }
54              
55             # Special case: There are only two items in the queue. This item
56             # naturally belongs between them (as we have excluded the other
57             # possible positions before)
58 41 100       44 if (@{$self->{queue}} == 2) {
  41         91  
59 1         3 splice(@{$self->{queue}}, 1, 0, $payload);
  1         6  
60 1         3 return;
61             }
62              
63             # And finally we have a nontrivial queue. Insert the item using a
64             # binary seek.
65             # Do this until the upper and lower bounds crossed... in which case we
66             # will insert at the lower point
67 40         44 my $midpoint;
68 40         78 while ($upper >= $lower) {
69 141         213 $midpoint = ($upper + $lower) >> 1;
70              
71             # We're looking for a priority lower than the one at the midpoint.
72             # Set the new upper point to just before the midpoint.
73 141 100       321 if ($priority < $self->{prios}->{$self->{queue}->[$midpoint]}) {
74 68         74 $upper = $midpoint - 1;
75 68         127 next;
76             }
77              
78             # We're looking for a priority greater or equal to the one at the
79             # midpoint. The new lower bound is just after the midpoint.
80 73         127 $lower = $midpoint + 1;
81             }
82              
83 40         44 splice(@{$self->{queue}}, $lower, 0, $payload);
  40         159  
84             }
85              
86             sub _find_payload_pos {
87 8     8   16 my ($self, $payload) = @_;
88 8         21 my $priority = $self->{prios}->{$payload};
89 8 100       46 if (!defined($priority)) {
90 2         4 return undef;
91             }
92              
93             # Find the item with binary search.
94             # Do this until the bounds are crossed, in which case the lower point
95             # is aimed at an element with a higher priority than the target
96 6         10 my $lower = 0;
97 6         15 my $upper = @{$self->{queue}} - 1;
  6         29  
98 6         10 my $midpoint;
99 6         30 while ($upper >= $lower) {
100 14         23 $midpoint = ($upper + $lower) >> 1;
101              
102             # We're looking for a priority lower than the one at the midpoint.
103             # Set the new upper point to just before the midpoint.
104 14 100       46 if ($priority < $self->{prios}->{$self->{queue}->[$midpoint]}) {
105 5         7 $upper = $midpoint - 1;
106 5         16 next;
107             }
108              
109             # We're looking for a priority greater or equal to the one at the
110             # midpoint. The new lower bound is just after the midpoint.
111 9         22 $lower = $midpoint + 1;
112             }
113              
114             # The lower index is now pointing to an element with a priority higher
115             # than our target. Scan backwards until we find the target.
116 6         32 while ($lower-- >= 0) {
117 6 50       33 return $lower if ($self->{queue}->[$lower] eq $payload);
118             }
119             }
120              
121             sub delete {
122 8     8 1 34 my ($self, $payload) = @_;
123 8         28 my $pos = $self->_find_payload_pos($payload);
124 8 100       31 if (!defined($pos)) {
125 2         9 return undef;
126             }
127              
128 6         18 delete($self->{prios}->{$payload});
129 6         38 splice(@{$self->{queue}}, $pos, 1);
  6         20  
130              
131 6         23 return $pos;
132             }
133              
134             sub unchecked_update {
135 3     3 1 70 my ($self, $payload, $new_prio) = @_;
136 3         10 my $old_prio = $self->{prios}->{$payload};
137              
138             # delete the old item
139 3         19 my $old_pos = $self->delete($payload);
140              
141             # reinsert the item, limiting the range for the binary search (if needed)
142             # a bit by checking how the priority changed.
143 3         7 my ($upper, $lower);
144 3 50       31 if ($new_prio - $old_prio > 0) {
145 3         7 $upper = @{$self->{queue}};
  3         9  
146 3         7 $lower = $old_pos;
147             } else {
148 0         0 $upper = $old_pos;
149 0         0 $lower = 0;
150             }
151 3         15 $self->unchecked_insert($payload, $new_prio, $lower, $upper);
152             }
153              
154             sub update {
155 59     59 1 1157 my ($self, $payload, $prio) = @_;
156 59 100       170 if (!defined($self->{prios}->{$payload})) {
157 57         152 goto &unchecked_insert;
158             } else {
159 2         8 goto &unchecked_update;
160             }
161             }
162             *insert = \&update;
163              
164             1;
165              
166             __END__