File Coverage

blib/lib/Tree/Binary/Search.pm
Criterion Covered Total %
statement 195 197 98.9
branch 101 112 90.1
condition 21 29 72.4
subroutine 35 35 100.0
pod 18 18 100.0
total 370 391 94.6


line stmt bran cond sub pod time code
1              
2             package Tree::Binary::Search;
3              
4 5     5   48508 use strict;
  5         6  
  5         123  
5 5     5   17 use warnings;
  5         4  
  5         116  
6              
7 5     5   14 use Scalar::Util qw(blessed);
  5         4  
  5         336  
8              
9 5     5   1604 use Tree::Binary::Search::Node;
  5         8  
  5         114  
10              
11 5     5   21 use constant TRUE => 1;
  5         4  
  5         298  
12 5     5   20 use constant FALSE => 0;
  5         4  
  5         190  
13              
14 5     5   17 use constant EQUAL_TO => 0;
  5         6  
  5         195  
15 5     5   17 use constant LESS_THAN => -1;
  5         6  
  5         171  
16 5     5   15 use constant GREATER_THAN => 1;
  5         4  
  5         6759  
17              
18             our $VERSION = '1.07';
19              
20             ## ----------------------------------------------------------------------------
21             ## Tree::Binary::Search
22             ## ----------------------------------------------------------------------------
23              
24             ### constructor
25              
26             sub new {
27 31     31 1 743 my ($_class, $root) = @_;
28 31   33     125 my $class = ref($_class) || $_class;
29 31         48 my $binary_search_tree = {};
30 31         44 bless($binary_search_tree, $class);
31 31         59 $binary_search_tree->_init($root);
32 31         54 return $binary_search_tree;
33             }
34              
35             ### ---------------------------------------------------------------------------
36             ### methods
37             ### ---------------------------------------------------------------------------
38              
39             ## ----------------------------------------------------------------------------
40             ## private methods
41              
42             sub _init {
43 31     31   41 my ($self, $root) = @_;
44 31   100     176 $self->{_root} = $root || "Tree::Binary::Search::Node";
45 31         44 $self->{_comparison_func} = undef;
46             }
47              
48             sub _compare {
49 11684     11684   8175 my ($self, $current_key, $btree_key) = @_;
50 11684         10661 my $result = $self->{_comparison_func}->($btree_key, $current_key);
51             # catch non-numeric values here
52             # as well as numbers that are not
53             # within our acceptable range
54 11684 100 100     43200 ($result =~ /\d/ && ($result >= LESS_THAN && $result <= GREATER_THAN))
      66        
55             || die "Bad Value : got a bad value from the comparison function ($result)";
56 11681         10816 return $result;
57             }
58              
59             ## ----------------------------------------------------------------------------
60             ## mutators
61              
62             sub useStringComparison {
63 8     8 1 1025 my ($self) = @_;
64 8     91   27 $self->{_comparison_func} = sub { $_[0] cmp $_[1] };
  91         100  
65             }
66              
67             sub useNumericComparison {
68 23     23 1 3506 my ($self) = @_;
69 23     11590   95 $self->{_comparison_func} = sub { $_[0] <=> $_[1] };
  11590         8877  
70             }
71              
72             sub setComparisonFunction {
73 6     6 1 1598 my ($self, $func) = @_;
74 6 100       38 (ref($func) eq "CODE")
75             || die "Incorrect Object Type : comparison function is not a function";
76 3         5 $self->{_comparison_func} = $func;
77             }
78              
79             ## ----------------------------------------------------------------------------
80             ## accessors
81              
82             sub getTree {
83 31     31 1 132 my ($self) = @_;
84 31         110 return $self->{_root};
85             }
86              
87             ## ----------------------------------------------------------------------------
88             ## informational
89              
90             sub isEmpty {
91 2231     2231 1 1466 my ($self) = @_;
92 2231 100       4184 return (ref($self->{_root})) ? FALSE : TRUE;
93             }
94              
95             ## ----------------------------------------------------------------------------
96             ## methods for underlying tree
97              
98             sub accept {
99 18     18 1 46 my ($self, $visitor) = @_;
100 18         40 $self->{_root}->accept($visitor);
101             }
102              
103             sub size {
104 11     11 1 21 my ($self) = @_;
105 11         22 return $self->{_root}->size();
106             }
107              
108             sub height {
109 1     1 1 2 my ($self) = @_;
110 1         7 return $self->{_root}->height();
111             }
112              
113             sub DESTROY {
114 31     31   1896 my ($self) = @_;
115             # be sure to call call the DESTROY method
116             # on the underlying tree to ensure it is
117             # cleaned up properly
118 31 100       137 ref($self->{_root}) && $self->{_root}->DESTROY();
119             }
120              
121             ## ----------------------------------------------------------------------------
122             ## search methods
123              
124             sub insert {
125 955     955 1 3764 my ($self, $key, $value) = @_;
126 955         586 my $btree;
127 955 100 100     2356 if (defined $key && defined $value) {
    100 100        
      33        
128 950         1540 $btree = $self->{_root}->new($key, $value);
129             }
130             elsif (!defined $value &&
131             (blessed($key) && $key->isa("Tree::Binary::Search::Node"))) {
132 1         1 $btree = $key;
133             }
134             else {
135 4         33 die "Insufficient Arguments : bad arguments to insert";
136             }
137             # if the root is not a reference, then
138             # we dont yet have a root, so ...
139 951 100       1078 if ($self->isEmpty()) {
140 32 100       75 (defined($self->{_comparison_func}))
141             || die "Illegal Operation : No comparison function set";
142 31         60 $self->{_root} = $btree;
143             }
144             else {
145 919         639 my $current = $self->{_root};
146 919         608 while (1) {
147 4880         6353 my $comparison = $self->_compare($current->getNodeKey(), $btree->getNodeKey());
148             # if it is equal to, then throw
149             # an exception since you can insert
150             # duplicates
151 4877 100       5879 die "Illegal Operation : you cannot insert a duplicate key" if $comparison == EQUAL_TO;
152             # otherwise ...
153 4875 100       5638 if ($comparison == LESS_THAN) {
    50          
154             # if it is less than, then we need
155             # to insert it down the left arm of
156             # the tree, unless of course we
157             # dont have a left arm, in which case
158             # we just make one out of these vaules
159 2262 100       3142 if ($current->hasLeft()) {
160 1802         2287 $current = $current->getLeft();
161 1802         1638 next;
162             }
163             else {
164 460         663 $current->setLeft($btree);
165 460         734 last;
166             }
167             }
168             elsif ($comparison == GREATER_THAN) {
169             # if it is greater than, then we need
170             # to insert it down the right arm of
171             # the tree, unless of course we
172             # dont have a right arm, in which case
173             # we just make one out of these vaules
174 2613 100       3580 if ($current->hasRight()) {
175 2159         2690 $current = $current->getRight();
176             }
177             else {
178 454         724 $current->setRight($btree);
179 454         718 last;
180             }
181             }
182             }
183             }
184             }
185              
186             sub update {
187 8     8 1 1863 my ($self, $key, $value) = @_;
188 8 100       14 (!$self->isEmpty())
189             || die "Illegal Operation : Cannot update without first inserting";
190 7 100 100     44 (defined $key && defined $value)
191             || die "Insufficient Arguments : Must supply a key to find and a value to update";
192             # now go about inserting
193 4         4 my $current = $self->{_root};
194 4         5 while (1) {
195 8         13 my $comparison = $self->_compare($current->getNodeKey(), $key);
196             # if it is equal to 0, then we have
197             # found out value, and we update it
198 8 100       21 if ($comparison == EQUAL_TO) {
    100          
    50          
199 2         7 $current->setNodeValue($value);
200 2         2 last;
201             }
202             elsif ($comparison == LESS_THAN) {
203             # if it is less than, then we need
204             # to ...
205 2 100       8 ($current->hasLeft()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
206 1         3 $current = $current->getLeft();
207 1         1 next;
208             }
209             elsif ($comparison == GREATER_THAN) {
210             # if it is greater than, then we need
211             # to ...
212 4 100       11 ($current->hasRight()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
213 3         5 $current = $current->getRight();
214 3         4 next;
215             }
216             }
217             }
218              
219             sub select : method {
220 18     18 1 1218 my ($self, $key) = @_;
221 18 100       30 (!$self->isEmpty())
222             || die "Illegal Operation : Cannot lookup anything without first inserting";
223 17 100       37 (defined $key)
224             || die "Insufficient Arguments : Must supply a key to find";
225              
226 16         16 my $current = $self->{_root};
227 16         11 while (1) {
228 52         84 my $comparison = $self->_compare($current->getNodeKey(), $key);
229 52 100       92 if ($comparison == EQUAL_TO) {
    100          
    50          
230             # if it is equal to, then we are
231             # have found it, so return
232 13         13 last;
233             }
234             elsif ($comparison == LESS_THAN) {
235             # if it is less than, then we need
236             # to look down the left arm of
237             # the tree, unless of course we
238             # dont have a left arm, in which case
239             # we just die
240 21 100       33 ($current->hasLeft()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
241 20         92 $current = $current->getLeft();
242 20         16 next;
243             }
244             elsif ($comparison == GREATER_THAN) {
245             # if it is greater than, then we need
246             # to look down the right arm of
247             # the tree, unless of course we
248             # dont have a right arm, in which case
249             # we just dies
250 18 100       30 ($current->hasRight()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
251 16         19 $current = $current->getRight();
252 16         11 next;
253             }
254             }
255 13         29 return $current->getNodeValue();
256             }
257              
258             sub exists : method {
259 1223     1223 1 4050 my ($self, $key) = @_;
260 1223 100       1488 (defined $key)
261             || die "Insufficient Arguments : Must supply a key to find";
262 1222 100       1283 return FALSE if $self->isEmpty();
263              
264 1201         979 my $current = $self->{_root};
265 1201         764 while (1) {
266 6703         8718 my $comparison = $self->_compare($current->getNodeKey(), $key);
267 6703 100       10213 if ($comparison == 0) {
    100          
    50          
268             # if it is equal to, then we are
269             # have found it, so return TRUE
270 350         469 return TRUE;
271             }
272             elsif ($comparison == -1) {
273             # if it is less than, then we need
274             # to look down the left arm of
275             # the tree, unless of course we
276             # dont have a left arm, in which case
277             # we just return FALSE
278 2969 100       3993 ($current->hasLeft()) || return FALSE;
279 2540         3453 $current = $current->getLeft();
280 2540         2006 next;
281             }
282             elsif ($comparison == 1) {
283             # if it is greater than, then we need
284             # to look down the right arm of
285             # the tree, unless of course we
286             # dont have a right arm, in which case
287             # we just return FALSE
288 3384 100       4737 ($current->hasRight()) || return FALSE;
289 2962         4066 $current = $current->getRight();
290 2962         2246 next;
291             }
292             }
293             }
294              
295             sub _max_node {
296 3     3   5 my ($self) = @_;
297 3 100       4 (!$self->isEmpty())
298             || die "Illegal Operation : Cannot get a max without first inserting";
299 2         3 my $current = $self->{_root};
300 2         4 $current = $current->getRight() while $current->hasRight();
301 2         6 return $current;
302             }
303              
304             sub _min_node {
305 3     3   4 my ($self) = @_;
306 3 100       5 (!$self->isEmpty())
307             || die "Illegal Operation : Cannot get a min without first inserting";
308 2         3 my $current = $self->{_root};
309 2         5 $current = $current->getLeft() while $current->hasLeft();
310 2         6 return $current;
311             }
312              
313             sub max_key {
314 1     1 1 2 my ($self) = @_;
315 1         3 return $self->_max_node()->getNodeKey();
316             }
317              
318             sub min_key {
319 1     1 1 2 my ($self) = @_;
320 1         2 return $self->_min_node()->getNodeKey();
321             }
322              
323             sub max {
324 2     2 1 302 my ($self) = @_;
325 2         7 return $self->_max_node()->getNodeValue();
326             }
327              
328             sub min {
329 2     2 1 299 my ($self) = @_;
330 2         7 return $self->_min_node()->getNodeValue();
331             }
332              
333             ## ------------------------------------------------------------------------
334             ## Delete was pretty much lifted from the description in:
335             ## http://www.msu.edu/~pfaffben/avl/libavl.html/Deleting-from-a-BST.html
336             ## ------------------------------------------------------------------------
337              
338             sub delete : method {
339 25     25 1 2697 my ($self, $key) = @_;
340 25 100       40 (!$self->isEmpty())
341             || die "Illegal Operation : Cannot delete without first inserting";
342 24 100       43 (defined($key))
343             || die "Insufficient Arguments : you must supply a valid key to lookup in the tree";
344              
345 23         24 my $current = $self->{_root};
346 23         26 while (1) {
347 41         68 my $comparison = $self->_compare($current->getNodeKey(), $key);
348 41 100       71 if ($comparison == 0) {
    100          
    50          
349             # if it is equal to,
350 21 100       40 if ($current->isLeaf()) {
351             # no children at all, then ...
352 1 50       3 if ($current->isRoot()) {
353             # if it has no children and is the root
354             # then we need to remove the root, and
355             # replace it with the package name of the
356             # tree the user wants to use
357 1         2 $self->{_root} = ref($current);
358 1         3 return TRUE;
359             }
360             else {
361             # otherwise we just want to remove
362             # outselves from the parent
363 0         0 $self->_replaceInParent($current);
364 0         0 return TRUE;
365             }
366             }
367             else {
368             # we know we have at least one child
369             # since we are not a leaf node
370 20 100       33 if (!$current->hasRight()) {
371             # if we dont have the right, then
372             # we know we have a left (otherwise
373             # we would be a leaf)
374             # remove the left then, then
375 4         8 my $left = $current->removeLeft();
376             # remove current from it parent
377             # and replace it with the left
378 4         7 $self->_replaceInParent($current, $left);
379 4         15 return TRUE;
380             }
381             # however, if we have a right side, then ...
382             else {
383             # remove the right side ...
384 16         23 my $right = $current->getRight();
385             # if the right itself has a left then ...
386 16 100       25 if (!$right->hasLeft()) {
387             # remove the right child
388 5         14 my $right = $current->removeRight();
389             # set the right child's left (if we have one)
390 5 50       9 $right->setLeft($current->removeLeft()) if $current->hasLeft();
391             # remove current from it parent
392             # and replace it with the right
393 5         9 $self->_replaceInParent($current, $right);
394 5         16 return TRUE;
395             }
396             else {
397             # go to the leftmost node in the right subtree
398 11         10 my $inorder_successor = $right;
399 11         6 my $current_right;
400              
401 11         13 do {
402 14         12 $current_right = $inorder_successor;
403 14         18 $inorder_successor = $inorder_successor->getLeft();
404             } while ( $inorder_successor->hasLeft() );
405              
406             # now that are here, we can adjust the tree
407 11 100       16 if ($inorder_successor->hasRight()) {
408 3         7 $current_right->setLeft($inorder_successor->getRight());
409             }
410             else {
411 8         14 $inorder_successor->getParent()->removeLeft();
412             }
413 11 100       24 $inorder_successor->setLeft($current->removeLeft()) if $current->hasLeft();
414 11 50       17 $inorder_successor->setRight($current->removeRight()) if $current->hasRight();
415 11         14 $self->_replaceInParent($current, $inorder_successor);
416 11         32 return TRUE;
417             }
418             }
419             }
420             }
421             elsif ($comparison == -1) {
422             # if it is less than, ...
423 14 100       26 ($current->hasLeft()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
424 13         19 $current = $current->getLeft();
425 13         14 next;
426             }
427             elsif ($comparison == 1) {
428             # if it is greater than, ...
429 6 100       11 ($current->hasRight()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
430 5         9 $current = $current->getRight();
431 5         5 next;
432             }
433             }
434             }
435              
436             # delete helper
437              
438             sub _replaceInParent {
439 20     20   20 my ($self, $tree, $replacement) = @_;
440 20 100       31 if ($tree->isRoot()) {
441 9         14 $replacement->makeRoot();
442 9         19 $self->{_root} = $replacement;
443             }
444             else {
445 11         14 my $parent = $tree->getParent();
446 11 100 66     16 if ($parent->hasLeft() && $parent->getLeft() eq $tree) {
    50 33        
447 9         12 $parent->removeLeft();
448 9 50       23 $parent->setLeft($replacement) if $replacement;
449             }
450             elsif ($parent->hasRight() && $parent->getRight() eq $tree) {
451 2         4 $parent->removeRight();
452 2 50       6 $parent->setRight($replacement) if $replacement;
453             }
454             }
455             }
456              
457             1;
458              
459             __END__