File Coverage

blib/lib/Tree/Binary/Search.pm
Criterion Covered Total %
statement 195 197 98.9
branch 101 112 90.1
condition 22 29 75.8
subroutine 35 35 100.0
pod 18 18 100.0
total 371 391 94.8


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