This is my response to The Weekly Challenge #212.
Input: $word = 'Perl' and @jump = (2,22,19,9)
Output: Raku
'P' jumps 2 place forward and becomes 'R'.
'e' jumps 22 place forward and becomes 'a'. (jump is cyclic i.e. after
'z' you go back to 'a')
'r' jumps 19 place forward and becomes 'k'.
'l' jumps 9 place forward and becomes 'u'.
Example 2:
Input: $word = 'Raku' and @jump = (24,4,7,17)
Output: 'Perl'
#! /usr/bin/env raku
unit sub MAIN ($word where $word ~~ /^<[a..zA..Z]>+$/, # [1]
*@jump where @jump.all ~~ UInt # [2]
&& @jump.all < 26 # [2a]
&& @jump.elems == $word.chars); # [2b]
for $word.comb -> $letter # [3]
{
my $is-lc = $letter eq any('a' ... 'z'); # [4]
my $jump = @jump.shift; # [5]
my $new = chr($letter.ord + $jump); # [6]
$new = chr($new.ord - 26) if $is-lc && $new.ord > 'z'.ord; # [7]
$new = chr($new.ord - 26) if !$is-lc && $new.ord > 'Z'.ord; # [8]
print $new; # [9]
}
say ""; # [10]
[1] The first argument is the word, which must consist of ascii lowercase and uppercase letters only, and at least one.
[2] The rest is a list of unsigned integers (the UInt
).
All of them must be lower than 26 (the length of the ascii alphabet), as
adding 26 would rountrip to the same letter [2a]. And finally we insist
on one number for each letter in the word [2b].
[3] Iterate over the letters in the word.
[4] Do we have a lowercase letter? (If not, it is uppercase courtesy of [1]).
[5] Get the current jump value.
[6]
Add the jump value to the character codepoint (ord
)
and convert that value back to a character (chr
).
See
docs.raku.org/routine/ord
for more information about ord
.
See
docs.raku.org/routine/chr
for more information about chr
.
[7] If we have a lowercase letter and the letter is higher than 'z' we have to roundtrip it back inside the lowercase range by subtracting 26 from the codepoint.
[8] As above, but for an uppercase letter. Here we check for 'Z' instead of 'z'.
[9] Print the character.
[10] Print a trailing newline, after the new word.
Running it:
$ ./jumping-letters Perl 2 22 19 9
Raku
$ ./jumping-letters Raku 24 4 7 17
Perl
Looking good.
Input: @list = (1,2,3,5,1,2,7,6,3) and $size = 3
Output: (1,2,3), (1,2,3), (5,6,7)
Example 2:
Input: @list = (1,2,3) and $size = 2
Output: -1
Example 3:
Input: @list = (1,2,4,3,5,3) and $size = 3
Output: (1,2,3), (3,4,5)
Example 4:
Input: @list = (1,5,2,6,4,7) and $size = 3
Output: -1
File: rearrange-group
#! /usr/bin/env raku
multi MAIN (UInt $size where $size >= 1, # [1]
*@list where @list.elems >= 2 # [1a]
&& @list.all ~~ Int # [1b]
&& @list.elems %% $size, # [1c]
:v(:$verbose))
{
say ": size: $size" if $verbose;
my @sorted = @list>>.Int.sort; # [2]
say ": Sorted source: @sorted[]" if $verbose;
my @res; # [3]
for 1 .. @list.elems / $size -> $group # [4]
{
my @group = @sorted.shift; # [5]
say ": New group $group starting with @group[0]" if $verbose;
while @group.elems < $size # [6]
{
my $target = @group.tail + 1; # [7]
my $index = @sorted.first($target, :k); # [8]
if defined $index # [9]
{
say ": Found target $target at index $index" if $verbose;
@group.append: @sorted.splice($index,1); # [9a]
say ": Group now: @group[] (Rest: @sorted[])" if $verbose;
}
else # [10]
{
say ": Did not find target $target" if $verbose;
say '-1'; # [10a]
exit; # [10b]
}
}
@res.push: @group; # [11]
say ": Added group to result: @group[]" if $verbose;
}
say @res>>.join(",").map({ "($_)" }).join(", "); # [12]
}
multi MAIN (*@slurp, :v(:$verbose)) # [13]
{
say '-1'; # [13a]
}
[1] I have chosen to use multiple dispatch, and this is the first version of MAIN. The first argument is the size, which must be positive. The second is s slurpy array with at least two elements [1a], they must all be integers [1b] (including negative values), and it must be possible to divide the list by the size [1c]. See [13] for the second version of MAIN.
[2] Sort the values, with the lowest first. And coerce them to integers, instead
of the default IntStr
we get as a result of using the command line.
[3] The result will end up here.
[4] Iterate over the number of groups we are going to (try to) create.
[5] Get the first value, the currently lowest unused value.
[6] As long as we have not filled up the current group.
[7] The target value to look for, one more than the last one
already in the group (with tail
).
See
docs.raku.org/routine/tail
for more information about tail
.
[8] Get the index of the target, with first
. Note the
:k
adverb, used to get the index instead of the actual value.
See
docs.raku.org/routine/first
for more information about first
.
[9]
Did we find it? (Note the use of defined
, as
0
is a perfectly valid index value). If so, use splice
to extract and remove that one character from the array and add it to the group.
Note the use of append
, as splice
gives a list - even
when we only get one value as here - instead of push
, so that we
keep the group as a single array.
See
docs.raku.org/routine/splice
for more information about splice
.
See
docs.raku.org/routine/append
for more information about append
.
See
docs.raku.org/routine/push
for more information about push
.
[10] If we did not find the value, print «-1» [10a] and exit [10b].
[11] Add the group to the result array.
[12] Print the two dimentional array, just as specified in the challenge.
[13] The second version of MAIN, which is used if the first one does not match the arguments.
Running it:
$ ./rearrange-group 3 1 2 3 5 1 2 7 6 3
(1,2,3), (1,2,3), (5,6,7)
$ ./rearrange-group 2 1 2 3
-1
$ ./rearrange-group 3 1 2 4 3 5 3
(1,2,3), (3,4,5)
$ ./rearrange-group 3 1 5 2 6 4 7
-1
Looking good.
With verbose mode:
$ ./rearrange-group -v 3 1 2 3 5 1 2 7 6 3
: size: 3
: Sorted source: 1 1 2 2 3 3 5 6 7
: New group 1 starting with 1
: Found target 2 at index 1
: Group now: 1 2 (Rest: 1 2 3 3 5 6 7)
: Found target 3 at index 2
: Group now: 1 2 3 (Rest: 1 2 3 5 6 7)
: Added group to result: 1 2 3
: New group 2 starting with 1
: Found target 2 at index 0
: Group now: 1 2 (Rest: 3 5 6 7)
: Found target 3 at index 0
: Group now: 1 2 3 (Rest: 5 6 7)
: Added group to result: 1 2 3
: New group 3 starting with 5
: Found target 6 at index 0
: Group now: 5 6 (Rest: 7)
: Found target 7 at index 0
: Group now: 5 6 7 (Rest: )
: Added group to result: 5 6 7
(1,2,3), (1,2,3), (5,6,7)
$ ./rearrange-group -v 2 1 2 3
-1
$ ./rearrange-group -v 3 1 2 4 3 5 3
: size: 3
: Sorted source: 1 2 3 3 4 5
: New group 1 starting with 1
: Found target 2 at index 0
: Group now: 1 2 (Rest: 3 3 4 5)
: Found target 3 at index 0
: Group now: 1 2 3 (Rest: 3 4 5)
: Added group to result: 1 2 3
: New group 2 starting with 3
: Found target 4 at index 0
: Group now: 3 4 (Rest: 5)
: Found target 5 at index 0
: Group now: 3 4 5 (Rest: )
: Added group to result: 3 4 5
(1,2,3), (3,4,5)
$ ./rearrange-group -v 3 1 5 2 6 4 7
: size: 3
: Sorted source: 1 2 4 5 6 7
: New group 1 starting with 1
: Found target 2 at index 0
: Group now: 1 2 (Rest: 4 5 6 7)
: Did not find target 3
-1
Note that the second example (-1) was catched by the second MULTI, whilst the fourth was detected by [10].
And that's it.