File Coverage

blib/lib/Math/NumSeq/Runs.pm
Criterion Covered Total %
statement 82 85 96.4
branch 26 30 86.6
condition 12 16 75.0
subroutine 15 15 100.0
pod 4 4 100.0
total 139 150 92.6


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015 Kevin Ryde
2              
3             # This file is part of Math-NumSeq.
4             #
5             # Math-NumSeq is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-NumSeq is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-NumSeq. If not, see .
17              
18              
19             # A053615 - 0toNto0
20             # A004738 - 1toNto0
21             # cf A165162 - Nto1,N-1to1 cf A057058 making fracs A165200
22             # A010751 - runs incr then decr, up=1,down=2,up=3,down=4
23             # A055087 - runs 0toNtwice each 0 .. N, 0 .. N,
24             # A055086 - n repeat floor(n/2)+1 times, DiagonalsOctant X+Y
25             # A082375 - k to 0 by 2s
26             # A122196 - count down by 2s
27             # A111650 - 2n repeated n times
28             # A000194 - n repeated 2n times
29             # A111651 - n repeated 3n times
30             # A111652 - 3n repeated n times
31             # A121997 - 1toN repeated N times
32             # A079944 - 2^n 0s then 2^n, is second highest bit of n
33             # A004525 - 1 even then 3 odd, OFFSET=0
34             # A007001 - 1toN repeating 1toN
35             #
36             # A049581 diagonals absdiff, abs(x-y) not plain runs
37             # A061579 descending NtoPrev, permutation of the integers
38             # A076478 0 to 2^k-1, each written out in k many bits
39             # A000267 1,2,3,3,4,4,5,5,5,6,6,6, twice repeat each run length
40             # A122196 down by 2s
41             #
42             # to nearest pronic
43             # pronic(i) = i*(i+1);
44             # pronic_to_i_floor(n) = floor((sqrt(4*n + 1) - 1)/2);
45             # is_pronic(n) = n==pronic(pronic_to_i_floor(n));
46             # next_pronic(n) = if(is_pronic(n),n, pronic(1+pronic_to_i_floor(n)));
47             # prev_pronic(n) = pronic(pronic_to_i_floor(n));
48             # to_pronic(n) = min(next_pronic(n) - n, n - prev_pronic(n));
49             # to_pronic_signed(n) = my(pos=next_pronic(n)-n, neg=prev_pronic(n)-n); if(pos>-neg,neg,pos);
50             # vector(40,n,to_pronic(n))
51             # vector(40,n,to_pronic_signed(n))
52             # to_oddeven(n) = n=to_pronic_signed(n); if(n<=0, -2*n, 2*n-1);
53             # vector(40,n,to_oddeven(n))
54             # vector(40,n,n + to_oddeven(n))
55             # vector(40,n,n + to_pronic_signed(n))
56              
57             package Math::NumSeq::Runs;
58 2     2   7291 use 5.004;
  2         5  
59 2     2   7 use strict;
  2         3  
  2         35  
60 2     2   6 use Carp;
  2         3  
  2         119  
61              
62 2     2   7 use vars '$VERSION','@ISA';
  2         3  
  2         94  
63             $VERSION = 72;
64              
65 2     2   386 use Math::NumSeq 21; # v.21 for oeis_anum field
  2         22  
  2         61  
66             @ISA = ('Math::NumSeq');
67              
68             # uncomment this to run the ### lines
69             #use Smart::Comments;
70              
71              
72             # use constant name => Math::NumSeq::__('Runs of Integers');
73 2     2   13 use constant description => Math::NumSeq::__('Runs of integers of various kinds.');
  2         2  
  2         5  
74 2     2   6 use constant characteristic_smaller => 1;
  2         2  
  2         69  
75 2     2   6 use constant characteristic_increasing => 0;
  2         2  
  2         62  
76 2     2   7 use constant characteristic_integer => 1;
  2         2  
  2         65  
77 2     2   6 use constant default_i_start => 0;
  2         3  
  2         186  
78              
79              
80 2         4 use constant parameter_info_array =>
81             [
82             {
83             name => 'runs_type',
84             display => Math::NumSeq::__('Runs Type'),
85             type => 'enum',
86             default => '0toN',
87             choices => ['0toN',
88             '0to2N',
89             '1toN',
90             '1to2N',
91             '1toFib',
92             '0toNinc',
93             'Nto0',
94             'Nto1',
95             'Nrep',
96             'N+1rep',
97             '2rep',
98             '3rep',
99             ],
100             choices_display => [Math::NumSeq::__('0toN'),
101             Math::NumSeq::__('0to2N'),
102             Math::NumSeq::__('1toN'),
103             Math::NumSeq::__('1to2N'),
104             Math::NumSeq::__('1toFib'),
105             Math::NumSeq::__('0toNinc'),
106             Math::NumSeq::__('Nto0'),
107             Math::NumSeq::__('Nto1'),
108             Math::NumSeq::__('Nrep'),
109             Math::NumSeq::__('N+1rep'),
110             Math::NumSeq::__('2rep'),
111             Math::NumSeq::__('3rep'),
112             ],
113             # description => Math::NumSeq::__(''),
114             },
115 2     2   6 ];
  2         3  
116              
117             my %runs_type_data
118             = ('0toN' => { i_start => 0,
119             value => -1, # initial
120             values_min => 0,
121             vstart => 0,
122             vstart_inc => 0,
123             value_inc => 1,
124             c => 1, # initial
125             count => 0,
126             count_inc => 1,
127             oeis_anum => 'A002262',
128             # OEIS-Catalogue: A002262 runs_type=0toN
129             },
130              
131             '1toN' => { i_start => 1,
132             value => 0, # initial
133             values_min => 1,
134             vstart => 1,
135             vstart_inc => 0,
136             value_inc => 1,
137             c => 1, # initial
138             count => 0,
139             count_inc => 1,
140             oeis_anum => 'A002260', # 1 to N, is 0toN + 1
141             # OEIS-Catalogue: A002260 runs_type=1toN
142             },
143             '0to2N' => { i_start => 0,
144             value => -1, # initial
145             values_min => 0,
146             vstart => 0,
147             vstart_inc => 0,
148             value_inc => 1,
149             c => 1, # initial
150             count => 0,
151             count_inc => 2,
152             oeis_anum => 'A053186',
153             # OEIS-Catalogue: A053186 runs_type=0to2N
154             },
155             '1to2N' => { i_start => 1,
156             value => 0, # initial
157             values_min => 1,
158             vstart => 1,
159             vstart_inc => 0,
160             value_inc => 1,
161             c => 2, # initial
162             count => 1,
163             count_inc => 2,
164             oeis_anum => 'A074294',
165             # OEIS-Catalogue: A074294 runs_type=1to2N
166             },
167             '1to2N+1' => { i_start => 1,
168             value => 0, # initial
169             values_min => 1,
170             vstart => 1,
171             vstart_inc => 0,
172             value_inc => 1,
173             c => 1, # initial
174             count => 0,
175             count_inc => 2,
176             oeis_anum => 'A071797', # "fractal" of odds
177             # OEIS-Catalogue: A071797 runs_type=1to2N+1
178             },
179             '1toFib' => { i_start => 1,
180             value => 0, # initial
181             values_min => 1,
182             vstart => 1,
183             vstart_inc => 0,
184             value_inc => 1,
185             c => 1, # initial
186             count => 0,
187             count_inc_func => sub {
188             my ($self) = @_;
189             (my $ret, $self->{'f0'}, $self->{'f1'})
190             = ($self->{'f0'},
191             $self->{'f1'},
192             $self->{'f0'}+$self->{'f1'});
193             return $ret-1;
194             },
195             f0 => 1,
196             f1 => 2,
197             oeis_anum => 'A194029', # 1 to Fibonacci(N)
198             # OEIS-Catalogue: A194029 runs_type=1toFib
199             },
200             'Nto0' => { i_start => 0,
201             value => 1, # initial
202             values_min => 0,
203             vstart => 0,
204             vstart_inc => 1,
205             value_inc => -1,
206             c => 1, # initial
207             count => 0,
208             count_inc => 1,
209             oeis_anum => 'A025581',
210             # OEIS-Catalogue: A025581 runs_type=Nto0
211             },
212             'Nto1' => { i_start => 1,
213             value => 2, # initial
214             values_min => 1,
215             vstart => 1,
216             vstart_inc => 1,
217             value_inc => -1,
218             c => 1, # initial
219             count => 0,
220             count_inc => 1,
221             oeis_anum => 'A004736',
222             # OEIS-Catalogue: A004736 runs_type=Nto1
223             },
224             'Nrep' => { i_start => 1,
225             value => 1,
226             values_min => 1,
227             value_inc => 0,
228             vstart => 1,
229             vstart_inc => 1,
230             c => 1, # initial
231             count => 0,
232             count_inc => 1,
233             oeis_anum => 'A002024', # N appears N times
234             # OEIS-Catalogue: A002024 runs_type=Nrep
235             },
236             'N+1rep' => { i_start => 0,
237             value => 0,
238             values_min => 0,
239             value_inc => 0,
240             vstart => 0,
241             vstart_inc => 1,
242             c => 1, # initial
243             count => 0,
244             count_inc => 1,
245             oeis_anum => 'A003056', # N appears N+1 times
246             # OEIS-Catalogue: A003056 runs_type=N+1rep
247             },
248             '0toNinc' => { i_start => 0,
249             value => -1,
250             values_min => 0,
251             value_inc => 1,
252             vstart => 0,
253             vstart_inc => 1,
254             c => 1, # initial
255             count => 0,
256             count_inc => 1,
257             oeis_anum => 'A051162',
258             # OEIS-Catalogue: A051162 runs_type=0toNinc
259             },
260             );
261             # {
262             # my @a = keys %runs_type_data;
263             # ### assert: scalar(@{parameter_info_array()->[0]->{'choices'}}) == scalar(@a)
264             # }
265              
266             my @rep_oeis_anum
267             = ([ undef, # 0rep, nothing
268              
269             'A001477', # 1rep, integers 0 upwards
270             # OEIS-Other: A001477 runs_type=1rep
271              
272             'A004526', # 2rep, N appears 2 times, starting from 0
273             # OEIS-Catalogue: A004526 runs_type=2rep
274              
275             'A002264', # 3rep, N appears 3 times
276             # OEIS-Catalogue: A002264 runs_type=3rep
277              
278             'A002265', # 4rep
279             # OEIS-Catalogue: A002265 runs_type=4rep
280              
281             'A002266', # 5rep
282             # OEIS-Catalogue: A002266 runs_type=5rep
283              
284             'A152467', # 6rep
285             # OEIS-Catalogue: A152467 runs_type=6rep
286              
287             # no, A132270 has OFFSET=1 (with 7 initial 0s)
288             # 'A132270', # 7rep
289             # # OEIS-Catalogue: A132270 runs_type=7rep
290              
291             # no, A132292 has OFFSET=1 (with 8 initial 0s)
292             # 'A132292', # 8rep
293             # # OEIS-Catalogue: A132292 runs_type=8rep
294              
295             ],
296              
297             # starting i=1
298             [ undef, # 0rep, nothing
299              
300             'A000027', # 1rep, integers 1 upwards
301             # OEIS-Other: A000027 runs_type=1rep i_start=1
302              
303             # Not quite, A008619 starts OFFSET=0 value=1,1,2,2,
304             # 'A008619', # 2rep, N appears 2 times, starting from 0
305             # # OEIS-Catalogue: A008619 runs_type=2rep i_start=1
306             ] );
307              
308             sub rewind {
309 46     46 1 5216 my ($self) = @_;
310 46   50     83 $self->{'runs_type'} ||= '0toN';
311              
312 46         34 my $data;
313 46 100       137 if ($self->{'runs_type'} =~ /^(\d+)rep/) {
314 12         14 my $rep = $1;
315 12   50     43 my $i_start = ($self->{'i_start'} || 0);
316 12 50       88 $data = { i_start => $i_start,
317             value => $i_start,
318             values_min => $i_start,
319             value_inc => 0,
320             vstart => $i_start,
321             vstart_inc => 1,
322             c => $rep, # initial
323             count => $rep-1,
324             count_inc => 0,
325             oeis_anum => ($i_start >= 0
326             ? $rep_oeis_anum[$i_start][$rep]
327             : undef),
328             };
329             } else {
330             $data = $runs_type_data{$self->{'runs_type'}}
331 34   33     77 || croak "Unrecognised runs_type: ", $self->{'runs_type'};
332             }
333 46         421 %$self = (%$self, %$data);
334 46         157 $self->{'i'} = $self->i_start;
335             }
336              
337             sub next {
338 342     342 1 4704 my ($self) = @_;
339 342         255 my $i = $self->{'i'}++;
340 342 100       387 if (--$self->{'c'} >= 0) {
341             return ($i,
342 248         286 ($self->{'value'} += $self->{'value_inc'}));
343             } else {
344 94 100       96 if (my $func = $self->{'count_inc_func'}) {
345 8         11 $self->{'c'} = &$func($self);
346             } else {
347 86         80 $self->{'c'} = ($self->{'count'} += $self->{'count_inc'});
348             }
349             return ($i,
350 94         114 ($self->{'value'} = ($self->{'vstart'} += $self->{'vstart_inc'})));
351             }
352             }
353              
354             sub ith {
355 558     558 1 771 my ($self, $i) = @_;
356             ### Runs ith(): $i
357              
358 558         407 my $i_start = $self->{'i_start'};
359 558 100       669 if ($i < $i_start) {
360 51         56 return undef;
361             }
362              
363 507 100 100     2850 if ($self->{'runs_type'} eq 'Nto0' || $self->{'runs_type'} eq 'Nto1') {
    100 100        
    100 100        
    100          
    100          
    100          
    100          
364             # d-(i-(d-1)*d/2)
365             # = d-i+(d-1)*d/2
366             # = d*(1+(d-1)/2) - i
367             # = d*((d+1)/2) - i
368             # = (d+1)d/2 - i
369              
370 47         31 $i -= $self->{'i_start'};
371 47         50 my $d = int((sqrt(8*$i+1) + 1) / 2);
372             ### $d
373             ### base: ($d-1)*$d/2
374             ### rem: $i - ($d-1)*$d/2
375 47         86 return -$i + ($d+1)*$d/2 - 1 + $self->{'values_min'};
376              
377             } elsif ($self->{'runs_type'} eq 'Nrep'
378             || $self->{'runs_type'} eq 'N+1rep') {
379 59         40 $i -= $self->{'i_start'};
380 59         110 return int((sqrt(8*$i+1) - 1) / 2) + $self->{'values_min'};
381              
382             } elsif ($self->{'runs_type'} eq '0toNinc') {
383             # i-(d-1)d/2 + d
384             # = i-((d-1)d/2 - d)
385             # = i-(d-3)d/2
386 30         36 my $d = int((sqrt(8*$i+1) + 1) / 2);
387 30         52 return $i - ($d-3)*$d/2 - 1;
388              
389             } elsif ($self->{'runs_type'} =~ /^(\d+)rep/) {
390 129         133 my $rep = $1;
391             ### $rep
392 129 50       172 if ($rep < 1) {
393 0         0 return undef;
394             }
395 129         236 return int($i/$rep);
396              
397             } elsif ($self->{'runs_type'} eq '1to2N') {
398             # runs beginning i=1,3,7,13,21,31 (Math::NumSeq::Pronic + 1)
399             # N = (d^2 - d + 1)
400             # = ($d**2 - $d + 1)
401             # = (($d - 1)*$d + 1)
402             # d = 1/2 + sqrt(1 * $n + -3/4)
403             # = (1 + sqrt(4*$n - 3)) / 2
404              
405 35         41 my $d = int( (sqrt(4*$i-3) + 1) / 2);
406              
407             ### $d
408             ### base: ($d-1)*$d/2
409             ### rem: $i - ($d-1)*$d/2
410              
411 35         50 return $i - ($d-1)*$d;
412              
413             } elsif ($self->{'runs_type'} eq '0to2N'
414             || $self->{'runs_type'} eq '1to2N+1') {
415             ### 1to2N+1 ...
416             # values 1, 1,2,3, 1,2,3,4,5
417             # run beginning i=1,2,5,10,17,26
418             # N = (d^2 - 2 d + 2), starting d=1
419             # d = 1 + sqrt(1 * $n + -1)
420             # = 1 + sqrt($n-1)
421              
422 125         86 $i -= $self->{'i_start'};
423 125         100 my $d = int(sqrt($i)) + 1;
424              
425             ### $d
426             ### base: ($d-2)*$d
427             ### rem: $i - ($d-2)*$d
428              
429 125         210 return $i - ($d-2)*$d + $self->{'vstart'}-1;
430              
431             } elsif ($self->{'runs_type'} eq '1toFib') {
432 35         24 my $f0 = ($i*0) + 1; # inherit bignum 1
433 35         22 my $f1 = $f0 + 1; # inherit bignum 2
434 35         43 while ($f1 <= $i) {
435 107         134 ($f0,$f1) = ($f1,$f0+$f1);
436             }
437 35         51 return $i - $f0 + 1;
438              
439             } else { # 0toN, 1toN
440 47         37 $i -= $i_start;
441 47         52 my $d = int((sqrt(8*$i+1) + 1) / 2);
442              
443             ### $d
444             ### base: ($d-1)*$d/2
445             ### rem: $i - ($d-1)*$d/2
446              
447 47         83 return $i - ($d-1)*$d/2 + $self->{'vstart'};
448             }
449             }
450              
451             sub pred {
452 171     171 1 406 my ($self, $value) = @_;
453             ### Runs pred(): $value
454              
455 171 50       196 unless ($value == int($value)) {
456 0         0 return 0;
457             }
458 171 50       151 if (defined $self->{'values_min'}) {
459 171         193 return ($value >= $self->{'values_min'});
460             } else {
461 0           return ($value <= $self->{'values_max'});
462             }
463             }
464              
465             1;
466             __END__