Perl Weekly Challenge: Week 353

Challenge 1:

Max Words

You are given an array of sentences.

Write a script to return the maximum number of words that appear in a single sentence.

Example 1
Input: @sentences = ("Hello world", "This is a test", "Perl is great")
Output: 4
Example 2
Input: @sentences = ("Single")
Output: 1
Example 3
Input: @sentences = ("Short", "This sentence has seven words in total", "A B C", "Just four words here")
Output: 7
Example 4
Input: @sentences = ("One", "Two parts", "Three part phrase", "")
Output: 3
Example 5
Input: @sentences = ("The quick brown fox jumps over the lazy dog", "A", "She sells seashells by the seashore", "To be or not to be that is the question")
Output: 10

A Raku one-liner. We take the input from command-line arguments. Then, using .map() we take each sentence and transform it by splitting it into words with .words() and counting the words using .elems(). At this point, we have an array of word counts, so callinf .max() on it will give us the length of the longest sentence which we can print out with .say().

@*ARGS.map({ .words.elems }).max.say

(Full code on Github.)

Perl doesn't have .words() or .max() etc. will we still be able to have a one-line solution? Happily, the answer is yes. Instead of .words() we can use split /\s+/. scalar will count the length of the resulting array of words just like .elems(). If we sort() the array of word counts in descending numeric order, the 0th element will be the length of the longest sentence.

say [ sort { $b <=> $a } map { scalar split /\s+/ } @ARGV ]->[0]

(Full code on Github.)

Challenge 2:

Validate Coupon

You are given three arrays, @codes, @names and @status.

Write a script to validate codes in the given array.

A code is valid when the following conditions are true:
- codes[i] is non-empty and consists only of alphanumeric characters (a-z, A-Z, 0-9) and underscores (_).
- names[i] is one of the following four categories: "electronics", "grocery", "pharmacy", "restaurant".
- status[i] is true.

Return an array of booleans indicating validity: output[i] is true if and only if codes[i], names[i] and status[i] are all valid.

Example 1
Input: @codes  = ("A123", "B_456", "C789", "D@1", "E123")
       @names  = ("electronics", "restaurant", "electronics", "pharmacy", "grocery")
       @status = ("true", "false", "true", "true", "true")
Output: (true, false, true, false, true)
Example 2
Input: @codes  = ("Z_9", "AB_12", "G01", "X99", "test")
       @names  = ("pharmacy", "electronics", "grocery", "electronics", "unknown")
       @status = ("true", "true", "false", "true", "true")
Output: (true, true, false, true, false)
Example 3
Input: @codes  = ("_123", "123", "", "Coupon_A", "Alpha")
       @names  = ("restaurant", "electronics", "electronics", "pharmacy", "grocery")
       @status = ("true", "true", "false", "true", "true")
Output: (true, true, false, true, true)
Example 4
Input: @codes  = ("ITEM_1", "ITEM_2", "ITEM_3", "ITEM_4")
       @names  = ("electronics", "electronics", "grocery", "grocery")
       @status = ("true", "true", "true", "true")
Output: (true, true, true, true)
Example 5
Input: @codes  = ("CAFE_X", "ELEC_100", "FOOD_1", "DRUG_A", "ELEC_99")
      @names  = ("restaurant", "electronics", "grocery", "pharmacy" "electronics")
      @status = ("true", "true", "true", "true", "false")
Output: (true, true, true, true, false)

The first order of business is to get the input into the script. The method I chose was to have three command-line string arguments enclosed in "" for the three arrays. Within each argument, the individual elements are separated by whitespace. This way, they can easily be reconstitued into arrays with .words().

There is one added complication for @codes; if you look at example 3, there is an empty element which would be impossible to represent if we delimited the elements with whitespace. So for @codes only, I wrapped each element in '' and when reconstituting the array, the quotes are removed with .map() and .subat().

my @codes = $codes.words.map({ .subst(/(^^ \') || (\' $$)/, q{}, :g) });
my @names = $names.words;
my @status = $status.words;

We also need an array to hold the validity status of each iten.

my @validity = ();

As all three arrays are the same length, we can use the indices of @codes (obtained with .keys()) as indices for the other two. Each item...

for @codes.keys -> $i {

...is tested with the criteria given in the spec. .so is added to the first two to force boolean context so the whole statement returns True or False. This is added to @validity.

    @validity.push(
        @codes[$i].match(/^^ <alnum>+ $$/).so &&
        <electronics grocery pharmacy restaurant>.grep(@names[$i]).so &&
        @status[$i] eq 'true'
    );
}

Finally, we print the values of @validity joined by spaces.

say @validity.join(q{ });

(Full code on Github.)

This is the Perl version.

my @codes = map { $_ =~ s/^ \' | \' $//gx; $_ } split /\s+/, $ARGV[0];
my @names = split /\s+/, $ARGV[1];
my @status = split /\s+/, $ARGV[2];
my @validity = ();

for my $i (keys @codes) {
    push @validity,
        $codes[$i] =~ /^[A-Za-z0-9_]+$/ &&
        (grep {$_ eq $names[$i]} qw/electronics grocery pharmacy restaurant/) &&
        $status[$i] eq 'true'
        ? 'true'
        : 'false'
    ;
}

say join q{ }, @validity;

(Full code on Github.)