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   48191 use strict;
  5         7  
  5         110  
5 5     5   17 use warnings;
  5         4  
  5         100  
6              
7 5     5   15 use Scalar::Util qw(blessed);
  5         4  
  5         324  
8              
9 5     5   1630 use Tree::Binary::Search::Node;
  5         8  
  5         112  
10              
11 5     5   24 use constant TRUE => 1;
  5         4  
  5         288  
12 5     5   18 use constant FALSE => 0;
  5         5  
  5         164  
13              
14 5     5   17 use constant EQUAL_TO => 0;
  5         5  
  5         167  
15 5     5   15 use constant LESS_THAN => -1;
  5         5  
  5         156  
16 5     5   15 use constant GREATER_THAN => 1;
  5         4  
  5         6767  
17              
18             our $VERSION = '1.08';
19              
20             ## ----------------------------------------------------------------------------
21             ## Tree::Binary::Search
22             ## ----------------------------------------------------------------------------
23              
24             ### constructor
25              
26             sub new {
27 31     31 1 1058 my ($_class, $root) = @_;
28 31   33     130 my $class = ref($_class) || $_class;
29 31         54 my $binary_search_tree = {};
30 31         47 bless($binary_search_tree, $class);
31 31         65 $binary_search_tree->_init($root);
32 31         51 return $binary_search_tree;
33             }
34              
35             ### ---------------------------------------------------------------------------
36             ### methods
37             ### ---------------------------------------------------------------------------
38              
39             ## ----------------------------------------------------------------------------
40             ## private methods
41              
42             sub _init {
43 31     31   30 my ($self, $root) = @_;
44 31   100     164 $self->{_root} = $root || "Tree::Binary::Search::Node";
45 31         49 $self->{_comparison_func} = undef;
46             }
47              
48             sub _compare {
49 17764     17764   12025 my ($self, $current_key, $btree_key) = @_;
50 17764         15705 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 17764 100 100     65842 ($result =~ /\d/ && ($result >= LESS_THAN && $result <= GREATER_THAN))
      66        
55             || die "Bad Value : got a bad value from the comparison function ($result)";
56 17761         17029 return $result;
57             }
58              
59             ## ----------------------------------------------------------------------------
60             ## mutators
61              
62             sub useStringComparison {
63 8     8 1 777 my ($self) = @_;
64 8     91   26 $self->{_comparison_func} = sub { $_[0] cmp $_[1] };
  91         102  
65             }
66              
67             sub useNumericComparison {
68 23     23 1 4227 my ($self) = @_;
69 23     17670   90 $self->{_comparison_func} = sub { $_[0] <=> $_[1] };
  17670         13307  
70             }
71              
72             sub setComparisonFunction {
73 6     6 1 1237 my ($self, $func) = @_;
74 6 100       34 (ref($func) eq "CODE")
75             || die "Incorrect Object Type : comparison function is not a function";
76 3         6 $self->{_comparison_func} = $func;
77             }
78              
79             ## ----------------------------------------------------------------------------
80             ## accessors
81              
82             sub getTree {
83 31     31 1 122 my ($self) = @_;
84 31         96 return $self->{_root};
85             }
86              
87             ## ----------------------------------------------------------------------------
88             ## informational
89              
90             sub isEmpty {
91 3046     3046 1 1932 my ($self) = @_;
92 3046 100       5440 return (ref($self->{_root})) ? FALSE : TRUE;
93             }
94              
95             ## ----------------------------------------------------------------------------
96             ## methods for underlying tree
97              
98             sub accept {
99 18     18 1 42 my ($self, $visitor) = @_;
100 18         36 $self->{_root}->accept($visitor);
101             }
102              
103             sub size {
104 11     11 1 19 my ($self) = @_;
105 11         25 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   1498 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       130 ref($self->{_root}) && $self->{_root}->DESTROY();
119             }
120              
121             ## ----------------------------------------------------------------------------
122             ## search methods
123              
124             sub insert {
125 1284     1284 1 3669 my ($self, $key, $value) = @_;
126 1284         841 my $btree;
127 1284 100 100     3138 if (defined $key && defined $value) {
    100 100        
      33        
128 1279         2018 $btree = $self->{_root}->new($key, $value);
129             }
130             elsif (!defined $value &&
131             (blessed($key) && $key->isa("Tree::Binary::Search::Node"))) {
132 1         2 $btree = $key;
133             }
134             else {
135 4         28 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 1280 100       1389 if ($self->isEmpty()) {
140 32 100       72 (defined($self->{_comparison_func}))
141             || die "Illegal Operation : No comparison function set";
142 31         71 $self->{_root} = $btree;
143             }
144             else {
145 1248         928 my $current = $self->{_root};
146 1248         853 while (1) {
147 7327         9359 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 7324 100       9112 die "Illegal Operation : you cannot insert a duplicate key" if $comparison == EQUAL_TO;
152             # otherwise ...
153 7322 100       8957 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 3831 100       5168 if ($current->hasLeft()) {
160 3203         3891 $current = $current->getLeft();
161 3203         2778 next;
162             }
163             else {
164 628         843 $current->setLeft($btree);
165 628         997 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 3491 100       4688 if ($current->hasRight()) {
175 2876         3462 $current = $current->getRight();
176             }
177             else {
178 615         833 $current->setRight($btree);
179 615         939 last;
180             }
181             }
182             }
183             }
184             }
185              
186             sub update {
187 8     8 1 1146 my ($self, $key, $value) = @_;
188 8 100       14 (!$self->isEmpty())
189             || die "Illegal Operation : Cannot update without first inserting";
190 7 100 100     40 (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         6 my $current = $self->{_root};
194 4         4 while (1) {
195 8         14 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       25 if ($comparison == EQUAL_TO) {
    100          
    50          
199 2         8 $current->setNodeValue($value);
200 2         4 last;
201             }
202             elsif ($comparison == LESS_THAN) {
203             # if it is less than, then we need
204             # to ...
205 2 100       9 ($current->hasLeft()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
206 1         2 $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       12 ($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 761 my ($self, $key) = @_;
221 18 100       29 (!$self->isEmpty())
222             || die "Illegal Operation : Cannot lookup anything without first inserting";
223 17 100       48 (defined $key)
224             || die "Insufficient Arguments : Must supply a key to find";
225              
226 16         17 my $current = $self->{_root};
227 16         13 while (1) {
228 52         74 my $comparison = $self->_compare($current->getNodeKey(), $key);
229 52 100       95 if ($comparison == EQUAL_TO) {
    100          
    50          
230             # if it is equal to, then we are
231             # have found it, so return
232 13         11 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       30 ($current->hasLeft()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
241 20         25 $current = $current->getLeft();
242 20         18 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       31 ($current->hasRight()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
251 16         22 $current = $current->getRight();
252 16         15 next;
253             }
254             }
255 13         32 return $current->getNodeValue();
256             }
257              
258             sub exists : method {
259 1709     1709 1 5029 my ($self, $key) = @_;
260 1709 100       2122 (defined $key)
261             || die "Insufficient Arguments : Must supply a key to find";
262 1708 100       1723 return FALSE if $self->isEmpty();
263              
264 1687         1275 my $current = $self->{_root};
265 1687         1059 while (1) {
266 10336         13288 my $comparison = $self->_compare($current->getNodeKey(), $key);
267 10336 100       15760 if ($comparison == 0) {
    100          
    50          
268             # if it is equal to, then we are
269             # have found it, so return TRUE
270 507         660 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 5164 100       7076 ($current->hasLeft()) || return FALSE;
279 4567         5964 $current = $current->getLeft();
280 4567         3383 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 4665 100       5967 ($current->hasRight()) || return FALSE;
289 4082         5505 $current = $current->getRight();
290 4082         2936 next;
291             }
292             }
293             }
294              
295             sub _max_node {
296 3     3   3 my ($self) = @_;
297 3 100       8 (!$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         5 return $current;
302             }
303              
304             sub _min_node {
305 3     3   3 my ($self) = @_;
306 3 100       6 (!$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         5 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         3 return $self->_min_node()->getNodeKey();
321             }
322              
323             sub max {
324 2     2 1 176 my ($self) = @_;
325 2         8 return $self->_max_node()->getNodeValue();
326             }
327              
328             sub min {
329 2     2 1 176 my ($self) = @_;
330 2         5 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 1948 my ($self, $key) = @_;
340 25 100       36 (!$self->isEmpty())
341             || die "Illegal Operation : Cannot delete without first inserting";
342 24 100       45 (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         15 while (1) {
347 41         73 my $comparison = $self->_compare($current->getNodeKey(), $key);
348 41 100       68 if ($comparison == 0) {
    100          
    50          
349             # if it is equal to,
350 21 100       41 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       34 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         6 $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         24 my $right = $current->getRight();
385             # if the right itself has a left then ...
386 16 100       26 if (!$right->hasLeft()) {
387             # remove the right child
388 5         13 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         8 $self->_replaceInParent($current, $right);
394 5         18 return TRUE;
395             }
396             else {
397             # go to the leftmost node in the right subtree
398 11         6 my $inorder_successor = $right;
399 11         11 my $current_right;
400              
401 11         10 do {
402 14         7 $current_right = $inorder_successor;
403 14         22 $inorder_successor = $inorder_successor->getLeft();
404             } while ( $inorder_successor->hasLeft() );
405              
406             # now that are here, we can adjust the tree
407 11 100       17 if ($inorder_successor->hasRight()) {
408 3         6 $current_right->setLeft($inorder_successor->getRight());
409             }
410             else {
411 8         13 $inorder_successor->getParent()->removeLeft();
412             }
413 11 100       26 $inorder_successor->setLeft($current->removeLeft()) if $current->hasLeft();
414 11 50       18 $inorder_successor->setRight($current->removeRight()) if $current->hasRight();
415 11         14 $self->_replaceInParent($current, $inorder_successor);
416 11         33 return TRUE;
417             }
418             }
419             }
420             }
421             elsif ($comparison == -1) {
422             # if it is less than, ...
423 14 100       25 ($current->hasLeft()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
424 13         21 $current = $current->getLeft();
425 13         13 next;
426             }
427             elsif ($comparison == 1) {
428             # if it is greater than, ...
429 6 100       12 ($current->hasRight()) || die "Key Does Not Exist : the key ($key) does not exist in this tree";
430 5         7 $current = $current->getRight();
431 5         5 next;
432             }
433             }
434             }
435              
436             # delete helper
437              
438             sub _replaceInParent {
439 20     20   21 my ($self, $tree, $replacement) = @_;
440 20 100       33 if ($tree->isRoot()) {
441 9         16 $replacement->makeRoot();
442 9         18 $self->{_root} = $replacement;
443             }
444             else {
445 11         16 my $parent = $tree->getParent();
446 11 100 66     18 if ($parent->hasLeft() && $parent->getLeft() eq $tree) {
    50 33        
447 9         11 $parent->removeLeft();
448 9 50       22 $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__