File Coverage

blib/lib/Math/NumSeq/BalancedBinary.pm
Criterion Covered Total %
statement 219 229 95.6
branch 55 68 80.8
condition 20 26 76.9
subroutine 26 26 100.0
pod 10 10 100.0
total 330 359 91.9


line stmt bran cond sub pod time code
1             # Copyright 2012, 2013, 2014 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             # http://www.iki.fi/~kartturi/matikka/tab9766.htm
20             # A009766 Catalan triangle
21             # A099039 Riordan array
22              
23             package Math::NumSeq::BalancedBinary;
24 2     2   7446 use 5.004;
  2         7  
25 2     2   9 use strict;
  2         3  
  2         43  
26 2     2   6 use List::Util 'max';
  2         2  
  2         135  
27              
28 2     2   7 use vars '$VERSION', '@ISA';
  2         2  
  2         96  
29             $VERSION = 72;
30              
31 2     2   386 use Math::NumSeq;
  2         3  
  2         79  
32             @ISA = ('Math::NumSeq');
33             *_is_infinite = \&Math::NumSeq::_is_infinite;
34             *_to_bigint = \&Math::NumSeq::_to_bigint;
35              
36 2     2   327 use Math::NumSeq::Repdigits;
  2         4  
  2         112  
37             *_digit_split_lowtohigh = \&Math::NumSeq::Repdigits::_digit_split_lowtohigh;
38              
39 2     2   349 use Math::NumSeq::Fibonacci;
  2         2  
  2         69  
40             *_blog2_estimate = \&Math::NumSeq::Fibonacci::_blog2_estimate;
41              
42             # uncomment this to run the ### lines
43             # use Smart::Comments;
44              
45              
46             # use constant name => Math::NumSeq::__('Balanced Binary');
47 2     2   7 use constant description => Math::NumSeq::__('Bits 1,0 balanced like parentheses.');
  2         2  
  2         5  
48 2     2   6 use constant characteristic_increasing => 1;
  2         5  
  2         67  
49 2     2   5 use constant characteristic_integer => 1;
  2         3  
  2         68  
50 2     2   5 use constant default_i_start => 1;
  2         2  
  2         76  
51 2     2   9 use constant values_min => 2;
  2         10  
  2         339  
52              
53             # pred() works, next() doesn't.
54             # Any merit in bit-reversed form ?
55             #
56             # use constant parameter_info_array =>
57             # [ { name => 'direction',
58             # share_key => 'pairs_fsb',
59             # display => Math::NumSeq::__('Direction'),
60             # type => 'enum',
61             # default => 'HtoL',
62             # choices => ['HtoL','LtoH'],
63             # choices_display => [Math::NumSeq::__('HtoL'),
64             # Math::NumSeq::__('LtoH'),
65             # ],
66             # },
67             # ];
68              
69             sub new {
70 4     4 1 1861 my $self = shift->SUPER::new(@_);
71 4   50     20 $self->{'direction'} ||= 'HtoL';
72 4         7 return $self;
73             }
74              
75             #------------------------------------------------------------------------------
76             # A063171 same in binary
77             # A080116 predicate 0,1
78             # A080300 inverse, ranking value->i or 0
79             # A080237 num trailing zeros
80             # A085192 first diffs
81             # A000108 Catalan numbers, count of values in 4^k blocks
82             # A071152 balanced/2, Lukasiewicz words for the rooted plane binary trees
83             # A075171 trees by binary runs in integers, coded to Dyck words
84             # A071153 Lukasiewicz coded digits
85             #
86             my %oeis_anum = (HtoL => 'A014486', # balanced binary
87             # OEIS-Catalogue: A014486
88              
89             LtoH => undef,
90             );
91             sub oeis_anum {
92 1     1 1 3 my ($self) = @_;
93 1         2 return $oeis_anum{$self->{'direction'}};
94             }
95              
96              
97             #------------------------------------------------------------------------------
98              
99             # When $self->{'wantlen'} becomes equal to _WANTLEN_TO_BIGINT then go to
100             # Math::BigInt since the bits of a UV would be overflowed.
101             #
102             # _WANTLEN_TO_BIGINT is floor(numbits(UV)/2), but calculated by a probe of
103             # "~0".
104             #
105             # On a 32-bit UV balance binary value=0xFFFF0000 is reached at i=48_760_366
106             # which is fairly big but might be reached by a long running loop. On a
107             # 64-bit UV the corresponding limit is i=75*10^15 so would not be reached in
108             # any reasonable time.
109             #
110 2         5 use constant 1.02 _WANTLEN_TO_BIGINT => do {
111 2         2 my $uv = ~0;
112 2         3 my $limit = 0;
113 2         5 while ($uv >= 3) {
114 64         33 $uv >>= 2;
115 64         66 $limit++;
116             }
117             ### $limit
118             $limit
119 2     2   7 };
  2         25  
  2         2522  
120              
121             sub rewind {
122 6     6 1 372 my ($self) = @_;
123 6         16 $self->{'wantlen'} = 0;
124 6         11 $self->{'bits'} = [];
125 6         7 $self->{'value'} = 0;
126 6         22 $self->{'i'} = $self->i_start;
127             }
128              
129             sub next {
130 123     123 1 953 my ($self) = @_;
131             ### BalancedBinary next() ...
132 123         110 my $bits = $self->{'bits'};
133 123         80 my $value;
134              
135 123 100       149 if (! @$bits) {
136             ### initial 2 ...
137 4         8 push @$bits, 2;
138 4         3 $value = 2;
139              
140             } else {
141 119         87 $value = $self->{'value'};
142 119         67 for (;;) {
143             ### at: "bits=".join(',',@{$self->{'bits'}})
144 181 100       212 if (scalar(@$bits) < 2) {
145             ### extend to wantlen: $self->{'wantlen'}+1
146 14         18 $value = ($bits->[0] *= 4);
147 14 50       25 if (++$self->{'wantlen'} == _WANTLEN_TO_BIGINT) {
148             ### promote to BigInt from now on ...
149 0         0 $bits->[0] = $value = _to_bigint($value);
150             }
151 14         11 last;
152             }
153              
154 167         128 $value -= $bits->[-1];
155 167         126 my $bit = ($bits->[-1] *= 2);
156 167 100       205 if ($bit < $bits->[-2]) {
157             ### shifted bit ...
158 105         71 $value += $bit;
159 105         85 last;
160             }
161             ### drop this bit ...
162 62         51 pop @$bits;
163             }
164              
165             ### pad for: "value=$value bits=".join(',',@$bits)
166             # trailing bits ...,128, 32, 8, 2
167 119         105 my $bit = 2 + ($bits->[0]&1); # inherit BigInt
168 119         159 foreach my $pos (reverse scalar(@$bits) .. $self->{'wantlen'}) {
169             ### pad with: $bit
170 76         57 $bits->[$pos] = $bit;
171 76         39 $value += $bit;
172 76         64 $bit *= 4;
173             }
174             }
175              
176             ### return: "value=$value bits=".join(',',@$bits)
177             return ($self->{'i'}++,
178 123         204 $self->{'value'} = $value);
179             }
180              
181             sub ith {
182 36     36 1 65 my ($self, $i) = @_;
183             ### BalancedBinary ith(): $i
184              
185 36 100       50 if ($i < 1) {
186 4         4 return undef;
187             }
188 32 50       51 if (_is_infinite($i)) {
189 0         0 return $i;
190             }
191              
192 32         29 $i -= 1;
193             ### initial i remainder: $i
194              
195 32         26 my $zero = ($i*0);
196              
197 32         20 my @num;
198 32         37 $num[0][0] = 0;
199 32         29 $num[1][0] = 1;
200 32         21 $num[1][1] = 1;
201 32         18 my $z = 1;
202              
203 32         24 for ( ; ; $z++) {
204 97         80 my $prev = $num[$z][0] = 1; # all zeros, no ones
205 97 50       111 if ($z == 16) {
206 0         0 $prev += $zero;
207 0 0       0 if (! ref $zero) {
208             ### promote to BigInt ...
209 0         0 $zero = _to_bigint($zero);
210             }
211             }
212 97         99 foreach my $o (1 .. $z) {
213 211   100     406 $num[$z][$o]
214             = ($prev # 1... $num[$z][$o-1]
215             += ($num[$z-1][$o] || 0)); # 0... if $z>=1
216             }
217 97         67 my $catalan = $num[$z][$z];
218 97 100       127 if ($i < $catalan) {
219 32         29 last;
220             }
221             ### subtract catalan: $catalan
222 65         64 $i -= $catalan;
223             }
224              
225             ### i remaining: $i
226 32         32 my @ret = (1);
227 32         29 my $o = $z-1;
228 32         38 while ($o >= 1) {
229             ### at: "i=$i z=$z o=$o ret=".join('',@ret)
230             ### assert: $z >= $o
231              
232 110 100       121 if ($z > $o) {
233             ### compare: "z=".($z-1).",o=$o num=".($num[$z-1][$o]||'undef')
234 75         65 my $znum = $num[$z-1][$o];
235 75 100       83 if ($i < $znum) {
236             ### 0 ...
237 45         52 push @ret, 0;
238 45         30 $z--;
239 45         53 next;
240             }
241 30         17 $i -= $znum;
242             }
243             ### 1 ...
244 65         49 push @ret, 1;
245 65         78 $o--;
246             }
247              
248 32         34 push @ret, (0) x $z;
249             ### final: "ret=".join('',@ret)
250              
251 32 50 33     83 if (! ref $zero && @ret >= 2*_WANTLEN_TO_BIGINT) {
252             ### promote to BigInt ...
253 0         0 $zero = _to_bigint($zero);
254             }
255              
256 32         27 @ret = reverse @ret;
257             ### return: _digit_join(\@ret, 2, $zero)
258 32         43 return _digit_join(\@ret, 2, $zero);
259             }
260              
261             # $aref->[0] low digit
262             sub _digit_join {
263 32     32   29 my ($aref, $radix, $zero) = @_;
264 32         21 my $n = $zero;
265 32         33 foreach my $digit (reverse @$aref) { # high to low
266 194         106 $n *= $radix;
267 194         133 $n += $digit;
268             }
269 32         87 return $n;
270             }
271              
272             sub value_to_i {
273 58     58 1 168 my ($self, $value) = @_;
274             ### BalancedBinary value_to_i(): $value
275              
276 58 100 100     187 if ($value < 2 || $value != int($value)) {
277 26         36 return undef;
278             }
279 32 50       987 if (_is_infinite($value)) {
280 0         0 return $value;
281             }
282              
283 32 50       2231 my @bits = _digit_split_lowtohigh($value,2)
284             or return undef;
285              
286 32 100       62 _pred_on_bits($self,\@bits)
287             or return undef;
288              
289 22         24 my @num;
290 22         23 $num[0][0] = 0;
291 22         22 $num[1][0] = 1;
292 22         18 $num[1][1] = 1;
293 22         28 my $w = scalar(@bits) / 2;
294             ### $w
295              
296 22         33 my $zero = $value*0;
297 22         1173 my $i = 1 + $zero;
298              
299 22         905 foreach my $z (1 .. $w) {
300 64         1718 my $prev = $num[$z][0] = 1; # all zeros, no ones
301 64 50       94 if ($z > 16) {
302 0         0 $prev += $zero;
303             }
304 64         71 foreach my $o (1 .. $z) {
305 134   100     316 $num[$z][$o]
306             = ($prev # 1... $num[$z][$o-1]
307             += ($num[$z-1][$o] || 0)); # 0... if $z>=1
308             }
309 64         105 $i += $num[$z-1][$z-1];
310             }
311             ### base i: $i
312              
313             ### bits: join('',@bits)
314 22         805 shift @bits; # skip high 1-bit
315 22         20 my $z = $w;
316 22         22 my $o = $w-1;
317 22         24 foreach my $bit (@bits) { # high to low
318             ### at: "z=$z o=$o bit=$bit"
319             ### assert: $o >= 0
320             ### assert: $z >= $o
321 106 100       117 if ($bit) {
322             ### bit 1 add: $num[$z-1][$o]
323 42   100     124 $i += $num[$z-1][$o] || 0;
324 42         1690 $o--;
325             } else {
326 64         57 $z--;
327             }
328             }
329 22         59 return $i;
330             }
331              
332             sub value_to_i_floor {
333 954     954 1 4374 my ($self, $value) = @_;
334 954         897 return _value_to_i_round ($self, $value, 0);
335             }
336             sub value_to_i_ceil {
337 952     952 1 4333 my ($self, $value) = @_;
338 952         833 return _value_to_i_round ($self, $value, 1);
339             }
340              
341             # Return the i corresponding to $value.
342             # If $value is not balanced binary then round according to $ceil.
343             # $ceil=1 to round up, or $ceil=0 to round down.
344             #
345             sub _value_to_i_round {
346 1906     1906   1290 my ($self, $value, $ceil) = @_;
347             ### _value_to_i_round(): $value
348              
349 1906 100       2171 if ($value < 2) {
350 21         36 return $self->i_start();
351             }
352 1885 50       3512 if (_is_infinite($value)) {
353 0         0 return $value;
354             }
355              
356             {
357 1885         5788 my $int = int($value);
  1885         1241  
358 1885 100       2401 if ($value != $int) {
359 32         34 $value = $int + $ceil; # +1 if not integer and want ceil
360             }
361             }
362 1885         2799 my @bits = reverse _digit_split_lowtohigh($value,2);
363              
364 1885 100       2580 if (scalar(@bits) & 1) {
365             # ENHANCE-ME: this is Catalan cumulative, or cumulative+1
366             ### odd num bits ...
367 681         1045 @bits = ((0) x scalar(@bits), 1);
368             }
369             ### assert: (scalar(@bits) % 2) == 0
370              
371 1885         1319 my @num;
372 1885         1834 $num[0][0] = 0;
373 1885         1319 $num[1][0] = 1;
374 1885         1195 $num[1][1] = 1;
375 1885         1518 my $w = scalar(@bits)/2;
376             ### $w
377              
378 1885         1320 my $zero = $value*0;
379 1885         3636 my $i = 1 + $zero;
380              
381 1885         3874 foreach my $z (1 .. $w) {
382 8492         10507 my $prev = $num[$z][0] = 1; # all zeros, no ones
383 8492 50       9123 if ($z > 16) {
384 0         0 $prev += $zero;
385             }
386 8492         6942 foreach my $o (1 .. $z) {
387 24000   100     39339 $num[$z][$o]
388             = ($prev # 1... $num[$z][$o-1]
389             += ($num[$z-1][$o] || 0)); # 0... if $z>=1
390             }
391 8492         7752 $i += $num[$z-1][$z-1];
392             }
393             ### base i: $i
394              
395             ### bits: join('',@bits)
396 1885         2871 shift @bits; # skip high 1-bit
397 1885         1267 my $z = $w;
398 1885         1170 my $o = $w-1;
399              
400 1885         1579 foreach my $bit (@bits) { # high to low
401             ### at: "z=$z o=$o bit=$bit"
402             ### assert: $o >= 0
403             ### assert: $z >= $o
404 7186 100       6114 if ($bit) {
405 2581 100       2705 if ($o == 0) {
406             ### all 1s used, rest round down to zeros, so done: $i + $ceil
407 387         869 return $i + $ceil;
408             }
409             ### bit 1 add: $num[$z-1][$o]
410 2194   100     3420 $i += $num[$z-1][$o] || 0;
411 2194         4954 $o--;
412              
413             } else {
414 4605 100       5033 if ($z == $o) {
415             ### 0 out of place, round up to 101010 ...
416 1323         1466 while ($o) {
417 4413   50     8333 $i += $num[$o-1][$o] || 0;
418 4413         4721 $o--;
419             }
420 1323         3004 return $i - (1-$ceil);
421             }
422              
423 3282         2524 $z--;
424             }
425             }
426 175         437 return $i;
427             }
428              
429              
430              
431             # 1 10,
432             # 2 1010,
433             # 3 1100,
434             # 4 101010,
435             # 5 101100,
436             # 6 110010,
437             # 7 110100,
438             # 8 111000,
439             # 9 10101010, 170
440             #
441             # 10xxxx
442             # 1xxxx0
443             # num(width) = 2*num(width-1) + extra
444             # num(1) = 1
445             # num(2) = 2*1 = 2
446             # num(3) = 2*2 + 1 = 5
447             # total(width)
448             # = num(1) + num(2) + num(3) + ... + num(width)
449             # = 1 + 2*1 + 2*2+1 +
450              
451             sub pred {
452 364     364 1 848 my ($self, $value) = @_;
453             ### BalancedBinary pred(): $value
454              
455 364 100 66     620 if ($value != int($value) || _is_infinite($value)) {
456 176         146 return 0;
457             }
458              
459 188 50       274 my @bits = _digit_split_lowtohigh($value,2)
460             or return 0;
461 188         218 return _pred_on_bits($self,\@bits);
462             }
463             sub _pred_on_bits {
464 220     220   155 my ($self, $bits) = @_;
465             ### _pred_on_bits(): $bits
466              
467 220 100       266 if (scalar(@$bits) & 1) {
468 84         94 return 0;
469             }
470              
471 136 50       220 if ($self->{'direction'} eq 'HtoL') {
472 136         219 @$bits = reverse @$bits;
473             ### reversed bits: @$bits
474             }
475              
476 136         134 my @count = (0,0);
477 136         118 foreach my $bit (@$bits) {
478 666         387 $count[$bit]++;
479 666 100       754 if ($count[0] > $count[1]) {
480 58         83 return 0;
481             }
482             }
483             ### @count
484 78         133 return ($count[0] == $count[1]);
485             }
486              
487             # w = log2(value) / 2
488             # Catalan = 4^w / (sqrt(Pi * w) * (w + 1))
489             # = 4^(log2(v)/2) / (sqrt(pi*log2(v)/2) * (log2(v)/2+1))
490             # = v / (sqrt(pi*log2(v)/2) * (log2(v)/2+1))
491             # = v / (sqrt(pi*log2(v)/2) * (log2(v)+2)/2)
492             # = v / (sqrt(pi*log2(v)/2)/2 * (log2(v)+2))
493             # = v / (sqrt(pi/8*log2(v)) * (log2(v)+2))
494             #
495             # if value has 2*w bits then cumulative catalan
496             # i = sum w=0 to log2(value)/2
497             # of value / (sqrt(pi/8*log2(value)) * (log2(value)+2))
498             #
499             # is cumulative close enough to the plain ?
500             #
501             # 2*w bits, ipart=Catalan(w) value=4^w
502             # so w = log4(value)
503             # ipart = Catalan(log4(value))
504             # =
505              
506             sub value_to_i_estimate {
507 20     20 1 1205 my ($self, $value) = @_;
508             ### value_to_i_estimate: $value
509              
510 20 100       27 if ($value <= 2) {
511 8         8 return 1;
512             }
513              
514 12         82 my $log2 = _blog2_estimate($value);
515 12 100       240 if (! defined $log2) {
516 11         16 $log2 = log($value) * (1/log(2));
517             }
518 12         16 $log2 = max($log2,1);
519              
520 12 100 66     23 if (ref $value && $value->isa('Math::BigInt')) {
521             # oldish BigInt doesn't like to divide BigInt/NV
522 1         924 require Math::BigFloat;
523 1         13417 $value = Math::BigFloat->new($value);
524             }
525              
526 12         663 return max(1,
527             int($value / (sqrt((3.141592/8)*$log2) * ($log2+1))));
528             }
529              
530             1;
531             __END__