Uncommon as X
with Raku

by Arne Sommer

Uncomon as X with Raku

[286] Published 28. April 2024.

This is my response to The Weekly Challenge #266.

Challenge #266.1: Uncommon Words

You are given two sentences, $line1 and $line2.

Write a script to find all uncommmon words in any order in the given two sentences. Return ('') if none found.

A word is uncommon if it appears exactly once in one of the sentences and doesn’t appear in other sentence.

Example 1:
Input: $line1 = 'Mango is sweet'
       $line2 = 'Mango is sour'
Output: ('sweet', 'sour')
Example 2:
Input: $line1 = 'Mango Mango'
       $line2 = 'Orange'
Output: ('Orange')
Example 3:
Input: $line1 = 'Mango is Mango'
       $line2 = 'Orange is Orange'
Output: ('')

Why restrict ourselves to just two lines, when we can have as many as we want - with a slurpy array?

File: uncommon-words
#! /usr/bin/env raku

unit sub MAIN (*@lines where @lines.elems > 1, :v(:$verbose));  # [1]

my $bag = Bag.new;                                              # [2]

for @lines -> $line                                             # [3]
{
  my @words = $line.words;                                      # [4]

  $bag (+)= @words;                                             # [5]

  say ": Bag: { $bag.raku }" if $verbose;
}

say '(' ~ $bag.grep({ $_.value == 1 }).map({ "'" ~ $_.key ~ "'" })
  .join(", ")  ~ ')';                                           # [6}

[1] Two or more lines.

[2] Set up an empty Bag, to collect the letters from the words.

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

[3] Iterate over the lines.

[4] Turn the current line into words (with words).

[5] Add the words to the Bag, where (+)= is the assign back version of (+).

[6] Pretty print the result, starting with grep to filter out entries with a frequency of 1, then a map to print the key wrapped in quotes, and finally a join to get a comma separated string.

Running it:


$ ./uncommon-words 'Mango is sweet' 'Mango is sour'
('sour', 'sweet')

$ ./uncommon-words 'Mango Mango' 'Orange'
('Orange')

$ ./uncommon-words 'Mango is Mango' 'Orange is Orange'
()

Looking good.

Note that the word order is random:

$ ./uncommon-words 'Mango is sweet' 'Mango is sour'
('sweet', 'sour')

$ ./uncommon-words 'Mango is sweet' 'Mango is sour'
('sour', 'sweet')

We could have fixed that by insering a sort before the grep, but the order would not be the same as specifed in the input - which would most likely be considered the correct order. I'll leave that up to the readers...

With verbose mode:

$ ./uncommon-words -v 'Mango is sweet' 'Mango is sour'
: Bag: ("Mango"=>1,"sweet"=>1,"is"=>1).Bag
: Bag: ("sour"=>1,"Mango"=>2,"sweet"=>1,"is"=>2).Bag
('sour', 'sweet')

$ ./uncommon-words -v 'Mango Mango' 'Orange'
: Bag: ("Mango"=>2).Bag
: Bag: ("Mango"=>2,"Orange"=>1).Bag
('Orange')

$ ./uncommon-words -v 'Mango is Mango' 'Orange is Orange'
: Bag: ("Mango"=>2,"is"=>1).Bag
: Bag: ("Mango"=>2,"is"=>2,"Orange"=>2).Bag
()

Challenge #266.2: X Matrix

You are given a square matrix, $matrix.

Write a script to find if the given matrix is X Matrix.

A square matrix is an X Matrix if all the elements on the main diagonal and antidiagonal are non-zero and everything else are zero.

Example 1:
Input: $matrix = [ [1, 0, 0, 2],
                   [0, 3, 4, 0],
                   [0, 5, 6, 0],
                   [7, 0, 0, 1],
                 ]
Output: true
Example 2:
Input: $matrix = [ [1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9],
                 ]
Output: false
Example 3:
Input: $matrix = [ [1, 0, 2],
                   [0, 3, 0],
                   [4, 0, 5],
                 ]
Output: true

The «matrix on the command line» code has been copied from e.g. Currently Reduced with Raku; Challenge #257.2: Reduced Row Echelon.

File: x-matrix
#! /usr/bin/env raku

unit sub MAIN ($string = "1 0 0 2 | 0 3 4 0 | 0 5 6 0 | 7 0 0 1",   # [1]
               :v(:$verbose));

my $matrix = $string.split("|")>>.words>>.Int>>.Array;              # [2]

die "The rows must have the same size" unless [==] $matrix>>.elems; # [3]

die "The number of rows and columns are not the same"
  unless $matrix.elems ==  $matrix[0].elems;                        # [4]

my $size = $matrix.elems;                                           # [5]

for ^$size -> $row                                                  # [6]
{
  for ^$size -> $col                                                # [7]
  {
    my $is-on-x = on-the-x(:$row, :$col, :$size);                   # [8]
    my $value   = $matrix[$row][$col];                              # [9]
    my $ok      = $is-on-x ?? $value != 0 !! $value == 0;           # [10]

    say ": [$row,$col]: $value { $is-on-x ?? "X" !! "-" } \
      { $ok ?? "ok" !! "error" }";

    unless $ok                                                      # [11]
    {
      say 'false';                                                  # [11a]
      exit;                                                         # [11b]
    }
  }
}

say 'true';                                                         # [12]

sub on-the-x (:$row, :$col, :$size)                                 # [13]
{
  my $last-index = $size -1;                                        # [14]
  
  return True if $row == $col;                                      # [15]
  return True if $last-index - $row == $col;                        # [16]
  return False;                                                     # [17]
}

[1] Note the default matrix.

[2] Turn the matrix string into a two dimentional array (of integers).

Note that >>.Int makes it legal to specify e.g. «3.14», which will be truncated to «3».

[3] All the rows must have the same size.

[4] The number of rows and columns must be the same.

[5] The matrix size, as we are going to use it multiple times.

[6] Iterate over the row indices (from 0 to one less than the size).

[7] Iterate over the column indices.

[8] Is the current position on the X (i.e. on the main diagonal or antidiagonal)? Note the named argument syntax (the leading colon).

[9] Get the value at the current position.

[10] All is well if we have a non-zero value on the X, and zero otherwise.

[11] Not ok? Say so [11a] and exit [11b].

[12] Not falsified? Then we have succeeded. Say so.

[13] Note the three named arguments.

[14] Calculate the last index.

[15] Success if we are on the main diagonal.

[16] Success if we are on the antidiagonal.

[17] Failure otherwise.

Running it:

$ ./x-matrix
true

$ ./x-matrix "1 2 3 | 4 5 6 | 7 8 9"
false

$ ./x-matrix "1 0 2 | 0 3 0 | 4 0 5"
true

Looking good.

With verbose mode:

$ ./x-matrix -v
: [0,0]: 1 X ok
: [0,1]: 0 - ok
: [0,2]: 0 - ok
: [0,3]: 2 X ok
: [1,0]: 0 - ok
: [1,1]: 3 X ok
: [1,2]: 4 X ok
: [1,3]: 0 - ok
: [2,0]: 0 - ok
: [2,1]: 5 X ok
: [2,2]: 6 X ok
: [2,3]: 0 - ok
: [3,0]: 7 X ok
: [3,1]: 0 - ok
: [3,2]: 0 - ok
: [3,3]: 1 X ok
true

$ ./x-matrix -v "1 2 3 | 4 5 6 | 7 8 9"
: [0,0]: 1 X ok
: [0,1]: 2 - error
false

$ ./x-matrix -v "1 0 2 | 0 3 0 | 4 0 5"
: [0,0]: 1 X ok
: [0,1]: 0 - ok
: [0,2]: 2 X ok
: [1,0]: 0 - ok
: [1,1]: 3 X ok
: [1,2]: 0 - ok
: [2,0]: 4 X ok
: [2,1]: 0 - ok
: [2,2]: 5 X ok
true

And that's it.