This is my response to The Weekly Challenge #208.
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")
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.