Perl Weekly Challenge: Week 366

Challenge 1:

Count Prefixes

You are given an array of words and a string (contains only lowercase English letters).

Write a script to return the number of words in the given array that are a prefix of the given string.

Example 1
Input: @array = ("a", "ap", "app", "apple", "banana"), $str = "apple"
Output: 4
Example 2
Input: @array = ("cat", "dog", "fish"), $str = "bird"
Output: 0
Example 3
Input: @array = ("hello", "he", "hell", "heaven", "he"), $str = "hello"
Output: 4
Example 4
Input: @array = ("", "code", "coding", "cod"), $str = "coding"
Output: 3
Example 5
Input: @array = ("p", "pr", "pro", "prog", "progr", "progra", "program"), $str = "program"
Output: 7

This is the kind of task Raku and Perl can handle very easily—in one line of code in fact. For a change, I'll start by showing the Perl version first.

We get the input from the command-line. The first argument will be the string we are searching; what the spec refers to as $str though in the interests of conciseness, here we will shorten it to $s. All the other command-line arguments in @ARGV will form @array though to keep things short we'll keep using @ARGV rather than assigning to a new array.

Finding the prefix of a string is just a special case of finding one string within another and Perl has a library function for this very purpose. index() takes two (there is a variant that takes three but we're not using that) arguments. The first is the string to be searched and the second is the string we are searching for. If this search string is found, index() returns the position of its' first occurrence within the searched string as a 0-based index from the beginning. If the search string cannot be found, index() returns -1. This means we can use index() to search $s for each of the remaining arguments in @ARGV and if it returns 0 (i.e. the beginning of $s) we know it is a prefix. Below, we use grep() to perform this procedure on the elements of @ARGV, filtering out the ones for which index() returns 0. As this is a one-liner, I used the shortcut of employing the negation operator ! to compare to 0. Then we count the number of prefixes found with scalar and print the results with say.

$s = shift; say scalar grep { !index($s, $_) } @ARGV

(Full code on Github.)

The Raku version is uncharacteristically longer than the Perl one but still a one-liner. A little problem I ran into is the Raku .index() method returns Nil instead of -1 if a match was not found and that is not a numeric value. So we have to use the smart match operator ~~ and directly compare to 0.

my $s = @*ARGS.shift; @*ARGS.grep({ $s.index($_) ~~ 0 }).elems.say

(Full code on Github.)

?#### Challenge 2:

Valid Times

You are given a time in the form ‘HH:MM’. The earliest possible time is ‘00:00’ and the latest possible time is ‘23:59’. In the string time, the digits represented by the ‘?’ symbol are unknown, and must be replaced with a digit from 0 to 9.

Write a script to return the count different ways we can make it a valid time.

Example 1
Input: $time = "?2:34"
Output: 3

0 -> "02:34" valid
1 -> "12:34" valid
2 -> "22:34" valid
Example 2
Input: $time = "?4:?0"
Output: 12

Hours: tens digit ?, ones digit 4 -> can be 04, and 14 (2 possibilities)
Minutes: tens digit ?, ones digit 0 -> tens can be 0-5 (6 possibilities)

Total: 2 × 6 = 12
Example 3
Input: $time = "??:??"
Output: 1440

Hours: from 00 to 23 -> 24 possibilities
Minutes: from 00 to 59 -> 60 possibilities

Total: 24 × 60 = 1440Input: $time = "2?:15"
Output: 4

Tens digit is 2, so hours can be 20-23
Ones digit can be 0,1,2,3 (4 possibilities)

Therefore: 4 valid times
Example 4
Input: $time = "?3:45"
Output: 3

If tens digit is 0 or 1 -> any ones digit works, so 03 and 13 are valid
If tens digit is 2 -> ones digit must be 0-3, but here ones digit is 3, so 23 is valid

Therefore: 0,1,2 are all valid -> 3 possibilities
Example 5
Input: $time = "2?:15"
Output: 4

Tens digit is 2, so hours can be 20-23
Ones digit can be 0,1,2,3 (4 possibilities)

Therefore: 4 valid times

This is a conceptually simple problem to solve, it just has a number of cases that need to be handled separately. Once again, i'll show the Perl version first.

We begin by assigning a variable to store the number of possibilities.

my $possibilities = 0;

Then we split() the input into individual characters and assign them to variables. We don't need to save the colon in the middle so we assign it to undef effectively discarding it.

my ($hourTens, $hourOnes, undef, $minuteTens, $minuteOnes) = split //, $time;

For each of the hour and minute components of the time, there may be be 2, 1 or 9 question marks.

If there are two question marks in the hour component, there are 24 possibilities for 24 hours.

if ($hourTens eq q{?} && $hourOnes eq q{?}) {
    $possibilities = 24;

if there is a question mark in the hour 10's position, the number of possibilities depends on whether the hour 1's position is between 0 and 3 or not.

} elsif ($hourTens eq q{?}) {
    $possibilities = ($hourOnes >= 0 && $hourOnes <= 3) ? 3 : 2; 

If there is a question mark in the hour 1's position, the number of possibilities depends on whether the hour 10's position is 2 or not.

} elsif ($hourOnes eq q{?}) {
    $possibilities = ($hourTens == 2) ? 4 : 10;

And if there are no question marks at all in the hours component, possibilites is set to 1.

} else {
    $possibilities = 1;
}

The minutes component is a little simpler but it also may contain 2, 1 or 0 question marks. The number of posssibilities calculated is multiplied by the number derived in hours to provide the final number.

if ($minuteTens eq q{?} && $minuteOnes eq q{?}) {
    $possibilities *= 60;
} elsif ($minuteTens eq q{?}) {
    $possibilities *= 6; 
} elsif ($minuteOnes eq q{?}) {
    $possibilities *= 10;
}  else {

There is one more extra-special case. If there were no question marks in the hours component, we only have to deal with possibilities from the minutes component. (That's why me multiplied by 1.) But if there are no question marks in the minutes component either, we don't have 1 times 1 alternatives, we have 0. (Atleast by my interpretation. You could also argue that the original time should count as a possibility in which case the answer should be 1.)

    if $possibilities == 1 {
        $possibilities = 0;
    }
}

Whichever path we took, the final step is to print out the number of possibilities found. say $possibilities;

(Full code on Github.)

The Raku version is a mostly straightforward translation from Perl.

my $possibilities = 0;

I say mostly straightforward because Raku does'nt have an undef value which can be used to skip unneded elements when assigning variables to an array. I could swear up and down that in Raku you can use _ for the same purpose. Well, actually you cannot. It goes to shoe it isn't only A.I.s that hallucinate.

Using the empty array () did the trick but I don't know if that's the idiomatic way.

my ($hourTens, $hourOnes, (), $minuteTens, $minuteOnes) = $time.comb;

if $hourTens eq q{?} && $hourOnes eq q{?} {
    $possibilities = 24;
} elsif $hourTens eq q{?} {
    $possibilities = ($hourOnes ~~ 0 .. 3) ?? 3 !! 2; 
} elsif $hourOnes eq q{?} {
    $possibilities = ($hourTens == 2) ?? 4 !! 10;
} else {
    $possibilities = 1;

}
if $minuteTens eq q{?} && $minuteOnes eq q{?} {
    $possibilities *= 60;
} elsif $minuteTens eq q{?} {
    $possibilities *= 6; 
} elsif $minuteOnes eq q{?} {
    $possibilities *= 10;
} else {
    if $possibilities == 1 {
        $possibilities = 0;
    }
}

say $possibilities;

(Full code on Github.)