The Bear's Den

Enter at your own risk

Odd Sets

Task 1: Arrays Intersection

Submitted by: Mohammad Sajid Anwar


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: ()

Solution

PDL has a function setops (and intersect as a wrapper) to find set intersections.

Both have in common that these operate on two sets, whereas in this task we shall handle a variable number of sets. Simply looping over the given sets would solve the task, but we may play around with some of PDL’s features.

When encasing the loop in a broadcast_define‘ed function, it becomes broadcasting aware. Let’s try it. Obviously this will not lead to the simplest solution, but it is a nice new lesson in PDL for me.

We define a function intersect_over that takes a 2-d ndarray as input - the list of sets - and produces a 1-d ndarray as output - the intersection over all sets.

When we take the sets as columns of a 2-d ndarray, all of them have to be of the same size. As this is obviously not a precondition in this task, we need to fill the sets with some kind of “non-elements”. Here PDL’s BAD values come handy. But then the question arises, how to handle a set that has BAD values only: shall we treat it as the empty set or shall we ignore it? Though it might seem counter-intuitive, we’ll follow the latter interpretation. See below for the reason behind this decision.

We take a temporary set that will be initialized with the first given set and afterwards is replaced with the intersection of itself and the next set from the list. The result is filled with BAD values to the uniform length of the input sets.

Here is the definition of intersect_over:

use strict;
use warnings;
use PDL;
use PDL::NiceSlice;

broadcast_define 'intersect_over(a(m,n);[o]b(n))' => over {
    my $tmp;
    for my $set ($_[0]->xchg(0, 1)->dog) {
        $tmp = $set, next unless defined $tmp;
        next unless $set->ngood;
        $tmp = intersect $tmp, $set;
    }
    my $card = $tmp->nelem;
    $tmp->reshape($_[1]->dims);
    $_[1] .= $tmp->setbadif(sequence($tmp) >= $card);
};

This function enables a simple solution for this task, where we instruct the PDL constructor to append BAD values for missing elements.

sub arrays_intersection {
    local $PDL::undefval = long->badvalue;
    my $list = long(@_)->xchg(0, 1);
    $list->badflag(1);
    intersect_over($list, my $res = null);
    $res;
}

Performing example 1 in the pdl shell, where we need to filter out the BAD values from the result:

pdl> do "./ch-1.pl"

pdl> do_print 1
1
pdl> $ex1 = arrays_intersection([1, 2, 3, 4], [4, 5, 6, 1], [4, 2, 1, 3])
[1 4 BAD BAD]
pdl> $ex1->($ex1->isgood;?)
[1 4]

Now coming back to broadcasting. As our function intersect_over is broadcasting-aware, we may provide higher-dimensional ndarrays as input, such as processing all three examples simultaneously by “stacking” the arrays:


pdl> $all = arrays_intersection([[1, 2, 3, 4], [4, 5, 6, 1], [4, 2, 1, 3]],
> [[1, 0, 2, 3], [2, 4, 5]],
> [[1, 2, 3], [4, 5], [6]])

[
 [  1   4 BAD BAD]
 [  2 BAD BAD BAD]
 [BAD BAD BAD BAD]
]

pdl> p $_($_->isgood;?) for $all->dog
[1 4] 
[2] 
Empty[0] 

Finally: Why do we interpret an all-BAD column as “missing” instead of “empty”?
Example 2 has only two sets. When stacking all three examples together, there is one complete set missing in example 2 that will be represented by BAD values. Interpreting it as “empty”, would give a different result. In contrast, an all-BAD row in the output represents the empty set.

See the full solution to task 1.

Task 2: Sort Odd Even

Submitted by: Mohammad Sajid Anwar


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)

Solution

Using PDL we create two slices from the list of numbers corresponding to even resp. odd indices. As the slices are connected to the original data, we may individually sort them inplace according to the requested order.

use strict;
use warnings;
use PDL;
use PDL::NiceSlice;

sub sort_odd_even {
    my $ints = long \@_;
    $ints(0:-1:2)->inplace->qsort;
    $ints(1:-1:2)->(-1:0)->inplace->qsort;
    $ints;
}

See the full solution to task 2.


If you have a question about this post or if you like to comment on it, feel free to open an issue in my github repository.