File Coverage

blib/lib/Paranoid/Data/AVLTree.pm
Criterion Covered Total %
statement 462 552 83.7
branch 113 194 58.2
condition 34 68 50.0
subroutine 58 64 90.6
pod 14 14 100.0
total 681 892 76.3


line stmt bran cond sub pod time code
1             # Paranoid::Data::AVLTree -- AVL-Balanced Tree Class
2             #
3             # $Id: lib/Paranoid/Data/AVLTree.pm, 2.10 2022/03/08 00:01:04 acorliss Exp $
4             #
5             # This software is free software. Similar to Perl, you can redistribute it
6             # and/or modify it under the terms of either:
7             #
8             # a) the GNU General Public License
9             # as published by the
10             # Free Software Foundation ; either version 1
11             # , or any later version
12             # , or
13             # b) the Artistic License 2.0
14             # ,
15             #
16             # subject to the following additional term: No trademark rights to
17             # "Paranoid" have been or are conveyed under any of the above licenses.
18             # However, "Paranoid" may be used fairly to describe this unmodified
19             # software, in good faith, but not as a trademark.
20             #
21             # (c) 2005 - 2020, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2020, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::Data::AVLTree;
33              
34 1     1   512 use 5.008;
  1         3  
35              
36 1     1   4 use strict;
  1         2  
  1         29  
37 1     1   5 use warnings;
  1         2  
  1         20  
38 1     1   4 use vars qw($VERSION);
  1         2  
  1         30  
39 1     1   4 use base qw(Exporter);
  1         2  
  1         48  
40 1     1   4 use Paranoid;
  1         2  
  1         43  
41 1     1   351 use Paranoid::Data;
  1         1  
  1         55  
42 1     1   6 use Paranoid::Debug qw(:all);
  1         2  
  1         128  
43 1     1   384 use Paranoid::Data::AVLTree::AVLNode;
  1         2  
  1         38  
44 1     1   455 use Paranoid::IO;
  1         2  
  1         85  
45 1     1   6 use Fcntl qw(:DEFAULT :flock :mode :seek);
  1         1  
  1         369  
46 1     1   7 use Carp;
  1         1  
  1         80  
47              
48             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\d+(?:\.\d+)+)/sm );
49              
50 1     1   6 use constant AVLROOT => 0;
  1         1  
  1         54  
51 1     1   7 use constant AVLKEYS => 1;
  1         2  
  1         36  
52 1     1   5 use constant AVLPROF => 2;
  1         1  
  1         42  
53 1     1   5 use constant AVLSTATS => 3;
  1         1  
  1         36  
54              
55 1     1   4 use constant STAT_INSERTS => 0;
  1         2  
  1         41  
56 1     1   6 use constant STAT_DELETES => 1;
  1         1  
  1         35  
57 1     1   4 use constant STAT_REBALANCE => 2;
  1         2  
  1         41  
58 1     1   5 use constant STAT_ROTATIONS => 3;
  1         7  
  1         47  
59              
60 1     1   5 use constant AVLZEROLS => 1;
  1         2  
  1         35  
61 1     1   4 use constant AVLUNDEF => 2;
  1         2  
  1         35  
62              
63             # Record signature format:
64             # PDAVL KFLAG VFLAG KLEN VLEN
65             # Z6 Cx Cx NNx NNx
66             # 28 bytes
67 1     1   5 use constant SIGNATURE => 'Z6CxCxNNxNNx';
  1         1  
  1         53  
68 1     1   5 use constant SIG_LEN => 28;
  1         2  
  1         43  
69 1     1   5 use constant SIG_TYPE => 'PDAVL';
  1         2  
  1         2960  
70              
71             #####################################################################
72             #
73             # Module code follows
74             #
75             #####################################################################
76              
77             sub new {
78              
79             # Purpose: instantiates an AVLTree object
80             # Returns: Object reference if successful, undef otherwise
81             # Usage: $obj = Paranoid::Data::AVLTree->new();
82              
83 4     4 1 31 my ( $class, %args ) = splice @_;
84 4         10 my $self = [undef];
85              
86 4         361 subPreamble( PDLEVEL1, '%', %args );
87              
88 4         7 bless $self, $class;
89              
90 4         15 subPostamble( PDLEVEL1, '$', $self );
91              
92 4         26 return $self;
93             }
94              
95             sub profile {
96              
97             # Purpose: Enables/disables performance profiling
98             # Returns: Boolean
99             # Usage: $rv = $obj->profile(1);
100              
101 1     1 1 4 my $self = shift;
102 1         2 my $enable = shift;
103              
104 1         3 $$self[AVLPROF] = $enable;
105 1 50       3 if ($enable) {
106              
107             # Reset counters
108 1         3 $$self[AVLSTATS] = [];
109 1         2 $$self[AVLSTATS][STAT_INSERTS] = 0;
110 1         2 $$self[AVLSTATS][STAT_DELETES] = 0;
111 1         2 $$self[AVLSTATS][STAT_REBALANCE] = 0;
112 1         2 $$self[AVLSTATS][STAT_ROTATIONS] = 0;
113             }
114              
115 1         4 return 1;
116             }
117              
118             sub stats {
119              
120             # Purpose: Returns the values of the current perf counters
121             # Returns: Hash
122             # Usage: %stats = $obj->stats;
123              
124 1     1 1 509 my $self = shift;
125 1         2 my %stats;
126              
127 1 50       4 if ( defined $$self[AVLSTATS] ) {
128 1         8 %stats = (
129             insertions => $$self[AVLSTATS][STAT_INSERTS],
130             deletions => $$self[AVLSTATS][STAT_DELETES],
131             rebalances => $$self[AVLSTATS][STAT_REBALANCE],
132             rotations => $$self[AVLSTATS][STAT_ROTATIONS],
133             );
134             } else {
135 0         0 %stats = (
136             insertions => 0,
137             deletions => 0,
138             rebalances => 0,
139             rotations => 0,
140             );
141             }
142              
143 1         6 return %stats;
144             }
145              
146             sub count {
147              
148             # Purpose: Returns the number of nodes in the tree
149             # Returns: Integer
150             # Usage: $count = $obj->count;
151              
152 9     9 1 22 my $self = shift;
153              
154 9 100       41 return defined $$self[AVLROOT] ? $$self[AVLROOT]->count : 0;
155             }
156              
157             sub height {
158              
159             # Purpose: Returns the height of the tree based on the longest branch
160             # Returns: Integer
161             # Usage: $height = $obj->height;
162              
163 13     13 1 1516 my $self = shift;
164              
165 13 100       57 return defined $$self[AVLROOT] ? $$self[AVLROOT]->height : 0;
166             }
167              
168             sub _printKeys {
169              
170             # Purpose: Prints the key structure of a tree starting with the node
171             # passed, and includes all children
172             # Returns: String
173             # Usage: $output = _printKeys($root);
174              
175 0     0   0 my $i = shift;
176 0         0 my $node = shift;
177 0         0 my $side = shift;
178 0         0 my $h = $node->height;
179 0         0 my $b = $node->balance;
180 0         0 my $line = '';
181              
182 0 0       0 $line = ' ' x $i if $i;
183 0 0       0 $line .= defined $side ? "($side/$h/$b) " : "($h/$b) ";
184 0         0 $line .= $node->key;
185 0         0 $line .= "\n";
186              
187 0         0 $i++;
188 0 0       0 $line .= _printKeys( $i, $node->left, 'l' ) if defined $node->left;
189 0 0       0 $line .= _printKeys( $i, $node->right, 'r' ) if defined $node->right;
190              
191 0         0 return $line;
192             }
193              
194             sub dumpKeys {
195              
196             # Purpose: A wrapper method that calls _printKeys() with the root node
197             # stored in the object
198             # Returns: String
199             # Usage: $obj->dumpKeys;
200              
201 0     0 1 0 my $self = shift;
202 0         0 my $line = '';
203              
204 0 0       0 $line = _printKeys( 1, $$self[AVLROOT] ) if defined $$self[AVLROOT];
205              
206 0         0 pderror($line);
207              
208 0         0 return 1;
209             }
210              
211             sub _keys {
212              
213             # Purpose: Returns an array containing all the keys in tree starting
214             # with the passed node
215             # Returns: List of Strings
216             # Usage: @keys = _keys($rootNode);
217              
218 23     23   28 my $node = shift;
219 23         39 my @stack = $node->key;
220              
221 23 100       45 push @stack, _keys( $node->left ) if defined $node->left;
222 23 100       45 push @stack, _keys( $node->right ) if defined $node->right;
223              
224 23         49 return @stack;
225             }
226              
227             sub nodeKeys {
228              
229             # Purpose: A wrapper method that calles _keys with the root node
230             # stored in the object
231             # Returns: List of Strings
232             # Usage: @keys = $obj->nodeKeys;
233              
234 6     6 1 10 my $self = shift;
235 6         8 my @k;
236              
237 6 100       24 @k = _keys( $$self[AVLROOT] ) if defined $$self[AVLROOT];
238              
239 6         14 return @k;
240             }
241              
242             sub _findNode {
243              
244             # Purpose: Checks for the existence of a matching node in the tree
245             # and updates the passed references for the path to where
246             # the node would be positioned, as well as the node, if
247             # there is one.
248             # Returns: Boolean
249             # Usage: $rv = $obj->_findNode($key, $node, @path);
250              
251 147     147   198 my $self = shift;
252 147         183 my $key = shift;
253 147         168 my $nref = shift;
254 147         177 my $pref = shift;
255 147         205 my $root = $$self[AVLROOT];
256 147         163 my $rv = 0;
257 147         171 my ( @path, $node );
258              
259 147         310 subPreamble( PDLEVEL4, '$$$', $key, $nref, $pref );
260              
261 147         210 $$nref = undef;
262 147         203 @$pref = ();
263 147 100       258 if ( defined $root ) {
264 143         171 $node = $root;
265 143         236 while ( defined $node ) {
266 389 100       700 if ( $node->key eq $key ) {
267 123         149 $rv = 1;
268 123         149 $$nref = $node;
269 123         253 pdebug( 'node found: %s', PDLEVEL4, $$nref );
270 123         192 last;
271             } else {
272 266         410 push @path, $node;
273 266 100       410 $node = $key gt $node->key ? $node->right : $node->left;
274             }
275             }
276 143         240 @$pref = @path;
277 143         267 pdebug( 'path to node position: %s', PDLEVEL4, @$pref );
278             }
279              
280 147         350 subPostamble( PDLEVEL4, '$', $rv );
281              
282 147         331 return $rv;
283             }
284              
285             sub nodeExists {
286              
287             # Purpose: Checks for the existence of a matching node
288             # Returns: Boolean
289             # Usage: $rv = $obj->nodeExists($key):
290              
291 11     11 1 1989 my $self = shift;
292 11         17 my $key = shift;
293 11         15 my $rv = 0;
294 11         13 my ( @path, $node );
295              
296 11         37 pdebug( 'entering w/%s', PDLEVEL3, $key );
297 11         37 pIn();
298              
299 11 50       22 if ( defined $key ) {
300 11         31 $self->_findNode( $key, \$node, \@path );
301 11         16 $rv = defined $node;
302             }
303              
304 11         35 pOut();
305 11         23 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
306              
307 11         37 return $self->_findNode( $key, \$node, \@path );
308             }
309              
310             sub fetchVal {
311              
312             # Purpose: Returns the associated value for the key, if it's present
313             # Returns: Scalar
314             # Usage: $val = obj->fetchVal($key);
315              
316 12     12 1 21 my $self = shift;
317 12         16 my $key = shift;
318 12         16 my ( @path, $node, $val );
319              
320 12         30 subPreamble( PDLEVEL3, '$', $key );
321              
322 12 50       24 if ( defined $key ) {
323 12         43 $self->_findNode( $key, \$node, \@path );
324 12         39 pdebug( 'node is %s', PDLEVEL4, $node );
325 12 50       56 $val = $node->val if defined $node;
326             }
327              
328 12         33 subPostamble( PDLEVEL3, 'b', $val );
329              
330 12         48 return $val;
331             }
332              
333             sub _addNode {
334              
335             # Purpose: Adds or updates a node for the key/value passed
336             # Returns: Boolean
337             # Usage: $rv = $obj->_addNode($key, $value);
338              
339 25     25   33 my $self = shift;
340 25         34 my $key = shift;
341 25         36 my $val = shift;
342 25         35 my $root = $$self[AVLROOT];
343 25         28 my $rv = 1;
344 25         32 my ( @path, $nn, $node, $parent );
345              
346 25         60 subPreamble( PDLEVEL3, '$b', $key, $val );
347              
348             # Validation check
349 25         79 $nn = Paranoid::Data::AVLTree::AVLNode->new( $key, $val );
350              
351 25 50       48 if ( defined $nn ) {
352 25 100       35 if ( defined $root ) {
353 20 50       49 if ( $self->_findNode( $key, \$node, \@path ) ) {
354 0         0 $node->setVal($val);
355 0         0 pdebug( 'updating existing node', PDLEVEL4 );
356             } else {
357              
358             # Attach the new node
359 20         45 foreach (@path) {
360 48 100       117 if ( $key gt $_->key ) {
361 30         59 $_->incrRHeight;
362             } else {
363 18         39 $_->incrLHeight;
364             }
365             }
366 20 100       39 if ( $key gt $path[-1]->key ) {
367 16         34 $path[-1]->setRight($nn);
368             } else {
369 4         9 $path[-1]->setLeft($nn);
370             }
371 20         50 pdebug( 'added node at the end of the branch', PDLEVEL4 );
372 20 100       43 $$self[AVLSTATS][STAT_INSERTS]++ if $$self[AVLPROF];
373              
374             }
375              
376             } else {
377 5         10 $$self[AVLROOT] = $nn;
378 5         13 pdebug( 'adding node as the tree root: %s',
379             PDLEVEL4, $$self[AVLROOT] );
380 5 100       14 $$self[AVLSTATS][STAT_INSERTS]++ if $$self[AVLPROF];
381             }
382              
383             } else {
384 0         0 $rv = 0;
385 0         0 pdebug( 'invalid key submitted: %s', PDLEVEL1, $key );
386             }
387              
388 25         64 subPostamble( PDLEVEL3, '$', $rv );
389              
390 25         61 return $rv;
391             }
392              
393             sub _updtRootRef {
394              
395             # Purpose: Updates the parent link matching the original ref
396             # with the new ref. Link can be on either side of
397             # the branch, or could be root of the tree, entirely.
398             # Returns: Boolean
399             # Usage: $rv = $obj->_updtRootRef($root, $oldref, $newref);
400              
401 19     19   24 my $self = shift;
402 19         33 my $root = shift;
403 19         20 my $oref = shift;
404 19         21 my $nref = shift;
405 19         27 my $rv = 1;
406              
407 19 100       30 if ( defined $root ) {
408              
409             # Update the parent link (could be on either side) to the child
410 10 100 100     24 if ( defined $root->left and $root->left == $oref ) {
    50 33        
411 6         26 pdebug( 'updating link on the root\'s right side', PDLEVEL4 );
412 6         13 $root->setLeft($nref);
413             } elsif ( defined $root->right and $root->right == $oref ) {
414 4         12 pdebug( 'updating link on the root\'s left side', PDLEVEL4 );
415 4         13 $root->setRight($nref);
416             } else {
417 0         0 pdebug( 'ERROR: old ref not linked to root!', PDLEVEL1 );
418 0         0 $rv = 0;
419             }
420              
421             } else {
422              
423             # No parent means we're rotating the root
424 9         27 pdebug( 'updating root node link', PDLEVEL4 );
425 9         17 $$self[AVLROOT] = $nref;
426             }
427              
428 19         32 return $rv;
429             }
430              
431             sub _rrr {
432              
433             # Purpose: Performs a single rotate right
434             # Returns: Boolean
435             # Usage: $rv = $self->_rrr($root, $node);
436              
437 6     6   10 my $self = shift;
438 6         19 my $root = shift;
439 6         10 my $x = shift;
440 6         13 my $z = $x->left;
441 6         7 my $rv;
442              
443 6         17 subPreamble( PDLEVEL4, '$$', $root, $x );
444              
445             # Update root node as a prerequisite to continuing
446 6 50 33     36 $rv = defined $x and defined $z and $self->_updtRootRef( $root, $x, $z );
447              
448             # Update x & z refs
449 6 50       12 if ($rv) {
450 6         20 $x->setLeft( $z->right );
451 6         14 $z->setRight($x);
452              
453             # Update heights
454 6         36 $x->updtHeights;
455 6         14 $z->updtHeights;
456 6 100       10 if ( defined $root ) {
457 2         5 $root->updtHeights;
458             } else {
459 4         17 $$self[AVLROOT]->updtHeights;
460             }
461 6 100       17 $$self[AVLSTATS][STAT_ROTATIONS]++ if $$self[AVLPROF];
462             }
463              
464 6         17 subPostamble( PDLEVEL4, '$', $rv );
465              
466 6         14 return $rv;
467             }
468              
469             sub _rll {
470              
471             # Purpose: Performs a single rotate left
472             # Returns: Boolean
473             # Usage: $rv = $self->_rll($root, $node);
474              
475 8     8   12 my $self = shift;
476 8         9 my $root = shift;
477 8         11 my $x = shift;
478 8         14 my $z = $x->right;
479 8         12 my $rv;
480              
481 8         20 subPreamble( PDLEVEL4, '$$', $root, $x );
482              
483             # Update root node as a prerequisite to continuing
484 8 50 33     42 $rv = defined $x and defined $z and $self->_updtRootRef( $root, $x, $z );
485              
486             # Update x & z refs
487 8 50       12 if ($rv) {
488 8         23 $x->setRight( $z->left );
489 8         20 $z->setLeft($x);
490              
491             # Update heights
492 8         21 $x->updtHeights;
493 8         18 $z->updtHeights;
494 8 100       13 if ( defined $root ) {
495 4         9 $root->updtHeights;
496             } else {
497 4         24 $$self[AVLROOT]->updtHeights;
498             }
499 8 50       38 $$self[AVLSTATS][STAT_ROTATIONS]++ if $$self[AVLPROF];
500             }
501              
502 8         24 subPostamble( PDLEVEL4, '$', $rv );
503              
504 8         18 return $rv;
505             }
506              
507             sub _rrl {
508              
509             # Purpose: Performs a double rotation of right-left
510             # Returns: Boolean
511             # Usage: $rv = $self->_rrl($root, $node);
512              
513 0     0   0 my $self = shift;
514 0         0 my $root = shift;
515 0         0 my $x = shift;
516 0         0 my $z = $x->right;
517 0         0 my $y = $z->left;
518 0         0 my $rv = 0;
519              
520 0         0 subPreamble( PDLEVEL4, '$$', $root, $x );
521              
522 0         0 $rv = $self->_rrr( $x, $z );
523 0 0       0 if ($rv) {
524 0         0 $z = $x->right;
525 0 0       0 if ( $z == $y ) {
526 0         0 $rv = $self->_rll( $root, $x );
527             } else {
528 0         0 pdebug( 'double rotation incorrect results on first rotation',
529             PDLEVEL1 );
530             }
531             } else {
532 0         0 pdebug( 'double rotation failed on first rotation', PDLEVEL1 );
533             }
534              
535 0         0 subPostamble( PDLEVEL4, '$', $rv );
536              
537 0         0 return $rv;
538             }
539              
540             sub _rlr {
541              
542             # Purpose: Performs a double rotation of left-right
543             # Returns: Boolean
544             # Usage: $rv = $self->_rlr($root, $node);
545              
546 0     0   0 my $self = shift;
547 0         0 my $root = shift;
548 0         0 my $x = shift;
549 0         0 my $z = $x->left;
550 0         0 my $y = $z->right;
551 0         0 my $rv = 0;
552              
553 0         0 subPreamble( PDLEVEL4, '$$', $root, $x );
554              
555 0         0 $rv = $self->_rll( $x, $z );
556 0 0       0 if ($rv) {
557 0         0 $z = $x->left;
558 0 0       0 if ( $z == $y ) {
559 0         0 $rv = $self->_rrr( $root, $x );
560             } else {
561 0         0 pdebug( 'double rotation incorrect results on first rotation',
562             PDLEVEL1 );
563             }
564             } else {
565 0         0 pdebug( 'double rotation failed on first rotation', PDLEVEL1 );
566             }
567              
568 0         0 subPostamble( PDLEVEL4, '$', $rv );
569              
570 0         0 return $rv;
571             }
572              
573             sub _rotate {
574              
575             # Purpose: Performs the appropriate rotation for the specified node under
576             # the specified root. This includes not only what direction to
577             # rotate, but whether to perform a single or double rotation.
578             # Returns: Boolean
579             # Usage: $rv = $obj->_rotate($root, $node);
580              
581 14     14   19 my $self = shift;
582 14         15 my $root = shift;
583 14         15 my $x = shift;
584 14         23 my $rv = 1;
585              
586 14         31 subPreamble( PDLEVEL4, '$$', $root, $x );
587              
588 14 100       31 if ( $x->balance > 1 ) {
    50          
589              
590             # Rotate left
591 8 50       16 if ( $x->right->balance < 0 ) {
592              
593             # Perform a double rotation
594 0         0 $rv = $self->_rrl( $root, $x );
595              
596             } else {
597              
598             # Perform a single rotation
599 8         19 $rv = $self->_rll( $root, $x );
600             }
601              
602             } elsif ( $x->balance < -1 ) {
603              
604             # Rotate right
605 6 50       14 if ( $x->left->balance > 0 ) {
606              
607             # Perform a double rotation
608 0         0 $rv = $self->_rlr( $root, $x );
609              
610             } else {
611              
612             # Perform a single rotation
613 6         15 $rv = $self->_rrr( $root, $x );
614             }
615             }
616              
617 14         35 subPostamble( PDLEVEL4, '$', $rv );
618              
619 14         20 return $rv;
620             }
621              
622             sub _rebalance {
623              
624             # Purpose: Rebalances the tree for the branch extending to the specified
625             # node by performing rotations on all nodes that are unbalanced
626             # Returns: Boolean
627             # Usage: $rv = $obj->($node);
628              
629 30     30   39 my $self = shift;
630 30         32 my $node = shift;
631 30         59 my $key = $node->key;
632 30         46 my ( @path, $parent, $n );
633              
634 30         66 subPreamble( PDLEVEL4, '$', $node );
635              
636 30 50       69 if ( $self->_findNode( $key, \$node, \@path ) ) {
637              
638             # Start at the bottom of the chain
639 30         48 push @path, $node;
640 30         60 $key = $node->key;
641              
642             # Find number of nodes that are unbalanced
643 30         51 $n = scalar grep { abs( $_->balance ) > 1 } @path;
  87         151  
644 30 100 100     74 $$self[AVLSTATS][STAT_REBALANCE]++ if $$self[AVLPROF] and $n;
645 30         57 while ($n) {
646 14         34 pdebug( 'found %s node(s) in the branch that are unbalanced',
647             PDLEVEL4, $n );
648 14         26 $node = $parent = undef;
649              
650 14         24 foreach (@path) {
651 20         30 $node = $_;
652 20 100       36 if ( abs( $node->balance ) > 1 ) {
653 14         32 pdebug( 'key: %s balance: %s',
654             PDLEVEL4, $node->key, $node->balance );
655              
656             # Determine type of rotation and execute it
657 14         41 $self->_rotate( $parent, $node );
658 14         40 $self->_findNode( $key, \$node, \@path );
659 14         23 push @path, $node;
660 14         43 $n = scalar grep { abs( $_->balance ) > 1 } @path;
  41         71  
661 14         31 last;
662             } else {
663 6         11 $parent = $node;
664             }
665             }
666             }
667             }
668              
669 30         74 subPostamble( PDLEVEL4, '$', 1 );
670              
671 30         53 return 1;
672             }
673              
674             sub addPair {
675              
676             # Purpose: Adds or updates a node for the key/value passed
677             # Returns: Boolean
678             # Usage: $rv = $obj->addPair($key, $value);
679              
680 25     25 1 59 my $self = shift;
681 25         32 my $key = shift;
682 25         35 my $val = shift;
683 25         30 my $rv = 1;
684 25         36 my ( @path, $node );
685              
686 25         58 subPreamble( PDLEVEL3, '$b', $key, $val );
687              
688 25         66 $rv = $self->_addNode( $key, $val );
689 25 50       44 if ($rv) {
690 25         62 $self->_findNode( $key, \$node, \@path );
691 25         62 $rv = $self->_rebalance($node);
692             }
693              
694 25         60 subPostamble( PDLEVEL3, '$', $rv );
695              
696 25         76 return $rv;
697             }
698              
699             sub _splice {
700              
701             # Purpose: Splices off the requested node and reattaches any
702             # sub-branches, and returns a new target key useful
703             # for tracing for rebalancing purposes.
704             # Returns: String
705             # Usage: $key = $obj->_splice($root, $node);
706              
707 1     1   2 my $self = shift;
708 1         2 my $root = shift;
709 1         2 my $node = shift;
710 1         1 my ( $rv, $ln, $rn, $cn, @path, $height );
711              
712 1         4 subPreamble( PDLEVEL4, '$$', $root, $node );
713              
714 1         4 $ln = $node->left;
715 1         3 $rn = $node->right;
716              
717 1 50 33     7 if ( defined $ln and defined $rn ) {
718              
719             # Attach the longer branch underneath the shorter branch
720 1 50       4 if ( $ln->height < $rn->height ) {
721              
722             # Right branch is longer
723             #
724             # Find a place to attach on the left branch
725 0         0 push @path, $ln;
726 0         0 $cn = $ln;
727 0         0 while ( defined $cn->right ) {
728 0         0 $cn = $cn->right;
729 0         0 push @path, $cn;
730             }
731 0         0 $cn->setRight($rn);
732              
733             # Update the height back up to the root of the left branch
734 0         0 $height = $cn->height;
735 0         0 foreach ( reverse @path ) {
736 0 0       0 if ( $_->rHeight < $height ) {
737 0         0 $_->addRHeight( $height - $_->rHeight );
738             }
739 0         0 $height++;
740             }
741              
742             # Now, attach the left branch to the root
743 0         0 $self->_updtRootRef( $root, $node, $ln );
744              
745             # Hand back the node key that we're going to seek to in the
746             # calling function
747 0         0 $rv = $rn->key;
748              
749             } else {
750              
751             # Left branch is longer
752             #
753             # Find a place to attach on the right branch
754 1         3 push @path, $rn;
755 1         2 $cn = $rn;
756 1         3 while ( defined $cn->left ) {
757 1         2 $cn = $cn->left;
758 1         3 push @path, $cn;
759             }
760 1         3 $cn->setLeft($ln);
761              
762             # Update the height back up to the root of the left branch
763 1         3 $height = $cn->height;
764 1         5 foreach ( reverse @path ) {
765 2 50       6 if ( $_->lHeight < $height ) {
766 2         6 $_->addLHeight( $height - $_->lHeight );
767             }
768 2         8 $height++;
769             }
770              
771             # Now, attach the left branch to the root
772 1         3 $self->_updtRootRef( $root, $node, $rn );
773              
774             # Hand back the node key that we're going to seek to in the
775             # calling function
776 1         2 $rv = $ln->key;
777              
778             }
779              
780             } else {
781 0         0 pdebug( 'this function shouldn\'t be called without two branches',
782             PDLEVEL4 );
783             }
784              
785 1         5 subPostamble( PDLEVEL4, '$', $rv );
786              
787 1         2 return $rv;
788             }
789              
790             sub delNode {
791              
792             # Purpose: Removes the specifed node
793             # Returns: Boolean
794             # Usage: $rv = $obj->delNode($key);
795              
796 5     5 1 10 my $self = shift;
797 5         10 my $key = shift;
798 5         8 my $rv = 0;
799 5         8 my ( $root, $node, @path, $height );
800              
801 5         17 subPreamble( PDLEVEL3, '$', $key );
802              
803 5 50       27 if ( $self->_findNode( $key, \$node, \@path ) ) {
804 5         9 $root = $path[-1];
805              
806             # Test for simplest deletion conditions
807 5 100       17 if ( scalar $node->children <= 1 ) {
808              
809             # Node for deletion only has one or zero children
810 4 50       9 $rv =
    50          
811             defined $node->left
812             ? $self->_updtRootRef( $root, $node, $node->left )
813             : defined $node->right
814             ? $self->_updtRootRef( $root, $node, $node->right )
815             : $self->_updtRootRef( $root, $node, undef );
816              
817             # Adjust heights
818 4         9 foreach ( reverse @path ) {
819 11 100       17 if ( $key lt $_->key ) {
820 2 50 33     10 $_->decrLHeight
821             if defined $_->left
822             and $_->left->height < $_->lHeight;
823             } else {
824 9 100 100     18 $_->decrRHeight
825             if defined $_->right
826             and $_->right->height < $_->rHeight;
827             }
828             }
829              
830             } else {
831              
832             # Splice the node out
833 1         11 $key = $self->_splice( $root, $node );
834              
835 1 50       16 if ( $self->_findNode( $key, \$node, \@path ) ) {
836 1         3 $rv = 1;
837 1         2 $root = $node;
838              
839             } else {
840 0         0 pdebug( 'something went horribly wrong', PDLEVEL1 );
841             }
842              
843             }
844 5 50 33     14 $$self[AVLSTATS][STAT_DELETES]++ if $$self[AVLSTATS] and $rv;
845              
846             # Rebalance
847 5 50       11 $root = $$self[AVLROOT] unless defined $root;
848 5 50 33     41 $rv = $self->_rebalance($root) if defined $root and $rv;
849             }
850              
851 5         18 subPostamble( PDLEVEL3, '$', $rv );
852              
853 5         23 return $rv;
854             }
855              
856             sub purgeNodes {
857              
858             # Purpose: Deletes the root reference, essentially purging the entire
859             # tree
860             # Returns: Boolean
861             # Usage: $rv = $obj->purgeNodes;
862              
863 3     3 1 523 my $self = shift;
864              
865 3         9 subPreamble(PDLEVEL3);
866              
867 3         11 $$self[AVLROOT] = undef;
868              
869 3         7 subPostamble( PDLEVEL3, '$', 1 );
870              
871 3         9 return 1;
872             }
873              
874             sub _writeRecord {
875              
876             # Purpose: Writes the passed node to file
877             # Returns: Boolean
878             # Usage: $rv = _writeRecord($filename, $node);
879              
880 5     5   9 my $file = shift;
881 5         6 my $node = shift;
882 5         7 my ( $rv, $rec, $k, $v, $kf, $vf );
883              
884             # Get key/val
885 5         13 $k = $node->key;
886 5         11 $v = $node->val;
887              
888             # Set flag values
889 5 50       12 $kf =
    50          
890             !defined $k ? AVLUNDEF
891             : length $k ? 0
892             : AVLZEROLS;
893 5 50       10 $vf =
    50          
894             !defined $v ? AVLUNDEF
895             : length $v ? 0
896             : AVLZEROLS;
897              
898             {
899 1     1   8 use bytes;
  1         7  
  1         8  
  5         7  
900 5 50       18 $rec = pack SIGNATURE, SIG_TYPE, $kf, $vf,
    50          
901             quad2Longs( $kf ? 0 : length $k ),
902             quad2Longs( $vf ? 0 : length $v );
903 5 50       13 $rec .= $k unless $kf;
904 5 50       8 $rec .= $v unless $vf;
905              
906 5 50       12 $rv = pwrite( $file, $rec ) == length $rec ? 1 : 0;
907             }
908              
909 5 50       11 pdebug( 'failed to write record', PDLEVEL1 ) unless $rv;
910              
911 5         10 return $rv;
912             }
913              
914             sub save2File {
915              
916             # Purpose: Saves binary tree to a file
917             # Returns: Boolean
918             # Usage: $rv = $obj->save($file);
919              
920 1     1 1 3 my $self = shift;
921 1         2 my $file = shift;
922 1         2 my $rv;
923 1         3 my ( @lc, @rc, @ln, @rn, $node );
924              
925 1         3 subPreamble( PDLEVEL1, '$', $file );
926              
927 1 50 33     16 if ( defined $file and length $file ) {
928 1 50       9 if ( popen( $file, O_RDWR | O_CREAT ) ) {
929 1         6 pseek( $file, 0, SEEK_SET );
930 1         5 ptruncate($file);
931              
932             # Start descending the tree one level at a time
933 1 50       4 if ( defined $$self[AVLROOT] ) {
934 1         3 $rv = _writeRecord( $file, $$self[AVLROOT] );
935 1         5 @lc = ( grep {defined} $$self[AVLROOT]->left );
  1         4  
936 1         3 @rc = ( grep {defined} $$self[AVLROOT]->right );
  1         3  
937              
938             # Note: the whole point of this is to attempt to retrieve
939             # nodes from both sides of the tree in a way that, when read,
940             # will require minimal rebalances.
941              
942             # Start descending and writing
943 1   66     14 while ( $rv and ( @lc or @rc ) ) {
      33        
944              
945             # Extract a list of all left and right nodes
946 2         6 @ln = grep {defined} map { $_->left } @lc;
  3         7  
  3         8  
947 2         4 push @ln, grep {defined} map { $_->right } @lc;
  3         5  
  3         7  
948 2         4 @rn = grep {defined} map { $_->left } @rc;
  1         3  
  1         3  
949 2         4 push @rn, grep {defined} map { $_->right } @rc;
  1         3  
  1         2  
950              
951             # Record all of the current level of children
952 2   66     7 while ( $rv and ( @lc or @rc ) ) {
      33        
953              
954             # Shift off of the left side
955 2         3 $node = shift @lc;
956 2 50       20 $rv = _writeRecord( $file, $node ) if defined $node;
957              
958             # Shift off of the right side
959 2         6 $node = shift @rc;
960 2 100 66     11 $rv = _writeRecord( $file, $node )
961             if $rv and defined $node;
962              
963             # Pop off of the left side
964 2         6 $node = pop @lc;
965 2 100 66     12 $rv = _writeRecord( $file, $node )
966             if $rv and defined $node;
967              
968             # Pop off of the right side
969 2         4 $node = pop @rc;
970 2 50 33     21 $rv = _writeRecord( $file, $node )
971             if $rv and defined $node;
972              
973             }
974              
975             # Start with the next level
976 2         6 @lc = @ln;
977 2         16 @rc = @rn;
978             }
979              
980             } else {
981 0         0 pdebug( 'nothing in the tree to write', PDLEVEL2 );
982 0         0 $rv = 1;
983             }
984              
985 1         8 pclose($file);
986             }
987             }
988              
989 1         6 subPostamble( PDLEVEL1, '$', $rv );
990              
991 1         5 return $rv;
992             }
993              
994             sub _readRecord {
995              
996             # Purpose: Reads the node from the file
997             # Returns: Boolean
998             # Usage: $rv = _readRecord($self, $filename);
999              
1000 5     5   6 my $self = shift;
1001 5         7 my $file = shift;
1002 5         13 my ( $rv, $node, $sig, $content );
1003 5         0 my ( $stype, $kf, $vf, $kl, $vl, $kv, $vv );
1004 5         0 my ( $kl1, $kl2, $vl1, $vl2 );
1005              
1006             # Read Signature
1007 5 50       12 if ( pread( $file, $sig, SIG_LEN ) == SIG_LEN ) {
1008 5         64 ( $stype, $kf, $vf, $kl1, $kl2, $vl1, $vl2 ) = unpack SIGNATURE, $sig;
1009 5 50       21 if ( $stype eq SIG_TYPE ) {
1010 5         9 $rv = 1;
1011 5         15 $kl = longs2Quad( $kl1, $kl2 );
1012 5         14 $vl = longs2Quad( $vl1, $vl2 );
1013              
1014 5 50 33     26 if ( !defined $kl or !defined $vl ) {
1015 0         0 $rv = 0;
1016 0         0 pdebug( '64-bit values not supported on 32-bit platforms',
1017             PDLEVEL1 );
1018             }
1019             } else {
1020 0         0 pdebug( 'PDAVL signature failed basic validation: %s',
1021             PDLEVEL1, $sig );
1022             }
1023             } else {
1024 0         0 pdebug( 'failed to read PDAVL signature', PDLEVEL1 );
1025             }
1026              
1027             # Extract key/val lengths/values
1028 5 50       13 if ($rv) {
1029 5 50       10 if ($kf) {
1030 0 0       0 $kv = '' if $kf == AVLZEROLS;
1031             }
1032 5 50       11 if ($vf) {
1033 0 0       0 $vv = '' if $vf == AVLZEROLS;
1034             }
1035             }
1036              
1037             # Read key
1038 5 50 33     18 if ( $rv and $kl ) {
1039 5 50       10 if ( pread( $file, $content, $kl ) == $kl ) {
1040 5         12 $kv = $content;
1041             } else {
1042 0         0 pdebug( 'failed to read full length of key content', PDLEVEL1 );
1043 0         0 $rv = 0;
1044             }
1045             }
1046              
1047             # Read value
1048 5 50 33     24 if ( $rv and $vl ) {
1049 5 50       11 if ( pread( $file, $content, $vl ) == $vl ) {
1050 5         13 $vv = $content;
1051             } else {
1052 0         0 pdebug( 'failed to read full length of key content', PDLEVEL1 );
1053 0         0 $rv = 0;
1054             }
1055             }
1056              
1057             # Add the key/pair
1058 5 50       16 $rv = $self->addPair( $kv, $vv ) if $rv;
1059              
1060 5 50       12 pdebug( 'failed to read record', PDLEVEL1 ) unless $rv;
1061              
1062 5         36 return $rv;
1063             }
1064              
1065             sub loadFromFile {
1066              
1067             # Purpose: Loads content from file
1068             # Returns: Boolean
1069             # Usage: $rv = $obj->loadFromFile($file);
1070              
1071 1     1 1 2 my $self = shift;
1072 1         2 my $file = shift;
1073 1         1 my ( $rv, $eof );
1074              
1075 1         4 subPreamble( PDLEVEL1, '$', $file );
1076              
1077             # Purge current hash contents
1078 1         4 $self->purgeNodes;
1079              
1080             # Make sure file is open and at the beginning
1081 1 50       4 if ( defined popen( $file, O_RDWR ) ) {
1082 1         4 $eof = pseek( $file, 0, SEEK_END );
1083 1         5 pseek( $file, 0, SEEK_SET );
1084              
1085             # Read records
1086 1   66     2 do {
1087 5         12 $rv = _readRecord( $self, $file );
1088             } while $rv and ptell($file) != $eof;
1089              
1090 1         4 pclose($file);
1091             }
1092              
1093 1         8 subPostamble( PDLEVEL1, '$', $rv );
1094              
1095 1         6 return $rv;
1096             }
1097              
1098             sub TIEHASH {
1099 1     1   6 return new Paranoid::Data::AVLTree;
1100             }
1101              
1102             sub FETCH {
1103 1     1   23 my $self = shift;
1104 1         2 my $key = shift;
1105 1         2 my $rv;
1106              
1107 1         4 return $self->fetchVal($key);
1108             }
1109              
1110             sub STORE {
1111 10     10   525 my $self = shift;
1112 10         13 my $key = shift;
1113 10         15 my $val = shift;
1114              
1115 10         17 return $self->addPair( $key, $val );
1116             }
1117              
1118             sub EXISTS {
1119 3     3   8 my $self = shift;
1120 3         4 my $key = shift;
1121              
1122 3         8 return $self->nodeExists($key);
1123             }
1124              
1125             sub DELETE {
1126 1     1   3 my $self = shift;
1127 1         2 my $key = shift;
1128              
1129 1         6 return $self->delNode($key);
1130             }
1131              
1132             sub CLEAR {
1133 1     1   3 my $self = shift;
1134              
1135 1         3 return $self->purgeNodes;
1136             }
1137              
1138             sub FIRSTKEY {
1139 5     5   859 my $self = shift;
1140 5         15 my @k = $self->nodeKeys();
1141 5         8 my ( $key, $node, @path, %rv );
1142              
1143 5 100       13 if (@k) {
1144 3         5 $key = shift @k;
1145 3         9 $self->_findNode( $key, \$node, \@path );
1146 3         8 %rv = ( $node->key() => $node->val() );
1147 3         10 $$self[AVLKEYS] = [@k];
1148             }
1149              
1150 5         27 return each %rv;
1151             }
1152              
1153             sub NEXTKEY {
1154 18     18   23 my $self = shift;
1155 18         28 my ( $key, $node, @path, %rv );
1156              
1157 18 100 50     36 if ( defined $$self[AVLKEYS] and scalar @{ $$self[AVLKEYS] } ) {
  18         46  
1158 15         16 $key = shift @{ $$self[AVLKEYS] };
  15         26  
1159 15         38 $self->_findNode( $key, \$node, \@path );
1160 15         33 %rv = ( $node->key() => $node->val() );
1161             }
1162              
1163 18         71 return each %rv;
1164             }
1165              
1166             sub SCALAR {
1167 0     0     my $self = shift;
1168              
1169 0           return $self->count;
1170             }
1171              
1172             sub UNTIE {
1173 0     0     return 1;
1174             }
1175              
1176             1;
1177              
1178             __END__