File Coverage

blib/lib/Tree/SEMETrie.pm
Criterion Covered Total %
statement 148 208 71.1
branch 51 76 67.1
condition 8 17 47.0
subroutine 14 18 77.7
pod 13 13 100.0
total 234 332 70.4


line stmt bran cond sub pod time code
1             package Tree::SEMETrie;
2              
3 6     6   165740 use 5.006;
  6         25  
  6         224  
4 6     6   32 use strict;
  6         10  
  6         195  
5 6     6   29 use warnings;
  6         16  
  6         140  
6              
7 6     6   39 use List::Util ();
  6         12  
  6         117  
8 6     6   11006 use Tree::SEMETrie::Iterator ();
  6         16  
  6         14581  
9              
10             =head1 NAME
11              
12             Tree::SEMETrie - Single-Edge Multi-Edge Trie
13              
14             =head1 VERSION
15              
16             Version 0.03
17              
18             =cut
19              
20             our $VERSION = '0.04';
21              
22             #Class Constants
23             my $VALUE = 0;
24             my $CHILDS = 1;
25             my $SINGLE_CHILD_KEY = 0;
26             my $SINGLE_CHILD_NODE = 1;
27              
28             #Private Helper Functions
29              
30             #compression algorithm :
31             # if node->value is null and node is only child
32             # node->value = child->value
33             # parent->key += child->key
34             # parent->childs = node->childs
35             my $compress_trie_ref = sub {
36             my ($node, $parent) = @_;
37              
38             #The node must not have a value and have no siblings
39             return if $node->[$VALUE] || ref($parent->[$CHILDS]) ne 'ARRAY';
40              
41             $parent->[$CHILDS][$SINGLE_CHILD_KEY] .= $node->[$CHILDS][$SINGLE_CHILD_KEY];
42             $parent->[$CHILDS][$SINGLE_CHILD_NODE] = $node->[$CHILDS][$SINGLE_CHILD_NODE];
43              
44             return;
45             };
46              
47             my $default_strategy_ref = sub { $_[0] };
48              
49             my $find_match_length_ref = sub {
50             my $max_match_length = List::Util::min(length($_[0]), length($_[1]));
51             my $char_iter = 0;
52             for (; $char_iter < $max_match_length; ++$char_iter) {
53             last if substr($_[0], $char_iter, 1) ne substr($_[1], $char_iter, 1);
54             }
55             return $char_iter;
56             };
57              
58             my $make_new_trie_ref = sub { bless $_[0], ref($_[1]) };
59              
60             my $split_string_at_position_ref = sub {
61             return (
62             substr($_[0], 0, $_[1]),
63             substr($_[0], $_[1], 1),
64             substr($_[0], $_[1] + 1),
65             );
66             };
67              
68             =head1 SYNOPSIS
69              
70             COMING SOON
71              
72             use Tree::SEMETrie;
73              
74             my $trie = Tree::SEMETrie->new();
75             $trie->add('a long word', 23.7);
76             $trie->add('a longer word', 102);
77              
78             for (my $iterator = $self->iterator; ! $iterator->is_done; $iterator->next) {
79             print $iterator->key . ' => ' . $trie->find($iterator->key)->has_children
80             if $trie->find_value($iterator->key) eq $iterator->value;
81             }
82              
83             $trie->remove($_->[0]) for $trie->all;
84              
85             =head1 SUBROUTINES/METHODS
86              
87             =head2 Constructors
88              
89             =head3 new
90              
91             Create a new empty trie.
92              
93             my $trie = Tree::SEMETrie->new;
94              
95             =cut
96              
97             sub new {
98 21     21 1 8490 my $class = shift;
99 21   66     93 $class = ref $class || $class;
100 21         69 return bless [], $class;
101             }
102              
103             =head2 Root Accessors/Mutators
104              
105             =head3 children
106              
107             Get the list of all immediate [edge => subtrie] pairs.
108              
109             my @edge_subtrie_pairs = $trie->children;
110             my ($edge, $subtrie) = @{$edge_subtrie_pairs[0]};
111              
112             =head3 childs
113              
114             Alias for children.
115              
116             =cut
117              
118             sub childs {
119 1     1 1 2 my $self = shift;
120 1         3 my $childs_ref = $self->[$CHILDS];
121 1         2 my $childs_type = ref($childs_ref);
122             return
123 0         0 $childs_type eq 'ARRAY' ? [$childs_ref->[$SINGLE_CHILD_KEY] => $make_new_trie_ref->($childs_ref->[$SINGLE_CHILD_NODE], $self)] :
124 1 50       8 $childs_type eq 'HASH' ? map { [$_ => $make_new_trie_ref->($childs_ref->{$_}, $self)] } keys %$childs_ref :
    50          
125             ();
126             }
127             *children = \&childs;
128              
129             =head3 value
130              
131             Get/Set the value of the root. Return undef if there is no value.
132              
133             my $new_value = $trie->value($new_value);
134              
135             =cut
136              
137             sub value {
138 14     14 1 19 my $self = shift;
139 14 100       36 if (@_) { ${$self->[$VALUE]} = $_[0] }
  4         5  
  4         9  
140 14 100       37 return $self->[$VALUE] ? ${$self->[$VALUE]} : undef;
  13         51  
141             }
142              
143             =head2 Root Verifiers
144              
145             =head3 has_children
146              
147             Return true if the root has any child paths.
148              
149             $trie->has_children;
150              
151             =head3 has_childs
152              
153             Alias for has_children.
154              
155             =cut
156              
157 1     1 1 15 sub has_childs { ref($_[0][$CHILDS]) ne '' }
158              
159             *has_children = \&has_childs;
160              
161             =head3 has_value
162              
163             Return true if the root has an associated value.
164              
165             $trie->has_value;
166              
167             =cut
168              
169 24     24 1 93 sub has_value { defined $_[0][$VALUE] }
170              
171             =head2 Trie Accessors
172              
173             =head3 find
174              
175             Find the root of a subtrie that matches the given key. If no such subtrie exists, return undef.
176              
177             my $subtrie = $trie->find($key);
178              
179             =head3 lookup
180              
181             Alias for find.
182              
183             =cut
184              
185             sub find {
186 41     41 1 200 my $self = shift;
187 41         57 my ($key) = @_;
188              
189 41 100       99 return undef unless defined $key;
190              
191 40         40 my $node = $self;
192              
193 40         69 my ($key_iter, $key_length) = (0, length $key);
194 40         81 while ($key_iter < $key_length) {
195 68         109 my $childs_type = ref($node->[$CHILDS]);
196              
197             #Key does not exist since we're at the end of the trie
198 68 100       148 if (! $childs_type) { $node = undef; last }
  6 100       9  
  6         8  
199              
200             #Check within the compressed trie node
201             elsif ($childs_type eq 'ARRAY') {
202             #Determine where the keys match
203 27         37 my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY];
204 27         36 my $old_key_length = length $old_key;
205 27         71 my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key);
206              
207             #The new key contains all of the old key
208 27 100       66 if($match_length == $old_key_length) {
    50          
209             #Move to the end of the compressed node
210 26         38 $node = $node->[$CHILDS][$SINGLE_CHILD_NODE];
211             #Move to the next part of the key
212 26         67 $key_iter += $match_length;
213              
214             #The old key contains all of the new key
215             } elsif($match_length == $key_length - $key_iter) {
216             #Create a new trie containing the unmatched suffix of the matched key and its sub-trie
217 1         2 my $new_node = [];
218 1         2 $new_node->[$CHILDS][$SINGLE_CHILD_KEY] = substr($old_key, $match_length);
219 1         3 $new_node->[$CHILDS][$SINGLE_CHILD_NODE] = $node->[$CHILDS][$SINGLE_CHILD_NODE];
220 1         1 $node = $new_node;
221 1         2 last;
222              
223             #There was a mismatch in the comparison so the key doesn't exist
224 0         0 } else { $node = undef; last }
  0         0  
225              
226             #Keep expanding down the trie
227             } else {
228 35         60 $node = $node->[$CHILDS]{substr($key, $key_iter, 1)};
229 35         67 ++$key_iter;
230             }
231             }
232              
233 40 100       124 return $node ? $make_new_trie_ref->($node, $self) : undef;
234             }
235             *lookup = \&find;
236              
237             =head3 find_value
238              
239             Find the value associated with the given key. If no such key exists, return undef.
240              
241             my $value = $trie->find_value($key);
242              
243             =head3 lookup_value
244              
245             Alias for find_value.
246              
247             =cut
248              
249             sub find_value {
250 3     3 1 10 my $self = shift;
251              
252 3         543 my $entry = $self->find(@_);
253 3 50       10 return $entry ? $entry->value : undef;
254             }
255             *lookup_value = \&find_value;
256              
257             =head2 Trie Mutators
258              
259             =head3 add
260              
261             Insert a key into the trie. Return a reference to the key's value. In the case
262             of a pre-existing key, the strategy function determines which value is stored.
263             The default strategy function chooses the original value.
264              
265             $trie->add('some path');
266             $trie->add('some path', 'optional value');
267             $trie->add('some path', 'new value to be ignored', sub { $_[0] });
268             $trie->add('some path', 'new value to be inserted', sub { $_[1] });
269              
270             A custom strategy must conform to the following interface:
271              
272             sub new_strategy {
273             my ($current_value, $new_value) = @_;
274             return $desired_value;
275             }
276              
277             =head3 insert
278              
279             Alias for add.
280              
281             =cut
282              
283             sub add {
284 81     81 1 209 my $self = shift;
285 81         107 my ($key, $value, $strategy_ref) = @_;
286              
287             #No path should ever exist for undef
288 81 100       155 return undef unless defined $key;
289              
290 80   66     238 $strategy_ref ||= $default_strategy_ref;
291              
292 80         75 my $node = $self;
293              
294 80         110 my ($key_iter, $key_length) = (0, length $key);
295 80         137 while ($key_iter < $key_length) {
296 178         242 my $childs_type = ref($node->[$CHILDS]);
297              
298             #There are no branches so we've found a new key
299 178 100       307 if (! $childs_type) {
    100          
300             #Create a new branch for the suffix and move down the trie
301 50         78 my $single_child = $node->[$CHILDS] = [];
302              
303 50         96 $single_child->[$SINGLE_CHILD_KEY] = substr($key, $key_iter);
304 50         70 $node = $single_child->[$SINGLE_CHILD_NODE] = [];
305 50         67 last;
306              
307             #There is exactly 1 current branch
308             } elsif ($childs_type eq 'ARRAY') {
309              
310             #Determine where the keys match
311 50         60 my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY];
312 50         53 my $old_key_length = length $old_key;
313 50         116 my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key);
314              
315             #The new key contains all of the old key
316 50 100       126 if($match_length == $old_key_length) {
    100          
317 5         8 $node = $node->[$CHILDS][$SINGLE_CHILD_NODE];
318 5         10 $key_iter += $match_length;
319              
320             #The old key contains all of the new key
321             } elsif($match_length == $key_length - $key_iter) {
322              
323             #Fetch and save the current child branch so that we can split it
324 8         9 my $old_single_child = $node->[$CHILDS];
325             #The unmatched suffix still points to the same trie
326 8         13 $old_single_child->[$SINGLE_CHILD_KEY] = substr($old_key, $match_length);
327              
328             #Create a new branch point
329 8         9 my $new_single_child = $node->[$CHILDS] = [];
330             #Insert the matched prefix
331 8         13 $new_single_child->[$SINGLE_CHILD_KEY] = substr($key, $key_iter);
332             #Move down the trie to the newly inserted branch point
333 8         12 $node = $new_single_child->[$SINGLE_CHILD_NODE] = [];
334             #Make the unmatched suffix a subtrie of the matched prefix
335 8         10 $node->[$CHILDS] = $old_single_child;
336 8         8 last;
337              
338             } else {
339              
340 37         62 my ($key_match, $old_key_diff, $old_key_tail) = $split_string_at_position_ref->($old_key, $match_length);
341 37         54 my $new_key_diff = substr($key, $key_iter + $match_length, 1);
342              
343             #Fetch and save the current child branch so that we can split it later
344 37         42 my $old_single_child = $node->[$CHILDS];
345              
346             #The match may occur in the middle
347 37 100       70 if ($key_match ne '') {
348             #Create a new branch to represent the match
349 10         13 my $match_childs_ref = $node->[$CHILDS] = [];
350 10         16 $match_childs_ref->[$SINGLE_CHILD_KEY] = $key_match;
351             #Move down the branch to the end fo the match
352 10         20 $node = $match_childs_ref->[$SINGLE_CHILD_NODE] = [];
353             }
354              
355             #Create a new branch to represent the divergence
356 37         51 my $branch_childs_ref = $node->[$CHILDS] = {};
357              
358             #The match may occur at the end of the old key, so the old key's child becomes the divergence's child
359 37 100       83 if ($old_key_tail eq '') {
360 19         33 $branch_childs_ref->{$old_key_diff} = $old_single_child->[$SINGLE_CHILD_NODE];
361              
362             #Otherwise make the old branch a child of the old branch's divergence point
363             } else {
364             #Replace the old key with the suffix after the difference
365 18         20 $old_single_child->[$SINGLE_CHILD_KEY] = $old_key_tail;
366 18         38 $branch_childs_ref->{$old_key_diff}[$CHILDS] = $old_single_child;
367             }
368              
369             #Make the new branch a child of the new branch's divergence point
370 37         66 $node = $branch_childs_ref->{$new_key_diff} = [];
371              
372             #Move past the branch point
373 37         112 $key_iter += $match_length + 1;
374             }
375              
376             #Otherwise this node has multiple branches
377             } else {
378             #Retrieve the next node in the trie, creating a new one when necessary
379 78   50     168 $node = $node->[$CHILDS]{substr($key, $key_iter, 1)} ||= [];
380 78         133 ++$key_iter;
381             }
382             }
383              
384             #Assign the value based on the strategy
385 80         118 ${$node->[$VALUE]} = $node->[$VALUE]
  2         8  
386 80 100       153 ? $strategy_ref->(${$node->[$VALUE]}, $value)
387             : $value;
388              
389 80         203 return $node->[$VALUE];
390             }
391             *insert = \&add;
392              
393             =head3 erase
394              
395             Remove a key from the trie. Return the value associated with the removed key.
396            
397             my $optional_value = $trie->erase('some path');
398              
399             =head3 remove
400              
401             Alias for erase.
402              
403             =cut
404              
405             sub erase {
406 14     14 1 43 my $self = shift;
407 14         19 my ($key) = @_;
408              
409             #No path should ever exist for undef
410 14 100       39 return undef unless defined $key;
411              
412 13         14 my $grand_parent_node = undef;
413 13         13 my $parent_node = undef;
414 13         13 my $node = $self;
415              
416 13         21 my ($key_iter, $key_length) = (0, length $key);
417 13         618 while ($key_iter < $key_length) {
418 26         50 my $childs_type = ref($node->[$CHILDS]);
419              
420             #Key does not exist since we're at the end of the trie
421 26 100       60 if (! $childs_type) { $node = undef; last }
  1 100       2  
  1         1  
422              
423             #Check within the compressed trie node
424             elsif ($childs_type eq 'ARRAY') {
425              
426             #Determine where the keys match
427 10         15 my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY];
428 10         11 my $old_key_length = length $old_key;
429 10         26 my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key);
430              
431             #The deleted key contains all of the old key
432 10 100       25 if($match_length == $old_key_length) {
433              
434             #Save the parent
435 9         13 $grand_parent_node = $parent_node;
436 9         10 $parent_node = $node;
437             #Move to the end of the compressed node
438 9         14 $node = $node->[$CHILDS][$SINGLE_CHILD_NODE];
439             #Move to the next part of the key
440 9         24 $key_iter += $match_length;
441              
442             #There was a mismatch in the comparison so the deleted key doesn't exist
443 1         1 } else { $node = undef; last }
  1         2  
444              
445             #Keep expanding down the trie
446             } else {
447              
448             #Save the parent
449 15         49 $grand_parent_node = $parent_node;
450 15         13 $parent_node = $node;
451             #Move to the next node
452 15         20 $node = $node->[$CHILDS]{substr($key, $key_iter, 1)};
453 15         27 ++$key_iter;
454             }
455             }
456              
457 13         12 my $deleted_value;
458 13 100 100     57 if ($node && $node->[$VALUE]) {
459 10         13 $deleted_value = ${delete $node->[$VALUE]};
  10         17  
460              
461 10         16 my $childs_type = ref($node->[$CHILDS]);
462              
463             #The node has no children
464 10 100       25 if (! $childs_type) {
    100          
465 8         12 my $parent_childs_ref = $parent_node->[$CHILDS];
466 8         11 my $parent_childs_type = ref($parent_childs_ref);
467              
468             #The node may have siblings
469 8 100       20 if ($parent_childs_type eq 'HASH') {
470             #Final character of the key must be the branch point
471 1         3 delete $parent_childs_ref->{substr($key, -1)};
472              
473             #The sibling may now be an only child
474 1 50       4 if (keys(%$parent_childs_ref) == 1) {
475             #Fix the representation
476 1         2 $parent_node->[$CHILDS] = [];
477 1         2 @{$parent_node->[$CHILDS]}[$SINGLE_CHILD_KEY, $SINGLE_CHILD_NODE] = each %$parent_childs_ref;
  1         4  
478              
479             #Try to repair the divergence, which splits a key into 3
480 1         3 $compress_trie_ref->($parent_node->[$CHILDS][$SINGLE_CHILD_NODE], $parent_node);
481 1         1 $compress_trie_ref->($parent_node, $grand_parent_node);
482             }
483              
484             #The node has no siblings
485             } else {
486 7         15 delete $parent_node->[$CHILDS];
487             }
488              
489             #The node has 1 child
490             } elsif ($childs_type eq 'ARRAY') {
491 1         3 $compress_trie_ref->($node, $parent_node);
492             }
493             }
494              
495 13         76 return $deleted_value;
496             }
497             *remove = \&erase;
498              
499             =head3 merge
500              
501             IN DEVELOPMENT
502              
503             =cut
504              
505             sub merge {
506 0     0 1   my $self = shift;
507 0           my ($key, $trie, $strategy_ref) = @_;
508              
509             #No path should ever exist for undef
510 0 0         return undef unless defined $key;
511              
512 0   0       $strategy_ref ||= $default_strategy_ref;
513              
514 0           my $preexisting_value = $self->add($key);
515 0           my $merge_point = $self->find($key);
516              
517 0           my $childs_type = ref($merge_point->[$CHILDS]);
518 0 0         if (! $childs_type) {
519 0           $merge_point->[$CHILDS] = $trie->[$CHILDS];
520              
521 0 0         $merge_point->[$VALUE] = $preexisting_value
522             ? $trie->[$VALUE]
523             : $strategy_ref->($merge_point->[$VALUE], $trie->[$VALUE]);
524 0 0         $compress_trie_ref->($merge_point->[$CHILDS][$SINGLE_CHILD_NODE], $merge_point)
525             if ref($merge_point->[$CHILDS]) eq 'ARRAY';
526              
527             #We need to consider how to merge
528             } else {
529             #both single
530             #
531             #both multi
532             #
533             #
534              
535             #m-om-my - asdga
536             # ma - sdaa
537             #=
538             #m-om-m-y-asdga
539             # a-sdaa
540             #
541             #m-om-may
542             #m-om m-a
543             # d-ad
544             #=
545             #m-om-m-a-y
546             # d-ad
547             #
548             #m-om-m-y
549             # m-as
550             #m-om m-a
551             # d-ad
552             #=
553             #m-om-m-y
554             # a-s
555             # d-ad
556              
557             }
558              
559             }
560              
561             =head3 prune
562              
563             IN DEVELOPMENT
564              
565             Remove the entire subtrie of the given key. Return the removed subtrie.
566              
567             =cut
568              
569             sub prune {
570 0     0 1   my $self = shift;
571 0           my ($key) = @_;
572              
573             #No path should ever exist for undef
574 0 0         return undef unless defined $key;
575              
576 0           my $grand_parent_node = undef;
577 0           my $parent_node = undef;
578 0           my $node = $self;
579              
580 0           my ($key_iter, $key_length) = (0, length $key);
581 0           while ($key_iter < $key_length) {
582 0           my $childs_type = ref($node->[$CHILDS]);
583              
584             #Key does not exist since we're at the end of the trie
585 0 0         if (! $childs_type) { $node = undef; last }
  0 0          
  0            
586              
587             #Check within the compressed trie node
588             elsif ($childs_type eq 'ARRAY') {
589              
590             #Determine where the keys match
591 0           my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY];
592 0           my $old_key_length = length $old_key;
593 0           my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key);
594              
595             #The pruning key contains all of the old key
596 0 0         if($match_length == $old_key_length) {
    0          
597              
598             #Save the parent
599 0           $grand_parent_node = $parent_node;
600 0           $parent_node = $node;
601             #Move to the end of the compressed node
602 0           $node = $node->[$CHILDS][$SINGLE_CHILD_NODE];
603             #Move to the next part of the key
604 0           $key_iter += $match_length;
605              
606             #The old key contains all of the pruning key
607             } elsif($match_length == $key_length - $key_iter) {
608              
609             #Create a new trie containing the unmatched suffix of the matched key and its sub-trie
610 0           my $new_node = [undef, [substr($old_key, $match_length) => $node->[$CHILDS][$SINGLE_CHILD_NODE]]];
611              
612             #Save the parent
613 0           $grand_parent_node = $parent_node;
614 0           $parent_node = $node;
615             #Kill the dangling edge
616 0           delete $node->[$CHILDS];
617 0           $node = $new_node;
618              
619 0           last;
620 0           } else { $node = undef; last }
  0            
621              
622             #Keep expanding down the trie
623             } else {
624              
625             #Save the parent
626 0           $grand_parent_node = $parent_node;
627 0           $parent_node = $node;
628             #Move to the next node
629 0           $node = $node->[$CHILDS]{substr($key, $key_iter, 1)};
630 0           ++$key_iter;
631             }
632             }
633              
634 0           my $pruned_trie;
635 0 0 0       if ($node && $node->[$CHILDS]) {
636 0           my $new_trie = [];
637 0           $new_trie->[$CHILDS] = ${delete $node->[$CHILDS]};
  0            
638 0           $pruned_trie = $make_new_trie_ref->($new_trie);
639 0           $compress_trie_ref->($parent_node, $grand_parent_node);
640             }
641              
642 0           return $pruned_trie;
643             }
644              
645             =head2 Trie Traversal
646              
647             =head3 all
648              
649             Get a list of every key and its associated value as [key => value] pairs. Order
650             is not guaranteed.
651              
652             my @key_value_pairs = $trie->all;
653              
654             =cut
655              
656             sub all {
657 0     0 1   my $self = shift;
658              
659 0           my @results;
660 0           for (my $iterator = $self->iterator; ! $iterator->is_done; $iterator->next) {
661 0           push @results, [$iterator->key, $iterator->value];
662             }
663              
664 0           return @results;
665             }
666              
667             =head3 iterator
668              
669             Get a Tree::SEMETrie::Iterator for efficient trie traversal. Order is not
670             guaranteed.
671              
672             my $iterator = $trie->iterator;
673              
674             =cut
675              
676 0     0 1   sub iterator { Tree::SEMETrie::Iterator->new($_[0]) }
677              
678             =head1 AUTHOR
679              
680             Aaron Cohen, C<< >>
681              
682             =head1 BUGS
683              
684             Please report any bugs or feature requests to C, or through
685             the web interface at L. I will be notified, and then you'll
686             automatically be notified of progress on your bug as I make changes.
687              
688             =head1 TODO
689              
690             =over 4
691              
692             =item * Finish SYNOPSIS section.
693              
694             =item * Finish merge function.
695              
696             =item * Finish prune function.
697              
698             =item * Add benchmarking scripts.
699              
700             =item * Add SEE ALSO section.
701              
702             =back
703              
704             =head1 SUPPORT
705              
706             You can find documentation for this module with the perldoc command.
707              
708             perldoc Tree::SEMETrie
709              
710              
711             You can also look for information at:
712              
713             =over 4
714              
715             =item * Official GitHub Repository
716              
717             L
718              
719             =item * RT: CPAN's request tracker (report bugs here)
720              
721             L
722              
723             =item * AnnoCPAN: Annotated CPAN documentation
724              
725             L
726              
727             =item * CPAN Ratings
728              
729             L
730              
731             =item * Search CPAN
732              
733             L
734              
735             =back
736              
737             =head1 LICENSE AND COPYRIGHT
738              
739             Copyright 2011 Aaron Cohen.
740              
741             This program is free software; you can redistribute it and/or modify it
742             under the terms of either: the GNU General Public License as published
743             by the Free Software Foundation; or the Artistic License.
744              
745             See http://dev.perl.org/licenses/ for more information.
746              
747             =cut
748              
749             1; # End of Tree::SEMETrie