## Andrew Shitov Weekly Review: Challenge - 263

Monday, Apr 8, 2024| Tags: Raku

# Raku Solutions Weekly Review

In the first task, you have an array of integers `@ints` and a target number `\$k`. You need to print the indices of those elements, which are equal to the target, but you need to search in the sorted source data.

## Thoughts

What does Raku offer that can be used in this task? First, the `sort` routine, which you can use as a method or as a stand-alone function:

``````my @ints = 1, 5, 3, 2, 4, 2;

say @ints.sort;  # (1 2 2 3 4 5)
say sort(@ints); # (1 2 2 3 4 5)
``````

By the way, the `sort` routine in Raku is smart enough to take care of correclty sorting both numbers and strings. Compare the following two sortings:

``````my @ints = 1, 2, 10, 20;
say @ints.sort;

my @strs = '1', '2', '10', '20';
say @strs.sort;
``````

The result is expectedly correct:

``````(1 2 10 20)
(1 10 2 20)
``````

Back to the task, and the second mean in Raku to be used here is the `grep` routine that finds the elements with the given condition. Our condition is the equality with the target value `\$k`, so in terms of Raku you can choose one of the following approaches:

``````my @data = @ints.sort;
my \$k = 2;
say @data.grep: * == \$k;    # (2 2)
say @data.grep({\$_ == \$k}); # (2 2)
``````

The tricky point here is to get the indices of the elements that match the condition. You cannot do it directly after you have already filtered the data. Instead, you may think of making a loop over the values and gathering the indices:

``````say gather {
for ^@data -> \$index {
take \$index if @data[\$index] == \$k;
}
} # (1 2)
``````

Raku, nevertheless, has a better solution. Just use the `:k` flag of `grep`. In this case, instead of returing the values, `grep` returns the indices of the matched elements, which is exactly what is needed.

``````say @data.grep: * == \$k, :k; # (1 2)
``````

## Solutions

All of the above steps can be chained to get a construction like this, as you can see in the solutions propopsed by a number of authors. Notice that you even don’t have to explicitly use comparison and thus the condition can be shortened to a bare minumum: `\$k`.

``````@ints.sort.grep: \$k, :k
``````

Jan Krňávek does effectively the same but he prefers to use `andthen` instead of dots:

``````sub target-index (+ints,:\$k) {
ints
andthen .sort
andthen .grep: { \$_ == \$k },:k
}
``````

Luca Ferrari uses the smartmatch `~~` oprartor for comparison. In our case, it compares numbers, so it is equivalent to `==`.

``````@nums.sort.grep( * ~~ \$k, :k ).say;
``````

A slightly different algorithm is used by some authors. Instead of grepping and looking for an index, they use a tricky condition to grep indices directly:

``````my @sorted = @in.sort;
my @out = grep {@sorted[\$_] == \$target}, 0..@sorted.end;
``````

You can code it differently, for example:

``````sub targetindex(@a0, \$k) {
my @a = @a0.sort({\$^a <=> \$^b});
return [(0 .. @a.end).grep({@a[\$_] == \$k})];
}
``````

Further, let’s look at the solutions with a loop, for example:

``````for @sorted.kv -> \$i, \$v {
next unless \$v == \$k;
@output.push(\$i);
}
``````

Athanasius uses a loop too, but it is always interesting to see that Raku allows completely different style of programming with more control over the types of the variables, function arguments and return values.

``````#-------------------------------------------------------------------------------
sub find-target-indices( List:D[Int:D] \$ints, Int:D \$k --> List:D[UInt:D] )
#-------------------------------------------------------------------------------
{
my UInt @indices;
my Int  @sorted = \$ints.sort;

for 0 .. @sorted.end -> UInt \$i
{
@indices.push: \$i if @sorted[ \$i ] == \$k;
}

return @indices;
}
``````

BarrOff demonstrates another appoach. The program uses the `indices` function. It works on strings and returns the indices of the matching characters, so you need to convert the array of integers to a string first. The method works with the test samples, where all the numbers are below 10, so they can be spellt with a single character.

``````sub target-index(Int:D \$k, @ints --> Positional) {
indices(@ints.sort.join, \$k)
}
``````

## Bonus

As a bonus, examine the following alternative code submitted by Bruce Gray in Raku:

``````sub task1_single_pass_without_sorting ( UInt \$k, @ns ) {
return flat .{Less} + ^.{Same} given ( @ns »<=>» \$k ).Bag;
}
``````