MIS-ing
with Raku

by Arne Sommer

MIS-ing with Raku

[228] Published 18. March 2023.

This is my response to The Weekly Challenge #208.

Challenge #208.1: Minimum Index Sum

You are given two arrays of strings.

Write a script to find out all common strings in the given two arrays with minimum index sum. If no common strings found returns an empty list.

Example 1:
Input: @list1 = ("Perl", "Raku", "Love")
       @list2 = ("Raku", "Perl", "Hate")

Output: ("Perl", "Raku")

There are two common strings "Perl" and "Raku".
Index sum of "Perl": 0 + 1 = 1
Index sum of "Raku": 1 + 0 = 1
Example 2:
Input: @list1 = ("A", "B", "C")
       @list2 = ("D", "E", "F")

Output: ()

No common string found, so no result.
Example 3:
Input: @list1 = ("A", "B", "C")
       @list2 = ("C", "A", "B")

Output: ("A")

There are three common strings "A", "B" and "C".
Index sum of "A": 0 + 1 = 1
Index sum of "B": 1 + 2 = 3
Index sum of "C": 2 + 0 = 2

File: File: minimum-index-sum
#! /usr/bin/env raku

unit sub MAIN ($list1 = "Perl Raku Love", $list2 = "Raku Perl Hate",  # [1]
               :v($verbose));

my @list1 = $list1.words;                                             # [2]
my @list2 = $list2.words;                                             # [a2]

die "Repetitions in list1" if @list1.elems != @list1.unique.elems;    # [3]
die "Repetitions in list2" if @list2.elems != @list2.unique.elems;    # [3a]

my $mis   = Inf;                                                      # [4]
my @common;                                                           # [5]

for ^@list1.elems -> $i                                               # [6]
{
  next unless @list1[$i] eq any(@list2);                              # [7]
  
  for ^@list2.elems -> $j                                             # [8]
  {
    if @list1[$i] eq @list2[$j]                                       # [9]
    {
      my $sum = $i + $j;                                              # [10]
      say ": Index sum of \"@list1[$i]\": $i + $j = $sum" if $verbose;

      next if $sum > $mis;                                            # [11]

      @common = () if $sum < $mis;                                    # [12]
      $mis    = $sum;                                                 # [13]

      @common.push: @list1[$i];                                       # [14]

    }
  }
}

say @common.elems                                                     # [15]
 ?? "(" ~ @common.map({ "\"$_\"" }).join(", ") ~ ")"                  # [15a]
 !! "()";                                                             # [15b]

[1] The list of words as two space separated quoted strings, with default values.

[2] Get the words.

[3] The challenge does not tell us how to cope with duplicates in the lists themselves, but I have chosen to make that illegal (as the index for a duplicate word would give more than one answer. I enforce this by removing duplucates, and compare the array lengths.

[4] We are looking for the «minimum index sum», i.e. minimizing this value.

[5] The common strings with the currently «minimum index sum» will end up here.

[6] Iterate over the indices of the words in the first list. The caret (^) means «upto, but not including».

[7] Skip the inner loop (over the second list of words), if the any junction tells us that we do not have a match (i.e. no common words).

See docs.raku.org/routine/any for more information about the any Junction.

[8] Iterate over the indices of the words in the second list.

[9] Are the two words equal?

[10] Compute the index sum.

[11] Disregard this pair (of words, or indices) if the new sum is higher than the «minimum index sum».

[12] Empty the lits of empty strings if the new sum is lower than the old one, as we have found a new value for «minimum index sum».

[13] Update the «minimum index sum». Note that this will also be done if the two variables have the same value.

[14] Add the string (taken from the first list) to the list of common strings.

[15] Do we have a result (common strings). If so, print them with the required formating apllied [15a]. If not, print an ampty set of parens [15b].

Running it:

$ ./minimum-index-sum  
("Perl", "Raku")

$ ./minimum-index-sum "A B C" "D E F"
()

$ ./minimum-index-sum "A B C" "C A B"
("A")

Looking good.

With verbose mode:

$ ./minimum-index-sum -v 
: Index sum of "Perl": 0 + 1 = 1
: Index sum of "Raku": 1 + 0 = 1
("Perl", "Raku")

$ ./minimum-index-sum -v "A B C" "D E F"
()

$ ./minimum-index-sum -v "A B C" "C A B"
: Index sum of "A": 0 + 1 = 1
: Index sum of "B": 1 + 2 = 3
: Index sum of "C": 2 + 0 = 2
("A")

Challenge #208.2: Duplicate and Missing

You are given an array of integers in sequence with one missing and one duplicate.

Write a script to find the duplicate and missing integer in the given array. Return -1 if none found.

For the sake of this task, let us assume the array contains no more than one duplicate and missing.

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

Duplicate is 2 and Missing is 3.
Example 2:
Input: @nums = (1,2,3,4)
Output: -1

No duplicate and missing found.
Example 3:
Input: @nums = (1,2,3,3)
Output: (3,4)

Duplicate is 3 and Missing is 4.

File: duplicate-and-missing
! /usr/bin/env raku

unit sub MAIN ($nums = "1 2 2 4", :v($verbose));      # [1]

my @nums = $nums.words;                               # [2]

die "Not increasing values" unless [<=] @nums;        # [3]

my $duplicate = @nums.repeated;                       # [4]

say ": Duplicate: $duplicate" if $verbose;

my $current = @nums.shift;                            # [5]

while (@nums.elems)                                   # [6]
{
  if @nums[0] != $current + 1                         # [7]
  {
    say ": Missing:  {  $current + 1 }" if $verbose;
    say "($duplicate, { $current + 1 })";             # [8]
    exit;                                             # [9]
  }
  $current = @nums.shift;                             # [10]
}

say "-1";                                             # [11]

[1] A space separated quoted string (as in the first program) with the values, so that we can have a default value.

[2] Get the words.

[3] Using the Reduction Metaoperator [] to enforce that all the values are lower or equal (the <= operator) to the next value.

See docs.raku.org/language/operators#Reduction_metaoperators for more information about the Reduction Metaoperator [].

[4] The repeated methods gives us a list of repeated values, in this case just one element according to the challenge - and thus the duplicate we are looking for.

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

[5] Get the first value.

[6] As long as there are more elements.

[7] The first element in the remaining list should have the value after (+1) the current value. We have found the missing value if it is not.

[8] Print the duplicate value (from [4]) and the missing value.

[9] Exit.

[10] Get ready for the next iteration of the loop (in [6]).

[11] No match (i.e. not reached exit above); say so.

Running it:

$ ./duplicate-and-missing "1 2 2 4"
(2, 3)

$ ./duplicate-and-missing "1 2 3 4"
-1

$ ./duplicate-and-missing "1 2 3 3"
(3, 4)

Looking good.

With verbose mode, even though it does not really provide any new insights:

$ ./duplicate-and-missing -v "1 2 2 4"
: Duplicate: 2
: Missing:  3
(2, 3)

$ ./duplicate-and-missing -v "1 2 3 4"
: Duplicate: 
-1

$ ./duplicate-and-missing -v "1 2 3 3"
: Duplicate: 3
: Missing:  4
(3, 4)

And that's it.