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"; }
}
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"; }
}
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;
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;