This post is part of a series on Mohammad Anwar’s excellent Weekly Challenge, where hackers submit solutions in Perl, Raku, or any other language, to two different challenges every week. (It’s a lot of fun, if you’re into that sort of thing.)
I thought I’d take the rare (for me) step of implementing this week’s challenges in Python as well as Perl.
Happy Canada Day!
Task 1 – Complete Day
The first task has us look through a list of hours and count the number of pairs that add up to a multiple of 24.
Perl
My first version was done in a compact functional style, which may be more challenging for Perl novices:
sub complete_day {
sum0 map { my $m = shift @$_; map { ($m + $_) % 24 == 0 } @$_ }
map { [ @_[$_ .. $#_] ] } 0..$#_
}
Reading from bottom up as usual, we iterate through the indices of @_
(our hours array) and map { ... }
each index to a list of array refs from index to end of list. So, given (1, 2, 3, 4, 5)
, we would expect to get the following list for this intermediate step:
(
[ 1, 2, 3, 4, 5 ],
[ 2, 3, 4, 5 ],
[ 3, 4, 5 ],
[ 4, 5 ],
[ 5 ],
)
The first map { ... }
then splits this into $m
, with the rest of the values in @$_
(note the shift
). $m
and @$_
are effectively car
and cdr
if you recall your lisp (although not many of us still do, I suppose).
There is an inner map { ... }
that then adds $m
to every value in @$_
, and maps to 1 if it is a multiple of 24 and 0 if it is not. sum0
from the core module List::Util simply adds them all up to get a count.
More readable example
A more readable version is as follows:
sub complete_day {
my $count = 0;
while (my $m = shift) {
$count += sum0 map { ($m + $_) % 24 == 0 } @_
}
$count
}
This one simply maintains a $count
as it goes, peeling off a new $m
each time through the while() { ... }
loop, with a similar inner map { ... }
as before.
For Perl Weekly Challenge code, I like to show off some different programming styles. Which one I would actually use in production is another question entirely.
Python
I didn’t think too hard about this one:
def complete_day(hours):
count = 0
for i, m in enumerate(hours):
for n in filter(lambda n: ((m + n) % 24 == 0), hours[i+1:]):
count += 1
return(count)
This works similarly to the second Perl example.
Task 2 – Maximum Frequency
The second task this week has us looking at a list of values, finding the maximum frequency of any particular value, and then returning the total number of items with that maximum frequency. This is best demonstrated by example.
Given (1, 2, 2, 4, 1, 5)
, both 1 and 2 occur twice, so the maximum frequency is 2. Since there are two different values with that frequency, we would return 2 x 2 = 4.
Given (1, 2, 2, 4, 6, 1, 5, 6)
, 1, 2, and 6 occur twice, so the maximum frequency is 2, but now there are three different values with that frequency, so we return 3 x 2 = 6.
Perl
sub max_freq {
my %freq; # Frequency table
$freq{$_}++ for @_;
my $max_freq = max values %freq; # Maximal frequency
$max_freq * grep { $_ == $max_freq } values %freq;
}
There are three essential steps here. First, we build a %freq
uency table, mapping values to the number of times they appear. Then we find the $max_freq
with a quick pass through the values
of that hash.
The final answer is generated by multiplying $max_freq
by the count of values where the frequency is equal to $max_freq
. Easy!
Python
def max_freq(ints):
# Annoying special case for empty list
if len(ints) == 0:
return(0)
# Build the frequency table (freq[n] = # of times n is in ints)
freq = {}
for n in ints: freq[n] = freq.setdefault(n,0) + 1
max_freq = max(freq.values()) # Maximal frequency
return(sum(filter(lambda x: x == max_freq, freq.values())))
This roughly follows the Perl code, although we need a special case for empty lists (otherwise we get an error).