Perl Weekly Challenge: Week 305

Challenge 1:

Binary Prefix

You are given a binary array.

Write a script to return an array of booleans where the partial binary number up to that point is prime.

Example 1
Input: @binary = (1, 0, 1)
Output: (false, true, true)

Sub-arrays (base-10):
(1): 1 - not prime
(1, 0): 2 - prime
(1, 0, 1): 5 - prime
Example 2
Input: @binary = (1, 1, 0)
Output: (false, true, false)

Sub-arrays (base-10):
(1): 1 - not prime
(1, 1): 3 - prime
(1, 1, 0): 6 - not prime
Example 3
Input: @binary = (1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1)
Output: (false, true, true, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, true)

I solved this one by first defining a list variable to hold the results.

my @results;

Now for each index of @binary's elements...

for 0 .. @binary.end -> $i {

...we join()all the elements from 0 to the current one into one string which we can treat as a binary number and convert back to base 10 with .parse-base(2). Then we check if it is a prime number with .is-prime(). The result (true or false) is added to @results with .push().

    @results.push(@binary[0 .. $i].join.parse-base(2).is-prime);
}

When all the subarrays have been processed, the output is printed.

@results.join(q{ }).say;

(Full code on Github.)

The Perl version is equally straightforward.

my @results;

for my $i (0 .. (scalar @binary - 1)) {

The only annoying thing is that to make a string into a binary literal, you have to prefix 0b in front of it and parsing of it requires the unintuitively named oct() function.

    push @results, isPrime(oct('0b' . join q{}, @binary[0 .. $i]));
}

say join q{ }, map { $_ ? 'true' : 'false' } @results;

(Full code on Github.)

Challenge 2:

Alien Dictionary

You are given a list of words and alien dictionary character order.

Write a script to sort lexicographically the given list of words based on the alien dictionary characters.

Example 1
Input: @words = ("perl", "python", "raku")
       @alien = qw/h l a b y d e f g i r k m n o p q j s t u v w x c z/
Output: ("raku", "python", "perl")
Example 2
Input: @words = ("the", "weekly", "challenge")
       @alien = qw/c o r l d a b t e f g h i j k m n p q s w u v x y z/
Output: ("challenge", "the", "weekly")

For a change, I did the Perl version first. My script takes input from the command line with the first argument becomes $alien and the rest, @words. So for example 2, the input would look like this:

"corldabtefghijkmnpqswuvxyz" "the" "weekly" "challenge"

The next step is to create a hash, called %order whose keys are the individual letters in $alien and values are the position that letter occurs in. (i.e. c => 0, o => 1) etc.

my $i = 0;
my %order = map { $_ => $i++ } split //, $alien;

The next two lines are just for outputting the results.

say
    join q{ },

The heart of the matter is here. sort() can take a custom sorting subroutine so we can use the alien dictionary order instead of the standard ASCII (well, Unicode actually) order.

    sort {

The subroutine argument to sort() is passed two elements of the array to be sorted at a time. They are assigned the special variables $a and $b. We split() theses into lists of single characters.

        my @a = split //, $a;
        my @b = split //, $b;

We loop over @a comparing each element with the corresponding element in @b. The elements are compared using their %order. If the orders are not equal, the smallest numerically is returned.

        for my $i (keys @a) {
            if ($order{$a[$i]} != $order{$b[$i]}) {
                return $order{$a[$i]} <=> $order{$b[$i]};
            }
        }

If all characters compared so far are equal, we compare the lengths of @a and @b. This ensures that shorter words come before longer words if they are otherwise identical.

        return @a <=> @b;
    }
    @words;

(Full code on Github.)

The Raku version is a little bit shorter.

We can simply the creation of %order using .antipairs() which does the same as the map() block in the Perl version.

my %order = $alien.comb.antipairs;

@words
    .sort({

In Raku, instead of a loop, we can convert the entire lists of characters in one go as parameters to %order. Another quirk is that even though both lists consist of numbers, they are actually strings and not automatically converted to numeric form so we have to use cmp not <=> to make the comparison.

        %order{ $^a.comb } cmp %order{ $^b.comb };
    })
    .join(q{ })
    .say;

(Full code on Github.)