Perl Weekly Challenge: Week 133

Challenge 1:

Integer Square Root

You are given a positive integer \$N.

Write a script to calculate the integer square root of the given number.

Examples
Input: \$N = 10
Output: 3

Input: \$N = 27
Output: 5

Input: \$N = 85
Output: 9

Input: \$N = 101
Output: 10

The wikipedia link provided gives an algorithm and example C code for solving this problem and my solution is just a straight translation. Here's the Perl version:

sub squareRoot {
my (\$n) = @_;

my \$firstTry = \$n >> 1;

if ( \$firstTry ) {
my \$nextTry = ( \$firstTry + \$n / \$firstTry) >> 1;

while ( \$nextTry < \$firstTry) {
\$firstTry = \$nextTry;
\$nextTry = ( \$firstTry + \$n / \$firstTry) >> 1;
}

return \$firstTry;
} else {
return \$n;
}
}

(Full code on Github.)

And this is the Raku version. The funky right bit shift operator +> was the only stumbling block.

sub squareRoot(Int \$n) {
my \$firstTry = \$n +> 1;

if \$firstTry {
my \$nextTry = ( \$firstTry + \$n / \$firstTry) +> 1;

while \$nextTry < \$firstTry {
\$firstTry = \$nextTry;
\$nextTry = ( \$firstTry + \$n / \$firstTry) +> 1;
}

return \$firstTry;
} else {
return \$n;
}
}

(Full code on Github.)

Challenge 2:

Smith Numbers

Write a script to generate first 10 Smith Numbers in base 10.

According to Wikipedia:

In number theory, a Smith number is a composite number for which, in a given number base, the sum of its digits is equal to the sum of the digits in its prime factorization in the given number base.

I love the challenges where we can reuse code from previous weeks. I used the code I developed for prime factorization most recently in Challenge 123 but it goes way back before that. It is used to create an isSmith() function that return true or false if its' input is a Smith Number. In Perl looks it like this:

sub isSmith {
my (\$n) = @_;
my @digits = split //, \$n;

my @primeFactors;
factorize(\$n, \@primeFactors);

If we only have 1 prime factor or 0 (can that actually happen?) it's not going to be a Smith number so we can just stop here. Because Perl doesn't have actual true and false literals, we return undef to signify false.

if (scalar @primeFactors < 2) {
return undef;
}

Initially I got some weird answers and then I realized that if the prime factors themselves have multiple digits, they also have to be summed. The line below takes care of that. Incidently, sum() is another function I had created earlier and reused.

@primeFactors = map { my @d = split //, \$_; sum(\@d); } @primeFactors;

return sum(\@digits) == sum(\@primeFactors);
}

(Full code on Github.)

This is the Raku version. The [+] hyperoperator for summing a list or array is one of my favorite features of the language.

sub isSmith(\$n) {
my @digits = \$n.comb;

my @primeFactors;
factorize(\$n, @primeFactors);
if @primeFactors.elems < 2 {
return;
}

@primeFactors = @primeFactors.map({ [+] \$_.comb; });

if ([+] @digits) == ([+] @primeFactors) {
take \$n;
}
}

Initially I wrote this to return true or false like the Perl version and it worked but was slower than Perl. (Though not too shabby I must say.) I took another stab at it and refactored it to use take to return valid Smith numbers. This can be used together with gather like this:

my @smiths = lazy gather {
for 1 .. * -> \$n {
isSmith(\$n)
}
}

@smiths[^10].join(', ').say;

(Full code on Github.)

...to create a lazy list. This actually runs faster than Perl, the first time I've seen non-trivial Raku code do that.