Perl Weekly Challenge: Week 269

Challenge 1:

Bitwise OR

You are given an array of positive integers, @ints.

Write a script to find out if it is possible to select two or more elements of the given array such that the bitwise OR of the selected elements has atlest one trailing zero in its binary representation.

Example 1
Input: @ints = (1, 2, 3, 4, 5)
Output: true

Say, we pick 2 and 4, their bitwise OR is 6. The binary representation of 6 is 110.
Return true since we have one trailing zero.
Example 2
Input: @ints = (2, 3, 8, 16)
Output: true

Say, we pick 2 and 8, thier bitwise OR is 10. The binary representation of 10 is 1010.
Return true since we have one trailing zero.
Example 3
Input: @ints = (1, 2, 5, 7, 9)
Output: false

Raku does it again and solves this task in one line.

say @*ARGS.combinations(2 .. @*ARGS.elems).grep({ ([+|] @$_).base(2).ends-with(0) }).elems > 0

(Full code on Github.)

We use the command-line arguments as input and then get all combinations of 2 or more elements with .combinations(). Then using .grep() we go through each combination and OR all its elements with the [+|] operator. This gives a decimal result so we convert it to binary digits with .base(2). Testing if the binary representation ends in 0 is easy with .ends-with(0). If the number of suitable elements found by .grep() is greater than 0, say prints out True or otherwise False.

For Perl, we have to provide our own implementations of .combinations() and [+|]. The former has appeared in many previous challenges but the latter is new. The function below remedies the lack.

bitwiseOR() takes as input a reference to a list for no other reason than that combinations() returns each combination as a list reference. I will change that at some point.

sub bitwiseOR($listref) {

Especially because I immediately convert it back to a list anyway.

    my @list = @{$listref};

First $result is initialized with the first element of the list which is then removed.

    my $result = shift @list;

Then every other element in @list is ORed with $result.

    for my $elem (@list) {
        $result |= $elem;
    }

Finally, $result is returned.

    return $result;
}

Now we can (mostly) follow the same algorithm as Raku.

my @ints = @ARGV;

Because my version of combinations() doesn't support ranges like in Raku, a double for loop is needed.

for my $i (2 .. scalar @ints) {
    for my $combo (combinations(\@ints, $i)) {
        if ((split //, sprintf("%b", bitwiseOR($combo)))[-1] == '0') {

We don't need all OR combinations ending in 0; just one is enough for us to be able ro determine truth or falsefood. So if we find one, we print true and stop processing early. (Unlike Raku.)

            say 'true';
            exit(0);
        }
    }
}

If we didn't find any suitable combinations, we print false.

say 'false';

(Full code on Github.)

Challenge 2:

Distribute Elements

You are given an array of distinct integers, @ints.

Write a script to distribute the elements as described below:

1) Put the 1st element of the given array to a new array @arr1.
2) Put the 2nd element of the given array to a new array @arr2.

Once you have one element in each arrays, @arr1 and @arr2, then follow the rule below:

If the last element of the array @arr1 is greater than the last
element of the array @arr2 then add the first element of the
given array to @arr1 otherwise to the array @arr2.

When done distribution, return the concatenated arrays. @arr1 and @arr2.

Example 1
Input: @ints = (2, 1, 3, 4, 5)
Output: (2, 3, 4, 5, 1)

1st operation:
Add 1 to @arr1 = (2)

2nd operation:
Add 2 to @arr2 = (1)

3rd operation:
Now the last element of @arr1 is greater than the last element
of @arr2, add 3 to @arr1 = (2, 3).

4th operation:
Again the last element of @arr1 is greate than the last element
of @arr2, add 4 to @arr1 = (2, 3, 4)

5th operation:
Finally, the last element of @arr1 is again greater than the last
element of @arr2, add 5 to @arr1 = (2, 3, 4, 5)

Mow we have two arrays:
@arr1 = (2, 3, 4, 5)
@arr2 = (1)

Concatenate the two arrays and return the final array: (2, 3, 4, 5, 1).
Example 2
Input: @ints = (3, 2, 4)
Output: (3, 4, 2)

1st operation:
Add 1 to @arr1 = (3)

2nd operation:
Add 2 to @arr2 = (2)

3rd operation:
Now the last element of @arr1 is greater than the last element
of @arr2, add 4 to @arr1 = (3, 4).

Mow we have two arrays:
@arr1 = (3, 4)
@arr2 = (2)

Concatenate the two arrays and return the final array: (3, 4, 2).
Example 3
Input: @ints = (5, 4, 3 ,8)
Output: (5, 3, 4, 8)

1st operation:
Add 1 to @arr1 = (5)

2nd operation:
Add 2 to @arr2 = (4)

3rd operation:
Now the last element of @arr1 is greater than the last element
of @arr2, add 3 to @arr1 = (5, 3).

4th operation:
Again the last element of @arr2 is greate than the last element
of @arr1, add 8 to @arr2 = (4, 8)

Mow we have two arrays:
@arr1 = (5, 3)
@arr2 = (4, 8)

Concatenate the two arrays and return the final array: (5, 3, 4, 8).

The spec is sufficiently clear that we can translate it into Raku almost verbatim.

Put the 1st element of the given array to a new array @arr1.

my @arr1 = @ints.shift;

Put the 2nd element of the given array to a new array @arr2.

my @arr2 = @ints.shift;

If the last element of the array @arr1 is greater than the last element of the array @arr2 then add the first element of the given array to @arr1 otherwise to the array @arr2.

(Note: I used .keys() here to traverse through @ints by index. But in hindsight, that seems pointless. I could just as easily have traversed through the elements themselves.)

for @ints.keys -> $k {

(Also for readabilities' sake, I should have written this as an if-else statement rather than use the ternary operator.)

    @arr1[*-1] > @arr2[*-1] ?? @arr1.push(@ints[$k]) !! @arr2.push(@ints[$k]);
}

Return the concatenated arrays. @arr1 and @arr2.

(The additional code is merely to make the output pretty.)

say q{(}, @arr1.push(| @arr2).join(q{, }), q{)};

(Full code on Github.)

This is the Perl version. For once, we didn't need any additional code to mimic Raku features. It shares the same faults as the Raku version.

my @ints = @ARGV;
my @arr1 = shift @ints;
my @arr2 = shift @ints;
for my $k (keys @ints) {
    if ($arr1[-1] > $arr2[-1]) { 
        push @arr1, $ints[$k];
    } else {
        push @arr2, $ints[$k];
    }
}
push @arr1, @arr2;

say q{(}, (join q{, }, @arr1), q{)};

(Full code on Github.)