Perl Weekly Challenge 127.

My solutions (task 1, and task 2 ) to the The Weekly Challenge - 127.

Task 1: Disjoint Sets

Submitted by: Mohammad S Anwar
You are given two sets with unique integers.

Write a script to figure out if they are disjoint.

The two sets are disjoint if they don’t have any common
members.

Example
Input: @S1 = (1, 2, 5, 3, 4)
       @S2 = (4, 6, 7, 8, 9)
Output: 0 as the given two sets have common member 4.

Input: @S1 = (1, 3, 5, 7, 9)
       @S2 = (0, 2, 4, 6, 8)
Output: 1 as the given two sets do not have common member.

Assuming the sets are given by numbers, as in the examples above, there is a nice solution using the Perl Data Language PDL. If Ii are the members of the first set and Jj the members of the second, then the sets are disjoint provided all the elements of the matrix Mij=Ii-Jj are different from zero. PDL allows to easily promote a vector Jj to a matrix Qij=Ij (for any i) by adding dummy dimensions. Then, it may be subtracted from I, which is automátically promoted to a matrix Pij=Ii and check the results for the presence of a zero entry. This fits into a one-liner.

Example 1:

perl -MPDL -E '($I,$J)= map {pdl $_} @ARGV;
              say "Input:\n\t$I\n\t$J\nOutput: ",
	      ($I-$J->dummy(0))->all' \
	      "[1,2,5,3,4]" "[4,6,7,8,9]"

Results:

Input:
	[1 2 5 3 4]
	[4 6 7 8 9]
Output: 0

Example 2:

perl -MPDL -E '($I,$J)= map {pdl $_} @ARGV;
              say "Input:\n\t$I\n\t$J\nOutput: ",
	      ($I-$J->dummy(0))->all' \
	      "[1,3,5,7,9]" "[0,2,4,6,8]"

Results:

Input:
	[1 3 5 7 9]
	[0 2 4 6 8]
Output: 1

The full version would be:

# Perl weekly challenge 127
# Task 1:  disjoint sets
#
# See https://wlmb.github.io/2021/08/23/PWC127/#task-1-disjoint-sets
use warnings;
use strict;
use v5.12;
use PDL;

die 'Usage: ./ch-1.pl "[a1,a2,...]" "[b1,b2,...]" ... to test for disjointness'
    unless @ARGV && @ARGV%2==0;
# Assume arguments of the form "[n1,n2,n3...]"
while(scalar @ARGV){
    my ($s1, $s2)=map {pdl $_} splice @ARGV,0,2;
    my $m=$s1-$s2->dummy(0);
    my $disjoint=$m->all;
    my $repeated=$s2->where($m->andover==0);
    say "Input;\n\t$s1\n\t$s2\nOutput: $disjoint";
    say $disjoint?"As there are no common members"
                 :"As the subset $repeated is common to both sets."
}

Example:

./ch-1.pl "[1,2,5,3,4]" "[4,6,7,8,9]" \
          "[1,3,5,7,9]" "[0,2,4,6,8]"

Results:

Input;
	[1 2 5 3 4]
	[4 6 7 8 9]
Output: 0
As the subset [4] is common to both sets.
Input;
	[1 3 5 7 9]
	[0 2 4 6 8]
Output: 1
As there are no common members

Example with proper and improper subsets:

./ch-1.pl "[1,2,5,3,4]" "[5,2,1]" \
          "[1,2,5,3,4]" "[4,3,5,2,1]"

Results:

Input;
	[1 2 5 3 4]
	[5 2 1]
Output: 0
As the subset [5 2 1] is common to both sets.
Input;
	[1 2 5 3 4]
	[4 3 5 2 1]
Output: 0
As the subset [4 3 5 2 1] is common to both sets.

Example with empty sets:

./ch-1.pl "[1,2,5,3,4]" "[]" \
          "[]" "[1,2,5,3,4]" \
          "[]" "[]"

Results:

Input;
	[1 2 5 3 4]
	Empty[0]
Output: 1
As there are no common members
Input;
	Empty[0]
	[1 2 5 3 4]
Output: 1
As there are no common members
Input;
	Empty[0]
	Empty[0]
Output: 1
As there are no common members

Task 2: Conflict Intervals

Submitted by: Mohammad S Anwar
You are given a list of intervals.

Write a script to find out if the current interval conflicts
with any of the previous intervals.

Example
Input: @Intervals = [ (1,4), (3,5), (6,8), (12, 13), (3,20) ]
Output: [ (3,5), (3,20) ]

    - The 1st interval (1,4) do not have any previous
      intervals to compare with, so skip it.
    - The 2nd interval (3,5) does conflict with previous
      interval (1,4).
    - The 3rd interval (6,8) do not conflicts with any of the
      previous intervals (1,4) and (3,5), so skip it.
    - The 4th interval (12,13) again do not conflicts with any
      of the previous intervals (1,4), (3,5) and (6,8), so
      skip it.
    - The 5th interval (3,20) conflicts with the first
      interval (1,4).

Input: @Intervals = [ (3,4), (5,7), (6,9), (10, 12), (13,15) ]
Output: [ (6,9) ]

    - The 1st interval (3,4) do not have any previous
      intervals to compare with, so skip it.
    - The 2nd interval (5,7) do not conflicts with the
      previous interval (3,4), so skip it.
    - The 3rd interval (6,9) does conflict with one of the
      previous intervals (5,7).
    - The 4th interval (10,12) do not conflicts with any of
      the previous intervals (3,4), (5,7) and (6,9), so skip
      it.
    - The 5th interval (13,15) do not conflicts with any of
      the previous intervals (3,4), (5,7), (6,9) and (10,12),
      so skip it.

Although conflicting is not defined, from the examples it seems to mean overlapping. Thus, an interval conflicts with a previous one if its intersection is not empty. I assume the intervals are open, so they don’t contain their end-points, and non-empty. Then, two intervals (a,b) and (c,d) intersect iff c<b and a<d. I can use the Perl Data Language PDL to solve the task. I read all intervals as pairs of numbers from STDIN into a 2D array with the first index denoting the left or right boundary and the second index numbering the intervals. I use dummy indices to test each interval against all other intervals and build a matrix with 1’s for each pair ij that overlaps and 0’s elsewhere. I select the upper right triangular submatrix, corresponding to intervals that overlap previous intervals. Finally, I get the indices of conflicting intervals and use them to extract the actual conflicting intervals. This takes only half a dozen lines of PDL code.

# Perl weekly challenge 127
# Task 2:  Conflict intervals
#
# See https://wlmb.github.io/2021/08/23/PWC127/#task-2-conflict-intervals
use warnings;
use strict;
use v5.12;
use PDL;
use PDL::IO::Misc;
use PDL::NiceSlice;

# read all intervals into a 2D array
my $ints=(rcols *STDIN,[])->transpose;
# check constraint
die "Expected a<b for every interval (a,b)" if ($ints((0))>=$ints((1)))->any;
# build matrix: overlap(i,j)=1 iff i and j overlap
my $overlap=($ints((0),:,*1)<$ints((1),*1,:))&($ints((1),:,*1)>$ints((0),*1,:));
# Select intervals that overlap previous ones
my $overlap_previous=$overlap&($overlap->xvals>$overlap->yvals);
# Indices of conflicting intervals
my $conflicting_indices=$overlap_previous->transpose->orover->which;
# The actual conflicting intervals.
my $conf_ints=$ints->transpose->($conflicting_indices)->transpose;
# Print result
say "Input: $ints\nConflicting intervals: ",
     $conf_ints->isempty?"None":"$conf_ints\nConflicts array: $overlap_previous";

Example 1:

./ch-2.pl << " END"
  1  4
  3  5
  6  8
 12 13
  3 20
 END

Results:

Input:
[
 [ 1  4]
 [ 3  5]
 [ 6  8]
 [12 13]
 [ 3 20]
]

Conflicting intervals:
[
 [ 3  5]
 [ 3 20]
]

Conflicts array:
[
 [0 1 0 0 1]
 [0 0 0 0 1]
 [0 0 0 0 1]
 [0 0 0 0 1]
 [0 0 0 0 0]
]

Note: In PDL the first index of a matrix runs from left to right and the second from top to bottom. Thus the conflicts array above shows that the second interval conflicts with the first and fifth interval conflicts with all the previous ones.

Example 2:

./ch-2.pl <<" END"
  3  4
  5  7
  6  9
 10 12
 13 15
 END

Results:

Input:
[
 [ 3  4]
 [ 5  7]
 [ 6  9]
 [10 12]
 [13 15]
]

Conflicting intervals:
[
 [6 9]
]

Conflicts array:
[
 [0 0 0 0 0]
 [0 0 1 0 0]
 [0 0 0 0 0]
 [0 0 0 0 0]
 [0 0 0 0 0]
]

Example, conflict-free:

./ch-2.pl <<" END"
  1   4
  6   8
 12  13
 15  20
 END

Results:

Input:
[
 [ 1  4]
 [ 6  8]
 [12 13]
 [15 20]
]

Conflicting intervals: None
Written on August 23, 2021