Perl Weekly Challenge: Week 20

Challenge 1:

Write a script to accept a string from command line and split it on change of character. For example, if the string is "ABBCDEEF", then it should split like "A", "BB", "C", "D", "EE", "F".

This is easy enough to do as a one-liner. Here is what I came up with for Perl5 in only a few minutes.

perl -E '$_ = shift; s/ (.+?) \K (?!\g{1} | $) /\", \"/gx; say "\"$_\"";' "ABBCDEEF"

(Full code on Github.)

The important part is the s function. I am collecting a run of characters followed by a negative lookahead to see if the same run doesn't follow (or $ the end-of-string. This added complication is merely there to avoid a trailing comma in the output.) The \K effectively says "never mind about all that stuff before me" so only the second part of the regex (which is the point at which the current character stops being matched) is used for substitution.

regexp syntax is a little bit different in Perl6 Camelia Raku? but the same principles apply so I translated my Perl5 solution to this:

perl6 -e ' @*ARGS.shift.subst(/ <( (.+?) )> <!before $0> /, { "\"$0\"" }, :g).subst("\"\"", "\", \"", :g).say ' "ABBCDEEF"

But inexplicably that only gave me this output:

"A", "B", "B", "C", "D", "E", "E", "F"

After a lot of experimentation (and help from irced on IRC's #perl6) I discovered I had to change (.+?) to ( (.)$0* ) in order to make it work as intended. Now, arguably, this changed regex is more correct (you no longer need the lookahead.) and more efficient than the old one but in my opinion, the old one ought to work as it does work in Perl5. If it is deliberately different, then it should be documented as such. If it is a bug (I'll file a report and see what the experts think) then it should be fixed. Of course it is also possible that it could be a bug in Perl5 though that seems the unlikeliest alternative. Anyway, here is the final result:

perl6 -e ' @*ARGS.shift.subst(/ ( (.)$0* ) /, { "\"$0\"" }, :g).subst("\"\"", "\", \"", :g).say; ' "ABBCDEEF"

(Full code on Github.)

Challenge 2:

Write a script to print the smallest pair of Amicable Numbers. For more information, please checkout wikipedia page.

Amicable numbers (not to be confused with Friendly numbers or Sociable numbers) can be calculated by a theorem discovered by the mathematician Thabit Ibn Qurra. There is a more comprehensive one formulated by Leonhard Euler but Ibn Qurra's theorem is good enough for the purposes of this challenge. This is the Perl5 version:

my $n = 1;
while ($n++) {
    my $p = 3 * 2 ** ($n - 1) - 1;
    my $q = 3 * 2 ** ($n) - 1;
    my $r = 9 * 2 ** (2 * $n - 1) - 1;

    if (isPrime($p) && isPrime($q) && isPrime($r)) {
        say '(', (2 ** $n) * $p * $q, ', ',  (2 ** $n) * $r, ')';
        last;
    }
}

(Full code on Github.)

I reused the isPrime() function I wrote for challenge 12.

Here's Perl6:

for (2 .. *) -> $n {
    my $p = 3 * 2 ** ($n - 1) - 1;
    my $q = 3 * 2 ** ($n) - 1;
    my $r = 9 * 2 ** (2 * $n - 1) - 1;

    if ($p, $q, $r).all.is-prime() {
        say '(', (2 ** $n) * $p * $q, ', ',  (2 ** $n) * $r, ')';
        last;
    }
}

(Full code on Github.)