## Perl Weekly Challenge 212: Rearrange Groups

These are some answers to the Week 212 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on April 16, 2023 at 23:59). This blog post offers some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

You are given a word having alphabetic characters only, and a list of positive integers of the same length.

Write a script to print the new word generated after jumping forward each letter in the given word by the integer in the list. The given list would have exactly the number as the total alphabets in the given word.

Example 1

``````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'
``````

This task was the subject of this post published on April 10, 2023.

You are given a list of integers and group size greater than zero.

Write a script to split the list into equal groups of the given size where integers are in sequential order. If it can’t be done then print -1.

Example 1:

``````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
``````

First, there was initially an error in Example 3 above of the task specification (`\$size` was 2 instead of 3, and that did not fit with the suggested output), but this has been fixed now.

Next, I first thought of sorting the input list of integers and, assuming for example a group size of 3, to try sequences of 3 successive integers in the list. But then I thought that it would be easier to store the input in a Raku Bag and to remove items used to construct groups as we go. As this turned out to be quite simple, I decided, as you will see, to use the same method in Perl, simulating bags with hashes.

### Rearrange Groups in Raku

First, note that `bags` are immutable in Raku. This means that you cannot change the inner items of the bag (or add or remove items), but this does not preclude you from re-assigning bags, as we do in the last statement of the `while` loop in the code below.

Using bags means that we can use operators with set theory semantics, such as the infix `(<=)` or infix `⊆` is a subset of or equal to,infix⊆), or the infix `(-)`, infix `∖` set difference,infix%E2%88%96), operators.

In the `rearrange` subroutine, the `while` loop runs as long as there are some items left in the bag. The loop looks for the smallest item in the bag, construct a sequence (`\$list`) of `\$size` successive items. If `\$list` is a subset of (or equal to) the bag, we store the `\$list` into the `@result` and remove the items of the `\$list` from the bag. If `\$list` is not part of the bag, then we failed and return -1. If we get normally out of the loop (because the bag is now empty), then we succeeded to build equal groups of items and can return the `@result`.

``````sub rearrange (@in, \$size) {
my @result;
return -1 unless @in.elems %% \$size;
my \$bag  = @in.Bag;
while (\$bag) {
my \$min = \$bag.min.key;
my @list = \$min..^(\$min + \$size);
return -1 unless @list ⊆ \$bag;
push @result, @list;
\$bag = \$bag (-) @list;  # set difference
}
return @result;
}

for ((1,2,3,5,1,2,7,6,3), 3), ((1,2,3), 2), ((1,2,3), 3),
((1,2,4,3,5,3), 3), ((1,5,2,6,4,7), 3),
((1,5,2,6,4,7), 2) -> @test {
say @test;
say rearrange(|@test), "\n";
}
``````

This script displays the following output:

``````\$ raku ./rearrange-groups.raku
((1 2 3 5 1 2 7 6 3) 3)
[[1 2 3] [1 2 3] [5 6 7]]

((1 2 3) 2)
-1

((1 2 3) 3)
[[1 2 3]]

((1 2 4 3 5 3) 3)
[[1 2 3] [3 4 5]]

((1 5 2 6 4 7) 3)
-1

((1 5 2 6 4 7) 2)
[[1 2] [4 5] [6 7]]
``````

### Rearrange Groups in Perl

I initially thought of using a different technique for solving the task in Perl, since there is no built-in `bag` data structure in Perl, but then I found that it was quite easy to simulate a `bag` with a hash containing an histogram of the input values. So this program essentially works the same as the Raku implementation (read the previous section if you need explanations). The `for` loop checks that all the values of `\$list` exist in the bag and remove these items from the bag.

``````use warnings;
use feature "say";

sub rearrange {
my @in = @{\$_[0]};
my \$size = \$_[1];
my @result;
return "-1" if @in % \$size;
my %bag;
\$bag{\$_}++ for @in;
while (%bag) {
my \$min = (sort { \$a <=> \$b } keys %bag)[0];
my @list = \$min..(\$min + \$size -1);
for my \$item (@list) {
return "-1" unless exists \$bag{\$item};
\$bag{\$item}--;
delete \$bag{\$item} if \$bag{\$item} == 0;
}
push @result, \@list;
}
return @result;
}

for my \$test( [[1,2,3,5,1,2,7,6,3], 3],
[[1,2,3], 2], [[1,2,3], 3],
[[1,2,4,3,5,3], 3],
[[1,5,2,6,4,7], 3],
[[1,5,2,6,4,7], 2] )
{
say "(@{\$test->[0]})", " ", "(\$test->[1])";
my @result = rearrange(@\$test);
if (\$result[0] == "-1") {
say -1;
} else {
say map { "[@\$_] " } @result;
}
say " ";
}
``````

This script displays the following output:

``````\$ perl ./rearrange-groups.pl
(1 2 3 5 1 2 7 6 3) (3)
[1 2 3] [1 2 3] [5 6 7]

(1 2 3) (2)
-1

(1 2 3) (3)
[1 2 3]

(1 2 4 3 5 3) (3)
[1 2 3] [3 4 5]

(1 5 2 6 4 7) (3)
-1

(1 5 2 6 4 7) (2)
[1 2] [4 5] [6 7]
``````

## Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on April 23, 2023. And, please, also spread the word about the Perl Weekly Challenge if you can.