Perl Weekly Challenge: Week 373

Challenge 1:

Equal List

You are given two arrays of strings.

Write a script to return true if the two given array represent the same strings otherwise false.

Example 1
Input: @arr1 = ("a", "bc")
    @arr2 = ("ab", "c")
Output: true

Array 1: "a" + "bc" = "abc"
Array 2: "ab" + "c" = "abc"
Example 2
Input: @arr1 = ("a", "b", "c")
    @arr2 = ("a", "bc")
Output: true

Array 1: "a" + "b" + "c" = "abc"
Array 2: "a" + "bc" = "abc"
Example 3
Input: @arr1 = ("a", "bc")
    @arr2 = ("a", "c", "b")
Output: false

Array 1: "a" + "bc" = "abc"
Array 2: "a" + "c" + "b" = "acb"
Example 4
Input: @arr1 = ("ab", "c", "")
    @arr2 = ("", "a", "bc")
Output: true

Array 1: "ab" + "c" + "" = "abc"
Array 2: ""  + "a" + "bc" = "abc"
Example 5
Input: @arr1 = ("p", "e", "r", "l")
    @arr2 = ("perl")
Output: true

Array 1: "p" + "e" + "r" + "l" = "perl"
Array 2: "perl"

This was obviously going to be a one-liner. The only question was how would I get the input into the script as it consists of two arrays and there is no way to signify the end of an array. I chose to represent them as two command-line string arguments with the elements of each array separated within the string by whitespace.

So first we reconstitute the arrays (called @a and @b for brevity) with .words(). Then we join them back up into strings with .join(), compare them with the eq oporator and print the result with say(). If they are equal, True will be output, otherwise False.

my @a = @*ARGS[0].words; my @b = @*ARGS[1].words; say @a.join eq @b.join

(Full code on Github.)

The Perl version is also one line but substantially more verbose because we have to use split() instead of .words(), join() explcitly requires an empty strin to join and boolean operations don't automatically convert to true and false when printed.

my @a = split /\s+/, $ARGV[0]; my @b = split /\s+/, $ARGV[1]; say join(q{}, @a) eq join("",@b) ? "true" : "false"

(Full code on Github.)

Challenge 2:

List Division

You are given a list and a non-negative integer.

Write a script to divide the given list into given non-negative integer equal parts. Return -1 if the integer is more than the size of the list.

Example 1
Input: @list = (1,2,3,4,5), $n = 2
Output: ((1,2,3), (4,5))

5 / 2 = 2 remainder 1.
The extra element goes into the first chunk.
Example 2
Input: @list = (1,2,3,4,5,6), $n = 3
Output: ((1,2), (3,4), (5,6))

6 / 3 = 2 remainder 0.
Example 3
Input: @list = (1,2,3), $n = 2
Output: ((1,2), (3))
Example 4
Input: @list = (1,2,3,4,5,6,7,8,9,10), $n = 5
Output: ((1,2), (3,4), (5,6), (7,8), (9,10))
Example 5
Input: @list = (1,2,3), $n = 4
Output: -1
Example 6
Input: @list = (72,57,89,55,36,84,10,95,99,35), $n = 7;
Output: ((72,57), (89,55), (36,84), (10), (95), (99), (35))

We start by dealing with the case where the length of the @list is smaller than $n as in example 5. In that case -1 is printed out and nothing more needs to be done.

if @list.elems < $n {
    say "-1";

If @list is long enough...

} else {

We get the length of each part here by dividing the length of @list by $n. Notice we are using the integer division operator div here because we can't have a fractional length of a part.

    my $len = @list.elems div $n;

Instead, if $n does not divide evenly into the length of @list, we use the modulus operator to determine the remainder.

    my $rem = @list.elems % $n;

We create an array @groups with $n elements, each initially containing $len.

    my @groups = $len xx $n;

If there was a remainder it is distributed into the first $rem elements of @groups. my $i = 0; while $rem != 0 { @groups[$i++] += 1; $rem--; }

Now @list is divided into $n parts of an array called @results. The number of elements that get assigned to each part is determined by the value of the corresponding element in @groups.

    my @results = @groups.map({ @list.splice(0, $_) });

Finally, @results is printed out. The slightly complicated code below is so it can be formatted in the same style as the spec.

    say q{(},
        @results.map({ q{(} ~ @$_.join(q{,}) ~ q{)} }).join(q{, }),
    q{)};   
}

(Full code on Github.)

This is the Perl version.

if (scalar @list < $n) {
    say "-1";
} else {

We don't have a dedicated operator for integer division like Raku but we can simulate it easily enough with int().

    my $len = int(scalar @list / $n);
    my $rem = scalar @list % $n;

    my @groups = ($len) x $n;
    my $i = 0;
    while ($rem != 0) {
        $groups[$i++] += 1;
        $rem--;
    } 

An annoying thing about Perl is that if you want lists of lists, the inner ones have to be list references.

    my @results = map { [ splice @list, 0, $_ ] } @groups;

    say q{(},
        (join q{, }, map { q{(} . (join q{,}, @{$_}) . q{)} } @results),
    q{)};
}

(Full code on Github.)