Perl Weekly Challenge: Week 393

Challenge 1:

Pythagoras Multiplied

You are given a positive integer n.

Find the number of all positive integer triplets (a, b, c) so that a^2 + b^2 = c^2 and a, b and c are integers <= n.

Example 1
Input: $n = 20
Output: 12

(3,4,5),  (4,3,5),   (5,12,13),(6,8,10),
(8,6,10), (8,15,17), (9,12,15),(12,5,13),
(12,9,15),(12,16,20),(15,8,17),(16,12,20)
Example 2
Input: $n = 7
Output: 2

(3,4,5),(4,3,5)
Example 3
Input: $n = 1
Output: 0
Example 4
Input: $n = 15
Output: 8
Example 5
Input: $n = 30
Output: 22

First we define a place to store the number of triplets found.

my $count = 0;

Then in a double-loop we make combinations of every integer from 1 to $n with each other.

for 1 .. $n -> $a {
    for 1 .. $n -> $b {

We use the pythagorean formula to get the value of $c. I love how you can use unicode characters for many operators in Raku.

        my $c = ($a² + $b²).sqrt;

If $c is less than or equal to $n and $c is an integer...

        if  $c <= $n && $c.Int == $c {

...we increment $count.

            $count++;
        }
    }
}

When all possible triplets have been assessed, we print $count.

say $count;

(Full code on Github.)

The Perl version is almost the same as Raku except we have to square numbers the old-fashioned way.

my $count = 0;

for my $a (1 .. $n) {
    for my $b (1 .. $n) {
        my $c = sqrt($a ** 2 + $b ** 2);
        if  ($c <= $n && int($c) == $c) {
            $count++;
        }
    }
}

say $count;

(Full code on Github.)

Challenge 2:

Prime Step

You are given a string with English alphabetic characters only.

What is the absolute difference of the sum of the ASCII values of the characters in the string to the nearest prime number?

Example 1
Input: $str = "hello"
Output: 9

The ordinal values of "hello" are [104,101,108,108,111], summing up to 532.
The nearest prime number to 532 is 523, resulting in an absolute difference of 9.
Example 2
Input: $str = "football"
Output: 2

Starting with the values [102,111,111,116,98,97,108,108] and the sum 841.
We find 839 as the nearest prime number, so the difference is 2.
Example 3
Input: $str = "a"
Output: 0
Example 4
Input: $str = "challenge"
Output: 2

The ordinal values of "challenge" are [99, 104, 97, 108, 108, 101, 110, 103, 101], which sum up to 931.
The nearest prime number to 931 is 929, so the difference is 2.
Example 5
Input: $str = "perl"
Output: 2

The ordinal values of "perl" are [112, 101, 114, 108], summing up to 435.
Nearest prime is 433, so the difference is 2.

The first part of the solution for this challenge is straitforward. We break up $str into individual characters with .comb(), find the ordinal value for each one with .map() and .ord() and then add them all up with .sum(). The result is assigned to $sum.

my $sum = $str.comb.map({ .ord }).sum;

Now comes the tricky part; how to find the nearest prime number to $sum? In the examples the nearest prime is always lower than $sum but I've chosen to consider the case that it could be higher. Take the string 'pppp' for instance; its' $sum is 448 and the nearest prime to that is 449.

So first I looked for the nearest prime number lower than $sum with a function with the very ungainly name of nearestLowerPrimeDistance().

my $lower = nearestLowerPrimeDistance($sum);

This function takes one parameter, $sum.

sub nearestLowerPrimeDistance($sum) {

The $candidate for lowest prime starts at $sum.

    my $candidate = $sum;

Then while it is greater than 1 (2 is the absolute smallest prime number) and not a prime number, it is decremented.

    while $candidate > 1 && !$candidate.is-prime {
        $candidate--;
    }

Eventually, $candidate will be a prime number. Subtracting it from $sum gives the distance which is returned.

    return $sum - $candidate;
}

Back in MAIN(), we next need to find the nearest prime number greater than $sum. For this we call another function called nearestUpperPrimeDistance().

my $upper = nearestUpperPrimeDistance($sum, $sum + $lower);

This function takes two parameters, $sum and an upper bound which in MAIN() is calculated as $sum plus the lower distance we had already derived. Why this second value? Because if the upper distance is greater than this, we know the lower distance is nearer so there is no point in continuing.

sub nearestUpperPrimeDistance($sum, $upperBound) {

The $candidate once again starts at $sum.

    my $candidate = $sum;

But this time, while it is less than or equal to $upperBound and not a prime number, we increment it.

    while $candidate <= $upperBound && !$candidate.is-prime {
        $candidate++;
    }

We get the distance to return by subtracting $sum from $candidate.

    return $candidate - $sum;
}

Back in MAIN() again, now we know the distances to the nearest prime numbers before and after $sum, we can select whichever is smaller with .min() and orint it out.

say ($lower, $upper).min;

(Full code on Github.)

For Perl we need replacements for .sum() and .is-prime() both of which I copied and pasted from previous challenges.

my $sum = sum(map { ord } split //, $str);
my $lower = nearestLowerPrimeDistance($sum);
my $upper = nearestUpperPrimeDistance($sum, $sum + $lower);

.min() can also be easily replaced by the following line of code.

say $lower < $upper ? $lower : $upper;

sub nearestLowerPrimeDistance($sum) {
    my $candidate = $sum;

    while ($candidate > 1 && !isPrime($candidate)) {
        $candidate--;
    }

    return $sum - $candidate;
}

sub nearestUpperPrimeDistance($sum, $upperBound) {
    my $candidate = $sum;

    while ($candidate <= $upperBound && !isPrime($candidate)) {
        $candidate++;
    }

    return $candidate - $sum;
}

(Full code on Github.)