File Coverage

blib/lib/Algorithm/SkipList.pm
Criterion Covered Total %
statement 367 405 90.6
branch 101 140 72.1
condition 48 55 87.2
subroutine 61 61 100.0
pod 25 25 100.0
total 602 686 87.7


line stmt bran cond sub pod time code
1             package Algorithm::SkipList;
2            
3 13     13   202217 use 5.006;
  13         40  
  13         452  
4 13     13   70 use strict;
  13         30  
  13         451  
5 13     13   67 use warnings::register __PACKAGE__;
  13         23  
  13         3961  
6            
7             our $VERSION = '1.02';
8             # $VERSION = eval $VERSION;
9            
10 13     13   173521 use AutoLoader qw( AUTOLOAD );
  13         20519  
  13         76  
11 13     13   544 use Carp qw( carp croak );
  13         25  
  13         1329  
12            
13             require Algorithm::SkipList::Node;
14             require Algorithm::SkipList::Header;
15            
16             # Future versions should check Config module to determine if it is
17             # being run on a 64-bit processor, and set MAX_LEVEL to 64.
18            
19 13     13   75 use constant MIN_LEVEL => 2;
  13         24  
  13         925  
20 13     13   69 use constant MAX_LEVEL => 32;
  13         23  
  13         487  
21 13     13   61 use constant DEF_P => 0.25;
  13         22  
  13         501  
22 13     13   66 use constant DEF_K => 0;
  13         21  
  13         579  
23            
24 13     13   66 use constant BASE_NODE_CLASS => 'Algorithm::SkipList::Node';
  13         22  
  13         1540  
25            
26             # We use Exporter instead of something like Exporter::Lite because
27             # Carp uses it.
28            
29             require Exporter;
30            
31             our @EXPORT = ( );
32             our @EXPORT_OK = ( );
33            
34             sub new {
35 13     13   13078 no integer;
  13         127  
  13         62  
36            
37 103     103 1 49984 my $class = shift;
38            
39 103         1227 my $self = {
40             NODECLASS => BASE_NODE_CLASS, # node class used by list
41             LIST => undef, # pointer to the header node
42             SIZE => undef, # size of list
43             SIZE_THRESHOLD => undef, # size at which SIZE_LEVEL increased
44             LAST_SIZE_TH => undef, # previous SIZE_THRESHOLD
45             SIZE_LEVEL => undef, # maximum level random_level
46             MAXLEVEL => MAX_LEVEL, # absolute maximum level
47             P => 0, # probability for each level
48             K => 0, # minimum power of P
49             P_LEVELS => [ ], # array used by random_level
50             LIST_END => undef, # node with greatest key
51             LASTKEY => undef, # last key used by next_key
52             LASTINSRT => undef, # cached insertion fingers
53             DUPLICATES => 0, # allow duplicates?
54             };
55            
56 103         281 bless $self, $class;
57            
58 103         309 $self->_set_p( DEF_P ); # initializes P_LEVELS
59 103         338 $self->_set_k( DEF_K );
60            
61 103 100       369 if (@_) {
62 95         316 my %args = @_;
63 95         231 foreach my $arg_name (CORE::keys %args) {
64 163         301 my $method = "_set_" . $arg_name;
65 163 50       714 if ($self->can($method)) {
66 163         394 $self->$method( $args{ $arg_name } );
67             } else {
68 0         0 croak "Invalid parameter name: ``$arg_name\'\'";
69             }
70             }
71             }
72            
73 96         308 $self->clear;
74            
75 96         1161 return $self;
76             }
77            
78             sub _set_duplicates {
79 2     2   3 my ($self, $dup) = @_;
80 2   100     12 $self->{DUPLICATES} = $dup || 0;
81             }
82            
83             sub _set_node_class {
84 47     47   65 my ($self, $node_class) = @_;
85 47         145 $self->{NODECLASS} = $node_class;
86             }
87            
88             sub _node_class {
89 24990     24990   27350 my ($self) = @_;
90 24990         84784 $self->{NODECLASS};
91             }
92            
93             sub reset {
94 156     156 1 448 my ($self) = @_;
95 156         304 $self->{LASTKEY} = undef;
96             }
97            
98             sub clear {
99 99     99 1 1042 my ($self) = @_;
100            
101 99         183 $self->{SIZE} = 0;
102 99         140 $self->{SIZE_THRESHOLD} = 2;
103 99         139 $self->{LAST_SIZE_TH} = 0;
104 99         122 $self->{SIZE_LEVEL} = MIN_LEVEL;
105            
106 99         261 my $hdr = [ (undef) x $self->{SIZE_LEVEL} ];
107            
108 99         228 CORE::delete $self->{LIST};
109 99         521 $self->{LIST} = new Algorithm::SkipList::Header( undef, undef, $hdr );
110            
111 99         163 $self->{LIST_END} = undef;
112 99         130 $self->{LASTINSRT} = undef;
113            
114 99         284 $self->reset;
115             }
116            
117             sub _set_max_level {
118 161     161   241 my ($self, $level) = @_;
119 161 100 100     626 if ($level > MAX_LEVEL) {
    100          
    100          
120 1         198 croak "Cannot set max_level greater than ", MAX_LEVEL;
121             } elsif ($level < MIN_LEVEL) {
122 5         657 croak "Cannot set max_level less than ", MIN_LEVEL;
123             } elsif ((defined $self->list) && ($level < $self->list->level)) {
124 30         5440 croak "Current level exceeds specified level";
125             }
126 125         400 $self->{MAXLEVEL} = $level;
127             }
128            
129             sub max_level {
130 280     280 1 13936 my ($self, $level) = @_;
131            
132 280 100       550 if (defined $level) {
133 92         210 $self->_set_max_level($level);
134             } else {
135 188         830 $self->{MAXLEVEL};
136             }
137             }
138            
139             # We use the formula from Pugh's "Skip List Cookbook" paper. We
140             # generate a reverse-sorted array of values based on p and k. In
141             # _new_node_level() we look for the highest value in the array that is
142             # less than a random number n (0
143            
144             sub _build_distribution {
145 13     13   9592 no integer;
  13         22  
  13         52  
146            
147 256     256   373 my ($self) = @_;
148            
149 256         681 my $p = $self->p;
150 256         551 my $k = $self->k;
151            
152 256         1453 $self->{P_LEVELS} = [ (0) x MAX_LEVEL ];
153 256         791 for my $i (0..MAX_LEVEL) {
154 8448         20249 $self->{P_LEVELS}->[$i] = $p**($i+$k);
155             }
156             }
157            
158             sub _set_p {
159 13     13   1703 no integer;
  13         26  
  13         45  
160            
161 156     156   265 my ($self, $p) = @_;
162            
163 156 100 100     1120 unless ( ($p>0) && ($p<1) ) {
164 3         491 croak "Unvalid value for P (must be between 0 and 1)";
165             }
166            
167 153         308 $self->{P} = $p;
168 153         385 $self->_build_distribution;
169            
170             }
171            
172             sub p {
173 13     13   1586 no integer;
  13         42  
  13         65  
174            
175 314     314 1 453 my ($self, $p) = @_;
176            
177 314 100       634 if (defined $p) {
178 8         16 $self->_set_p($p);
179             } else {
180 306         763 $self->{P};
181             }
182             }
183            
184             sub _set_k {
185 103     103   153 my ($self, $k) = @_;
186            
187 103 50       300 unless ( $k>=0 ) {
188 0         0 croak "Unvalid value for K (must be at least 0)";
189             }
190            
191 103         198 $self->{K} = $k;
192 103         215 $self->_build_distribution;
193             }
194            
195             sub k {
196 256     256 1 336 my ($self, $k) = @_;
197            
198 256 50       436 if (defined $k) {
199 0         0 $self->_set_k($k);
200             } else {
201 256         757 $self->{K};
202             }
203             }
204            
205             sub size {
206 490     490 1 25949 my ($self) = @_;
207 490         2944 $self->{SIZE};
208             }
209            
210             sub list {
211 51387     51387 1 77402 my ($self) = @_;
212 51387         82915 $self->{LIST};
213             }
214            
215            
216             sub _adjust_level_threshold {
217 13     13   2798 use integer;
  13         37  
  13         51  
218            
219 24984     24984   27602 my ($self) = @_;
220            
221 24984 100       99392 if ($self->{SIZE} >= $self->{SIZE_THRESHOLD}) {
    100          
222 386         615 $self->{LAST_SIZE_TH} = $self->{SIZE_THRESHOLD};
223 386         571 $self->{SIZE_THRESHOLD} += $self->{SIZE_THRESHOLD};
224 386 100       1633 $self->{SIZE_LEVEL}++, if ($self->{SIZE_LEVEL} < $self->{MAXLEVEL});
225             } elsif ($self->{SIZE} < $self->{LAST_SIZE_TH}) {
226 22         40 $self->{SIZE_THRESHOLD} = $self->{LAST_SIZE_TH};
227 22         46 $self->{LAST_SIZE_TH} = $self->{LAST_SIZE_TH} / 2;
228 22 100       236 $self->{SIZE_LEVEL}--, if ($self->{SIZE_LEVEL} > MIN_LEVEL);
229             }
230             }
231            
232             sub _new_node_level { # previously _random_level
233 13     13   1745 no integer;
  13         520  
  13         48  
234            
235 24906     24906   25936 my ($self) = @_;
236            
237 24906         29508 my $n = CORE::rand();
238 24906         24376 my $level = 1;
239            
240 24906   100     91856 while (($n < $self->{P_LEVELS}->[$level]) &&
241             ($level < $self->{SIZE_LEVEL})) {
242 8161         27384 $level++;
243             }
244            
245 24906         38064 $level;
246             }
247            
248             sub _search_with_finger {
249 25202     25202   29231 my ($self, $key, $finger) = @_;
250            
251 13     13   1278 use integer;
  13         20  
  13         44  
252            
253 25202         39099 my $list = $self->list;
254 25202         61397 my $level = $list->level-1;
255            
256 25202   66     63090 my $node = $finger->[ $level ] || $list;
257            
258             # Iteresting Perl syntax quirk:
259             # do { my $x = ... } while ($x)
260             # doesn't work because it considers $x out of scope.
261             #
262             # However, benchmarking shows that it's faster to use
263             # my $x; do { $x = ... } while ($x)
264             #
265            
266 25202         23613 my $fwd;
267 25202         24122 my $cmp = -1;
268            
269             # This version of the search algorithm is based on Schneier, 1994.
270            
271 25202         24577 do {
272 127777   100     293822 while ( ($fwd = $node->header()->[$level]) &&
273             ($cmp = $fwd->key_cmp($key)) < 0) {
274 249632         633405 $node = $fwd;
275             }
276 127777         335137 $finger->[$level] = $node;
277             } while ((--$level>=0)); # && ($cmp));
278            
279 25202 100       47475 $node = $fwd, unless ($cmp);
280            
281             # Ideally we could stop when $cmp == 0, but the update vector would
282             # not be complete for levels below $level. insert still works, but
283             # delete and truncate have problems and need kluges to make up for
284             # that.
285            
286 25202         69271 ($node, $finger, $cmp);
287             }
288            
289             sub _search {
290 366     366   9373 my ($self, $key, $finger) = @_;
291            
292 13     13   1977 use integer;
  13         45  
  13         64  
293            
294 366         720 my $list = $self->list;
295 366         1151 my $level = $list->level-1;
296            
297             # $finger ||= [ ];
298            
299 366   66     1539 my $node = $finger->[ $level ] || $list;
300            
301             # This version of the search algorithm is based on Schneier, 1994.
302            
303 366         380 my $fwd;
304 366         432 my $cmp = -1;
305            
306 366   100     368 do {
307 707   100     2306 while ( ($fwd = $node->header()->[$level]) &&
308             ($cmp = $fwd->key_cmp($key)) < 0) {
309 809         4702 $node = $fwd;
310             }
311             } while ((--$level>=0) && ($cmp));
312            
313 366         1533 $node = $fwd; # , unless ($cmp); # Devel::Cover says it's never false
314            
315 366         2760 ($node, $finger, $cmp);
316             }
317            
318             sub insert {
319 24954     24954 1 89247 my ($self, $key, $value, $finger) = @_;
320            
321 13     13   1771 use integer;
  13         44  
  13         48  
322            
323 24954         40676 my $list = $self->list;
324            
325             # We save the node and finger of the last insertion. If the next key
326             # is larger, then we can use the "finger" to speed up insertions.
327            
328 24954         28558 my ($node, $cmp);
329            
330 24954 100       50269 unless ($finger) {
331 24812 100       60789 $node = $self->{LASTINSRT}->[0] and do {
332 24736 100       66836 $finger = $self->{LASTINSRT}->[1],
333             if ($node->key_cmp($key) <= 0);
334             };
335             }
336            
337 24954         51378 ($node, $finger, $cmp) = $self->_search_with_finger($key, $finger);
338            
339 24954 100 100     65040 if ($cmp || $self->{DUPLICATES}) {
340            
341 24906         42677 my $new_level = $self->_new_node_level;
342            
343 24906         37345 my $node_hdr = [ ];
344 24906         24584 my $fing_hdr;
345            
346 24906         41856 $node = $self->_node_class->new( $key, $value, $node_hdr );
347            
348 24906         64235 for (my $i=0;$i<$new_level;$i++) {
349 33067   66     112480 $fing_hdr = ($finger->[$i]||$list)->header();
350 33067         61719 $node_hdr->[$i] = $fing_hdr->[$i];
351 33067         87630 $fing_hdr->[$i] = $node;
352             }
353            
354            
355             # We no longer set the LIST_END value, since it is the job of the
356             # _greatest_node method to find it, as needed.
357            
358 24906         29210 $self->{SIZE}++;
359 24906         41642 $self->_adjust_level_threshold;
360             } else {
361 48         124 $node->value($value);
362             }
363 24954         43333 $self->{LASTINSRT}->[0] = $node;
364 24954         70262 $self->{LASTINSRT}->[1] = $finger;
365             }
366            
367             sub delete {
368            
369 75     75 1 17439 my ($self, $key, $finger) = @_;
370            
371 13     13   3298 use integer;
  13         22  
  13         57  
372            
373 75         136 my $list = $self->list;
374            
375 75         168 my ($node, $update_ref, $cmp) = $self->_search_with_finger($key, $finger);
376            
377 75 100       170 if ($cmp == 0) {
378 55         134 my $value = $node->value;
379            
380             # Note: it might make better sense to set $self->{LIST_END} = undef, and
381             # let the _greatest_node method search for it if it's needed again.
382            
383 55 100 100     217 if (($self->{LIST_END}) && ($node == $self->{LIST_END})) {
384 6         11 $self->{LIST_END} = $update_ref->[0];
385             }
386            
387 55         121 my $level = $node->level;
388            
389 55         125 for (my $i=0; $i<$level; $i++) {
390 72         160 $update_ref->[$i]->header()->[$i] = $node->header()->[$i];
391             }
392            
393             # There's probably a smarter way to handle the last insert and
394             # last key values, but this is the fastest, easiest, safest and
395             # most consistent way.
396            
397 55         80 $self->{LASTINSRT} = undef;
398 55         137 $self->reset;
399            
400 55         80 $self->{SIZE} --;
401 55         129 $self->_adjust_level_threshold;
402            
403             # We shouldn't need to "undef $node" here. The Garbage Collector
404             # should hanldle that (especially if there's a finger that points
405             # to it somewhere).
406            
407             # Note: It doesn't seem to be a wise idea to return a search
408             # finger for deletions without further analysis
409            
410 55         275 $value;
411            
412             } else {
413 20 50       370 carp "key not found", if (warnings::enabled);
414 20         1851 return;
415             }
416             }
417            
418             sub exists {
419            
420 42     42 1 115 my ($self, $key, $finger) = @_;
421            
422 42         137 (($self->_search($key, $finger))[2] == 0);
423             }
424            
425             sub find_with_finger {
426 120     120 1 47262 my ($self, $key, $finger) = @_;
427            
428 120         485 my ($x, $update_ref, $cmp) = $self->_search_with_finger($key, $finger);
429            
430 120 100       734 ($cmp == 0) ? (
    100          
431             (wantarray) ? ($x->value, $update_ref) : $x->value
432             ) : undef;
433            
434             }
435            
436             sub find {
437 235     235 1 72030 my ($self, $key, $finger) = @_;
438            
439 235         586 my ($node, $update_ref, $cmp) = $self->_search($key, $finger);
440            
441 235 100       1039 ($cmp == 0) ? $node->value : undef;
442             }
443            
444            
445             sub _first_node { # actually this is the second node
446 134     134   191 my $self = shift;
447            
448 134         267 my $list = $self->list;
449 134         405 my $node = $list->header()->[0];
450             }
451            
452            
453             sub last_key {
454 154     154 1 228 my ($self, $node, $index) = @_;
455            
456 154 100       586 if (@_ > 1) {
457 145         347 $self->{LASTKEY} = [ $node, $index ];
458 145   100     461 my $check = $index || 0;
459 145 50 33     514 if (($check < 0) || ($check >= $self->size)) {
460 0 0       0 carp "index out of bounds", if (warnings::enabled);
461             }
462             }
463             else {
464 9 50       29 unless ($self->{LASTKEY}) {
465 0         0 $self->{LASTKEY} = [ $self->_first_node, 0 ];
466             }
467 9         8 ($node, $index) = @{ $self->{LASTKEY} };
  9         21  
468             }
469            
470 154 100       307 if ($node) {
471 152 100       645 return (wantarray) ?
472             ( $node->key, [ $node ], $node->value, $index ) : $node->key;
473             } else {
474 2         30 return;
475             }
476             }
477            
478             sub first_key {
479 50     50 1 11771 my $self = shift;
480            
481 50         103 my $node = $self->_first_node;
482            
483 50 100       103 if ($node) {
484 47         114 return $self->last_key( $node, 0);
485             }
486             else {
487 3 50       195 carp "no _first_node", if (warnings::enabled);
488 3         243 return;
489             }
490             }
491            
492             sub next_key {
493 86     86 1 15009 my ($self, $last_key, $finger) = @_;
494            
495 86         116 my ($node, $cmp, $value, $index);
496            
497 86 50       178 if (defined $last_key) {
498 0         0 ($node, $finger, $cmp) = $self->_search_with_finger($last_key, $finger);
499            
500 0 0       0 if ($cmp) {
501 0 0       0 carp "cannot find last_key", if (warnings::enabled);
502 0         0 return;
503             }
504             }
505             else {
506 86 100       119 ($node, $index) = @{ $self->{LASTKEY} || [ ] };
  86         367  
507 86 100       210 unless ($node) {
508 25         50 return $self->first_key;
509             }
510             }
511            
512 61 50       122 if ($node) {
513 61         189 $node = $node->header()->[0];
514 61 100 66     397 return $self->last_key(
515             $node,
516             (($node && (defined $index)) ? ($index+1) : undef )
517             );
518             }
519             else {
520 0         0 return $self->reset;
521             }
522             }
523            
524            
525             BEGIN
526             {
527             # make aliases to methods...
528 13     13   21786 no strict;
  13         23  
  13         4358  
529 13     13   44 *TIEHASH = \&new;
530 13         29 *STORE = \&insert;
531 13         24 *FETCH = \&find;
532 13         31 *EXISTS = \&exists;
533 13         31 *CLEAR = \&clear;
534 13         22 *DELETE = \&delete;
535 13         28 *FIRSTKEY = \&first_key;
536 13         31 *NEXTKEY = \&next_key;
537            
538 13         450 *search = \&find;
539             }
540            
541             1;
542            
543             __END__