Perl Weekly Challenge: Ok, swing code… SWING!

“Validate Coupon” and “Max Words” are the tasks for Perl Weekly Challenge 353. I can’t believe I’ve done tasks with Max before and it never occurred to me that I needed to feature a song with Max Headroom.

Ok, swing code… swing!

Task 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

Approach

This one’s easy peasy: for each sentence in the array, split on whitespace to get the number of words, and then pass those counts through a max function.

Elixir

I really just wanted to do the Elixir solution first. I could have done the map on line 9 as Enum.map(fn s -> String.split(s) |> length end), but breaking out the function in the map into a separate function makes the flow a little more readable.

  def word_count(sentence) do
    sentence |> String.split |> length
  end

  def max_words(sentences) do
    sentences |> Enum.map(&word_count/1) |> Enum.max
  end

View the entire Elixir script for this task on GitHub.

$ elixir/ch-1.exs
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 song's just six words long",
                     "A B C", "Just four words here")
Output: 6

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

Raku

In Raku, it’s pretty much the same.

sub wordCount($sentence) {
  $sentence.split(/\s+/).elems;
}

sub maxWords(@sentences) {
  @sentences.map({ wordCount($_) }).max;
}

View the entire Raku script for this task on GitHub.

Perl

As is Perl, though we need to import max from one of the List util modules.

use List::AllUtils qw( max );

sub wordCount($sentence) {
  scalar(split(/\s+/, $sentence));
}

sub maxWords(@sentences) {
  max map { wordCount($_) } @sentences;
}

View the entire Perl script for this task on GitHub.

Python

Python is different because instead of map it has the construct [func(x) for x in list].

def word_count(sentences):
  return len(sentences.split())

def max_words(sentences):
  return max([word_count(s) for s in sentences])

View the entire Python script for this task on GitHub.


Task 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)

Approach

This problem, as well, is pretty straightforward. Pull an item off each of the codes, names, and status lists, do the tests described above, and return true or false. Checking the status is just a string comparison, checking the names is a series of string comparisons, and checking the code is just matching against the regular expression /^[a-zA-Z0-9_]+$/.

Elixir

Again, I tackled Elixir first. Enum.zip_with/2 made processing an item from each of the lists easy.

@good_names ["electronics", "grocery", "pharmacy", "restaurant"]

def check_coupon([code, name, status]) do
  cond do
    status == "true" and
    Enum.any?(@good_names, fn n -> n == name end) and
    Regex.match?(~r/^[a-zA-Z0-9_]+$/, code) -> "true"
    true -> "false"
  end
end

def validate_coupon(%{codes: codes, names: names,
                      status: status}) do
  Enum.zip_with([codes, names, status], &check_coupon/1)
end

View the entire Elixir script for this task on GitHub.

$ elixir/ch-2.exs
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)

Raku

In Raku, we were able to do pretty much the same thing with the List routine zip ( though I’m using zip‘s infix synonym, the Z operator):

my @good_names =
  ["electronics", "grocery", "pharmacy", "restaurant"];

sub checkCoupon($code, $name, $status) {
  return 'true' if $status eq 'true'
                && $name eq @good_names.any
                && $code ~~ /^[<alnum>]+$/;
  return 'false';
}

sub validateCoupon(:@codes, :@names, :@status) {
  my @coupons;
  for @codes Z @names Z @status -> ($c, $n, $s) {
    @coupons.push(checkCoupon($c, $n, $s));
  }
  return @coupons;
}

View the entire Raku script for this task on GitHub.

Perl

And in Perl, we just use List::AllUtils’ zip_by.

use List::AllUtils qw( any zip_by );

my @good_names =
  ("electronics", "grocery", "pharmacy", "restaurant");

sub checkCoupon($code, $name, $status) {
  return 'true' if $status eq 'true'
                && (any { $name eq $_ } @good_names)
                && $code =~ /^[a-zA-Z0-9_]+$/;
  return 'false';
}

sub validateCoupon($params) {
  zip_by {
    checkCoupon(@_)
  } $params->{codes}, $params->{names}, $params->{status};
}

View the entire Perl script for this task on GitHub.

Python

And Python’s built-in zip does the same thing. One thing is that zip returns tuples, and either I needed to unpack the tuple manually (by writing code, name, status = t) or I could unpack the tuple into the argument list of check_coupon.

import re

good_names = [
  "electronics", "grocery", "pharmacy", "restaurant"
]

def check_coupon(code, name, status):
  if (
    status == 'true' and
    name in good_names and
    re.fullmatch(r'^[a-zA-Z0-9_]+$', code)
  ):
    return 'true'
  else:
    return 'false'

def validate_coupon(params):
  coupons = []
  for t in zip(params["codes"],
               params["names"],
               params["status"]):
    coupons.append(check_coupon(*t))
  return coupons

View the entire Python script for this task on GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-353/packy-anderson

Leave a Reply