File Coverage

lib/Heap/Binomial.pm
Criterion Covered Total %
statement 131 190 68.9
branch 26 58 44.8
condition 13 15 86.6
subroutine 16 24 66.6
pod 0 21 0.0
total 186 308 60.3


line stmt bran cond sub pod time code
1             package Heap::Binomial;
2              
3 2     2   38635 use strict;
  2         4  
  2         87  
4 2     2   12 use vars qw($VERSION);
  2         5  
  2         5037  
5              
6             $VERSION = '0.80';
7              
8             # common names
9             # h - heap head
10             # el - linkable element, contains user-provided value
11             # v - user-provided value
12              
13             ################################################# debugging control
14              
15             my $debug = 0;
16             my $validate = 0;
17              
18             # enable/disable debugging output
19             sub debug {
20 0 0   0 0 0 @_ ? ($debug = shift) : $debug;
21             }
22              
23             # enable/disable validation checks on values
24             sub validate {
25 0 0   0 0 0 @_ ? ($validate = shift) : $validate;
26             }
27              
28             my $width = 3;
29             my $bar = ' | ';
30             my $corner = ' +-';
31             my $vfmt = "%3d";
32              
33             sub set_width {
34 0     0 0 0 $width = shift;
35 0 0       0 $width = 2 if $width < 2;
36              
37 0         0 $vfmt = "%${width}d";
38 0         0 $bar = $corner = ' ' x $width;
39 0         0 substr($bar,-2,1) = '|';
40 0         0 substr($corner,-2,2) = '+-';
41             }
42              
43             sub hdump {
44 0     0 0 0 my $el = shift;
45 0         0 my $l1 = shift;
46 0         0 my $b = shift;
47              
48 0         0 my $ch;
49              
50 0 0       0 unless( $el ) {
51 0         0 print $l1, "\n";
52 0         0 return;
53             }
54              
55 0         0 hdump( $ch = $el->{child},
56             $l1 . sprintf( $vfmt, $el->{val}->val),
57             $b . $bar );
58              
59 0         0 while( $ch = $ch->{sib} ) {
60 0         0 hdump( $ch, $b . $corner, $b . $bar );
61             }
62             }
63              
64             sub heapdump {
65 0     0 0 0 my $h;
66              
67 0         0 while( $h = shift ) {
68 0         0 my $el;
69              
70 0         0 for( $el = $$h; $el; $el = $el->{sib} ) {
71 0         0 hdump( $el, sprintf( "%02d: ", $el->{degree}), ' ' );
72             }
73 0         0 print "\n";
74             }
75             }
76              
77             sub bhcheck {
78              
79 0     0 0 0 my $pel = shift;
80 0         0 my $pdeg = $pel->{degree};
81 0         0 my $pv = $pel->{val};
82 0         0 my $cel;
83 0         0 for( $cel = $pel->{child}; $cel; $cel = $cel->{sib} ) {
84 0 0       0 die "degree not decreasing in heap"
85             unless --$pdeg == $cel->{degree};
86 0 0       0 die "heap order not preserved"
87             unless $pv->cmp($cel->{val}) <= 0;
88 0         0 bhcheck($cel);
89             }
90 0 0       0 die "degree did not decrease to zero"
91             unless $pdeg == 0;
92             }
93              
94              
95             sub heapcheck {
96 0     0 0 0 my $h;
97 0         0 while( $h = shift ) {
98 0 0       0 heapdump $h if $validate >= 2;
99 0 0       0 my $el = $$h or next;
100 0         0 my $pdeg = -1;
101 0         0 for( ; $el; $el = $el->{sib} ) {
102 0 0       0 $el->{degree} > $pdeg
103             or die "degree not increasing in list";
104 0         0 $pdeg = $el->{degree};
105 0         0 bhcheck($el);
106             }
107             }
108             }
109              
110              
111             ################################################# forward declarations
112              
113             sub elem;
114             sub elem_DESTROY;
115             sub link_to;
116             sub moveto;
117              
118             ################################################# heap methods
119              
120              
121             sub new {
122 202     202 0 1417 my $self = shift;
123 202   66     473 my $class = ref($self) || $self;
124 202         227 my $h = undef;
125 202         532 bless \$h, $class;
126             }
127              
128             sub DESTROY {
129 202     202   1469 my $h = shift;
130              
131 202         385 elem_DESTROY $$h;
132             }
133              
134             sub add {
135 200     200 0 509 my $h = shift;
136 200         182 my $v = shift;
137 200 50       322 $validate && do {
138 0 0       0 die "Method 'heap' required for element on heap"
139             unless $v->can('heap');
140 0 0       0 die "Method 'cmp' required for element on heap"
141             unless $v->can('cmp');
142             };
143 200         273 $$h = elem $v, $$h;
144 200         292 $h->self_union_once;
145             }
146              
147             sub top {
148 102     102 0 934 my $h = shift;
149 102 100       223 my $el = $$h or return undef;
150 100         132 my $top = $el->{val};
151 100         218 while( $el = $el->{sib} ) {
152 219 100       497 $top = $el->{val}
153             if $top->cmp($el->{val}) > 0;
154             }
155 100         204 $top;
156             }
157              
158             *minimum = \⊤
159              
160             sub extract_top {
161 102     102 0 125 my $h = shift;
162 102 100       227 my $mel = $$h or return undef;
163 100         134 my $top = $mel->{val};
164 100         117 my $mpred = $h;
165 100         96 my $el = $mel;
166 100         96 my $pred = $h;
167              
168             # find the heap with the lowest value on it
169 100         267 while( $pred = \$el->{sib}, $el = $$pred ) {
170 219 100       526 if( $top->cmp($el->{val}) > 0 ) {
171 51         63 $top = $el->{val};
172 51         55 $mel = $el;
173 51         142 $mpred = $pred;
174             }
175             }
176              
177             # found it, $mpred points to it, $mel is its container, $val is it
178             # unlink it from the chain
179 100         154 $$mpred = $mel->{sib};
180              
181             # we're going to return the value from $mel, but all of its children
182             # must be retained in the heap. Make a second heap with the children
183             # and then merge the heaps.
184 100         217 $h->absorb_children($mel);
185              
186             # finally break all of its pointers, so that we won't leave any
187             # memory loops when we forget about the pointer to $mel
188 100         219 $mel->{p} = $mel->{child} = $mel->{sib} = $mel->{val} = undef;
189              
190             # break the back link
191 100         238 $top->heap(undef);
192              
193             # and return the value
194 100         334 $top;
195             }
196              
197             *extract_minimum = \&extract_top;
198              
199             sub absorb {
200 200     200 0 201 my $h = shift;
201 200         200 my $h2 = shift;
202              
203 200         186 my $dest_link = $h;
204 200         214 my $el1 = $$h;
205 200         195 my $el2 = $$h2;
206 200   100     650 my $anymerge = $el1 && $el2;
207 200   100     722 while( $el1 && $el2 ) {
208 763 100       1561 if( $el1->{degree} <= $el2->{degree} ) {
209             # advance on h's list, it's already linked
210 249         377 $dest_link = \$el1->{sib};
211 249         942 $el1 = $$dest_link;
212             } else {
213             # move next h2 elem to head of h list
214 514         543 $$dest_link = $el2;
215 514         627 $dest_link = \$el2->{sib};
216 514         550 $el2 = $$dest_link;
217 514         1964 $$dest_link = $el1;
218             }
219             }
220              
221             # if h ran out first, move rest of h2 onto end
222 200 100       353 if( $el2 ) {
223 58         90 $$dest_link = $el2;
224             }
225              
226             # clean out h2, all of its elements have been move to h
227 200         218 $$h2 = undef;
228              
229             # fix up h - it can have multiple items at the same degree if we
230             # actually merged two non-empty lists
231 200 100       499 $anymerge ? $h->self_union: $h;
232             }
233              
234             # a key has been decreased, it may have to percolate up in its heap
235             sub decrease_key {
236 0     0 0 0 my $h = shift;
237 0         0 my $v = shift;
238 0 0       0 my $el = $v->heap or return undef;
239 0         0 my $p;
240              
241 0         0 while( $p = $el->{p} ) {
242 0 0       0 last if $v->cmp($p->{val}) >= 0;
243 0         0 moveto $el, $p->{val};
244 0         0 $el = $p;
245             }
246              
247 0         0 moveto $el, $v;
248              
249 0         0 $v;
250             }
251              
252             # to delete an item, we bubble it to the top of its heap (as if its key
253             # had been decreased to -infinity), and then remove it (as in extract_top)
254             sub delete {
255 100     100 0 124 my $h = shift;
256 100         103 my $v = shift;
257 100 50       202 my $el = $v->heap or return undef;
258              
259             # bubble it to the top of its heap
260 100         116 my $p;
261 100         254 while( $p = $el->{p} ) {
262 113         224 moveto $el, $p->{val};
263 113         270 $el = $p;
264             }
265              
266             # find it on the main list, to remove it and split up the children
267 100         92 my $n;
268 100   66     1141 for( $p = $h; ($n = $$p) && $n != $el; $p = \$n->{sib} ) {
269             ;
270             }
271              
272             # remove it from the main list
273 100         156 $$p = $el->{sib};
274              
275             # put any children back onto the main list
276 100         199 $h->absorb_children($el);
277              
278             # remove the link to $el
279 100         250 $v->heap(undef);
280              
281 100         322 return $v;
282             }
283              
284              
285             ################################################# internal utility functions
286              
287             sub elem {
288 200     200 0 162 my $v = shift;
289 200         181 my $sib = shift;
290 200         542 my $el = {
291             p => undef,
292             degree => 0,
293             child => undef,
294             val => $v,
295             sib => $sib,
296             };
297 200         398 $v->heap($el);
298 200         262 $el;
299             }
300              
301             sub elem_DESTROY {
302 202     202 0 234 my $el = shift;
303 202         220 my $ch;
304             my $next;
305              
306 202         805 while( $el ) {
307 0 0       0 $ch = $el->{child} and elem_DESTROY $ch;
308 0         0 $next = $el->{sib};
309              
310 0         0 $el->{val}->heap(undef);
311 0         0 $el->{child} = $el->{sib} = $el->{p} = $el->{val} = undef;
312 0         0 $el = $next;
313             }
314             }
315              
316             sub link_to {
317 679     679 0 755 my $el = shift;
318 679         596 my $p = shift;
319              
320 679         792 $el->{p} = $p;
321 679         856 $el->{sib} = $p->{child};
322 679         697 $p->{child} = $el;
323 679         1560 $p->{degree}++;
324             }
325              
326             sub moveto {
327 113     113 0 122 my $el = shift;
328 113         146 my $v = shift;
329              
330 113         132 $el->{val} = $v;
331 113         233 $v->heap($el);
332             }
333              
334             # we've merged two lists in degree order. Traverse the list and link
335             # together any pairs (adding 1 + 1 to get 10 in binary) to the next
336             # higher degree. After such a merge, there may be a triple at the
337             # next degree - skip one and merge the others (adding 1 + 1 + carry
338             # of 1 to get 11 in binary).
339             sub self_union {
340 185     185 0 213 my $h = shift;
341 185         188 my $prev = $h;
342 185         193 my $cur = $$h;
343 185         175 my $next;
344             my $n2;
345              
346 185         411 while( $next = $cur->{sib} ) {
347 874 100       1905 if( $cur->{degree} != $next->{degree} ) {
348 392         533 $prev = \$cur->{sib};
349 392         423 $cur = $next;
350 392         830 next;
351             }
352              
353             # two or three of same degree, need to do a merge. First though,
354             # skip over the leading one of there are three (it is the result
355             # [carry] from the previous merge)
356 482 100 100     2114 if( ($n2 = $next->{sib}) && $n2->{degree} == $cur->{degree} ) {
357 112         185 $prev = \$cur->{sib};
358 112         117 $cur = $next;
359 112         121 $next = $n2;
360             }
361              
362             # and now the merge
363 482 100       1275 if( $cur->{val}->cmp($next->{val}) <= 0 ) {
364 352         515 $cur->{sib} = $next->{sib};
365 352         581 link_to $next, $cur;
366             } else {
367 130         155 $$prev = $next;
368 130         209 link_to $cur, $next;
369 130         328 $cur = $next;
370             }
371             }
372 185         578 $h;
373             }
374              
375             # we've added one element at the front, keep merging pairs until there isn't
376             # one of the same degree (change all the low order one bits to zero and the
377             # lowest order zero bit to one)
378             sub self_union_once {
379 200     200 0 167 my $h = shift;
380 200         178 my $cur = $$h;
381 200         145 my $next;
382              
383 200         363 while( $next = $cur->{sib} ) {
384 389 100       902 return if $cur->{degree} != $next->{degree};
385              
386             # merge
387 197 100       401 if( $cur->{val}->cmp($next->{val}) <= 0 ) {
388 9         15 $cur->{sib} = $next->{sib};
389 9         13 link_to $next, $cur;
390             } else {
391 188         188 $$h = $next;
392 188         255 link_to $cur, $next;
393 188         382 $cur = $next;
394             }
395             }
396 8         16 $h;
397             }
398              
399             # absorb all the children of an element into a heap
400             sub absorb_children {
401 200     200 0 231 my $h = shift;
402 200         219 my $el = shift;
403              
404 200         325 my $h2 = $h->new;
405 200         324 my $child = $el->{child};
406 200         396 while( $child ) {
407 679         804 my $sib = $child->{sib};
408 679         827 $child->{sib} = $$h2;
409 679         740 $child->{p} = undef;
410 679         678 $$h2 = $child;
411 679         1412 $child = $sib;
412             }
413              
414             # merge them all in
415 200         360 $h->absorb($h2);
416             }
417              
418              
419             1;
420              
421             __END__