Boomerang or Similar
with Raku

by Arne Sommer

Boomerang or Similar with Raku

[314] Published 1. November 2024.

This is my response to The Weekly Challenge #293.

Challenge #293.1: Similar Dominos

You are given a list of dominos, @dominos.

Write a script to return the number of dominoes that are similar to any other domino.

$dominos[i] = [a, b] and $dominos[j] = [c, d] are same if either (a = c and b = d) or (a = d and b = c).

Example 1:
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.

Challenge #293.2: Boomerang

You are given an array of points, (x, y).

Write a script to find out if the given points are a boomerang.

A boomerang is a set of three points that are all distinct and not in a straight line.

Example 1:
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.