So, I don’t want to repeat my music selection from Seize The Day. I feel like this task needs another “day”… another day… oh. Another Day by James Taylor.
Well, with the music selection out of the way, let’s dive into Perl Weekly Challenge 276!
Task 1: Complete Day
You are given an array of integers, @hours
.
Write a script to return the number of pairs that forms a complete day.
A complete day is defined as a time duration that is an exact multiple of 24 hours.
Example 1
Input: @hours = (12, 12, 30, 24, 24)
Output: 2
Pair 1: (12, 12)
Pair 2: (24, 24)
Example 2
Input: @hours = (72, 48, 24, 5)
Output: 3
Pair 1: (72, 48)
Pair 2: (72, 24)
Pair 3: (48, 24)
Example 3
Input: @hours = (12, 18, 24)
Output: 0
Approach
Ok, it says “return the number of pairs that forms a complete day” where a “complete day” is “an exact multiple of 24 hours”. And, in all of the samples, the pairs shown have two elements. And, looking at example 2, we can use elements in multiple pairs; note that even though 72, 48, and 24 only appear in the input array once, they each appear in two different pairs.
So this feels like the double loop I do so often in this challenge: loop over the list as an outer loop, then, for each element, loop over the elements AFTER that element and see if we can build a pair.
Raku
sub completeDays(@hours) {
my @pairs;
for 0 .. @hours.end - 1 -> $i {
for $i + 1 .. @hours.end -> $j {
@pairs.push([ @hours[$i], @hours[$j] ])
if ( @hours[$i] + @hours[$j] ) % 24 == 0;
}
}
my $explain = "";
for @pairs.kv -> $i, @p {
$explain ~= "\n" ~ "Pair " ~ ($i+1) ~ ": ("
~ @p.join(", ") ~ ")"
}
return @pairs.elems, $explain;
}
View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: @hours = (12, 12, 30, 24, 24)
Output: 2
Pair 1: (12, 12)
Pair 2: (24, 24)
Example 2:
Input: @hours = (72, 48, 24, 5)
Output: 3
Pair 1: (72, 48)
Pair 2: (72, 24)
Pair 3: (48, 24)
Example 3:
Input: @hours = (12, 18, 24)
Output: 0
Perl
sub completeDays(@hours) {
my @pairs;
foreach my $i ( 0 .. $#hours - 1 ) {
foreach my $j ( $i + 1 .. $#hours) {
push @pairs, [ $hours[$i], $hours[$j] ]
if ( $hours[$i] + $hours[$j] ) % 24 == 0;
}
}
my $explain = "";
foreach my $i ( 0 .. $#pairs ) {
$explain .= "\n" . "Pair " . ($i+1) . ": ("
. join(", ", @{ $pairs[$i] }) . ")"
}
return scalar(@pairs), $explain;
}
View the entire Perl script for this task on GitHub.
Python
def comma_join(arr):
return ', '.join(map(lambda i: str(i), arr))
def completeDays(hours):
pairs = []
for i in range(len(hours) - 1):
for j in range(i+1, len(hours)):
if ( hours[i] + hours[j] ) % 24 == 0:
pairs.append([ hours[i], hours[j] ])
explain = ""
i = 1
for p in pairs:
explain += f"\nPair {i}: ({comma_join(p)})"
i += 1
return len(pairs), explain
View the entire Python script for this task on GitHub.
Elixir
def makePair([], _, pairs), do: pairs
def makePair([j], i, pairs) do
pairs ++ [ [i, j] ]
end
def makePair([j | rest], i, pairs) do
pairs = pairs ++ [ [i, j] ]
makePair(rest, i, pairs)
end
def findPairs([], pairs), do: pairs
def findPairs([i | rest], pairs) do
canPair = Enum.filter(rest, fn j -> rem(i + j, 24) == 0 end)
pairs = makePair(canPair, i, pairs)
findPairs(rest, pairs)
end
def explainPairs([], _, explain), do: explain
def explainPairs([pair | rest ], i, explain) do
pairList = "(" <> Enum.join(pair, ", ") <> ")"
pairNum = "Pair #{to_string(i)}: "
explainPairs(rest, i+1, explain <> "\n" <> pairNum <> pairList)
end
def completeDays(hours) do
pairs = findPairs(hours, [])
explain = explainPairs(pairs, 1, "")
{length(pairs), explain}
end
View the entire Elixir script for this task on GitHub.
Task 2: Maximum Frequency
You are given an array of positive integers, @ints
.
Write a script to return the total number of elements in the given array which have the highest frequency.
Example 1
Input: @ints = (1, 2, 2, 4, 1, 5)
Output: 4
The maximum frequency is 2.
The elements 1 and 2 has the maximum frequency.
Example 2
Input: @ints = (1, 2, 3, 4, 5)
Output: 5
The maximum frequency is 1.
The elements 1, 2, 3, 4 and 5 has the maximum frequency.
Approach
This feels familiar: counting up the occurrences of integers… we did this back in PWC 233.
Raku
I also remembered that Raku has a class just for counting collections of elements with integer weights: Bag. I’m also using the Lingua::Conjunction module I used back in PWC 231.
use Lingua::Conjunction;
sub maxFrequency(@ints) {
my $freq = bag @ints;
my $maxFreq = max($freq.values);
my @atMax;
for $freq.keys.sort -> $i {
if ($freq{$i} == $maxFreq) {
@atMax.push($i);
}
}
my $explain = "The maximum frequency is $maxFreq.\n"
~ "The element[|s] |list| [has|have] "
~ "the maximum frequency.";
return(
$maxFreq * @atMax.elems,
conjunction(@atMax, :str($explain))
);
}
View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: @ints = (1, 2, 2, 4, 1, 5)
Output: 4
The maximum frequency is 2.
The elements 1 and 2 have the maximum frequency.
Example 2:
Input: @ints = (1, 2, 3, 4, 5)
Output: 5
The maximum frequency is 1.
The elements 1, 2, 3, 4, and 5 have the maximum frequency.
Perl
For Perl, we can just use a Hash in place of a Bag, and the Lingua::EN::Inflexion module will handle making our list use commas and and
properly, and inflecting our nouns and verbs.
use List::Util qw( max );
use Lingua::EN::Inflexion qw( inflect wordlist );
sub maxFrequency(@ints) {
my %freq;
map { $freq{$_}++ } @ints;
my $maxFreq = max(values %freq);
my @atMax;
foreach my $i ( sort keys %freq ) {
if ($freq{$i} == $maxFreq) {
push @atMax, $i;
}
}
my $list = wordlist(@atMax);
my $count = scalar(@atMax);
my $explain = "<#d:$count>The maximum frequency is $maxFreq.\n"
. "The <N:element> $list <V:has> "
. "the maximum frequency.";
return(
$maxFreq * $count,
inflect($explain)
);
}
View the entire Perl script for this task on GitHub.
Python
In Python, we want to use the Counter
type in the collections
module as our Bag stand-in, and I’m pulling in the conjuction
function I wrote for PWC 247.
from collections import Counter
def comma_join(arr):
return ', '.join(map(lambda i: str(i), arr))
def conjunction(ints):
if len(ints) < 2:
return(ints)
elif len(ints) == 2:
return(f'{ints[0]} and {ints[1]}')
else:
last = ints.pop(-1)
l = comma_join(ints)
return(f'{l}, and {last}')
def maxFrequency(ints):
freq = Counter(ints)
maxFreq = max(freq.values())
atMax = []
for i in sorted(freq.keys()):
if freq[i] == maxFreq:
atMax.append(i)
numList = conjunction(atMax)
elements = "elements" if len(atMax) > 1 else "element"
have = "have" if len(atMax) > 1 else "has"
explain = (
f"The maximum frequency is {maxFreq}.\n" +
f"The {elements} {numList} {have} " +
f"the maximum frequency."
)
return (maxFreq * len(atMax), explain)
View the entire Python script for this task on GitHub.
Elixir
In Elixir, there’s an external module for working with Bags called Multiset, but because pulling an external module into a single-file Elixir script isn’t easy, I just lifted the code to implement the pieces of the module I needed right into my script.
def conjunction(l) do
if length(l) < 2 do
l
else
if length(l) == 2 do
to_string(List.first(l)) <> " and " <>
to_string(List.last(l))
else
{last, rest} = List.pop_at(l, -1)
Enum.join(rest, ", ") <> ", and " <> to_string(last)
end
end
end
def maxFrequency(ints) do
freq = Multiset.new(ints)
maxFreq = Enum.max(Multiset.multiplicities(freq))
atMax = Enum.filter(Multiset.values(freq), fn i ->
Multiset.multiplicity(freq, i) == maxFreq
end)
numList = conjunction(atMax)
{elements, have} = if length(atMax) > 1 do
{"elements", "have"}
else
{"element", "has"}
end
explain = "The maximum frequency is #{maxFreq}.\n" <>
"The #{elements} #{numList} #{have} " <>
"the maximum frequency."
{maxFreq * length(atMax), explain}
end
View the entire Elixir script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-276/packy-anderson