This is my response to The Weekly Challenge #293.
@dominos
.
$dominos[i] = [a, b]
$dominos[j] = [c, d]
(a = c and b = d)
(a = d and b = c)
Input: @dominos = ([1, 3], [3, 1], [2, 4], [6, 8])
Output: 2
Similar Dominos: $dominos[0], $dominos[1]
Example 2:
Input: @dominos = ([1, 2], [2, 1], [1, 1], [1, 2], [2, 2])
Output: 3
Similar Dominos: $dominos[0], $dominos[1], $dominos[3]
The first example has a domino with a value of 8. That surely cannot be right? Six surely is the maximum. I lost my dominoes a long time ago, but I am pretty sure that I have not lost my marbles yet...
So I looked it up, and en.wikipedia.org/wiki/Dominoes confirms my «six maximum» notion. So, let us support both six and nine (why stop at eight?).
File: similar-dominoes
#! /usr/bin/env raku
unit sub MAIN ($dominoes, :s(:$six), :v(:$verbose)); # [1]
subset DOMINOES6 where { /^ <[0 .. 6]> ** 2 $/ }; # [2]
subset DOMINOES9 where { /^ <[0 .. 9]> ** 2 $/ }; # [2a]
say ": Legal values: 0 .. { $six ?? "6" !! "9" }" if $verbose;
my @dominoes = $dominoes.words.map({ $_ > $_.flip ?? $_.flip !! $_}); # [3]
say ": Dominoes (normalised): @dominoes[]" if $verbose;
die "Illegal value(s) in the input; digits 0-{ $six ?? "6" !! "9" only"
unless all(@dominoes) ~~ ( $six ?? DOMINOES6 !! DOMINOES9 ); # [4]
my $bag = @dominoes.Bag.grep( *.value > 1); # [5]
say ": Bag (of duplicates): { $bag.raku }" if $verbose;
say $bag>>.value.sum; # [6]
[1] Specify the dominoes as a space separated string of pairs of
digits. Use the -s
option to enforce six as the maximum value,
thus invalidating the first example.
[2] A custom type set ut with subset
for the
two variants of the maximum value.
[3] Get the dominoes, as a list of two character strings.
We start with words
to split the string on space(like) character(s).
Then we use map
to flip
the dominoes if the first digit
is higher than the second, so that they are all normalised (which I
define as the «lowest digit first»).
[4] Check that all the dominoes have legal values, using the proper custom type from [2].
[5] Turn the list of dominoes into a Bag
, a hash like
structure with the frequency as the value of each key lookup. Then
we use grep
to get rid of elements that occur only once - thus
giving us the duplicates.
See docs.raku.org/routine/Bag for more information about Bag
.
[6] For each element in the Bag
(with >>.
) get the value
(i.e. frequency) and add them all together with sum
. Print this
sum.
Running it:
$ ./similar-dominos "13 31 24 68"
2
$ ./similar-dominos "12 21 11 12 22"
3
Looking good.
With 6 as the upper limit:
$ ./similar-dominos -s "13 31 24 68"
Illegal value(s) in the input; digits 0-6 only
in sub MAIN at ./similar-dominos line 14
in block <unit> at ./similar-dominos line 1
With verbose mode:
$ ./similar-dominos -v "13 31 24 68"
: Legal values: 0 .. 9
: Dominoes (normalised): 13 13 24 68
: Bag (of duplicates): $(("13" => 2,).Seq)
2
$ ./similar-dominos -v "12 21 11 12 22"
: Legal values: 0 .. 9
: Dominoes (normalised): 12 12 11 12 22
: Bag (of duplicates): $(("12" => 3,).Seq)
3
Unicode has Domnio Tiles (see e.g. en.wikipedia.org/wiki/Domino_Tiles). 6 is the upper limit, so it would not be able to satisfy the first example.
(x, y)
.
Input: @points = ( [1, 1], [2, 3], [3,2] )
Output: true
Example 2:
Input: @points = ( [1, 1], [2, 2], [3, 3] )
Output: false
Example 3:
Input: @points = ( [1, 1], [1, 2], [2, 3] )
Output: true
Example 4:
Input: @points = ( [1, 1], [1, 2], [1, 3] )
Output: false
Example 5:
Input: @points = ( [1, 1], [2, 1], [3, 1] )
Output: false
Example 6:
Input: @points = ( [0, 0], [2, 3], [4, 5] )
Output: true
We can do this by calculating the difference between point 1 and point 2 (which is a vector), and the difference between point 1 and point 3, and see if the two vectors are equal. If they are, the three points form a single line. Which means that we do not have a boomerang.
File: boomerang
#! /usr/bin/env raku
subset POINT where { /^ \d+ \, \s? \d+ $/ }; # [1]
unit sub MAIN (POINT $point1, POINT $point2, POINT $point3, # [2]
:v(:$verbose));
unit sub MAIN ($point1, $point2, $point3, :v(:$verbose)); # [2]
my @point1 = $point1.split(/\,\s?/); # [3]
my @point2 = $point2.split(/\,\s?/); # [3a]
my @point3 = $point3.split(/\,\s?/); # [3b]
say ": Points: [{ @point1.join(",") }], [{ @point2.join(",") }], \
[{ @point3.join(",") }]" if $verbose;
my @vector12 = ( @point2[0] - @point1[0], @point2[1] - @point1[1] ); # [4]
my @vector13 = ( @point3[0] - @point1[0], @point3[1] - @point1[1] ); # [4a]
say ": Vectors: [{ @vector12.join(",") }], [{ @vector13.join(",") }]" if $verbose;
if (@vector12[0] == @vector13[0] == 0 || @vector12[1] == @vector13[1] == 0) # [5]
{
say ": Both vectors are on one of the axis, and thus equal)" if $verbose;
say False; # [5a]
exit; # [5b]
}
say ": Delta x: { @vector12[0] / @vector13[0] } { ( @vector12[0] / @vector13[0] \
== @vector12[1] / @vector13[1] ) ?? "==" !! "<>" } Delta y: \
{ @vector12[1] / @vector13[1] }" if $verbose;
say ! (@vector12[0] / @vector13[0] == @vector12[1] / @vector13[1]); # [6]
[1] A custom type (with subset
) for validation of the points. Note the
\s?
that matches one or zero spaces after the comma we split the point
on, as the third point in the first example does not have a space after the
comma. All the others do.
[2] Three points, using the custom type.
[3] Get the x and y values.
[4] Compute the two vectors.
[5] Division by zero is not really recommended, so we check if both x- or both y-coordinates of the two vectors are zero. In that case all three points reside on the same line (either the x or y axis itself).
[6] Are the two vectors equal (after scaling them)? Then we print the negated result.
Running it:
$ ./boomerang "1,1" "2,3" "3,2"
True
$ ./boomerang "1,1" "2,2" "3,3"
False
$ ./boomerang "1,1" "2,2" "2,3"
True
$ ./boomerang "1,1" "1,2" "1,3"
False
$ ./boomerang "1,1" "2,1" "3,1"
False
$ ./boomerang "0,0" "2,3" "4,5"
True
Looking good.
With verbose mode:
$ ./boomerang -v "1,1" "2,3" "3,2"
: Points: [1,1], [2,3], [3,2]
: Vectors: [1,2], [2,1]
: Delta x: 0.5 <> Delta y: 2
True
$ ./boomerang -v "1,1" "2,2" "3,3"
: Points: [1,1], [2,2], [3,3]
: Vectors: [1,1], [2,2]
: Delta x: 0.5 == Delta y: 0.5
False
$ ./boomerang -v "1,1" "2,2" "2,3"
: Points: [1,1], [2,2], [2,3]
: Vectors: [1,1], [1,2]
: Delta x: 1 <> Delta y: 0.5
True
$ ./boomerang -v "1,1" "1,2" "1,3"
: Points: [1,1], [1,2], [1,3]
: Vectors: [0,1], [0,2]
: Both vectors are on one of the axis, and thus equal)
False
$ ./boomerang -v "1,1" "2,1" "3,1"
: Points: [1,1], [2,1], [3,1]
: Vectors: [1,0], [2,0]
: Both vectors are on one of the axis, and thus equal)
False
$ ./boomerang -v "0,0" "2,3" "4,5"
: Points: [0,0], [2,3], [4,5]
: Vectors: [2,3], [4,5]
: Delta x: 0.5 <> Delta y: 0.6
True
And that's it.