File Coverage

lib/Heap/Fibonacci.pm
Criterion Covered Total %
statement 118 186 63.4
branch 32 76 42.1
condition 1 24 4.1
subroutine 14 23 60.8
pod 0 20 0.0
total 165 329 50.1


line stmt bran cond sub pod time code
1             package Heap::Fibonacci;
2              
3 4     4   48963 use strict;
  4         9  
  4         182  
4 4     4   27 use vars qw($VERSION);
  4         8  
  4         9789  
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              
45             sub hdump {
46 0     0 0 0 my $el = shift;
47 0         0 my $l1 = shift;
48 0         0 my $b = shift;
49              
50 0         0 my $ch;
51             my $ch1;
52              
53 0 0       0 unless( $el ) {
54 0         0 print $l1, "\n";
55 0         0 return;
56             }
57              
58 0         0 hdump $ch1 = $el->{child},
59             $l1 . sprintf( $vfmt, $el->{val}->val),
60             $b . $bar;
61              
62 0 0       0 if( $ch1 ) {
63 0         0 for( $ch = $ch1->{right}; $ch != $ch1; $ch = $ch->{right} ) {
64 0         0 hdump $ch, $b . $corner, $b . $bar;
65             }
66             }
67             }
68              
69             sub heapdump {
70 0     0 0 0 my $h;
71              
72 0         0 while( $h = shift ) {
73 0 0       0 my $top = $$h or last;
74 0         0 my $el = $top;
75              
76 0         0 do {
77 0         0 hdump $el, sprintf( "%02d: ", $el->{degree}), ' ';
78 0         0 $el = $el->{right};
79             } until $el == $top;
80 0         0 print "\n";
81             }
82             }
83              
84             sub bhcheck;
85              
86             sub bhcheck {
87 0     0 0 0 my $el = shift;
88 0         0 my $p = shift;
89              
90 0         0 my $cur = $el;
91 0         0 my $prev;
92             my $ch;
93 0         0 do {
94 0         0 $prev = $cur;
95 0         0 $cur = $cur->{right};
96 0 0       0 die "bad back link" unless $cur->{left} == $prev;
97 0 0 0     0 die "bad parent link"
      0        
      0        
      0        
98             unless (defined $p && defined $cur->{p} && $cur->{p} == $p)
99             || (!defined $p && !defined $cur->{p});
100 0 0 0     0 die "bad degree( $cur->{degree} > $p->{degree} )"
101             if $p && $p->{degree} <= $cur->{degree};
102 0 0 0     0 die "not heap ordered"
103             if $p && $p->{val}->cmp($cur->{val}) > 0;
104 0 0       0 $ch = $cur->{child} and bhcheck $ch, $cur;
105             } until $cur == $el;
106             }
107              
108              
109             sub heapcheck {
110 0     0 0 0 my $h;
111             my $el;
112 0         0 while( $h = shift ) {
113 0 0       0 heapdump $h if $validate >= 2;
114 0 0       0 $el = $$h and bhcheck $el, undef;
115             }
116             }
117              
118              
119             ################################################# forward declarations
120              
121             sub ascending_cut;
122             sub elem;
123             sub elem_DESTROY;
124             sub link_to_left_of;
125              
126             ################################################# heap methods
127              
128             # Cormen et al. use two values for the heap, a pointer to an element in the
129             # list at the top, and a count of the number of elements. The count is only
130             # used to determine the size of array required to hold log(count) pointers,
131             # but perl can set array sizes as needed and doesn't need to know their size
132             # when they are created, so we're not maintaining that field.
133             sub new {
134 9     9 0 3581 my $self = shift;
135 9   33     65 my $class = ref($self) || $self;
136 9         15 my $h = undef;
137 9         49 bless \$h, $class;
138             }
139              
140             sub DESTROY {
141 9     9   9208 my $h = shift;
142              
143 9         52 elem_DESTROY $$h;
144             }
145              
146             sub add {
147 107850     107850 0 278163 my $h = shift;
148 107850         123644 my $v = shift;
149 107850 50       203668 $validate && do {
150 0 0       0 die "Method 'heap' required for element on heap"
151             unless $v->can('heap');
152 0 0       0 die "Method 'cmp' required for element on heap"
153             unless $v->can('cmp');
154             };
155 107850         184328 my $el = elem $v;
156 107850         120230 my $top;
157 107850 100       205106 if( !($top = $$h) ) {
158 107         244 $$h = $el;
159             } else {
160 107743         191480 link_to_left_of $top->{left}, $el ;
161 107743         157592 link_to_left_of $el,$top;
162 107743 100       273474 $$h = $el if $v->cmp($top->{val}) < 0;
163             }
164             }
165              
166             sub top {
167 502     502 0 691143 my $h = shift;
168 502 100       11447 $$h && $$h->{val};
169             }
170              
171             *minimum = \⊤
172              
173             sub extract_top {
174 29732     29732 0 175959 my $h = shift;
175 29732 100       77351 my $el = $$h or return undef;
176 29730         50583 my $ltop = $el->{left};
177 29730         40617 my $cur;
178             my $next;
179              
180             # $el is the heap with the lowest value on it
181             # move all of $el's children (if any) to the top list (between
182             # $ltop and $el)
183 29730 100       70635 if( $cur = $el->{child} ) {
184             # remember the beginning of the list of children
185 29468         63654 my $first = $cur;
186 29468         31974 do {
187             # the children are moving to the top, clear the p
188             # pointer for all of them
189 252496         901364 $cur->{p} = undef;
190             } until ($cur = $cur->{right}) == $first;
191              
192             # remember the end of the list
193 29468         43162 $cur = $cur->{left};
194 29468         54416 link_to_left_of $ltop, $first;
195 29468         48819 link_to_left_of $cur, $el;
196             }
197              
198 29730 100       68211 if( $el->{right} == $el ) {
199             # $el had no siblings or children, the top only contains $el
200             # and $el is being removed
201 101         152 $$h = undef;
202             } else {
203 29629         66932 link_to_left_of $el->{left}, $$h = $el->{right};
204             # now all those loose ends have to be merged together as we
205             # search for the
206             # new smallest element
207 29629         56546 $h->consolidate;
208             }
209              
210             # extract the actual value and return that, $el is no longer used
211             # but break all of its links so that it won't be pointed to...
212 29730         71164 my $top = $el->{val};
213 29730         105185 $top->heap(undef);
214 29730         82243 $el->{left} = $el->{right} = $el->{p} = $el->{child} = $el->{val} =
215             undef;
216 29730         155994 $top;
217             }
218              
219             *extract_minimum = \&extract_top;
220              
221             sub absorb {
222 0     0 0 0 my $h = shift;
223 0         0 my $h2 = shift;
224              
225 0         0 my $el = $$h;
226 0 0       0 unless( $el ) {
227 0         0 $$h = $$h2;
228 0         0 $$h2 = undef;
229 0         0 return $h;
230             }
231              
232 0 0       0 my $el2 = $$h2 or return $h;
233              
234             # add $el2 and its siblings to the head list for $h
235             # at start, $ell -> $el -> ... -> $ell is on $h (where $ell is
236             # $el->{left})
237             # $el2l -> $el2 -> ... -> $el2l are on $h2
238             # at end, $ell -> $el2l -> ... -> $el2 -> $el -> ... -> $ell are
239             # all on $h
240 0         0 my $el2l = $el2->{left};
241 0         0 link_to_left_of $el->{left}, $el2;
242 0         0 link_to_left_of $el2l, $el;
243              
244             # change the top link if needed
245 0 0       0 $$h = $el2 if $el->{val}->cmp( $el2->{val} ) > 0;
246              
247             # clean out $h2
248 0         0 $$h2 = undef;
249              
250             # return the heap
251 0         0 $h;
252             }
253              
254             # a key has been decreased, it may have to percolate up in its heap
255             sub decrease_key {
256 0     0 0 0 my $h = shift;
257 0         0 my $top = $$h;
258 0         0 my $v = shift;
259 0 0       0 my $el = $v->heap or return undef;
260 0         0 my $p;
261              
262             # first, link $h to $el if it is now the smallest (we will
263             # soon link $el to $top to properly put it up to the top list,
264             # if it isn't already there)
265 0 0       0 $$h = $el if $top->{val}->cmp( $v ) > 0;
266              
267 0 0 0     0 if( $p = $el->{p} and $v->cmp($p->{val}) < 0 ) {
268             # remove $el from its parent's list - it is now smaller
269              
270 0         0 ascending_cut $top, $p, $el;
271             }
272              
273 0         0 $v;
274             }
275              
276              
277             # to delete an item, we bubble it to the top of its heap (as if its key
278             # had been decreased to -infinity), and then remove it (as in extract_top)
279             sub delete {
280 100     100 0 102 my $h = shift;
281 100         94 my $v = shift;
282 100 50       174 my $el = $v->heap or return undef;
283              
284             # if there is a parent, cut $el to the top (as if it had just had its
285             # key decreased to a smaller value than $p's value
286 100         92 my $p;
287 100 100       256 $p = $el->{p} and ascending_cut $$h, $p, $el;
288              
289             # $el is in the top list now, make it look like the smallest and
290             # remove it
291 100         98 $$h = $el;
292 100         156 $h->extract_top;
293             }
294              
295              
296             ################################################# internal utility functions
297              
298             sub elem {
299 107850     107850 0 124307 my $v = shift;
300 107850         136900 my $el = undef;
301 107850         501858 $el = {
302             p => undef,
303             degree => 0,
304             mark => 0,
305             child => undef,
306             val => $v,
307             left => undef,
308             right => undef,
309             };
310 107850         180421 $el->{left} = $el->{right} = $el;
311 107850         268403 $v->heap($el);
312 107850         172834 $el;
313             }
314              
315             sub elem_DESTROY {
316 39069     39069 0 44645 my $el = shift;
317 39069         36644 my $ch;
318             my $next;
319 39069         88946 $el->{left}->{right} = undef;
320              
321 39069         79794 while( $el ) {
322 78123 100       367051 $ch = $el->{child} and elem_DESTROY $ch;
323 78123         310713 $next = $el->{right};
324              
325 78123 100       302035 defined $el->{val} and $el->{val}->heap(undef);
326 78123         179098 $el->{child} = $el->{right} = $el->{left} = $el->{p} = $el->{val}
327             = undef;
328 78123         180865 $el = $next;
329             }
330             }
331              
332             sub link_to_left_of {
333 1227616     1227616 0 1483133 my $l = shift;
334 1227616         1364929 my $r = shift;
335              
336 1227616         1791713 $l->{right} = $r;
337 1227616         2046084 $r->{left} = $l;
338             }
339              
340             sub link_as_parent_of {
341 330646     330646 0 447776 my $p = shift;
342 330646         399990 my $c = shift;
343              
344 330646         377585 my $pc;
345              
346 330646 100       812343 if( $pc = $p->{child} ) {
347 262107         565682 link_to_left_of $pc->{left}, $c;
348 262107         442759 link_to_left_of $c, $pc;
349             } else {
350 68539         120753 link_to_left_of $c, $c;
351             }
352 330646         475965 $p->{child} = $c;
353 330646         426968 $c->{p} = $p;
354 330646         422233 $p->{degree}++;
355 330646         439712 $c->{mark} = 0;
356 330646         460053 $p;
357             }
358              
359             sub consolidate {
360 29629     29629 0 38884 my $h = shift;
361              
362 29629         41890 my $cur;
363             my $this;
364 29629         38820 my $next = $$h;
365 29629         46686 my $last = $next->{left};
366 29629         33570 my @a;
367 29629         34031 do {
368             # examine next item on top list
369 519830         644154 $this = $cur = $next;
370 519830         785549 $next = $cur->{right};
371 519830         719730 my $d = $cur->{degree};
372 519830         553406 my $alt;
373 519830         1142720 while( $alt = $a[$d] ) {
374             # we already saw another item of the same degree,
375             # put the larger valued one under the smaller valued
376             # one - switch $cur and $alt if necessary so that $cur
377             # is the smaller
378 330646 100       1019180 ($cur,$alt) = ($alt,$cur)
379             if $cur->{val}->cmp( $alt->{val} ) > 0;
380             # remove $alt from the top list
381 330646         799811 link_to_left_of $alt->{left}, $alt->{right};
382             # and put it under $cur
383 330646         548042 link_as_parent_of $cur, $alt;
384             # make sure that $h still points to a node at the top
385 330646         415990 $$h = $cur;
386             # we've removed the old $d degree entry
387 330646         444220 $a[$d] = undef;
388             # and we now have a $d+1 degree entry to try to insert
389             # into @a
390 330646         843627 ++$d;
391             }
392             # found a previously unused degree
393 519830         1641264 $a[$d] = $cur;
394             } until $this == $last;
395 29629         41625 $cur = $$h;
396 29629         124135 for $cur (grep defined, @a) {
397 189184 100       553792 $$h = $cur if $$h->{val}->cmp( $cur->{val} ) > 0;
398             }
399             }
400              
401             sub ascending_cut {
402 56     56 0 54 my $top = shift;
403 56         51 my $p = shift;
404 56         51 my $el = shift;
405              
406 56         51 while( 1 ) {
407 59 100       93 if( --$p->{degree} ) {
408             # there are still other children below $p
409 48         47 my $l = $el->{left};
410 48         48 $p->{child} = $l;
411 48         71 link_to_left_of $l, $el->{right};
412             } else {
413             # $el was the only child of $p
414 11         16 $p->{child} = undef;
415             }
416 59         98 link_to_left_of $top->{left}, $el;
417 59         71 link_to_left_of $el, $top;
418 59         54 $el->{p} = undef;
419 59         57 $el->{mark} = 0;
420              
421             # propagate up the list
422 59         97 $el = $p;
423              
424             # quit at the top
425 59 100       118 last unless $p = $el->{p};
426              
427             # quit if we can mark $el
428 27 100       69 $el->{mark} = 1, last unless $el->{mark};
429             }
430             }
431              
432              
433             1;
434              
435             __END__