Perl Weekly Challenge: Week 276

Challenge 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

A Raku one-liner is enough to solve this.

We get the array of integers from the command-line and find all the pairs with .combinations(2). These are filtered with .grep(). The filter involves adding each pair with .sum() and checking that it is evenly divisible by 24 with the integer modulus operator %%. Pairs that are found are counted with .elems() and the answer is printed with .say().

@*ARGS.combinations(2).grep({ $_.sum %% 24}).elems.say

(Full code on Github.)

The Perl version is longer because we have to provide our own implementations of sum() and combinations() and %% is not available so we have to use the regular % operator instead. But with these, the core of the Perl version is also just one line.

say scalar grep { sum(@{$_}) % 24 == 0 } combinations(\@hours, 2);

(Full code on Github.)

Challenge 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.

My First try at a solution gave me this:

my %freq;
@ints.classify({ $_ }, into => %freq);

my $max = %freq.values.map({ $_.elems }).max;
%freq.values.grep({ $_.elems == $max }).elems.say;

It was quick, simple and got the job done. However it is not as efficient as it could be. For instance, .classify() puts each integer found into the hash under the key for it but we only need the frequency of values not the values themselves so this is wasted effort. Also we have to iterate through %freqs values twice; once to get the max integer and then to .grep() which values are that length. I wondered if I could do better. For this task we don't really need to but in real life ad hoc scripts have a bad habit of suddenly neeeding to scale far beyond the scope originally envisioned and I had some free time so I came up with this.

We are stilling storing the frequencies in a hash but this time the values are the count of how many times the integer appears not the integer itself. This way I can eliminate several uses of .elems().

my %freq;

for @ints -> $int {
    %freq{$int}++;
}

Storage is assigned to store the current max integer and the output.

my $max = 0;
my $output = 0;

We only iterate through %freq once by keys and values.

for %freq.kv -> $k, $v {

If the count (the value) for a particular integer (the key) is greater than the current value of $max it becomes the new value. As this must be the first time we have seen this count, $output is set to 1.

    if $v > $max {
        $max = $v;
        $output = 1;

If the count is equal to $max, $output is incremented by 1.

    } elsif $v == $max {
        $output++;
    }

Otherwise the count is ignored and we move on to the next one.

}

After the whole of %freq has been examined, all tht remains is to print the output.

say $output;

(Full code on Github.)

The Perl version is very similar. We would have had to do it this way anyway because Perl lacks some of the features used in my first Raku version.

my %freq;

for my $int (@ints) {
    $freq{$int}++;
}

my $max = 0;
my $output = 0;

while (my ($k, $v) = each %freq) {
    if ($v > $max) {
        $max = $v;
        $output = 1;
    } elsif ($v == $max) {
        $output++;
    }
}

say $output;

(Full code on Github.)