Perl Weekly Challenge: Week 310
Challenge 1:
Arrays Intersection
You are given a list of array of integers.
Write a script to return the common elements in all the arrays.
Example 1
Input: $list = ( [1, 2, 3, 4], [4, 5, 6, 1], [4, 2, 1, 3] )
Output: (1, 4)
Example 2
Input: $list = ( [1, 0, 2, 3], [2, 4, 5] )
Output: (2)
Example 3
Input: $list = ( [1, 2, 3], [4, 5], [6] )
Output: ()
This is another challenge we can solve with a Raku one-liner:
say q{(},([∩] @*ARGS».words).keys.sort({$^a <=> $^b}).join(q{, }),q{)}
The input is entered in the form of a series of command-line arguments where each argument is a string
containing a list of integers separated by whitespace. So for example the input for example 1 would look like
"1 2 3 4" "4 5 6 1" "4 2 1 3
. These arguments (contained in @*ARGS
) are split back into List
s with the hyper operator »
and .words()
.
The intersection operator ∩
is run over all these lists by enclosing it in the reduction operator []
. The
result of this is a Set
of all the common elements. We can extract the elements from this with .keys()
. For
good measure, the result is .sort()
ed in ascending numeric order and the rest of the code is just for printing
it out in the style of the examples.
For Perl we have to work around missing Raku features. In lieu of ».words
, we use map()
and split()
. This creates a list of list references.
my @list = map { [ split /\s+/, $_ ] } @ARGV;
We need a replacement for [∩]
and I had one; well, almost. My intersection()
function only works on two
arguments. Luckily, we can emulate reduction like this:
The result will be in, naturally, @lists
. We initialize @lists
with the first of our lists which we extract with
shift()
. As it is a list reference, first we need to cast it back into a list.
my @result = @{shift @list};
Now we find the intersection of @result
and each consecutive list and assign it back to @result
.
for my $arr (@list) {
@result = intersection(\@result, $arr);
}
When this is done, we have the intersection or in other words, all the common elements of the input. All that remains is to sort and print the result as in Raku.
say q{(}, (join q{, }, sort { $a <=> $b } @result), q{)};
Challenge 2:
Sort Odd Even
You are given an array of integers.
Write a script to sort odd index elements in decreasing order and even index elements in increasing order in the given array.
Example 1
Input: @ints = (4, 1, 2, 3)
Output: (2, 3, 4, 1)
Even index elements: 4, 2 => 2, 4 (increasing order)
Odd index elements : 1, 3 => 3, 1 (decreasing order)
Example 2
Input: @ints = (3, 1)
Output: (3, 1)
Example 3
Input: @ints = (5, 3, 2, 1, 4)
Output: (2, 3, 4, 1, 5)
Even index elements: 5, 2, 4 => 2, 4, 5 (increasing order)
Odd index elements : 3, 1 => 3, 1 (decreasing order)
Raku has a very nice method that can do most of the work for this challenge for us, called .classify()
. In the
line below, first we find the indices of each element of @ints
with .keys()
. Then in the call to .classify()
,
the first paramter is a test which will be performed on each element of the keys. Depending on the result, the element will be assigned to the 'odd' or 'even' key of the hash specified by the third or into
parameter. As we actually want the value of the element not the index, we can transform what actually gets added to the hash with the code in the seconnd or 'as' parameter.
@ints.keys.classify({ $_ %% 2 ?? 'evens' !! 'odds' }, as => { @ints[$_] }, into => my %oe);
Once we have the odds and evens we can sort them the way the spec suggests and assign them to arrays.
my @evens = %oe<evens>.sort({$^a <=> $^b});
my @odds = %oe<odds>.sort({$^b <=> $^a});
I thought an easy way to create the result would be two take elements from each of the arrays with
the Z
operator but the problem is that operator does not deal very well with arrays of different lengths.
In example 2, for instance, the @evens
array has 0 elements and @odds
has 2. So instead, we iterate through
each array and add elements by shift()
ing thme from tha array and adding it to the @result
array.
my @result;
while @evens || @odds {
if @evens {
@result.push(@evens.shift);
}
if @odds {
@result.push(@odds.shift);
}
}
After this process, we can just print out @result
in the style suggested by the spec.
say q{(}, @result.join(q{, }), q{)};
For the Perl version, we don't need addition code but not having .classify()
means we have to do the
first part a little differently. Instead of building a hash first, we push keys of @ints
directly into @evens
and @odds
based on, well, whether they are even or odd.
my @evens;
my @odds;
for my $k (keys @ints) {
if ($k % 2 == 0) {
push @evens, $ints[$k];
} else {
push @odds, $ints[$k];
}
}
The rest work exactly the same as in Raku.
@evens = sort {$a <=> $b} @evens;
@odds = sort {$b <=> $a} @odds;
my @result;
while (@evens || @odds) {
if (@evens) {
push @result, shift @evens;
}
if (@odds) {
push @result, shift @odds;
}
}
say q{(}, (join q{, }, @result), q{)};