Perl Weekly Challenge: Week 194

Thankfully the challenges weren't too hard this week because Advent of Code is consuming nearly of all of my free time and I'm still falling behind. Hopefully I can catch up this week.

Challenge 1:

Digital Clock

You are given time in the format hh:mm with one missing digit.

Write a script to find the highest digit between 0-9 that makes it valid time.

Example 1
Input: $time = '?5:00'
Output: 1

Since 05:00 and 15:00 are valid time and no other digits can fit in the missing place.
Example 2
Input: $time = '?3:00'
Output: 2
Example 3
Input: $time = '1?:00'
Output: 9
Example 4
Input: $time = '2?:00'
Output: 3
Example 5
Input: $time = '12:?5'
Output: 5
Example 6
Input: $time =  '12:5?'
Output: 9

There are only 4 possible positions for the missing digit however for two of them we need surrounding context. Still this only adds another 2 cases so it is ok to just make a big switch statement out of them.

.index() gives the position of the ? character within the time string. given $time.index('?') {

If the first digit is missing, we have to look at the one after it to determine the answer.

    when 0 { 
        given ($time.substr(1, 1)) {
            when 0 .. 3 { say 2; }
            default { say 1; }
        }
    }

Similarly, if the second digit is missing, we have to look at the one before to determine the answer.

    when 1 {
        given $time.substr(0, 1) {
            when 0 .. 1 { say 9; }
            when 2 { say 3; }

The spec doesn't say what to do if the user inputs a time such as 32:00 but as this is invalid, I've chosen to throw an error.

            default { die "Illegal time\n"; }
        }
    }
    when 3 { say 5; }
    when 4 { say 9; }

If there was no ? or it is i.e. in the place of the : etc. the string is invalid so again I threw an error.

    default { die "No ? or ? is in an illegal position.\n"; }
}

(Full code on Github.)

One possible error which I didn't check for but really should have for robustness is if there are 2 or more ? in the string.

The Perl version is almost exactly the same except as of version 5.30, given ... when is still considered experimental so you need this line:

use experimental 'switch';

... to prevent a warning message.

Here is the Perl code for completeness.

given (index $time, '?') {
    when (0) { 
        given (substr($time, 1, 1)) {
            when ([0 .. 3]) { say 2; }
            default { say 1; }
        }
    }
    when (1) {
        given (substr($time, 0, 1)) {
            when ([0 .. 1]) { say 9; }
            when (2) { say 3; }
            default { die "Illegal time\n"; }
        }
    }
    when (3) { say 5; }
    when (4) { say 9; }
    default { die "No ? or ? is in an illegal position.\n"; }
}

(Full code on Github.)

Challenge 2:

Frequency Equalizer

You are given a string made of alphabetic characters only, a-z.

Write a script to determine whether removing only one character can make the frequency of the remaining characters the same.

Example 1
Input: $s = 'abbc'
Output: 1 since removing one alphabet 'b' will give us 'abc' where each alphabet frequency is the same.
Example 2
Input: $s = 'xyzyyxz'
Output: 1 since removing 'y' will give us 'xzyyxz'.
Example 3
Input: $s = 'xzxz'
Output: 0 since removing any one alphabet would not give us string with same frequency alphabet.

We start the Raku solution by splitting $s into an array of individual characters.

my @chars = $s.comb;

We will also need a variable to store the result. By default we assume the script will fail.

my $result = 0;

Now for each character...

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

We make a copy of the character array and remove the character we are considering.

    my @others = @chars;
    @others.splice($i, 1);

With .classify() we can create a hash where the keys are unique characters in the @others array and the values are the number of times that character has occurred. Well not quite; If there were 5 'e's in the array, we would get a hash element with the key 'e' and the value ['e', 'e', 'e', 'e', 'e']. The call to .map() chained on at the end converts this value to 5 which is what we actually need.

    my %freq = @others.classify({ $_ }).map({ $_.key => $_.value.elems ;});

Now that we know the frequency of other characters in our input, we can compare them to see if they are all equal using the [eq] operator. If they are we can change the result and stop processing. (There may well be other viable candidates but the spec says we don't need to care about them.)

    if [eq] %freq.values {
        $result = 1;
        last;
    } 
}

Finally we print the result.

say $result;

(Full code on Github.)

As is usually the case, translating a Raku script to Perl mainly involves finding alternatives for features Raku has standard but Perl does not.

my @chars = split //, $s;
my $result = 0;

Perl doesn't have .end() so I used scalar @chars - 1. There are other ways to do it too but this is usually what I use.

for my $i (0 .. scalar @chars - 1) {
    my @others = @chars;
    splice @others, $i, 1;

Instead of .classify() I just walked through the array populating the hash. One could argue this is actually simpler as I didn't have to go through the extra transformation with .map().

    my %freq;
    for my $elem (@others) {
        $freq{$elem}++;
    }

The biggest pain was the lack of [eq]. So I wrote my own function which looks like this:

sub allEqual {

It takes an array reference.

    my @arr = @{ shift @_ };

The first element of the array is shifted off.

    my $first = shift @arr;

Then all the other elements of the array are compared to it.

    for my $elem (@arr) {

If any are not equal to $first it means the array as a whole does not contain equal elements. No need to continue, we can return false (or undef due to Perl lacking an actual Boolean data type.)

        if ($elem != $first) {
            return undef;
        }
    }

If we got all the way through it means all the elements are equal so we can return true (or 1.)

    return 1;
}

Now we have allEqual(), the rest of the solution looks like this:

    if (allEqual([values %freq])) {
        $result = 1;
        last;
    } 
}

say $result;

(Full code on Github.)