Common Find
with Raku

by Arne Sommer

Common Find with Raku

[359] Published 24. August 2025.

This is my response to The Weekly Challenge #335.

Challenge #335.1: Common Characters

You are given an array of words.

Write a script to return all characters that is in every word in the given array including duplicates.

Example 1:
Input: @words = ("bella", "label", "roller")
Output: ("e", "l", "l")
Example 2:
Input: @words = ("cool", "lock", "cook")
Output: ("c", "o")
Example 3:
Input: @words = ("hello", "world", "pole")
Output: ("l", "o")
Example 4:
Input: @words = ("abc", "def", "ghi")
Output: ()
Example 5:
Input: @words = ("aab", "aac", "aaa")
Output: ("a", "a")
File: common-characters
#! /usr/bin/env raku

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

my @bags = @words>>.comb>>.Bag;                                          # [2]

say ": Bags; { @bags.raku }" if $verbose;

my $intersection = [(&)] @bags;                                          # [3]

say ": Common: { $intersection.raku }" if $verbose;

say "(" ~ $intersection.kxxv.sort.map( '"' ~ * ~ '"' ).join(", ") ~ ")"; # [4]

[1] At least one word, without any rules on the content.

[2] Split each word into an array of individual characters (with comb). Then we coerce each of these arrays into a Bag, a hash like structure that gives us the frequency of each unique value.

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

[3] Use the intersection operator (&) on the entire array with the Reduction Metaoperator [] to get the intersection.

See docs.raku.org/routine/(&),%20infix%20%E2%88%A9 for more information about the intersection operator (&).

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

[4] Pretty print the intersection, i.e. the result.

Running it:

$ ./common-characters bella label roller
("e", "l", "l")

$ ./common-characters cool lock cook
("c", "o")

$ ./common-characters hello world pole
("l", "o")

$ ./common-characters abc def ghi
()

$ ./common-characters aab aac aaa
("a", "a")

Looking good.

With verbose mode:

$ ./common-characters -v bella label roller
: Bags; [("l"=>2,"a"=>1,"e"=>1,"b"=>1).Bag, \
         ("a"=>1,"b"=>1,"e"=>1,"l"=>2).Bag, \
         ("r"=>2,"l"=>2,"o"=>1,"e"=>1).Bag]
: Common: ("l"=>2,"e"=>1).Bag
("e", "l", "l")

$ ./common-characters -v cool lock cook
: Bags; [("c"=>1,"l"=>1,"o"=>2).Bag, \
         ("k"=>1,"l"=>1,"c"=>1,"o"=>1).Bag, \
         ("k"=>1,"o"=>2,"c"=>1).Bag]
: Common: ("c"=>1,"o"=>1).Bag
("c", "o")

$ ./common-characters -v hello world pole
: Bags; [("l"=>2,"e"=>1,"o"=>1,"h"=>1).Bag, \
         ("l"=>1,"o"=>1,"d"=>1,"w"=>1,"r"=>1).Bag, \
         ("p"=>1,"l"=>1,"o"=>1,"e"=>1).Bag]
: Common: ("o"=>1,"l"=>1).Bag
("l", "o")

$ ./common-characters -v abc def ghi
: Bags; [("b"=>1,"c"=>1,"a"=>1).Bag, \
         ("d"=>1,"f"=>1,"e"=>1).Bag, \
         ("h"=>1,"i"=>1,"g"=>1).Bag]
: Common: ().Bag
()

$ ./common-characters -v aab aac aaa
: Bags; [("a"=>2,"b"=>1).Bag, ("c"=>1,"a"=>2).Bag, ("a"=>3).Bag]
: Common: ("a"=>2).Bag
("a", "a")

Challenge #335.2:

You are given an array of all moves by the two players.

Write a script to find the winner of the TicTacToe game, if found. Based on the moves provided in the given array.

UPDATE: Order move is in the order - A, B, A, B, A, ….

Example 1:
Input: @moves = ([0,0],[2,0],[1,1],[2,1],[2,2])
Output: A

Game Board:
[ A _ _ ]
[ B A B ]
[ _ _ A ]
Example 2:
Input: @moves = ([0,0],[1,1],[0,1],[0,2],[1,0],[2,0])
Output: B

Game Board:
[ A A B ]
[ A B _ ]
[ B _ _ ]
Example 3:
Input: @moves = ([0,0],[1,1],[2,0],[1,0],[1,2],[2,1],[0,1],[0,2],[2,2])
Output: Draw

Game Board:
[ A A B ]
[ B B A ]
[ A B A ]
Example 4:
Input: @moves = ([0,0],[1,1])
Output: Pending

Game Board:
[ A _ _ ]
[ _ B _ ]
[ _ _ _ ]
Example 5:
Input: @moves = ([1,1],[0,0],[2,2],[0,1],[1,0],[0,2])
Output: B

Game Board:
[ B B B ]
[ A A _ ]
[ _ _ A ]

Note that the Game Board shown for the first example above is wrong. See the verbose mode output later on for the correct board.

File: find-winner
#! /usr/bin/env raku

unit sub MAIN (*@moves where @moves.elems > 0                        # [1]
                 && all(@moves) ~~/^<[012]> \, <[012]>$/,            # [1a]
               :v(:$verbose));

my @board = [ [ '-', '-', '-' ], ['-', '-', '-'], ['-', '-', '-'] ]; # [2]

my $player = 'A';                                                    # [3]

for @moves -> $move                                                  # [4]
{
  my ($x, $y) = $move.split(",");                                    # [5]

  die "Position $x,$y already taken" unless @board[$x][$y] eq "-";   # [6]
  
  @board[$x][$y] = $player;                                          # [7]

  $player = $player eq 'A' ?? 'B' !! 'A';                            # [8]
}

if $verbose
{
  say ": " ~ @board[0].join(" ");
  say ": " ~ @board[1].join(" ");
  say ": " ~ @board[2].join(" ");
}

for 'A', 'B' -> $ab                                                  # [9]
{
  if all(@board[0;*]) eq $ab ||                                      # [10]
     all(@board[1;*]) eq $ab ||                                      # [10a]
     all(@board[2;*]) eq $ab ||                                      # [10b]
     all(@board[*;0]) eq $ab ||                                      # [10c]
     all(@board[*;1]) eq $ab ||                                      # [10d]
     all(@board[*;2]) eq $ab ||                                      # [10e]
     @board[0][0] eq @board[1][1] eq @board[2][2] eq $ab ||          # [10f]
     @board[2][0] eq @board[1][1] eq @board[0][2] eq $ab             # [10g]
  {
    say $ab;                                                         # [11]
    exit;                                                            # [11a]
  }
}

say @moves.elems == 9 ?? 'Draw' !! 'Pending';                        # [12]

[1] An array of moves, where each move contains the row (0-2) and column numbers (0-2) separated by a comma [1a]. At least one move.

[2] Set up the initially empty board.

[3] Start with player 'A'.

[4] For each move.

[5] Get the column (x) and row (y) values.

[6] Terminate the program if the current position already is in use.

[7] Set the current player's mark on the current position.

[8] Prepare for the next iteration of the loop by changing users.

[9] For each user,

[10] Does the user have the entire column 0, 1 or 2 [10a/b/c], row 0, 1 or 2 [10c/d/e], or one of the diagonals [10f/g]?

[11] If so, we have a winner. Say so and exit [11a].

[12] No winner. The message is 'Pending' if we have untaken positions, and 'Draw' if the board is full.

Running it:

$ ./find-winner 0,0 2,0 1,1 2,1 2,2
A

$ ./find-winner 0,0 1,1 0,1 0,2 1,0 2,0
B

$ ./find-winner 0,0 1,1 2,0 1,0 1,2 2,1 0,1 0,2 2,2
Draw

$ ./find-winner 0,0 1,1
Pending

$ ./find-winner 1,1 0,0 2,2 0,1 1,0 0,2
B

Looking good.

With verbose mode:

$ ./find-winner -v 0,0 2,0 1,1 2,1 2,2
: A - -
: - A -
: B B A
A

$ ./find-winner -v 0,0 1,1 0,1 0,2 1,0 2,0
: A A B
: A B -
: B - -
B

$ ./find-winner -v 0,0 1,1 2,0 1,0 1,2 2,1 0,1 0,2 2,2
: A A B
: B B A
: A B A
Draw

$ ./find-winner -v 0,0 1,1
: A - -
: - B -
: - - -
Pending

$ ./find-winner -v 1,1 0,0 2,2 0,1 1,0 0,2
: B B B
: A A -
: - - A
B

And that's it.