File Coverage

blib/lib/Algorithm/Loops.pm
Criterion Covered Total %
statement 157 176 89.2
branch 80 94 85.1
condition 16 22 72.7
subroutine 17 19 89.4
pod 8 8 100.0
total 278 319 87.1


line stmt bran cond sub pod time code
1             package Algorithm::Loops;
2             # The command "perldoc Algorithm::Loops" will show you the
3             # documentation for this module. You can also seach for
4             # "=head" below to read the unformatted documentation.
5              
6 2     2   8040 use strict;
  2         2  
  2         107  
7             BEGIN { # Some still don't have warnings.pm:
8 2 50   2   2 if( eval { require warnings } ) {
  2         12  
9 2         18 warnings->import();
10 2 50       1 if( eval { require warnings::register; } ) {
  2         13  
11 2         215 warnings::register->import();
12             }
13             } else {
14             # $^W= 1;
15             }
16             }
17              
18             require Exporter;
19 2     2   8 use vars qw( $VERSION @EXPORT_OK );
  2         2  
  2         199  
20             BEGIN {
21 2     2   3 $VERSION= 1.032_00;
22 2         6 @EXPORT_OK= qw(
23             Filter
24             MapCar MapCarE MapCarU MapCarMin
25             NestedLoops
26             NextPermute NextPermuteNum
27             );
28 2         5 { my @nowarn= ( *import, *isa ) }
  2         8  
29 2         5 *import= \&Exporter::import;
30 2         2625 *isa= \&UNIVERSAL::isa;
31             }
32              
33              
34             sub _Type
35             {
36 8     8   14 my( $val )= @_;
37 8 100 66     50 return ! defined($val) ? "undef" : ref($val) || $val;
38             }
39              
40              
41             sub _Croak
42             {
43 17     17   23 my $depth= 1;
44 17         14 my $sub;
45 17         17 do {
46 23         293 ( $sub= (caller($depth++))[3] ) =~ s/.*:://;
47             } while( $sub =~ /^_/ );
48 17 100 66     24 if( eval { require Carp; 1; }
  17         93  
  17         93  
49             && defined &Carp::croak ) {
50 13         39 unshift @_, "$sub: ";
51 13         1510 goto &Carp::croak;
52             }
53 4         75 die "$sub: ", @_, ".\n";
54             }
55              
56              
57             sub Filter(&@)
58             {
59 8     8 1 1953 my( $code, @vals )= @_;
60 8 100       26 isa($code,"CODE") or _Croak(
61             "No code reference given" );
62             # local( $_ ); # Done by the loop.
63 7         8 for( @vals ) {
64 14         31 $code->();
65             }
66 6 100       29 wantarray ? @vals : join "", @vals;
67             }
68              
69              
70             sub MapCarE(&@)
71             {
72 4     4 1 511 my $sub= shift(@_);
73 4 100       21 isa($sub,"CODE") or _Croak(
74             "No code reference given" );
75 3         4 my $size= -1;
76 3         8 for my $av ( @_ ) {
77 6 100       14 isa( $av, "ARRAY" ) or _Croak(
78             "Not an array reference (", _Type($av), ")" );
79 5 100       17 if( $size < 0 ) {
    100          
80 2         5 $size= @$av;
81             } elsif( $size != @$av ) {
82 1         6 _Croak( "Arrays with different sizes",
83             " ($size and ", 0+@$av, ")" );
84             }
85             }
86 1         1 my @ret;
87 1         3 for( my $i= 0; $i < $size; $i++ ) {
88 3         10 push @ret, &$sub( map { $_->[$i] } @_ );
  9         10  
89             }
90 1 50       6 return wantarray ? @ret : \@ret;
91             }
92              
93              
94             sub MapCarMin(&@)
95             {
96 4     4 1 310 my $sub= shift(@_);
97 4 100       15 isa($sub,"CODE") or _Croak(
98             "No code reference given" );
99 3         5 my $min= -1;
100 3         6 for my $av ( @_ ) {
101 7 100       18 isa( $av, "ARRAY" ) or _Croak(
102             "Not an array reference (", _Type($av), ")" );
103 6 100 100     18 $min= @$av if $min < 0 || @$av < $min;
104             }
105 2         2 my @ret;
106 2         3 for( my $i= 0; $i < $min; $i++ ) {
107 4         12 push @ret, &$sub( map { $_->[$i] } @_ );
  12         13  
108             }
109 2 50       12 return wantarray ? @ret : \@ret;
110             }
111              
112              
113             sub MapCarU(&@)
114             {
115 4     4 1 353 my $sub= shift(@_);
116 4 100       15 isa($sub,"CODE") or _Croak(
117             "No code reference given" );
118 3         14 my $max= 0;
119 3         6 for my $av ( @_ ) {
120 7 100       28 isa( $av, "ARRAY" ) or _Croak(
121             "Not an array reference (", _Type($av), ")" );
122 6 100       9 $max= @$av if $max < @$av;
123             }
124 2         2 my @ret;
125 2         10 for( my $i= 0; $i < $max; $i++ ) {
126 6         18 push @ret, &$sub( map { $_->[$i] } @_ );
  18         23  
127             }
128 2 50       17 return wantarray ? @ret : \@ret;
129             }
130              
131              
132             sub MapCar(&@)
133             {
134 4     4 1 415 my $sub= shift(@_);
135 4 100       15 isa($sub,"CODE") or _Croak(
136             "No code reference given" );
137 3         4 my $max= 0;
138 3         6 for my $av ( @_ ) {
139 7 100       16 isa( $av, "ARRAY" ) or _Croak(
140             "Not an array reference (", _Type($av), ")" );
141 6 100       11 $max= @$av if $max < @$av;
142             }
143 2         1 my @ret;
144 2         7 for( my $i= 0; $i < $max; $i++ ) {
145 6 100       14 push @ret, &$sub( map { $i < @$_ ? $_->[$i] : () } @_ );
  18         26  
146             # If we assumed Want.pm, we could consider an early return.
147             }
148 2 50       12 return wantarray ? @ret : \@ret;
149             }
150              
151              
152             sub NextPermute(\@)
153             {
154 720     720 1 1341 my( $vals )= @_;
155 720         409 my $last= $#{$vals};
  720         512  
156 720 50       816 return !1 if $last < 1;
157             # Find last item not in reverse-sorted order:
158 720         455 my $i= $last-1;
159 720   100     2846 $i-- while 0 <= $i && $vals->[$i] ge $vals->[$i+1];
160             # If complete reverse sort, we are done!
161 720 100       768 if( -1 == $i ) {
162             # Reset to starting/sorted order:
163 120         185 @$vals= reverse @$vals;
164 120         133 return !1;
165             }
166             # Re-sort the reversely-sorted tail of the list:
167 600 100       856 @{$vals}[$i+1..$last]= reverse @{$vals}[$i+1..$last]
  240         263  
  240         231  
168             if $vals->[$i+1] gt $vals->[$last];
169             # Find next item that will make us "greater":
170 600         427 my $j= $i+1;
171 600         821 $j++ while $vals->[$i] ge $vals->[$j];
172             # Swap:
173 600         369 @{$vals}[$i,$j]= @{$vals}[$j,$i];
  600         619  
  600         447  
174 600         574 return 1;
175             }
176              
177              
178             sub NextPermuteNum(\@)
179             {
180 0     0 1 0 my( $vals )= @_;
181 0         0 my $last= $#{$vals};
  0         0  
182 0 0       0 return !1 if $last < 1;
183             # Find last item not in reverse-sorted order:
184 0         0 my $i= $last-1;
185 0   0     0 $i-- while 0 <= $i && $vals->[$i+1] <= $vals->[$i];
186             # If complete reverse sort, we are done!
187 0 0       0 if( -1 == $i ) {
188             # Reset to starting/sorted order:
189 0         0 @$vals= reverse @$vals;
190 0         0 return !1;
191             }
192             # Re-sort the reversely-sorted tail of the list:
193 0 0       0 @{$vals}[$i+1..$last]= reverse @{$vals}[$i+1..$last]
  0         0  
  0         0  
194             if $vals->[$last] < $vals->[$i+1];
195             # Find next item that will make us "greater":
196 0         0 my $j= $i+1;
197 0         0 $j++ while $vals->[$j] <= $vals->[$i];
198             # Swap:
199 0         0 @{$vals}[$i,$j]= @{$vals}[$j,$i];
  0         0  
  0         0  
200 0         0 return 1;
201             }
202              
203              
204             sub _NL_Args
205             {
206 14     14   21 my $loops= shift(@_);
207 14 100       43 isa( $loops, "ARRAY" ) or _Croak(
208             "First argument must be an array reference,",
209             " not ", _Type($loops) );
210              
211 13         14 my $n= 1;
212 13         23 for my $loop ( @$loops ) {
213 21 100 66     49 if( ! isa( $loop, "ARRAY" )
214             && ! isa( $loop, "CODE" ) ) {
215 1         6 _Croak( "Invalid type for loop $n specification (",
216             _Type($loop), ")" );
217             }
218 20         21 $n++;
219             }
220              
221 12         20 my( $opts )= @_;
222 12 100       28 if( isa( $opts, "HASH" ) ) {
223 6         7 shift @_;
224             } else {
225 6         9 $opts= {};
226             }
227              
228 12         11 my $code;
229 12 100       29 if( 0 == @_ ) {
    100          
230 3         4 $code= 0;
231             } elsif( 1 != @_ ) {
232 1         3 _Croak( "Too many arguments" );
233             } else {
234 8         9 $code= pop @_;
235 8 100       20 isa($code,"CODE") or _Croak(
236             "Expected CODE reference not ", _Type($code) );
237             }
238              
239             my $when= delete($opts->{OnlyWhen})
240 9   100 1091   49 || sub { @_ == @$loops };
  1091         1166  
241 9 100       25 if( keys %$opts ) {
242 1         6 _Croak( "Unrecognized option(s): ",
243             join ' ', keys %$opts );
244             }
245              
246 8         17 return( $loops, $code, $when );
247             }
248              
249             sub _NL_Iter
250             {
251 8     8   10 my( $loops, $code, $when )= @_;
252              
253 8         7 my @list;
254 8         4 my $i= -1;
255 8         7 my @idx;
256 8         12 my @vals= @$loops;
257              
258 0     0   0 return sub { return }
259 8 50       18 if ! @vals;
260              
261             return sub {
262 1089     1089   767 while( 1 ) {
263             # Prepare to append one more value:
264 1262 100       1514 if( $i < $#$loops ) {
265 204         147 $idx[++$i]= -1;
266 204 100       342 if( isa( $loops->[$i], 'CODE' ) ) {
267 155         134 local( $_ )= $list[-1];
268 155         166 $vals[$i]= $loops->[$i]->(@list);
269             }
270             }
271             ## return if $i < 0;
272             # Increment furthest value, chopping if done there:
273 1262         1578 while( @{$vals[$i]} <= ++$idx[$i] ) {
  1459         1922  
274 204         136 pop @list;
275 204 100       245 return if --$i < 0;
276             }
277 1255         957 $list[$i]= $vals[$i][$idx[$i]];
278 1255         643 my $act;
279 1255 100       1145 $act= !ref($when) ? $when : do {
280 1091         932 local( $_ )= $list[-1];
281 1091         989 $when->(@list);
282             };
283 1255 100       2840 return @list if $act;
284             }
285 8         45 };
286              
287             }
288              
289             sub NestedLoops
290             {
291 14     14 1 2078 my( $loops, $code, $when )= _NL_Args( @_ );
292              
293 8         14 my $iter= _NL_Iter( $loops, $code, $when );
294              
295 8 100       17 if( ! $code ) {
296 2 100       7 if( ! defined wantarray ) {
297 1         2 _Croak( "Useless in void context",
298             " when no code given" );
299             }
300 1         2 return $iter;
301             }
302              
303 6         4 my @ret;
304             my @list;
305 6         8 while( @list= $iter->() ) {
306 962         1097 @list= $code->( @list );
307 962 100       1901 if( wantarray ) {
308 90         124 push @ret, @list;
309             } else {
310 872         942 $ret[0] += @list;
311             }
312             }
313 6 100 100     78 return wantarray ? @ret : ( $ret[0] || 0 );
314             }
315              
316              
317             "Filtering should not be straining";
318             __END__