Perl Weekly Challenge 149: Fibonacci Digit Sum and Largest Square

These are some answers to the Week 149 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on January 30, 2022 at 24:00). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Task 1: Fibonacci Digit Sum

Given an input $N, generate the first $N numbers for which the sum of their digits is a Fibonacci number.

Example:

f(20)=[0, 1, 2, 3, 5, 8, 10, 11, 12, 14, 17, 20, 21, 23, 26, 30, 32, 35, 41, 44]

Fibonacci Digit Sum in Raku

We first populate a Set (for fast look-up) with the Fibonacci numbers up to 1000. Note that we could choose a much smaller maximum value, but it doesn’t cost much and it will work with very large sequences.

To generate the Fibonacci numbers, we use the sequence operator, in which we provide the first two numbers of the Fibonacci sequence and a formula (* + *) to compute a Fibonacci number from the two previous ones. The last part of the code line is the stopping condition. This idiomatic construction makes the code very concise and fast. This is an example in a Raku one-liner:

$ raku -e 'say 0, 1, * + * ...^  * > 100'
(0 1 1 2 3 5 8 13 21 34 55 89)

Then, we use the gather / take construct to loop over successive integers and store those satisfying the criteria, and we stop when we reach the limit.

sub MAIN (Int $max = 20) {       # 20: default input value
    my $fibo = set(0, 1, * + * ...^  * > 1000);
    my $count = 0;
    my @numbers = gather {
        for 0 .. Inf -> $num {
            if $num.comb.sum (elem) $fibo {
                take $num;
                $count++;
                last if $count >= $max;
            }
        }
    }
    say @numbers;
}

This script displays the following output:

$ raku ./fib_digit_sum.raku
[0 1 2 3 5 8 10 11 12 14 17 20 21 23 26 30 32 35 41 44]

$ raku ./fib_digit_sum.raku 25
[0 1 2 3 5 8 10 11 12 14 17 20 21 23 26 30 32 35 41 44 49 50 53 58 62]

Fibonacci Digit Sum in Perl

The Perl solution follows essentially the same ideas as the Raku implementation, except that we use a plain while loop (instead of a sequence) to generate the Fibonacci numbers and we store them in a hash instead of a set for fast look-up.

use strict;
use warnings;
use feature "say";

sub fib_digit_sum {
    my $max = shift;
    my @fib = (0, 1);
    my $count = 1;
    while ($count < 100) {
        push @fib, $fib[-1] + $fib[-2];
        $count ++;
    }
    my %fibo = map { $_ => 1 } @fib;
    my @numbers;
    my $count_result = 0;
    my $i = 0;
    while ($count_result < $max) {
        my $sum = 0;
        $sum += $_ for split //, $i;
        if (exists $fibo{$sum}) {
            push @numbers, $i;
            $count_result++;
        }
        $i++
    }           
     say "@numbers";
}

fib_digit_sum(shift);

This program displays the following output:

$ perl ./fib_digit_sum.pl 20
0 1 2 3 5 8 10 11 12 14 17 20 21 23 26 30 32 35 41 44

$ perl ./fib_digit_sum.pl 50
0 1 2 3 5 8 10 11 12 14 17 20 21 23 26 30 32 35 41 44 49 50 53 58 62 67 71 76 80 85 94 100 101 102 104 107 110 111 113 116 120 122 125 131 134 139 140 143 148 152

Task 2: Largest Square

Given a number base, derive the largest perfect square with no repeated digits and return it as a string. (For base>10, use ‘A’..‘Z’.)

Example:

f(2)="1"
f(4)="3201"
f(10)="9814072356"
f(12)="B8750A649321"

Note that any program that I can think of for solving this task is bound to become very slow for large bases (especially some of them such as 13 in the case of the programs below).

Largest Square in Raku

We start with the largest number that can be expressed in $base digits in the given $base. We compute largest_root, the integer part of the root of that number. Then we iterate down one by one from largest_root and check whether the square of each such number has no duplicate digit when expressed in the input base.

sub largest ($base) {
    my $largest_num = $base ** $base - 1;
    my $largest_root = $largest_num.sqrt.Int;
    for (1..$largest_root).reverse -> $i {
        my $sq = $i².base($base);
        next if $sq.chars != $sq.comb.Set;
        say "f($base) = $sq" and last;
    }
}
for 1..14 -> $test { largest $test }

This program displays the following output:

$ raku ./largest_sq.raku
f(2) = 1
f(3) = 1
f(4) = 3201
f(5) = 4301
f(6) = 452013
f(7) = 6250341
f(8) = 47302651
f(9) = 823146570
f(10) = 9814072356
f(11) = A8701245369
f(12) = B8750A649321
f(13) = CBA504216873
f(14) = DC71B30685A924

Largest Square in Perl

This is essentially the same idea in Perl. We use two helper subroutines: has_unique_digits to discard squares having duplicate digits (using a hash), and base_to_b to convert a number into a string representing that number in the given base.

use strict;
use warnings;
use feature "say";

sub base_to_b {
    my ($n, $b) = @_;
    my $s = "";
    while ($n) {
        $s .= ('0'..'9','A'..'Z')[$n % $b];
        $n = int($n / $b);
    }
    scalar(reverse($s));
}

sub has_unique_digits {
    my $in = shift; 
    my %seen = map { $_ => 1 } split //,  $in;
    return length $in == scalar keys %seen;
}

sub largest {
    my $base = shift;
    my $largest_num = $base ** $base - 1;
    my $largest_root = int sqrt $largest_num;
    for my $i ( reverse 1..$largest_root) {
        my $sq = base_to_b ($i*$i, $base);
        next unless has_unique_digits $sq;
        say "f($base) = $sq" and last;
    }
}

largest $_ for 1..14;

This script displays the following output:

$ perl ./largest_sq.pl
f(2) = 1
f(3) = 1
f(4) = 3201
f(5) = 4301
f(6) = 452013
f(7) = 6250341
f(8) = 47302651
f(9) = 823146570
f(10) = 9814072356
f(11) = A8701245369
f(12) = B8750A649321
f(13) = CBA504216873
f(14) = DC71B30685A924

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on February 6, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.