Perl Weekly Challenge 398‘s tasks are “Divisible Average” and “String Difference”.
This week, let’s listen to Ella Fitzgerald (accompanied by Duke Ellington and his orchestra) explaining that It Don’t Mean A Thing If It Ain’t Got That Swing…
Task 1: Divisible Average
You are given an array of positive integers.
Write a script to return the closest integer to the mean value of all even integers that are divisible by 3. If none is divisible, return 0.
Input: @arr = (18, 5, 7, 11)
Output: 18
18 is the only even number and divisible by 3.
Average: 18 / 1 = 18.
Input: @arr = (6, 12, 18, 24, 30, 40)
Output: 18
The qualifying numbers are 6, 12, 18, 24, and 30.
Average: (6 + 12 + 18 + 24 + 30) / 5 = 90 / 5 = 18
Input: @arr = (3, 9, 15, 21, 27)
Output: 0
None of the elements are even.
Since no elements qualify, return 0.
Input: @arr = (6, 12, 14, 20)
Output: 9
The qualifying numbers are 6 and 12.
Average: (6 + 12) / 2 = 9.
Input: @arr = (6, 6, 6, 12, 12)
Output: 8
All five numbers are even and divisible by 3.
Average: (6 + 6 + 6 + 12 + 12) / 5 = 8.4 => 8
Approach
This is fairly straightforward. First we filter the @arr list to eliminate numbers that aren’t even (divisible by 2) or divisible by 3. We can test this by negating ((n mod 2) or (n mod 3)), because if either mod operation yields a number, n isn’t even or divisible by 3.
If there’s no candidates, we return early with 0. Otherwise we calculate the average or the candidates, and round to the nearest integer.
Raku
We’re using the mod operator, class List’s .grep routine, class List’s .sum routine, class List’s .elems routine, and class Cool’s .round routine.
sub divisibleAverage(@arr) {
my @qualifying = @arr.grep({ ! (($_ mod 2) || ($_ mod 3)) });
return 0 unless @qualifying;
(@qualifying.sum / @qualifying.elems).round;
}View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1
Input: @arr = (18, 5, 7, 11)
Output: 18
Example 2
Input: @arr = (6, 12, 18, 24, 30, 40)
Output: 18
Example 3
Input: @arr = (3, 9, 15, 21, 27)
Output: 0
Example 4
Input: @arr = (6, 12, 14, 20)
Output: 9
Example 5
Input: @arr = (6, 6, 6, 12, 12)
Output: 8Perl
In Perl, the modulo operator is %. We’re using built-ins grep and scalar, and we’re importing sum from List::AllUtils. I was using int($n + 0.5) to do my rounding, but when .I read the documentation for int, I discovered that it was recommending that I use a POSIX routine instead, so I used lround (which was added in Perl v.5.22).
use List::AllUtils qw( sum );
use POSIX qw( lround );
sub divisibleAverage(@arr) {
my @qualifying = grep { ! (($_ % 2) || ($_ % 3)) } @arr;
return 0 unless @qualifying;
lround(sum(@qualifying) / scalar(@qualifying));
}View the entire Perl script for this task on GitHub.
Python
In Python, it’s easy to use a list comprehension to filter the input array, and round, sum, and len are all built-ins.
def divisible_average(arr):
qualifying = [ n for n in arr if not ((n % 2) or (n % 3)) ]
if not qualifying: return 0
return round(sum(qualifying) / len(qualifying))View the entire Python script for this task on GitHub.
Elixir
Elixir doesn’t treat integers like booleans, however, so I had to do a Bitwise.bor/2 of the two remainders (calculated using Kernel.rem/2) and then test that against 0. Kernel.length/1 yields the length of Lists, and Enum.sum/1 does the adding. Enum.filter/2 was used to filter the input array.
def divisible_average(arr) do
qualifying = arr
|> Enum.filter(& Bitwise.bor(rem(&1, 2), rem(&1, 3)) == 0)
if length(qualifying) == 0, do: 0,
else: round(Enum.sum(qualifying) / length(qualifying))
endView the entire Elixir script for this task on GitHub.
Task 2: String Difference
You are given an array of equal length strings.
Write a script to find the difference integer array of each word in the given array. There will be one word with a different array; return that word.
Each words[i] can be converted into a difference integer array difference[i] of length n – 1 where difference[i][j] = words[i][j+1] – words[i][j]
Input: @words = ("ab", "bc", "cd", "zy")
Output: "zy"
"ab" => [1 - 0] = [1]
"bc" => [2 - 1] = [1]
"cd" => [3 - 2] = [1]
"zy" => [24 - 25] = [-1]
Input: @words = ("cba", "fed", "azy")
Output: "azy"
"cba" => [1 - 2, 0 - 1] = [-1, -1]
"fed" => [4 - 5, 3 - 4] = [-1, -1]
"azy" => [25 - 0, 24 - 25] = [25, -1]
Input: @words = ("pop", "ada", "beb", "cfc")
Output: "pop"
"pop" => [14 - 15, 15 - 14] = [-1, 1]
"ada" => [3 - 0, 0 - 3] = [3, -3]
"beb" => [4 - 1, 1 - 4] = [3, -3]
"cfc" => [5 - 2, 2 - 5] = [3, -3]
Input: @words = ("aaaa", "zzzz", "mmmm", "aaba")
Output: "aaba"
"aaaa" => [0 - 0, 0 - 0, 0 - 0] = [0, 0, 0]
"zzzz" => [25 - 25, 25 - 25, 25 - 25] = [0, 0, 0]
"mmmm" => [12 - 12, 12 - 12, 12 - 12] = [0, 0, 0]
"aaba" => [0 - 0, 1 - 0, 0 - 1] = [0, 1, -1]
Input: @words = ("aceg", "bdfh", "cegi", "azbz")
Output: "azbz"
"aceg" => [2 - 0, 4 - 2, 6 - 4] = [2, 2, 2]
"bdfh" => [3 - 1, 5 - 3, 7 - 5] = [2, 2, 2]
"cegi" => [4 - 2, 6 - 4, 8 - 6] = [2, 2, 2]
"azbz" => [25 - 0, 1 - 25, 25 - 1] = [25, -24, 24]
Approach
Rather than having to do an i, j loop over the words as if it was a 2D array, I decided instead to just calculate the integer array difference for each word, so for word, we calculate the diff array by diff[j] = word[j + 1] - word[j] for j <- 0 .. len(word) - 1. I also decided that it would make things easier to understand if I made that calculation into its own function. I then decided to convert the arrays into string values (i.e., [0, 1, -1] would become "0 1 -1") so they would be easy to compare.
Then I needed a way to count the words that had the same integer array difference. I figured if I used the string of the integer array difference as the key to a hash/map, and made each element a list of the words yielding that integer array difference, I could then filter the hash/map to find the entry with a list of one value, and then return that value.
Raku
To do this in Raku, we’re using class List’s .elems, .end, .grep, .join, and .map routines, class Str’s .comb and .ord routines, Array’s .push routine and Map’s .values method. On line 16, we’re using autovivication to create the list if it doesn’t exist.
sub diffArrayAsString($word) {
my @arr = $word.comb.map: *.ord - 97;
my @diff;
for 0 .. @arr.end - 1 -> $j {
@diff[$j] = @arr[$j + 1] - @arr[$j];
}
@diff.join(" ")
}
sub stringDifference(@words) {
my %diffs;
for @words -> $word {
%diffs{diffArrayAsString($word)}.push($word);
}
%diffs.values.grep({ $_.elems == 1 })[0];
}View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1
Input: @words = ("ab", "bc", "cd", "zy")
Output: "zy"
Example 2
Input: @words = ("cba", "fed", "azy")
Output: "azy"
Example 3
Input: @words = ("pop", "ada", "beb", "cfc")
Output: "pop"
Example 4
Input: @words = ("aaaa", "zzzz", "mmmm", "aaba")
Output: "aaba"
Example 5
Input: @words = ("aceg", "bdfh", "cegi", "azbz")
Output: "azbz"Perl
Perl works pretty much the same way, but the %diffs hash is holding array references instead of arrays, so when we grep through the values for the hash, we need to dereference those lists.
sub diffArrayAsString($word) {
my @arr = map { ord($_) - 97 } split //, $word;
my @diff;
for my $j ( 0 .. $#arr - 1 ) {
$diff[$j] = $arr[$j + 1] - $arr[$j];
}
join(" ", @diff)
}
sub stringDifference(@words) {
my %diffs;
for my $word ( @words ) {
push @{ $diffs{diffArrayAsString($word)} }, $word;
}
# grep will return a single element list of array refs
( grep { scalar(@$_) == 1 } values %diffs )[0]->[0];
}View the entire Perl script for this task on GitHub.
Python
Again, in Python we’re using a list comprehension to build the array of numeric values for each letter in the word, and then again to calculate the differences. Because Python can’t autovivify arrays when we try to append to them, on line 12 we check to see if the key exists in the diffs map; if it doesn’t exist, we set one with an empty list and then continue to out list append.
def diff_array_as_string(word):
arr = [ ord(l) - 97 for l in word ]
diff = [ arr[j + 1] - arr[j] for j in range(len(arr)-1) ]
return " ".join(map(str, diff))
def string_difference(words):
diffs = {}
for word in words:
diff = diff_array_as_string(word)
if diff not in diffs: diffs[diff] = [] # init empty list
diffs[diff].append(word)
return [ v[0] for v in diffs.values() if len(v) == 1 ][0]View the entire Python script for this task on GitHub.
Elixir
And for the Elixir solution, I’m using a function I can’t use yet at work: List.first!/1, which was added in Elixir v1.20.0… but at work, we’re still using v1.19.5 🙁. I’m importing Enum, because I’m using at/3, filter/2, map/2, and reduce/3, and not having to put Enum. in front of them let me fit line 11 on a single line. I’m also using String.to_charlist/1 (which breaks up the string into a list of character codes in a single step!), Kernel.length/1, and Map.values/1.
import Enum
def diff_array_as_string(word) do
arr = word
|> String.to_charlist
|> map(& &1 - 97)
map( 0..length(arr)-2, & at(arr, &1+1) - at(arr, &1) )
|> join(" ")
end
def string_difference(words) do
reduce(words, %{}, fn word, diffs ->
diff = diff_array_as_string(word)
Map.put(diffs, diff, Map.get(diffs, diff, []) ++ [word])
end)
|> Map.values
|> filter(& length(&1) == 1)
|> List.first! # first element from list of lists of length 1
|> List.first!
endView the entire Elixir script for this task on GitHub.
Here’s all my solutions in GitHub: https://github.com/packy/perlweeklychallenge-club/tree/challenge-398-packy-anderson/challenge-398/packy-anderson