File Coverage

blib/lib/Math/Sequence.pm
Criterion Covered Total %
statement 99 113 87.6
branch 32 46 69.5
condition 14 24 58.3
subroutine 11 11 100.0
pod 6 6 100.0
total 162 200 81.0


line stmt bran cond sub pod time code
1            
2             =head1 NAME
3            
4             Math::Sequence - Perl extension dealing with mathematic sequences
5            
6             =head1 SYNOPSIS
7            
8             use Math::Sequence;
9             my $x_n = Math::Sequence->new('x^2 - 1', 2);
10             print $x_n->next(), "\n" foreach 0..9;
11             # prints 2, 3, 8, 63...
12            
13             print $x_n->at_index(5);
14             # prints 15745023
15            
16             $x_n->cached(0); # don't cache the results (slow!)
17             $x_n->cached(1); # cache the results (default)
18            
19             =head1 DESCRIPTION
20            
21             Math::Sequence defines a class for simple mathematic sequences with a
22             recursive definition such as C. Creation of a
23             Math::Sequence object is described below in the paragraph about the
24             constructor.
25            
26             Math::Sequence uses Math::Symbolic to parse and modify the recursive
27             sequence definitions. That means you specify the sequence as a string which
28             is parsed by Math::Symbolic. Alternatively, you can pass the constructor
29             a Math::Symbolic tree directly.
30            
31             Because Math::Sequence uses Math::Symbolic for its implementation, all results
32             will be Math::Symbolic objects which may contain other variables than the
33             sequence variable itself.
34            
35             Each Math::Sequence object is an iterator to iterate over the elements of the
36             sequence starting at the first element (which was specified by the starting
37             element, the second argument to the new() constructor). It offers
38             facilities to cache all calculated elements and access any element directly,
39             though unless the element has been cached in a previous calculation, this
40             is just a shortcut for repeated use of the iterator.
41            
42             Every element in the sequence may only access its predecessor, not the elements
43             before that.
44            
45             =head2 EXAMPLE
46            
47             use strict;
48             use warnings;
49             use Math::Sequence;
50            
51             my $seq = Math::Sequence->new('x+a', 0, 'x');
52             print($seq->current_index(), ' => ', $seq->next(), "\n") for 1..10;
53            
54             =cut
55            
56             package Math::Sequence;
57            
58 1     1   859 use 5.006;
  1         5  
  1         36  
59 1     1   5 use strict;
  1         1  
  1         30  
60 1     1   24 use warnings;
  1         3  
  1         62  
61            
62             our $VERSION = '1.00';
63            
64 1     1   6 use Carp;
  1         2  
  1         98  
65            
66 1     1   943 use Math::Symbolic qw/:all/;
  1         157433  
  1         1671  
67            
68             =head2 CLASS DATA
69            
70             Math::Sequence defines the following package variables:
71            
72             =over 2
73            
74             =item $Math::Sequence::Parser
75            
76             This scalar contains a Parse::RecDescent parser to parse formulas.
77             It is derived from the Math::Symbolic::Parser grammar.
78            
79             =cut
80            
81             our $Parser = Math::Symbolic::Parser->new();
82            
83             #$Parser->Extend(<<'GRAMMAR');
84             #GRAMMAR
85            
86             =item $Math::Sequence::warnings
87            
88             This scalar indicates whether Math::Sequence should warn about the performance
89             implications of using the back() method on uncached sequences. It defaults
90             to true.
91            
92             =cut
93            
94             our $warnings = 1;
95            
96             =pod
97            
98             =back
99            
100             =head2 METHODS
101            
102             =over 2
103            
104             =item new()
105            
106             The constructor for Math::Sequence objects. Takes two or three arguments.
107             In the two argument form, the first argument specifies the recursion
108             definition. It must be either a string to be parsed by a Math::Symbolic
109             parser or a Math::Symbolic tree. In the two argument version, the
110             recursion variable (the one which will be recursively replaced by its
111             predecessor) will be inferred from the function signature. Thus, the formula
112             must contain exactly one variable. The second argument must be a starting
113             value. It may either be a constant or a Math::Symbolic tree or a string to be
114             parsed as such.
115            
116             The three argument version adds to the two argument version a string indicating
117             a variable name to be used as the recursion variable. Then, the recursion
118             formula may contain any number of variables.
119            
120             =cut
121            
122             sub new {
123 4     4 1 2176 my $proto = shift;
124 4   33     29 my $class = ref($proto) || $proto;
125            
126 4         7 my $formula = shift;
127 4 50       12 croak "Sequence->new() takes a formula as first argument."
128             if not defined $formula;
129            
130 4         33 my $parsed = $Parser->parse($formula);
131 4 50       18253 croak "Error parsing formula" if not defined $parsed;
132            
133 4         14 my $start = shift;
134 4 50       14 croak "A starting value must be supplied to Sequence->new() as second\n"
135             . "argument."
136             if not defined $start;
137 4         36 $start = $Parser->parse($start);
138 4 50       5471 croak "Error parsing starting value." if not defined $start;
139            
140 4         11 my $variable = shift;
141 4         24 my @sig = $parsed->signature();
142            
143 4 50 33     486 if ( @sig != 1 and not defined $variable ) {
144 0         0 croak "Formula must have one variable or a user defined\n"
145             . "variable must be supplied.";
146             }
147 4 100       20 $variable = $sig[0] if not defined $variable;
148            
149 4         37 my $self = {
150             cached => 1,
151             var => $variable,
152             formula => $parsed,
153             current => 0,
154             current_value => $start,
155             cache => [$start],
156             };
157 4         34 return bless $self => $class;
158             }
159            
160             =item next()
161            
162             The next() method returns the next element of the sequence and advances the
163             iterator by one. This is the prefered method of walking down a sequence's
164             recursion.
165            
166             =cut
167            
168             sub next {
169 87     87 1 4487 my $self = shift;
170 87         149 my $current_index = $self->{current};
171 87         117 my $current_value = $self->{current_value};
172 87         197 my $next_index = $current_index + 1;
173            
174 87 100 100     283 if ( $self->{cached} and defined $self->{cache}[$next_index] ) {
175 2         5 $self->{current_value} = $self->{cache}[$next_index];
176 2         5 $self->{current} = $next_index;
177 2         8 return $current_value;
178             }
179            
180 85         338 my $next_value = $self->{formula}->new();
181 85         6103 $next_value->implement( $self->{var} => $current_value );
182 85         17446 $next_value = $next_value->simplify();
183            
184 85 100       35311 $self->{cache}[$next_index] = $next_value if $self->{cached};
185 85         145 $self->{current} = $next_index;
186 85         114 $self->{current_value} = $next_value;
187            
188 85         361 return $current_value;
189             }
190            
191             =item cached()
192            
193             Returns a true value if the sequence is currently being cached, false if it
194             isn't. By default, new objects have caching enabled. It is suggested that you
195             only disable caching if space is an issue and you will only walk the sequence
196             uni-directionally and only once.
197            
198             cached() can be used to change the caching behaviour. If the first argument is
199             true, caching will be enabled. If it is false, caching will be disabled.
200            
201             =cut
202            
203             sub cached {
204 3     3 1 780 my $self = shift;
205 3 100       11 $self->{cached} = shift if @_;
206 3         20 return $self->{cached};
207             }
208            
209             =item current_index()
210            
211             Returns the index of the current element. That is, the index of the element
212             that will be returned by the next call to the next() method.
213            
214             This method also allows (re-)setting the element that will be next returned by
215             the next() method. In that case, the first argument shoudl be the appropriate
216             index.
217            
218             Returns undef and doesn't set the current index if the argument is below 0.
219            
220             =cut
221            
222             sub current_index {
223 44     44 1 17079 my $self = shift;
224 44 100 66     184 if ( @_ and defined $_[0] ) {
225 4         7 my $index = shift;
226 4 100       24 return undef if $index < 0;
227 2         7 $self->{current_value} = $self->at_index($index);
228 2         6 $self->{current} = $index;
229 2         12 return $index;
230             }
231             else {
232 40         162 return $self->{current};
233             }
234             }
235            
236             =item at_index()
237            
238             This method returns the sequence element with the index denoted by the first
239             argument to the method. It does not change the state of the iterator.
240             This method is extremely slow for uncached sequences.
241            
242             Returns undef for indices below 0.
243            
244             =cut
245            
246             sub at_index {
247 20     20 1 3158 my $self = shift;
248 20         28 my $index = shift;
249 20 50       49 croak "Sequence->at_index() takes an index as argument."
250             if not defined $index;
251 20 100       53 return undef if $index < 0;
252            
253 18 100 100     86 return $self->{cache}[$index]
254             if $self->{cached}
255             and defined $self->{cache}[$index];
256            
257 15 100       39 if ( $self->{cached} ) {
258 1 50       2 if ( $index > $#{ $self->{cache} } ) {
  1         6  
259 1         3 my $old_index = $self->{current};
260 1         8 $self->next() for 1 .. $index - $self->{current};
261 1         5 my $value = $self->{current_value};
262 1         3 $self->{current} = $old_index;
263 1         4 $self->{current_value} = $self->{cache}[$old_index];
264 1         8 return $value;
265             }
266             else {
267 0 0       0 return $self->{cache}[$index]
268             if defined $self->{cache}[$index];
269 0         0 my $last_defined = $index;
270 0   0     0 while ( not defined $self->{cache}[$last_defined]
271             and $last_defined >= 0 )
272             {
273 0         0 $last_defined--;
274             }
275 0 0       0 die "Sanity check!" if $last_defined < 0;
276 0         0 my $old_index = $self->{current};
277 0         0 $self->{current} = $last_defined;
278 0         0 $self->{current_value} = $self->{cache}[$last_defined];
279 0         0 $self->next() for 1 .. $index - $last_defined;
280 0         0 my $value = $self->{current_value};
281 0         0 $self->{current} = $old_index;
282 0         0 $self->{current_value} = $self->{cache}[$old_index];
283 0         0 return $value;
284             }
285             }
286             else { # not $self->{cached}
287 14 50       36 return $self->{current_value} if $index == $self->{current};
288 14         21 my $old_index = $self->{current};
289 14         20 my $old_value = $self->{current_value};
290 14         15 my $value;
291 14 100       32 if ( $index < $self->{current} ) {
292 11         14 $self->{current} = 0;
293 11         25 $self->{current_value} = $self->{cache}[0];
294 11         79 $self->next() for 1 .. $index;
295 11         28 $value = $self->{current_value};
296             }
297             else {
298 3         13 $self->next() for 1 .. $index - $old_index;
299 3         10 $value = $self->{current_value};
300             }
301 14         23 $self->{current} = $old_index;
302 14         27 $self->{current_value} = $old_value;
303 14         43 return $value;
304             }
305             }
306            
307             =item back()
308            
309             This methods returns the sequence element previously returned by the next()
310             method. Since it is extremely slow on uncached sequences, it warns about this
311             performance hit by default. To turn this warning off, set the
312             $Math::Sequence::warnings scalar to a false value.
313            
314             This method decrements the current iterator sequence element.
315            
316             Returns undef if the current index goes below 0.
317            
318             =cut
319            
320             sub back {
321 20     20 1 11119 my $self = shift;
322 20         34 my $current_index = $self->{current};
323 20         30 my $current_value = $self->{current_value};
324 20         29 my $prev_index = $current_index - 1;
325 20 50       48 return undef if $prev_index < 0;
326            
327 20 50 66     65 carp "Use of the back() method on uncached sequence is not advised."
328             if ( not $self->{cached} )
329             and $Math::Sequence::warnings;
330            
331 20 100 66     84 if ( $self->{cached} and defined $self->{cache}[$prev_index] ) {
332 10         22 $self->{current_value} = $self->{cache}[$prev_index];
333 10         12 $self->{current} = $prev_index;
334 10         42 return $self->{current_value};
335             }
336            
337 10         28 my $prev_value = $self->at_index($prev_index);
338 10         16 $self->{current} = $prev_index;
339 10         11 $self->{current_value} = $prev_value;
340            
341 10         42 return $prev_value;
342             }
343            
344             1;
345             __END__