Perl Weekly Challenge 40: Multiple Arrays Content and Sublist Sorting

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

Spoiler Alert: This weekly challenge deadline is due in a couple of days (December 29, 2019). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Challenge # 1: Multiple Arrays Content

You are given two or more arrays. Write a script to display values of each list at a given index.

For example:

Array 1: [ I L O V E Y O U ]
Array 2: [ 2 4 0 3 2 0 1 9 ]
Array 3: [ ! ? £ $ % ^ & * ]

We expect the following output:

I 2 !
L 4 ?
O 0 £
V 3 $
E 2 %
Y 0 ^
O 1 &
U 9 *

Multiple Arrays Content in Perl 5

Since this task seems fairly simple and does not require too much typing, I’ll suggest several solutions, with added features each time.

Considering the example provided with the task, we can see that all three arrays have the same number of items and that each item is just one character long. With such input data, the solution may be as simple as this:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

my @a1 = qw /I L O V E Y O U/;
my @a2 = qw /2 4 0 3 2 0 1 9/;
my @a3 = qw /! ? £ $ % ^ & */;

say "$a1[$_] $a2[$_] $a3[$_]" for 0..$#a1;

Running this script produces the following output:

$ perl mult_arrays.pl
I 2 !
L 4 ?
O 0 £
V 3 $
E 2 %
Y 0 ^
O 1 &
U 9 *

So job done in just one real code line, it seems, without any attempt at golfing, just normal relatively concise code.

But what if the arrays don’t have the same size? What if we don’t have 3 arrays but, for example, 2 or 4? What if the array’s items don’t have the same length? Of course, in any of these situations, our code may very well break.

Items With Different Sizes

Let’s start with items not having the same size. If we assume that the items all have less than seven characters, we can just use tabulations instead of spaces:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

my @a1 = qw /I L OOO V E Y O U/;
my @a2 = qw /244 42 0 1233 222 0 11 90/;
my @a3 = qw /! ???? £ $ %% ^ & */;

say "$a1[$_]\t$a2[$_]\t$a3[$_]" for 0..$#a1;

Running the script displays this:

$ perl mult_arrays.pl
I       244     !
L       42      ????
OOO     0       £
V       1233    $
E       222     %%
Y       0       ^
O       11      &
U       90      *

If any item can have a size greater than or equal to 7, then using tabulations is not sufficient. In this case, we can use formatted printing (with printf or sprintf). This is a boring exercise, and therefore best left as an exercise to the reader. (Just kidding, of course. If you want this feature and don’t know how to do it, please refer to the end of the Raku section below, where an example on how to do it is provided.)

Varying Number of Sublists

For going further, we probably want to change our data structure. Rather than having three hard-coded arrays, we will use an array of arrays (AoA), where the number of sub-arrays can be anything.

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

my @a = ( [ qw /I L O V E Y O U/ ], 
          [ qw /244 42 0 1233 222 0 11 90/ ],
          [ qw /! ???? £ $ %% ^ & */ ],
          [ qw /a b c d e f g f/ ] 
        );

my $sub_array_size = scalar @{$a[0]};
for my $i (0..$sub_array_size -1) {
    for (0..$#a) {
        print "$a[$_][$i]\t";
    }
    say "";
}

This works:

$ perl mult_arrays.pl
I       244     !       a
L       42      ????    b
O       0       £       c
V       1233    $       d
E       222     %%      e
Y       0       ^       f
O       11      &       g
U       90      *       f

but this starts to be somewhat unwieldy.

Matrix Transposition

At this point, we may want to transpose lines and columns of the @a array, store the transposed version into a @b array, and then simply print line by line the sub-arrays of @b:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

my @a = ( [ qw /I L O V E Y O U/ ], 
          [ qw /244 42 0 1233 222 0 11 90/ ],
          [ qw /! ???? £ $ %% ^ & */ ],
          [ qw /a b c d e f g f/ ] 
        );
my @b;
my $sub_array_size = scalar @{$a[0]};
for my $i (0..$sub_array_size -1) {
    push @b, [ map { $a[$_][$i]} 0 .. @a - 1];
}
say join "\t", @$_ for @b;

This displays the same as before:

$ perl  mult_arrays.pl
I       244     !       a
L       42      ????    b
O       0       £       c
V       1233    $       d
E       222     %%      e
Y       0       ^       f
O       11      &       g
U       90      *       f

Sublists of Different Sizes

This line-column transposition makes the program only moderately simpler, but it will make the next (and last) step easier. The next step is to handle the case where the sub-arrays don’t have the same number of elements. To handle this case, we first need to loop over the input array to find out the size of largest sub-array (the last one in the example below) and change the range of the main for loop header accordingly. The only additional change required is to handle empty slots in the last code line that prints out the result:

use strict;
use warnings;
use feature qw/say/;

my @a = ( [ qw /I L O V E Y O U / ], 
          [ qw /244 42 0 1233 222 0 / ],
          [ qw /! ???? £ $ %% ^ / ],
          [ qw /a b c d e f g h i j k/ ] 
        );
my $max_size = 0;
for (@a) {
    $max_size = scalar @$_ if @$_ > $max_size;
}
my @b;
for my $i (0..$max_size - 1) {
    push @b, [ map { $a[$_][$i]} 0 .. @a - 1];
}
say join "\t", map {$_ // "" } @$_ for @b;

Our final version displays the following output:

$ perl  mult_arrays.pl
I       244     !       a
L       42      ????    b
O       0       £       c
V       1233    $       d
E       222     %%      e
Y       0       ^       f
O                       g
U                       h
                        i
                        j
                        k

Multiple Arrays Content in Raku (formerly known as Perl 6)

We don’t need to try to solve the problem step by step in the Raku programming language, as the Z Zip operator, used together with the [] reduction metaoperator to act on several sub-arrays, gives us a very easy way to transpose lines and columns of a 2-D array:

use v6;

my @a = < I L O V E Y O U >, 
        < 244 42 0 1233 222 0 11 90 >,
        < ! ???? £ $ %% ^ & * >,
        < a b c d e f g f >;

my @b = [Z] @a;  # performs transposition
say join "\t", map {$_ // "" }, @$_ for @b;

This displays the following output:

$ perl6 mult_arrays.p6
I       244     !       a
L       42      ????    b
O       0       £       c
V       1233    $       d
E       222     %%      e
Y       0       ^       f
O       11      &       g
U       90      *       f

Sublists of Different Sizes

But that doesn’t work if the sub-arrays don’t have the same size, since the Z Zip operator will stop if one of the operands runs out of elements prematurely.

One possibility to solve the problem is to add dummy items (for example empty strings) to the smaller sublists. This means we now need to iterate twice over the input array, once to figure out the longest sublist, and a second time to add the dummy items. For some reason, I wasn’t able to modify the sublists (they appear to be immutable), so I had to create a copy of the @a input array.

use v6;

my @a = < I L O V E Y O U >, 
        < 244 42 0 1233 222 0 11 >,
        < ! ???? £ $ %% ^ & * >,
        < a b c d e f g f i j>;

my $max = max map { .elems }, @a;
my @b = map { (| $_, "" xx $max - .elems).flat }, @a;
my @c = [Z] @b;
say join "\t", map {$_ // "" }, @$_ for @c;

This produces the following output:

$ perl6 mult_arrays.p6
I       244     !       a
L       42      ????    b
O       0       £       c
V       1233    $       d
E       222     %%      e
Y       0       ^       f
O       11      &       g
U               *       f
                        i
                        j

Another way to do it is to use a for loop to copy the array elements one by one. In most programming languages, you would normally need two nested loops, but we can avoid that thanks to Raku’s X Cross operator used over the indices of the array of arrays:

use v6;

my @a = < I L O V E Y O U >, 
        < 244 42 0 1233 222 0 11 >,
        < ! ???? £ $ %% ^ & * >,
        < a b c d e f g f i j>;

my $max = max map { .elems }, @a;
my @b;
for ^$max X ^@a.elems -> ($i, $j) {
    @b[$i][$j] = @a[$j][$i] // "";
}
say join "\t", @$_ for @b;

This produces the same output as the previous implementation immediately above.

Item Lengths Exceeding the Tabulation Size

Now, what if some of the array items have a length exceeding the tabulation size (7 or more character)? Using tabulations is no longer sufficient. We can construct dynamically a formatting string to be used by the sprinf, printf, or fmt built-in functions:

use v6;

my @a = < I L O V E Y O U >, 
        < 244 42 0 123344556677 222 0 11 >,
        < ! ?????? £ $ %% ^ & * >,
        < a b c d e f g f i j>;

my $max = max map { .elems }, @a;
my @max-lengths = map { .map({.chars}).max  }, @a;
my $fmt = [~] map {"%-" ~ @max-lengths[$_] + 2 ~ "s"}, keys @max-lengths;
say "Format: ", $fmt;  # Displaying the resulting formatting string
my @b;
for ^$max X ^@a.elems -> ($i, $j) {
    @b[$i][$j] = @a[$j][$i] // "";
}
printf "$fmt\n", @$_ for @b;

This displays the following output:

$ perl6 mult_arrays.p6
Format: %-3s%-14s%-8s%-3s
I  244           !       a
L  42            ??????  b
O  0             £       c
V  123344556677  $       d
E  222           %%      e
Y  0             ^       f
O  11            &       g
U                *       f
                         i
                         j

Challenge # 2: Sort Sublists

You are given a list of numbers and set of indices belong to the list. Write a script to sort the values belongs to the indices.

For example,

List: [ 10, 4, 1, 8, 12, 3 ]
Indices: 0,2,5

We would sort the values at indices 0, 2 and 5 i.e. 10, 1 and 3.

Final List would look like below:

List: [ 1, 4, 3, 8, 12, 10 ]

Sorting Sublists in Perl 5

This is the perfect example for using array slices, which was the subject of a challenge a few weeks ago. We’ll use slices twice: one to extract from the list the values to be sorted, and once again for inserting the sorted values back into the array at their proper position. And we end up with a single line of code doing all the real work:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;
use Data::Dumper;

my @numbers = (10, 4, 1, 8, 12, 3);
my @indices = (0, 2, 5);

@numbers[@indices] = sort { $a <=> $b } @numbers[@indices];
say "@numbers";

This is the output displayed by the program:

$ perl  sublists.pl
1 4 3 8 12 10

Sorting Sublists in Raku

As in Perl 5, we can use array slices to make things really simple. The program is even simpler in Raku, since we don’t need the { $a <=> $b } code block used in Perl 5 to obtain numeric sort: Raku’s sort procedure is clever enough to discover that it should perform numeric sort when it sees numbers (well, more accurately, it is the default cmp operator used by sort which is smart enough to compare strings with string semantics and numbers with number semantics).

use v6;

my @numbers = 10, 4, 1, 8, 12, 3;
my @indices = 0, 2, 5;

@numbers[@indices] = sort @numbers[@indices];
say @numbers;

This program displays the following output:

$ perl6 sublists.p6
[1 4 3 8 12 10]

Wrapping up

The next week Perl Weekly Challenge is due to 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 Sunday, January 5, 2020. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.