Equal DI
with Raku

by Arne Sommer

Equal DI with Raku

[269] Published 26. December 2023.

This is my response to The Weekly Challenge #249.

Challenge #249.1: Equal Pairs

You are given an array of integers with even number of elements.

Write a script to divide the given array into equal pairs such that:
  1. Each element belongs to exactly one pair.
  2. The elements present in a pair are equal.
Example 1:
Input: @ints = (3, 2, 3, 2, 2, 2)
Output: (2, 2), (3, 3), (2, 2)

There are 6 elements in @ints.
They should be divided into 6 / 2 = 3 pairs.
@ints is divided into the pairs (2, 2), (3, 3), and (2, 2) satisfying
  all the conditions.
Example 2:
Input: @ints = (1, 2, 3, 4)
Output: ()

There is no way to divide @ints 2 pairs such that the pairs satisfy
  every condition.

This is actually quite easy:

  1. Sort the input values (numerically)
  2. Grab 2 values; abort if they are not equal
  3. Add the 2 values to the result
  4. More input values? If so go to 2.
  5. Print the result.

File: equal-pairs
#! /usr/bin/env raku

unit sub MAIN (*@ints where @ints.elems %% 2
                    && @ints.elems > 0
                    && all(@ints) ~~ Int,                           # [1]
               :v(:$verbose));

my @output;                                                         # [2]
my @sorted = @ints>>.Int.sort;                                      # [3]

say ":Sorted: { @sorted.join(",") }" if $verbose;

while @sorted                                                       # [4]
{
  my $first  = @sorted.shift;                                       # [4a]
  my $second = @sorted.shift;                                       # [4b]

  if $first == $second                                              # [5]
  {
    @output.push: ($first, $second);                                # [5a]
    say ":Pair: $first,$second" if $verbose;
  }
  else                                                              # [6]
  {
    say ":Non-pair: $first,$second" if $verbose;
    say "()";                                                       # [6a]
    exit;                                                           # [6b]
  }
}

say @output.map({ "($_[0], $_[1])"}).join(", ");                    # [7]

[1] Ensure that we have an even number of values (with the divisibility operator %%), that we have at least 1 (or 2, rather) and that they are all integers.

See docs.raku.org/routine/%% for more information about the Divisibility Operator %%.

[2] The result will end up here. We cannot print the pairs as we encounter them, as we may come across an unequal pair later on.

[3] Coerce the input values to integers (i.e. get rid of the IntStr type we got courtesy of the command line) and sort them (numerically).

See docs.raku.org/type/IntStr for more information about the IntStr type.

[4] As long as we have unfinished business, get the next two values [4a,4b].

[5] Are they equal? If so, add them to the output [5a].

[6] If not, print the empty array [6a] and exit [6b].

[7] Pretty print the result.

Running it:

$ ./equal-pairs 3 2 3 2 2 2
(2, 2), (2, 2), (3, 3)

$ ./equal-pairs 1 2 3 4
()

Looking good.

With verbose mode:

$ ./equal-pairs -v 3 2 3 2 2 2
:Sorted: 2,2,2,2,3,3
:Pair: 2,2
:Pair: 2,2
:Pair: 3,3
(2, 2), (2, 2), (3, 3)

$ ./equal-pairs -v 1 2 3 4
:Sorted: 1,2,3,4
:Non-pair: 1,2
()

The output from the first example as given by the challenge seems random, but I have chosen to print the pairs in increasing order instead.

Challenge #249.2: DI String Match

You are given a string s consisting of only the characters "D" and "I".

Find a permutation of the integers [0 .. length(s)] such that for each character s[i] in the string:

s[i] == 'I' ⇒ perm[i] < perm[i + 1]
s[i] == 'D' ⇒ perm[i] > perm[i + 1]
Example 1:
Input: $str = "IDID"
Output: (0, 4, 1, 3, 2)
Example 2:
Input: $str = "III"
Output: (0, 1, 2, 3)
Example 3:
Input: $str = "DDI"
Output: (3, 2, 0, 1)

This is even easier:

File: di-string-match
#! /usr/bin/env raku

unit sub MAIN ($s where $s ~~ /^<[ID]>+$/, :v(:$verbose));    # [1]

my @output;                                                   # [2]
my @integers = (0 .. $s.chars);                               # [3]

for $s.comb -> $char                                          # [4]
{
  if $char eq "I"                                             # [5]
  {
    @output.push: @integers.shift;                            # [5a]
    say ":I -> lowest integer { @output.tail }" if $verbose;  # [9]
  }
  else                                                        # [6]
  {
    @output.push: @integers.pop;                              # [6a]
    say ":D -> highest integer { @output.tail }" if $verbose;
  }
}

@output.push: @integers[0];                                   # [7]
say ":  -> remaining integer { @output.tail }" if $verbose;

say "({ @output.join(", ") })";                               # [8]

[1] Ensure that the string contains only «I» and «D», with at least one.

[2] The output will end up here.

[3] The integers to shuffle around, sorted by the lowest first.

[4] Iterate over the indiviual characters (with comb).

See docs.raku.org/routine/comb for more information about comb.

[5] If the character is «I», we take the lowest remaining integer (with shift, from the left hand side).

[6] Else (it is an «D») we take the highest remaining integer (with pop, from the right hand side).

See docs.raku.org/routine/pop for more information about pop.

[7] There is one more integer than characters, and we add it to the end.

[8] Pretty print the result.

[9] tail gives the last (rightmost) entry

See docs.raku.org/routine/tail for more information about tail.

Running it:

$ ./di-string-match IDID
(0, 4, 1, 3, 2)

$ ./di-string-match III
(0, 1, 2, 3)

$ ./di-string-match DDI
(3, 2, 0, 1)

Looking good.

With verbose mode:

$ ./di-string-match -v IDID
:I -> lowest integer 0
:D -> highest integer 4
:I -> lowest integer 1
:D -> highest integer 3
:  -> remaining integer 2
(0, 4, 1, 3, 2)

$ ./di-string-match -v III
:I -> lowest integer 0
:I -> lowest integer 1
:I -> lowest integer 2
:  -> remaining integer 3
(0, 1, 2, 3)

$ ./di-string-match -v DDI
:D -> highest integer 3
:D -> highest integer 2
:I -> lowest integer 0
:  -> remaining integer 1
(3, 2, 0, 1)

And that's it.