“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
endView 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: 10Raku
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)
endView 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 couponsView 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