Perl Weekly Challenge: Week 244

Challenge 1:

Count Smaller

You are given an array of integers.

Write a script to calculate the number of integers smaller than the integer at each index.

Example 1
Input: @int = (8, 1, 2, 2, 3)
Output: (4, 0, 1, 1, 3)

For index = 0, count of elements less 8 is 4.
For index = 1, count of elements less 1 is 0.
For index = 2, count of elements less 2 is 1.
For index = 3, count of elements less 2 is 1.
For index = 4, count of elements less 3 is 3.
Example 2
Input: @int = (6, 5, 4, 8)
Output: (2, 1, 0, 3)
Example 3
Input: @int = (2, 2, 2)
Output: (0, 0, 0)

For the past few months, I have been giving myself the extra challenge of solving these tasks in one line; more often than not, I can do it. I thought this would be one of those times.

my %s=@*ARGS.sort Z=>@*ARGS.keys;say q{(},@*ARGS.map({ %s{$_}}).join(q{, }),q{)};

There are two statements in the line above. The first creates a hash whose keys are the sorted elements of the command-line input and the values are the indices of those elements. This is done via the Z=> operator where Z takes consecutive elements from each of the arrays that are its' operands and => converts them into a Pair suitable for insertion into a hash. The second statement iterates through the input with .map() and for each element, returns the value from %s for which that element is a key. (The rest of the statement is just for pretty-printing the results.)

Unfortunately, for example 1, it gives us this:

(4, 0, 2, 2, 3)

Which is not quite right. The problem is that because 2 is repeated in the input. the first time the key %s{2} gets the proper value 1, but the second time, it is overwritten with 2. In this short piece of code there is no way of saying don't assign a key-value pair if the key already exists.

So I had to rewrite things in a more verbose way.

    my %s;
    for @int.sort Z=> @int.keys -> $p {

This time we check that a key doesn't already exist before trying to assign a value to it.

        unless %s{$p.key}:exists {
            %s{$p.key} = $p.value;
        }
    }
    say q{(}, @int.map({ %s{$_} }).join(q{, }), q{)};

(Full code on Github.)

And now we get the correct answer.

This is the Perl version. It has to be even more verbose as we don't have Raku conveniences such as Z=> and .keys().

my %s;
my $index = 0;

for my $elem (sort { $a <=> $b } @int) {
    unless (exists $s{$elem}) {
        $s{$elem} = $index;
    }
    $index++;
}

say q{(}, (join q{, }, map { $s{$_} } @int), q{)}; 

(Full code on Github.)

Challenge 2:

Group Hero

You are given an array of integers representing the strength.

Write a script to return the sum of the powers of all possible combinations; power is defined as the square of the largest number in a sequence, multiplied by the smallest.

Example 1
Input: @nums = (2, 1, 4)
Output: 141

Group 1: (2) => square(max(2)) * min(2) => 4 * 2 => 8
Group 2: (1) => square(max(1)) * min(1) => 1 * 1 => 1
Group 3: (4) => square(max(4)) * min(4) => 16 * 4 => 64
Group 4: (2,1) => square(max(2,1)) * min(2,1) => 4 * 1 => 4
Group 5: (2,4) => square(max(2,4)) * min(2,4) => 16 * 2 => 32
Group 6: (1,4) => square(max(1,4)) * min(1,4) => 16 * 1 => 16
Group 7: (2,1,4) => square(max(2,1,4)) * min(2,1,4) => 16 * 1 => 16

Sum: 8 + 1 + 64 + 4 + 32 + 16 + 16 => 141

This time we can do a one-liner.

@*ARGS.combinations(1..@*ARGS.elems).map({$_.max**2*$_.min}).sum.say

(Full code on Github.)

First we get all the combinations of the command-line input from single elements to the entire thing with @*ARGS.combinations(1..@*ARGS.elems). Then with .map() we take each combination and multiply the square (**2) of the largest (.max()) element of the group and multiply it by the smallest (.min()) element. All these values are added together with .sum() and the result is printed with .say().

For Perl we need to supply our own functions to replace the missing .combinations() and .sum() atleast which I did from code written for previous challenges. We need .min() and .max() too but rather than code direct replacements for them, I wrote a function called power that does the entire calculation mentioned in the spec.

sub power {

It takes an array reference as a parameter.

    my ($arr) = @_;

The array is sorted numerically.

    my @sorted = sort { $a <=> $b } @{$arr};

Now the maximum value will be the last element in the sorted array and the minimum value will be the first; we can plug them directly into the power formula.

    return $sorted[-1] ** 2 * $sorted[0];
}

In the main code, we first define a variable to hold a running total.

my $total = 0;

Because my version of combinations() cannot accept a range, we use a for loop to get all the combinations of one element upto all the elements of @nums.

for my $i (1 .. scalar @nums) {

Then for each of the combinations of @nums of a particular length, we get the power() using map(). We add all these powers together using sum() and add that number to the running total.

    $total += sum([ map { power($_) } combinations(\@nums, $i) ]);
}

Finally, we print the total.

say $total;

(Full code on Github.)