Skip to content

Commit

Permalink
- Added the Number next_palindrome(n, b=10) method.
Browse files Browse the repository at this point in the history
Efficiently returns the next palindrome in base-b greater than n.

Example:

	# All the base-10 palindromic numbers < 10^6
	for (var n = 0; n < 1e6; n = n.next_palindrome) {
		say n
	}
  • Loading branch information
trizen committed Jun 6, 2020
1 parent 85f9bb8 commit 2ffe027
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 7 deletions.
3 changes: 2 additions & 1 deletion MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,8 @@ scripts/Tests/one-dimensional_cellular_automata_2.sf
scripts/Tests/open.sf
scripts/Tests/pair_namedparam_force_ops.sf
scripts/Tests/pairs_test.sf
scripts/Tests/palindrome_recursive.sf
scripts/Tests/palindrome_detection_recursive.sf
scripts/Tests/palindromic_numbers.sf
scripts/Tests/pascal_matrix_generation.sf
scripts/Tests/pcg.sf
scripts/Tests/perfect_root_power.sf
Expand Down
161 changes: 155 additions & 6 deletions lib/Sidef/Types/Number/Number.pm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ package Sidef::Types::Number::Number {

state $HAS_PRIME_UTIL = eval { require Math::Prime::Util; 1 };

my %DIGITS_36;
@DIGITS_36{0 .. 9, 'a' .. 'z'} = (0 .. 35);

my %DIGITS_62;
@DIGITS_62{0 .. 9, 'A' .. 'Z', 'a' .. 'z'} = (0 .. 61);

my %FROM_DIGITS_36;
@FROM_DIGITS_36{0 .. 35} = (0 .. 9, 'a' .. 'z');

my %FROM_DIGITS_62;
@FROM_DIGITS_62{0 .. 61} = (0 .. 9, 'A' .. 'Z', 'a' .. 'z');

#<<<
use constant {
ONE => bless(\$ONE),
Expand Down Expand Up @@ -5962,12 +5974,6 @@ package Sidef::Types::Number::Number {
Sidef::Types::Array::Array->new([map { $_ ? ONE : ZERO } split(//, $bin)]);
}

my %DIGITS_36;
@DIGITS_36{0 .. 9, 'a' .. 'z'} = (0 .. 35);

my %DIGITS_62;
@DIGITS_62{0 .. 9, 'A' .. 'Z', 'a' .. 'z'} = (0 .. 61);

sub digits {
my ($n, $k) = @_;

Expand Down Expand Up @@ -6061,6 +6067,60 @@ package Sidef::Types::Number::Number {
Sidef::Types::Array::Array->new(\@digits);
}

sub __digits2num__ {
my ($base, $digits) = @_;

# $base is a native integer
# $digits is a non-empty array of native integers < base (msd first)

if ($base <= 10) {
return Math::GMPz::Rmpz_init_set_str(join('', @$digits), $base);
}

if ($base <= 36) {
return Math::GMPz::Rmpz_init_set_str(join('', map { $FROM_DIGITS_36{$_} } @$digits), $base);
}

if ($base <= 62) {
return Math::GMPz::Rmpz_init_set_str(join('', map { $FROM_DIGITS_62{$_} } @$digits), $base);
}

my @D = CORE::reverse(@$digits);
my $len = scalar(@D);

if (CORE::log($base) * $len < CORE::log(ULONG_MAX)) {
my $r = 0;
my $B = 1;

foreach my $d (@D) {
$r += $B * $d;
$B *= $base;
}

return ${__PACKAGE__->_set_uint($r)};
}

my @d = map { Math::GMPz::Rmpz_init_set_ui($_) } @D;
my $B = Math::GMPz::Rmpz_init_set_ui($base);
my $L = \@d;

for (my $k = $len ; $k > 1 ; $k = ($k >> 1) + ($k & 1)) {

my @T;
for (0 .. ($k >> 1) - 1) {
my $t = $L->[2 * $_];
Math::GMPz::Rmpz_addmul($t, $L->[2 * $_ + 1], $B);
push(@T, $t);
}

push(@T, $L->[-1]) if ($k & 1);
$L = \@T;
Math::GMPz::Rmpz_mul($B, $B, $B);
}

$L->[0];
}

sub digits2num {
my ($base, $D) = @_;

Expand Down Expand Up @@ -6091,6 +6151,8 @@ package Sidef::Types::Number::Number {

if ($all_mpz and Math::GMPz::Rmpz_cmp_ui($base, 2) >= 0) {

# TODO: use `__digits2num__` when possible.

if (Math::GMPz::Rmpz_cmp_ui($base, 62) <= 0) { # return faster for base in 2..62

my $str = '';
Expand Down Expand Up @@ -14518,6 +14580,93 @@ package Sidef::Types::Number::Number {

*is_palindromic = \&is_palindrome;

sub next_palindrome {
my ($n, $base) = @_;

if (defined($base)) {
_valid(\$base);
$base = _any2ui($$base) // goto &nan;
}
else {
$base //= 10;
}

$base <= 1 and goto &nan;

$n = _any2mpz($$n) // goto &nan;

my @d;

if ($base <= 10) {
@d = split(//, scalar CORE::reverse Math::GMPz::Rmpz_get_str($n, $base));
}
elsif ($base <= 36) {
@d = map { $DIGITS_36{$_} } split(//, scalar CORE::reverse Math::GMPz::Rmpz_get_str($n, $base));
}
elsif ($base <= 62) {
@d = map { $DIGITS_62{$_} } split(//, scalar CORE::reverse Math::GMPz::Rmpz_get_str($n, $base));
}
else {
@d = map { Math::GMPz::Rmpz_get_ui($$_) } @{$_[0]->digits($_[1])};
}

my $l = $#d;
my $i = ((scalar(@d) + 1) >> 1) - 1;

my $is_palindrome = 1;

foreach my $j (0 .. $i) {
if ($d[$j] != $d[$l - $j]) {
$is_palindrome = 0;
last;
}
}

if (!$is_palindrome) {
my @copy = @d;

foreach my $i (0 .. $i) {
$d[$i] = $d[$l - $i];
}

my $is_greater = 1;

foreach my $j (0 .. $i) {
my $cmp = $d[$i - $j] <=> $copy[$i - $j];

if ($cmp > 0) {
last;
}
if ($cmp < 0) {
$is_greater = 0;
last;
}
}

if ($is_greater) {
return bless \__digits2num__($base, \@d);
}
}

while ($i >= 0 and $d[$i] == $base - 1) {
$d[$i] = 0;
$d[$l - $i] = 0;
$i--;
}

if ($i >= 0) {
$d[$i]++;
$d[$l - $i] = $d[$i];
}
else {
@d = (0) x (scalar(@d) + 1);
$d[0] = 1;
$d[-1] = 1;
}

bless \__digits2num__($base, \@d);
}

sub reverse {
my ($n, $k) = @_;

Expand Down
File renamed without changes.
76 changes: 76 additions & 0 deletions scripts/Tests/palindromic_numbers.sf
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/ruby

# Tests for numeric palindromic-related methods.

func my_next_palindrome(n,b) {
n + n.is_palindrome(b) .. Inf -> first { .is_palindrome(b) }
}

for k in (1..100) {
var b = irand(2, 100)
assert_eq(next_palindrome(k, b), my_next_palindrome(k, b), "next_palindrome(#{k}, #{b})")
}

for b in (2..10) {
with (0) { |k|
var arr = gather {
100.times {
take(k)
k = next_palindrome(k, b)
}
}
assert_eq(arr, 100.by { .is_palindrome(b) })
}
}

assert_eq(next_palindrome(10), 11);
assert_eq(next_palindrome(11), 22);
assert_eq(next_palindrome(12), 22);
assert_eq(next_palindrome(110), 111);
assert_eq(next_palindrome(111), 121);
assert_eq(next_palindrome(112), 121);
assert_eq(next_palindrome(120), 121);
assert_eq(next_palindrome(121), 131);
assert_eq(next_palindrome(1234), 1331);
assert_eq(next_palindrome(12345), 12421);

assert_eq(next_palindrome(8887), 8888);
assert_eq(next_palindrome(8888), 8998);
assert_eq(next_palindrome(8889), 8998);
assert_eq(next_palindrome(88887), 88888);
assert_eq(next_palindrome(88888), 88988);
assert_eq(next_palindrome(88889), 88988);
assert_eq(next_palindrome(9998), 9999);
assert_eq(next_palindrome(99998), 99999);
assert_eq(next_palindrome(9999), 10001);
assert_eq(next_palindrome(99999), 100001);

assert_eq(next_palindrome(12311), 12321);
assert_eq(next_palindrome(1321), 1331);
assert_eq(next_palindrome(1331), 1441);
assert_eq(next_palindrome(13530), 13531);
assert_eq(next_palindrome(13520), 13531);
assert_eq(next_palindrome(13521), 13531);
assert_eq(next_palindrome(13530), 13531);
assert_eq(next_palindrome(13531), 13631);
assert_eq(next_palindrome(13540), 13631);
assert_eq(next_palindrome(13532), 13631);

assert_eq(next_palindrome(1234, 2), 1241);
assert_eq(next_palindrome(1234, 3), 1249);
assert_eq(next_palindrome(1234, 4), 1265);
assert_eq(next_palindrome(1234, 5), 1246);
assert_eq(next_palindrome(1234, 6), 1253);

assert_eq(next_palindrome(12345, 2), 12483);
assert_eq(next_palindrome(12345, 3), 12382);
assert_eq(next_palindrome(12345, 4), 12355);
assert_eq(next_palindrome(12345, 5), 12348);
assert_eq(next_palindrome(12345, 6), 12439);

assert_eq(next_palindrome(2**65), 36893488155188439863)
assert_eq(next_palindrome(2**67), 147573952595259375741)
assert_eq(next_palindrome(2**65, 100), 36893488151588348936)
assert_eq(next_palindrome(2**67, 100), 147573952595239574701)

say "** Test passed!"

0 comments on commit 2ffe027

Please sign in to comment.