File Coverage

blib/lib/Math/NumSeq/BalancedBinary.pm
Criterion Covered Total %
statement 220 230 95.6
branch 55 68 80.8
condition 20 26 76.9
subroutine 26 26 100.0
pod 10 10 100.0
total 331 360 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   15832 use 5.004;
  2         9  
  2         107  
25 2     2   15 use strict;
  2         4  
  2         90  
26 2     2   13 use List::Util 'max';
  2         6  
  2         353  
27              
28 2     2   12 use vars '$VERSION', '@ISA';
  2         4  
  2         156  
29             $VERSION = 71;
30              
31 2     2   706 use Math::NumSeq;
  2         5  
  2         130  
32             @ISA = ('Math::NumSeq');
33             *_is_infinite = \&Math::NumSeq::_is_infinite;
34             *_to_bigint = \&Math::NumSeq::_to_bigint;
35              
36 2     2   1678 use Math::NumSeq::Repdigits;
  2         5  
  2         207  
37             *_digit_split_lowtohigh = \&Math::NumSeq::Repdigits::_digit_split_lowtohigh;
38              
39 2     2   991 use Math::NumSeq::Fibonacci;
  2         6  
  2         115  
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   13 use constant description => Math::NumSeq::__('Bits 1,0 balanced like parentheses.');
  2         84  
  2         11  
48 2     2   13 use constant characteristic_increasing => 1;
  2         3  
  2         182  
49 2     2   14 use constant characteristic_integer => 1;
  2         3  
  2         102  
50 2     2   11 use constant default_i_start => 1;
  2         4  
  2         94  
51 2     2   17 use constant values_min => 2;
  2         4  
  2         466  
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 2639 my $self = shift->SUPER::new(@_);
71 4   50     29 $self->{'direction'} ||= 'HtoL';
72 4         10 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 5 my ($self) = @_;
93 1         3 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         8 use constant 1.02 _WANTLEN_TO_BIGINT => do {
111 2         4 my $uv = ~0;
112 2         3 my $limit = 0;
113 2         11 while ($uv >= 3) {
114 64         57 $uv >>= 2;
115 64         103 $limit++;
116             }
117             ### $limit
118             $limit
119 2     2   13 };
  2         42  
  2         7193  
120              
121             sub rewind {
122 6     6 1 613 my ($self) = @_;
123 6         25 $self->{'wantlen'} = 0;
124 6         18 $self->{'bits'} = [];
125 6         13 $self->{'value'} = 0;
126 6         32 $self->{'i'} = $self->i_start;
127             }
128              
129             sub next {
130 123     123 1 1567 my ($self) = @_;
131             ### BalancedBinary next() ...
132 123         203 my $bits = $self->{'bits'};
133 123         133 my $value;
134              
135 123 100       222 if (! @$bits) {
136             ### initial 2 ...
137 4         8 push @$bits, 2;
138 4         8 $value = 2;
139              
140             } else {
141 119         163 $value = $self->{'value'};
142 119         107 for (;;) {
143             ### at: "bits=".join(',',@{$self->{'bits'}})
144 181 100       319 if (scalar(@$bits) < 2) {
145             ### extend to wantlen: $self->{'wantlen'}+1
146 14         20 $value = ($bits->[0] *= 4);
147 14 50       31 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         19 last;
152             }
153              
154 167         183 $value -= $bits->[-1];
155 167         218 my $bit = ($bits->[-1] *= 2);
156 167 100       313 if ($bit < $bits->[-2]) {
157             ### shifted bit ...
158 105         100 $value += $bit;
159 105         135 last;
160             }
161             ### drop this bit ...
162 62         86 pop @$bits;
163             }
164              
165             ### pad for: "value=$value bits=".join(',',@$bits)
166             # trailing bits ...,128, 32, 8, 2
167 119         162 my $bit = 2 + ($bits->[0]&1); # inherit BigInt
168 119         297 foreach my $pos (reverse scalar(@$bits) .. $self->{'wantlen'}) {
169             ### pad with: $bit
170 76         95 $bits->[$pos] = $bit;
171 76         71 $value += $bit;
172 76         130 $bit *= 4;
173             }
174             }
175              
176             ### return: "value=$value bits=".join(',',@$bits)
177 123         415 return ($self->{'i'}++,
178             $self->{'value'} = $value);
179             }
180              
181             sub ith {
182 36     36 1 106 my ($self, $i) = @_;
183             ### BalancedBinary ith(): $i
184              
185 36 100       80 if ($i < 1) {
186 4         9 return undef;
187             }
188 32 50       79 if (_is_infinite($i)) {
189 0         0 return $i;
190             }
191              
192 32         52 $i -= 1;
193             ### initial i remainder: $i
194              
195 32         46 my $zero = ($i*0);
196              
197 32         36 my @num;
198 32         65 $num[0][0] = 0;
199 32         55 $num[1][0] = 1;
200 32         44 $num[1][1] = 1;
201 32         42 my $z = 1;
202              
203 32         37 for ( ; ; $z++) {
204 97         180 my $prev = $num[$z][0] = 1; # all zeros, no ones
205 97 50       200 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         161 foreach my $o (1 .. $z) {
213 211   100     756 $num[$z][$o]
214             = ($prev # 1... $num[$z][$o-1]
215             += ($num[$z-1][$o] || 0)); # 0... if $z>=1
216             }
217 97         170 my $catalan = $num[$z][$z];
218 97 100       189 if ($i < $catalan) {
219 32         46 last;
220             }
221             ### subtract catalan: $catalan
222 65         112 $i -= $catalan;
223             }
224              
225             ### i remaining: $i
226 32         61 my @ret = (1);
227 32         57 my $o = $z-1;
228 32         67 while ($o >= 1) {
229             ### at: "i=$i z=$z o=$o ret=".join('',@ret)
230             ### assert: $z >= $o
231              
232 110 100       195 if ($z > $o) {
233             ### compare: "z=".($z-1).",o=$o num=".($num[$z-1][$o]||'undef')
234 75         109 my $znum = $num[$z-1][$o];
235 75 100       144 if ($i < $znum) {
236             ### 0 ...
237 45         63 push @ret, 0;
238 45         54 $z--;
239 45         99 next;
240             }
241 30         42 $i -= $znum;
242             }
243             ### 1 ...
244 65         109 push @ret, 1;
245 65         123 $o--;
246             }
247              
248 32         65 push @ret, (0) x $z;
249             ### final: "ret=".join('',@ret)
250              
251 32 50 33     142 if (! ref $zero && @ret >= 2*_WANTLEN_TO_BIGINT) {
252             ### promote to BigInt ...
253 0         0 $zero = _to_bigint($zero);
254             }
255              
256 32         45 @ret = reverse @ret;
257             ### return: _digit_join(\@ret, 2, $zero)
258 32         76 return _digit_join(\@ret, 2, $zero);
259             }
260              
261             # $aref->[0] low digit
262             sub _digit_join {
263 32     32   54 my ($aref, $radix, $zero) = @_;
264 32         40 my $n = $zero;
265 32         50 foreach my $digit (reverse @$aref) { # high to low
266 194         202 $n *= $radix;
267 194         299 $n += $digit;
268             }
269 32         193 return $n;
270             }
271              
272             sub value_to_i {
273 58     58 1 290 my ($self, $value) = @_;
274             ### BalancedBinary value_to_i(): $value
275              
276 58 100 100     288 if ($value < 2 || $value != int($value)) {
277 26         69 return undef;
278             }
279 32 50       1540 if (_is_infinite($value)) {
280 0         0 return $value;
281             }
282              
283 32 50       3797 my @bits = _digit_split_lowtohigh($value,2)
284             or return undef;
285              
286 32 100       89 _pred_on_bits($self,\@bits)
287             or return undef;
288              
289 22         35 my @num;
290 22         131 $num[0][0] = 0;
291 22         51 $num[1][0] = 1;
292 22         39 $num[1][1] = 1;
293 22         53 my $w = scalar(@bits) / 2;
294             ### $w
295              
296 22         62 my $zero = $value*0;
297 22         1964 my $i = 1 + $zero;
298              
299 22         1445 foreach my $z (1 .. $w) {
300 64         2816 my $prev = $num[$z][0] = 1; # all zeros, no ones
301 64 50       157 if ($z > 16) {
302 0         0 $prev += $zero;
303             }
304 64         125 foreach my $o (1 .. $z) {
305 134   100     540 $num[$z][$o]
306             = ($prev # 1... $num[$z][$o-1]
307             += ($num[$z-1][$o] || 0)); # 0... if $z>=1
308             }
309 64         204 $i += $num[$z-1][$z-1];
310             }
311             ### base i: $i
312              
313             ### bits: join('',@bits)
314 22         1330 shift @bits; # skip high 1-bit
315 22         36 my $z = $w;
316 22         39 my $o = $w-1;
317 22         39 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       189 if ($bit) {
322             ### bit 1 add: $num[$z-1][$o]
323 42   100     173 $i += $num[$z-1][$o] || 0;
324 42         2649 $o--;
325             } else {
326 64         107 $z--;
327             }
328             }
329 22         121 return $i;
330             }
331              
332             sub value_to_i_floor {
333 954     954 1 6923 my ($self, $value) = @_;
334 954         1455 return _value_to_i_round ($self, $value, 0);
335             }
336             sub value_to_i_ceil {
337 952     952 1 6896 my ($self, $value) = @_;
338 952         1414 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   2265 my ($self, $value, $ceil) = @_;
347             ### _value_to_i_round(): $value
348              
349 1906 100       3517 if ($value < 2) {
350 21         51 return $self->i_start();
351             }
352 1885 50       5415 if (_is_infinite($value)) {
353 0         0 return $value;
354             }
355              
356             {
357 1885         8953 my $int = int($value);
  1885         2060  
358 1885 100       3716 if ($value != $int) {
359 32         59 $value = $int + $ceil; # +1 if not integer and want ceil
360             }
361             }
362 1885         4849 my @bits = reverse _digit_split_lowtohigh($value,2);
363              
364 1885 100       3870 if (scalar(@bits) & 1) {
365             # ENHANCE-ME: this is Catalan cumulative, or cumulative+1
366             ### odd num bits ...
367 681         1836 @bits = ((0) x scalar(@bits), 1);
368             }
369             ### assert: (scalar(@bits) % 2) == 0
370              
371 1885         2009 my @num;
372 1885         3307 $num[0][0] = 0;
373 1885         2629 $num[1][0] = 1;
374 1885         2474 $num[1][1] = 1;
375 1885         2425 my $w = scalar(@bits)/2;
376             ### $w
377              
378 1885         2282 my $zero = $value*0;
379 1885         5544 my $i = 1 + $zero;
380              
381 1885         5431 foreach my $z (1 .. $w) {
382 8492         17392 my $prev = $num[$z][0] = 1; # all zeros, no ones
383 8492 50       13454 if ($z > 16) {
384 0         0 $prev += $zero;
385             }
386 8492         10535 foreach my $o (1 .. $z) {
387 24000   100     64984 $num[$z][$o]
388             = ($prev # 1... $num[$z][$o-1]
389             += ($num[$z-1][$o] || 0)); # 0... if $z>=1
390             }
391 8492         14786 $i += $num[$z-1][$z-1];
392             }
393             ### base i: $i
394              
395             ### bits: join('',@bits)
396 1885         4585 shift @bits; # skip high 1-bit
397 1885         1939 my $z = $w;
398 1885         1943 my $o = $w-1;
399              
400 1885         2177 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       8904 if ($bit) {
405 2581 100       3857 if ($o == 0) {
406             ### all 1s used, rest round down to zeros, so done: $i + $ceil
407 387         1672 return $i + $ceil;
408             }
409             ### bit 1 add: $num[$z-1][$o]
410 2194   100     4685 $i += $num[$z-1][$o] || 0;
411 2194         7580 $o--;
412              
413             } else {
414 4605 100       6919 if ($z == $o) {
415             ### 0 out of place, round up to 101010 ...
416 1323         2218 while ($o) {
417 4413   50     12112 $i += $num[$o-1][$o] || 0;
418 4413         6889 $o--;
419             }
420 1323         5916 return $i - (1-$ceil);
421             }
422              
423 3282         3757 $z--;
424             }
425             }
426 175         867 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 2058 my ($self, $value) = @_;
453             ### BalancedBinary pred(): $value
454              
455 364 100 66     1299 if ($value != int($value) || _is_infinite($value)) {
456 176         387 return 0;
457             }
458              
459 188 50       532 my @bits = _digit_split_lowtohigh($value,2)
460             or return 0;
461 188         438 return _pred_on_bits($self,\@bits);
462             }
463             sub _pred_on_bits {
464 220     220   314 my ($self, $bits) = @_;
465             ### _pred_on_bits(): $bits
466              
467 220 100       483 if (scalar(@$bits) & 1) {
468 84         240 return 0;
469             }
470              
471 136 50       419 if ($self->{'direction'} eq 'HtoL') {
472 136         421 @$bits = reverse @$bits;
473             ### reversed bits: @$bits
474             }
475              
476 136         252 my @count = (0,0);
477 136         220 foreach my $bit (@$bits) {
478 666         716 $count[$bit]++;
479 666 100       1359 if ($count[0] > $count[1]) {
480 58         196 return 0;
481             }
482             }
483             ### @count
484 78         332 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 1889 my ($self, $value) = @_;
508             ### value_to_i_estimate: $value
509              
510 20 100       47 if ($value <= 2) {
511 8         19 return 1;
512             }
513              
514 12         123 my $log2 = _blog2_estimate($value);
515 12 100       398 if (! defined $log2) {
516 11         27 $log2 = log($value) * (1/log(2));
517             }
518 12         23 $log2 = max($log2,1);
519              
520 12 100 66     34 if (ref $value && $value->isa('Math::BigInt')) {
521             # oldish BigInt doesn't like to divide BigInt/NV
522 1         1844 require Math::BigFloat;
523 1         25157 $value = Math::BigFloat->new($value);
524             }
525              
526 12         1190 return max(1,
527             int($value / (sqrt((3.141592/8)*$log2) * ($log2+1))));
528             }
529              
530             1;
531             __END__