File Coverage

blib/lib/Tie/LazyList.pm
Criterion Covered Total %
statement 73 140 52.1
branch 26 42 61.9
condition 3 6 50.0
subroutine 11 24 45.8
pod 0 1 0.0
total 113 213 53.0


line stmt bran cond sub pod time code
1            
2             # Package implementing a "lazy lists" via a tied arrays
3             package Tie::LazyList;
4            
5 1     1   8457 use 5.006;
  1         4  
  1         34  
6 1     1   4 use strict;
  1         2  
  1         22  
7 1     1   5 use warnings;
  1         5  
  1         38  
8 1     1   4 use Carp;
  1         2  
  1         3245  
9            
10             require Exporter;
11            
12             our @ISA = qw( Exporter );
13             our %EXPORT_TAGS = ( 'all' => [ qw() ] );
14             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
15             our @EXPORT = qw();
16             our $VERSION = '0.05';
17            
18            
19             # debug variable that may be set to see the debug messages
20             our $debug = 0;
21 67444 50   67444 0 176462 sub debug ($) { print $_[0], "\n" if $debug }
22            
23            
24             # "Locality" factor - how many additional elements will be filled when
25             # extending an array
26             our $locality = 10;
27            
28            
29             # Returns TRUE is passed parameter is a number, FALSE otherwise
30             # ( thank's to Joseph Hall for the trick :)
31             sub _is_number {
32 180 100   180   400 my $number = @_ ? shift : $_;
33 180         1218 ( ~$number & $number ) eq '0';
34             }
35            
36             # Returns the result of applying the passed operation on two first numbers of array
37             # after checking that they're *really* numbers
38             sub _factor ($$){
39 80     80   127 local $_;
40 80         140 my ( $array_ref, $op ) = @_;
41 80         123 for ( @{ $array_ref }[ 0, 1 ] ){
  80         354  
42 160 50 33     523 ( defined and _is_number()) or croak "Illegal array init by not a number !";
43             }
44             # checking the "division by zero" case
45 80 50 66     445 if (( $op eq '/' ) and ( $array_ref->[0] == 0 )){
46 0         0 croak "Illegal attempt to divide by zero !";
47             }
48            
49 80         7909 eval "$array_ref->[1] $op $array_ref->[0]";
50             }
51            
52            
53            
54             # Predefined code abbreviations
55             my %CODES_ABBREV =
56             ( # Arithmetic progression
57             APROG => sub {
58             my ( $array_ref ) = @_;
59             my $factor = _factor( $array_ref, '-' ); # factor = arr[1] - arr[0]
60             sub {
61             my ( $array_ref, $n ) = @_;
62             $array_ref->[ $n - 1 ] + $factor;
63             }
64             },
65             # Geometric progression
66             GPROG => sub {
67             my ( $array_ref ) = @_;
68             my $factor = _factor( $array_ref, '/' ); # factor = arr[1] / arr[0]
69             sub {
70             my ( $array_ref, $n ) = @_;
71             $array_ref->[ $n - 1 ] * $factor;
72             }
73             },
74             # Summary of arithmetic progression
75             APROG_SUM => sub {
76             my ( $array_ref ) = @_;
77             my $factor = _factor( $array_ref, '-' ); # factor = arr[1] - arr[0]
78             return (
79             sub {
80             my ( $array_ref, $n ) = @_; # n - zero based
81             my $a_0 = $array_ref->[ 0 ]; # a0
82             my $a_n = $a_0 + ($factor * $n); # an = a0 + d*n
83             $array_ref->[ $n - 1 ] + $a_n; # S(n) = S(n-1) + an
84             },
85             # truncating the rest of the array - we have the first elem and the factor
86             [ $array_ref->[ 0 ]]
87             )
88             },
89             # Summary of geometric progression
90             GPROG_SUM => sub {
91             my ( $array_ref ) = @_;
92             my $factor = _factor( $array_ref, '/' ); # factor = arr[1] / arr[0]
93             return (
94             sub {
95             my ( $array_ref, $n ) = @_; # n - zero based
96             my $a_0 = $array_ref->[ 0 ]; # a0
97             my $a_n = $a_0 * ($factor ** $n); # an = a0 * q^n
98             $array_ref->[ $n - 1 ] + $a_n; # S(n) = S(n-1) + an
99             },
100             # truncating the rest of the array - we have the first elem and the factor
101             [ $array_ref->[ 0 ]]
102             )
103             },
104             FIBON => sub {
105             my ( $array_ref ) = @_;
106             @{ $array_ref } >= 2 or croak "Illegal array init - should be two elements at least !";
107             sub {
108             my ( $array_ref, $n ) = @_;
109             $array_ref->[ $n - 1 ] + $array_ref->[ $n - 2 ];
110             }
111             },
112             FACT => sub {
113             my ( $array_ref ) = @_;
114             @{ $array_ref } >= 1 or croak "Illegal array init - should be one element at least !";
115             sub {
116             my ( $array_ref, $n ) = @_;
117             $array_ref->[ $n - 1 ] * $n;
118             }
119             },
120             POW => sub {
121             my ( $array_ref ) = @_;
122             _is_number( my $x = $array_ref->[0] ) or croak "Illegal array init by not a number !";
123             $x == 0 and croak "Illegal array init with zero !";
124             return (
125             sub {
126             my ( $array_ref, $n ) = @_;
127             $array_ref->[ $n - 1 ] * $x;
128             },
129             [ 1 ] # starting with x^0 = 1
130             );
131             }
132             );
133            
134            
135            
136             sub TIEARRAY {
137 520     520   13513 local $_;
138 520 50       11636 my $class = shift or croak "Undefined class !";
139 520 50       1173 defined ( my $init = shift ) or croak "Undefined array init !"; # may be a scalar or ARRAY ref
140            
141             # List's initialization variables to be set now :
142 520         618 my ( @arr, # list's main array, should be initialized
143             $code_ref ); # list's generation function
144            
145             # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
146             # setting @arr and, possibly, $code_ref ( $code_ref will be set if the init
147             # passed is a reference to another array tied to LazyList )
148            
149 520         750 my $ref = ref $init;
150            
151 520 100       1380 unless ( $ref ){
    50          
152             # init is a simple scalar variable
153 120         313 @arr = ( $init );
154             } elsif ( $ref eq 'ARRAY' ){
155             # init is a reference to ARRAY and it may be :
156             # 1) ref to another array tied to LazyList
157             # 2) ref to a usual Perl array
158 400         408 my $tied_object = tied @{ $init };
  400         644  
159 400 100       694 if ( defined $tied_object ){
160             # 1)
161 260 50       1088 $tied_object->isa( $class )
162             or croak "Reference to a tied object passed which isn't a [$class] instance !";
163             # taking the initialization data from this tied object : init_array and code
164 260         763 my ( $init_array, $code ) = $tied_object->_init_data();
165 260         446 @arr = @{ $init_array };
  260         531  
166 260         479 $code_ref = $code;
167             }
168             else {
169             # 2)
170 140         231 @arr = @{ $init };
  140         418  
171             }
172             } else {
173             # init is an unexpected reference
174 0         0 croak "Unknown [$ref] referenece passed for initializing the list !";
175             }
176            
177            
178             # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179             # setting $code_ref ( if it wasn't set by above block ) and, possibly, @arr again
180             # ( @arr will be set again if the code abbreviation that was used will return a
181             # new array when fetched from the code's table )
182            
183 520 100       6991 unless ( defined $code_ref ){
184            
185             # code is sitting in @_ and should be a scalar or CODE ref
186 260 50       975 my $code = shift or croak "Undefined code !";
187 260         407 my $ref = ref $code;
188            
189 260 100       642 unless ( $ref ){
    50          
190             # code is a scalar variable, should be one of the predefined code abbreviations
191 140 50       447 exists $CODES_ABBREV{ $code } or croak "Unknown scalar [$code] passed as code abbreviation !";
192             # getting the code and, possibly, array_ref for the new array
193 140         561 my ( $returned_code, $array_ref ) = $CODES_ABBREV{ $code }->( \@arr );
194 140         300 $code_ref = $returned_code;
195 140 100       391 @arr = @{ $array_ref } if defined $array_ref;
  60         311  
196             } elsif ( $ref eq 'CODE' ){
197             # code is a CODE reference
198 120         261 $code_ref = $code;
199             } else {
200             # code is an unexpected reference
201 0         0 croak "Unknown [$code_ref] reference passed as a code !";
202             }
203             }
204            
205             # sanity-check of result initializations
206 520 50       1692 ref [ @arr ] eq 'ARRAY' or die "Failed to successfully initialize the array ! \a";
207 520 50       1508 ref $code_ref eq 'CODE' or die "Failed to successfully initialize code reference ! \a";
208            
209            
210 520         3716 bless { array => \@arr, # the main list that will be used and expanded
211             init_array => \@arr, # keeping the initialization array
212             code => $code_ref, # the generation function
213             'length' => scalar @arr }, # the current length, will be updated every time it changes
214             $class;
215             }
216            
217            
218             # returns the init_array ref and the code ref ( used for creating
219             # another tied array, initialized exactly as this one )
220             sub _init_data {
221 260     260   292 local $_;
222 260         332 my $self = shift;
223 260         333 @{ $self }{ qw ( init_array code ) };
  260         746  
224             }
225            
226            
227             sub FETCH {
228 66924     66924   822245 debug "FETCH(@_)";
229 66924         75732 local $_;
230 66924         100121 my $self = shift;
231 66924         84370 my ( $index ) = @_;
232 66924         73762 my ( $array_ref, $length, $code_ref ) = @{ $self }{ qw ( array length code )};
  66924         133797  
233            
234 66924 100       140074 unless ( $index < $length ){ # we should extend the array
235 2862         3651 my $top_fill = $index + $locality; # top index to be filled
236 2862         2871 $#{ $array_ref } = $top_fill; # pre-extending array for the efficiency
  2862         8415  
237 2862         5731 for ( $length .. $top_fill ){
238 69856         231766 $array_ref->[ $_ ] = $code_ref->( $array_ref, $_ );
239             }
240 2862         12357 $self->{ 'length' } = $top_fill + 1;
241             }
242            
243 66924         218488 $array_ref->[ $index ];
244             }
245            
246             sub STORE {
247 0     0   0 debug "STORE(@_)";
248 0         0 local $_;
249 0         0 my $self = shift;
250 0         0 my ( $index, $value ) = @_;
251 0 0       0 if ( defined $value ){
252 0 0       0 $self->{ array }[ $index ] == $value or # <-- used by Perl during 'for ( @array )' loop
253 0         0 croak "No STORE operation supported for class [@{[ ref $self ]}] !";
254             }
255             }
256            
257            
258             sub FETCHSIZE {
259 0     0   0 debug "FETCHSIZE(@_)";
260 0         0 local $_;
261 0         0 my $self = shift;
262 0         0 $self->{ 'length' } + 1; # to make 'for ( @array )' loop iterate infinitely
263             }
264            
265             sub STORESIZE {
266 0     0   0 debug "STORESIZE(@_)";
267 0         0 local $_;
268 0         0 my $self = shift;
269 0         0 croak "No STORESIZE operation supported for class [@{[ ref $self ]}] !";
  0         0  
270             }
271            
272             sub EXTEND {
273 0     0   0 debug "EXTEND(@_)";
274 0         0 local $_;
275 0         0 my $self = shift;
276 0         0 croak "No EXTEND operation supported for class [@{[ ref $self ]}] !";
  0         0  
277             }
278            
279             sub EXISTS {
280 0     0   0 debug "EXISTS(@_)";
281 0         0 local $_;
282 0         0 my $self = shift;
283 0         0 croak "No EXISTS operation supported for class [@{[ ref $self ]}] !";
  0         0  
284             }
285            
286             sub DELETE {
287 0     0   0 debug "DELETE(@_)";
288 0         0 local $_;
289 0         0 my $self = shift;
290 0         0 croak "No DELETE operation supported for class [@{[ ref $self ]}] !";
  0         0  
291             }
292            
293             sub CLEAR {
294 0     0   0 debug "CLEAR(@_)";
295 0         0 local $_;
296 0         0 my $self = shift;
297 0         0 croak "No CLEAR operation supported for class [@{[ ref $self ]}] !";
  0         0  
298             }
299            
300             sub PUSH {
301 0     0   0 debug "PUSH(@_)";
302 0         0 local $_;
303 0         0 my $self = shift;
304 0         0 croak "No PUSH operation supported for class [@{[ ref $self ]}] !";
  0         0  
305             }
306            
307             sub POP {
308 0     0   0 debug "POP(@_)";
309 0         0 local $_;
310 0         0 my $self = shift;
311 0         0 croak "No POP operation supported for class [@{[ ref $self ]}] !";
  0         0  
312             }
313            
314             sub SHIFT {
315 0     0   0 debug "SHIFT(@_)";
316 0         0 local $_;
317 0         0 my $self = shift;
318 0         0 croak "No SHIFT operation supported for class [@{[ ref $self ]}] !";
  0         0  
319             }
320            
321             sub UNSHIFT {
322 0     0   0 debug "UNSHIFT(@_)";
323 0         0 local $_;
324 0         0 my $self = shift;
325 0         0 croak "No UNSHIFT operation supported for class [@{[ ref $self ]}] !";
  0         0  
326             }
327            
328             sub SPLICE {
329 0     0   0 debug "SPLICE(@_)";
330 0         0 local $_;
331 0         0 my $self = shift;
332 0         0 croak "No SPLICE operation supported for class [@{[ ref $self ]}] !";
  0         0  
333             }
334            
335             sub UNTIE {
336 0     0   0 debug "UNTIE(@_)";
337 0         0 local $_;
338 0         0 my $self = shift;
339             }
340            
341             sub DESTROY {
342 520     520   4521 debug "DESTROY(@_)";
343 520         715 local $_;
344 520         6330 my $self = shift;
345             }
346            
347            
348             1;
349            
350             __END__