File Coverage

blib/lib/Tree/Easy.pm
Criterion Covered Total %
statement 89 169 52.6
branch 30 88 34.0
condition 6 24 25.0
subroutine 19 31 61.2
pod 18 18 100.0
total 162 330 49.0


line stmt bran cond sub pod time code
1             package Tree::Easy;
2              
3 2     2   71216 use warnings;
  2         5  
  2         64  
4 2     2   10 use strict;
  2         4  
  2         74  
5              
6 2     2   11 use Scalar::Util qw(refaddr);
  2         9  
  2         268  
7 2     2   11 use List::Util qw(max);
  2         3  
  2         230  
8 2     2   11 use Carp qw(croak carp);
  2         4  
  2         5998  
9              
10             our $VERSION = '0.01';
11              
12             my $_DUMPER_IS_LOADED = 0; # for dump method
13             my %_NODE_DATA;
14              
15             sub new
16             {
17 6     6 1 21 my $class = shift;
18 6         16 my $self = bless [ ], $class;
19              
20 6         9 my $data = shift;
21 6 50       16 if ( defined $data ) {
22 6         23 $_NODE_DATA{ refaddr($self) } = $data;
23             }
24 6         20 return $self;
25             }
26              
27             sub clone
28             {
29 4     4 1 6 my $self = shift;
30              
31             # Make a shallow copy of any data references...
32 4         11 my $data = $self->data;
33 4 50       19 my $new_data = ( ! ref $data ? $data :
    50          
    100          
34             ref $data eq 'ARRAY' ? [ @$data ] :
35             ref $data eq 'HASH' ? { %$data } :
36             die sprintf qq{Internal error, don't know how to clone data reference\n}.
37             q{of type "%s"}, ref $data );
38              
39 4         10 my $new_root = __PACKAGE__->new($new_data);
40              
41             # Recursively clone any descendants...
42 4         8 for my $child ( @$self ) {
43 2         7 $new_root->push_node($child->clone);
44             }
45              
46 4         24 return $new_root;
47             }
48              
49             sub DESTROY {
50 6     6   228 my $self = shift;
51 6         9 my $key = refaddr($self);
52 6         121 delete $_NODE_DATA{$key};
53             }
54              
55             sub data
56             {
57 18     18 1 45 my ($self, $data) = @_;
58              
59 18         37 my $key = refaddr($self);
60 18 100       30 if ( defined $data ) {
61 1         3 $_NODE_DATA{$key} = $data;
62             }
63              
64 18         89 return $_NODE_DATA{$key};
65             }
66              
67             sub insert_node
68             {
69 3 50   3 1 10 croak 'Invalid use of invoke_child method, not enough arguments'
70             if ( @_ < 2 );
71 3         5 my ($self, $child, $where) = @_;
72              
73 3 50       20 croak 'Child parameter must be a Tree::Easy object'
74             unless ( $child->isa('Tree::Easy') );
75              
76 3 50 33     10 croak '$where parameter must be numeric'
77             if ( defined $where && $where !~ /^-?\d$/ );
78              
79 3 50 33     11 if ( ! defined $where || $where > $#$self ) {
80 3 50 33     10 carp '$where parameter is past end of children'
81             if ( defined $where && $where > $#$self );
82 3         5 push @{$self}, $child;
  3         7  
83 3         13 return $child;
84             }
85              
86 0 0       0 if ( $where < 0 ) {
87 0         0 carp '$where parameter should not negative!';
88 0         0 $where = 0;
89             }
90              
91 0         0 splice @{$self}, $where, 0, $child;
  0         0  
92 0         0 return $child;
93             }
94              
95             sub push_node
96             {
97 3     3 1 10 return $_[0]->insert_node( $_[1] );
98             }
99              
100             sub unshift_node
101             {
102 0     0 1 0 return $_[0]->insert_node( $_[1], 0 );
103             }
104              
105             sub push_new
106             {
107 1     1 1 2 my $self = shift;
108 1         5 return $self->push_node( __PACKAGE__->new(@_) );
109             }
110              
111             sub unshift_new
112             {
113 0     0 1 0 my $self = shift;
114 0         0 return $self->unshift_node( __PACKAGE__->new(@_) );
115             }
116              
117             sub npush
118             {
119 1     1 1 2 my $self = shift;
120 1         3 my @new_nodes;
121              
122 1         3 for my $arg ( @_ ) {
123 1 50       2 push @new_nodes, ( eval { $arg->isa(__PACKAGE__) }
  1         15  
124             ? $self->push_node($arg)
125             : $self->push_new($arg) );
126             }
127              
128 1         14 return @new_nodes;
129             }
130              
131             sub nunshift
132             {
133 0     0 1 0 my $self = shift;
134 0         0 my @new_nodes;
135              
136 0         0 for my $arg ( @_ ) {
137 0 0       0 push @new_nodes, ( eval { $arg->isa(__PACKAGE__) }
  0         0  
138             ? $self->unshift_node($arg)
139             : $self->unshift_new($arg) );
140             }
141              
142 0         0 return @new_nodes;
143             }
144              
145             sub remove_node
146             {
147 0 0   0 1 0 croak 'Invalid use of remove_node method, not enough parameters'
148             if ( @_ < 2 );
149 0         0 my ($self, $where) = @_;
150              
151 0 0       0 croak qq{Invalid \$where parameter ($where)...\nmust be a numeric index}
152             unless ( $where =~ /\A \d+ \z/xms );
153              
154 0 0 0     0 croak qq{Invalid \$where parameter ($where)...\noutside of range}
      0        
155             if ( ( $where < 0 && $where*-1 > $#$self ) || $where > $#$self );
156              
157 0         0 return splice @$self, $where, 1;
158             }
159              
160             sub pop_node
161             {
162 0     0 1 0 return $_[0]->remove_node( -1 );
163             }
164              
165             sub shift_node
166             {
167 0     0 1 0 return $_[0]->remove_node( 0 );
168             }
169              
170             sub traverse
171             {
172 1 50   1 1 5 croak 'Invalid use of traverse method, not enough arguments'
173             if ( @_ < 2 );
174 1         2 my ($self, $code_ref, $how) = @_;
175              
176 1 50       4 $how = 0 unless ( defined $how );
177              
178 1 50 33     544 croak "\$how parameter is invalid ($how)
      33        
179             must be -1, 0, or 1 for prefix, infix (default)), or postfix"
180             unless ( $how eq '-1' || $how eq '0' || $how eq '1' );
181              
182 1         1 my $traverser_ref;
183             $traverser_ref =
184             ( $how == 0 ? sub { # infix
185 2     2   2 my $node = shift;
186              
187 2 100       6 if ( @$node == 0 ) {
188 1         3 $code_ref->($node);
189 1         2 return;
190             }
191              
192 1 50       3 if ( @$node == 1 ) {
193             # Treat one node like it's on the left...
194 1         4 $traverser_ref->($node->[0]);
195 1         2 $code_ref->($node);
196 1         2 return;
197             }
198              
199 0         0 my $mid = int( $#$node / 2 );
200 0         0 my $odd_kids = @$node % 2;
201              
202 0 0       0 if ( $odd_kids ) { --$mid; }
  0         0  
203              
204 0         0 for my $i ( 0 .. $mid ) {
205 0         0 $traverser_ref->($node->[$i]);
206             }
207              
208             # if ( $odd_kids ) {
209             # $traverser_ref->($node->[++$mid]);
210             # }
211              
212 0         0 $code_ref->($node);
213              
214 0         0 for my $i ( ++$mid .. $#$node ) {
215 0         0 $traverser_ref->($node->[$i]);
216             }
217             } :
218             $how == -1 ? sub { # preorder
219 0     0   0 my $node = shift;
220              
221 0         0 $code_ref->($node);
222              
223 0         0 for my $i ( 0 .. $#$node ) {
224 0         0 $traverser_ref->($node->[$i]);
225             }
226             } :
227             $how == 1 ? sub { # postorder
228 0     0   0 my $node = shift;
229              
230 0         0 for my $i ( 0 .. $#$node ) {
231 0         0 $traverser_ref->($node->[$i]);
232             }
233              
234 0         0 $code_ref->($node);
235             } :
236 1 0       8 die 'Internal error'
    0          
    50          
237             );
238              
239 1         3 $traverser_ref->($self);
240 1         2 return;
241             }
242              
243             sub search
244             {
245 4 50   4 1 10 croak 'Invalid use of search method, not enough arguments'
246             if ( @_ < 2 );
247 4         9 my ($self, $match, $how) = @_;
248              
249 4 50       10 $how = 'dfs' unless ( defined $how );
250 4         7 $how = lc $how;
251              
252 4 50 33     10 croak qq{\$how parameter is invalid ($how)
253             must be 'dfs' or 'bfs' for depth-first or breadth-first search}
254             if ( $how ne 'dfs' && $how ne 'bfs' );
255              
256             my $matcher_ref =
257             ( ref $match eq 'CODE' ? $match
258             : sub {
259 2     2   2 my $node = shift;
260 2         3 return $node->data eq $match;
261 4 100       12 } );
262              
263 4         4 my $searcher_ref;
264             $searcher_ref =
265             ( $how eq 'dfs' ?
266             sub {
267 8     8   7 my $node = shift;
268 8 100       49 return $node if ( $matcher_ref->($node) );
269 5         9 for my $child ( @$node ) {
270 4         13 return $searcher_ref->($child);
271             }
272             return undef
273 1         6 }
274             :
275             $how eq 'bfs' ?
276             sub {
277 0     0   0 my $node = shift;
278 0 0       0 return $node if ( $matcher_ref->($node) );
279 0         0 for my $child ( @$node ) {
280 0 0       0 return $child if ( $matcher_ref->($child) );
281             }
282 0         0 for my $child ( @$node ) {
283 0         0 return $searcher_ref->($child);
284             }
285 0         0 return undef;
286             }
287             :
288 4 0       16 die 'Internal error'
    50          
289             );
290              
291 4         9 return $searcher_ref->($self);
292             }
293              
294             sub get_height
295             {
296 2     2 1 2 my $self = shift;
297              
298 1         4 return 1 + ( @$self == 0 ? 0 :
299 2 100       15 max map { $_->get_height } @$self );
300             }
301              
302             sub dump_node_data
303             {
304 0     0 1   my $node = shift;
305              
306 0           my $data = $node->data;
307 0 0         return 'undef' unless ( defined $data );
308              
309 0           my $reftype = ref $data;
310             return ( ! $reftype ? $data :
311 0 0         do {
312 0 0         unless ( $_DUMPER_IS_LOADED ) {
313 0           require Data::Dumper;
314 0           $Data::Dumper::Indent = 0;
315 0           $Data::Dumper::Terse = 1;
316 0           $_DUMPER_IS_LOADED = 1;
317             }
318 0           Data::Dumper::Dumper($data);
319             } );
320             }
321              
322             sub dumper
323             {
324 0     0 1   my ($self, $file_handle, $col_limit) = @_;
325              
326 0 0         $file_handle = \*STDOUT unless ( defined $file_handle );
327 0 0         $col_limit = 78 unless ( defined $col_limit );
328              
329 0 0         croak "\$col_limit parameter ($col_limit) is invalid, must be numeric and positive"
330             if ( $col_limit !~ /\A \d+ \z/xms );
331              
332 0           require Text::Wrap;
333              
334 0           my $dumper_ref;
335             $dumper_ref = sub {
336 0     0     my ($node, $depth_counts) = @_;
337              
338 0           my $node_text = $node->dump_node_data;
339 0           my $prefix = '';
340              
341 0 0         if ( @$depth_counts ) {
342             # If there are no more items in a depth above us, they
343             # won't need a line to represent their branch.
344 0           for my $i ( 0 .. $#$depth_counts-1 ) {
345 0           my $nodes_on_depth = $depth_counts->[$i];
346 0 0         $prefix .= ( $nodes_on_depth > 0 ? '| ' : ' ' );
347             }
348              
349             # If this is the last item, make a curved "twig".
350 0           my $more_siblings = --$depth_counts->[-1];
351 0 0         $prefix .= ( $more_siblings ? '|-- ' : '`-- ' );
352             }
353              
354 0           print $file_handle Text::Wrap::wrap( $prefix, ' ' x length($prefix), "$node_text\n" );
355              
356             # Recurse through the the children nodes...
357 0           my $child_count = @$node;
358 0           for my $child ( @$node ) {
359 0           $dumper_ref->( $child,
360             [ @$depth_counts,
361             $child_count-- ] );
362             }
363              
364 0           return;
365 0           };
366              
367 0           $dumper_ref->( $self, [ ] );
368 0           return;
369             }
370              
371             1;