The Smaller Hero
with Raku

by Arne Sommer

The Smaller Hero with Raku

[264] Published 25. November 2023.

This is my response to The Weekly Challenge #244.

Challenge #244.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)
File: count-smaller
#! /usr/bin/env raku

unit sub MAIN (*@int where @int.elems > 0 && all(@int) ~~ Int, # [1]
               :v(:$verbose));

my $index = 0;
my @output;                                                    # [2]

for @int -> $int                                               # [3]
{
  my $count = @int.grep( * < $int).elems;                      # [4]

  say ": For index = { $index++ }, count of elements less $int is $count."
    if $verbose;

  @output.push: $count;                                        # [5]
}

say "(" ~ @output.join(", ") ~ ")";                            # [6]

[1] At least one element, all of which must be integers.

[2] The result will end up here.

[3] Iterate over the input values. Note that the index value is used by verbose mode only.

[4] Get the number of values (.elems) in the array that are less than the current one, using grep.

See docs.raku.org/routine/grep for more information about grep.

[5] Add the count (from [4]) to the output array.

[6] Pretty print the result.

Running it:

$ ./count-smaller 8 1 2 2 3
(4, 0, 1, 1, 3)

$ ./count-smaller 6 5 4 8
(2, 1, 0, 3)

$ ./count-smaller 2 2 2
(0, 0, 0)

Looking good.

With verbose mode:

$ ./count-smaller -v 8 1 2 2 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.
(4, 0, 1, 1, 3)

$ ./count-smaller -v 6 5 4 8
: For index = 0, count of elements less 6 is 2.
: For index = 1, count of elements less 5 is 1.
: For index = 2, count of elements less 4 is 0.
: For index = 3, count of elements less 8 is 3.
(2, 1, 0, 3)

$ ./count-smaller -v 2 2 2
: For index = 0, count of elements less 2 is 0.
: For index = 1, count of elements less 2 is 0.
: For index = 2, count of elements less 2 is 0.
(0, 0, 0)

Let us have a go at a oneliner version, using map:

  say "(" ~ @int.map({ @int.grep({ $_ < $_ }).elems }).join(", ") ~ ")";
  ##        #1         #2          #2   #1

See docs.raku.org/routine/map for more information about map.

The idea is that the grepped values (#2) should be lower than the current limit/value (#1). But this will not work, as $_ will refer to the grepped one only - and not to values from different scopes.

We can remedy that by saving the outermost $_ (the one from map) in a variable:

File: count-smaller-oneliner-my
#! /usr/bin/env raku

unit sub MAIN (*@int where @int.elems > 0 && all(@int) ~~ Int);

say "(" ~ @int.map({ my $map = $_; @int.grep({ $_ < $map }).elems }).join(", ") ~ ")";

Running this gives the expected result:

$ ./count-smaller-oneliner-my 2 2 2
(0, 0, 0)

$ ./count-smaller-oneliner-my 8 1 2 2 3
(4, 0, 1, 1, 3)

$ ./count-smaller-oneliner-my 6 5 4 8
(2, 1, 0, 3)

But... it is not elegant.

We can use a * (whatever star) (and get rid of the closure brackets) for one of the original $_ instances instead, thus removing the conflict:

File: count-smaller-oneliner
#! /usr/bin/env raku

unit sub MAIN (*@int where @int.elems > 0 && all(@int) ~~ Int);

say "(" ~ @int.map({ @int.grep( * < $_ ).elems }).join(", ") ~ ")";

Running it gives the expected result, again:

$ ./count-smaller-oneliner 8 1 2 2 3
(4, 0, 1, 1, 3)

$ ./count-smaller-oneliner 6 5 4 8
(2, 1, 0, 3)

$ ./count-smaller-oneliner 2 2 2
(0, 0, 0)

Challenge #244.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:
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
File: group-hero
#! /usr/bin/env raku

unit sub MAIN (*@nums where @nums.elems > 0 && all(@nums) ~~ Int,  # [1]
               :v(:$verbose));

my $group = 0;                                                     # [2]
my $total = 0;                                                     # [3]

for @nums.combinations(1..Inf) -> @candidate                       # [4]
{
  my $max = @candidate.max;                                        # [5]
  my $min = @candidate.min;                                        # [6]
  my $sum = $max ** 2 * $min;                                      # [7]

  $total += $sum;                                                  # [8]

  say ": Group { ++$group }: ({ @candidate.join(",") }) => \
   square(max({ @candidate.join(",") })) * min({ @candidate.join(",") }) \
   => { $max ** 2 } * $min => $sum" if $verbose;
}

say $total;                                                        # [9]

[1] As in «Count Smaller».

[2] Used by verbose mode only.

[3] The answer (grand total) is built up here.

[4] Get all the combinations of the input, which is exactly what we are after. Well, not absolutely all. The first one is an empty list, so we use an array slice to start at the second one (with index 1).

See docs.raku.org/routine/combinations for more information about combinations.

[5] Compute the maximum,

[6] the minimum,

[7] and the sum (using the formulae given in the challenge).

[8] Add this sum to the grand total.

[9] Print the grand total.

Running it:

$ ./group-hero 2 1 4
141

Looking good.

With verbose mode:

$ ./group-hero -v 2 1 4
: 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
141

And that's it.