Squarefully Common
with Raku

by Arne Sommer

Squarefully Common with Raku

[240] Published 5. June 2023.

This is my response to The Weekly Challenge #220.


  The Raku Conference
Rīga, Latvia, August 3–4, 2023



Challenge #220.1: Common Characters

You are given a list of words.

Write a script to return the list of common characters (sorted alphabetically) found in every word of the given list.

Example 1:
Input: @words = ("Perl", "Rust", "Raku")
Output: ("r")
Example 2:
Input: @words = ("love", "live", "leave")
Output: ("e", "l", "v")

It follows from the first example that we should downcase the letters (i.e. convert them to lowercase).

File: common-characters
#! /usr/bin/env raku

unit sub MAIN (*@words where @words.elems > 1
                 && all(@words) ~~ /^<[a..zA..Z]>+$/,               # [1]
              :v(:$verbose));

my $set = [(&)] @words>>.lc>>.comb;                                 # [2]
 # ###### # 2d # 2a # # 2b # 2c ### 

say ":Set: { $set.raku }" if $verbose;

say '(' ~ $set.keys.sort.map({ '"' ~ $_ ~ '"' }).join(", ") ~ ')';  # [3]

[1] Ensure at least two words, all of which can only contain the standard 52 lower- and uppercase letters. (No messing around with things like Æ, ß, Ü or ð.)

[2] We start with the words [2a], applying lc on each one (with >>.lc) to coerce them to lowercase [2b]. Then we apply comb on each word to coerce them into a list of individual characters [2c]. The result so far is a list of lists. And finally we use the intersection operator (&) - in combination with the Reduction Metaoperator [] - to compute the common characters [2d]. The result is a (one) Set, courtesy of the reduction.

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

See docs.raku.org/type/Set for more information about the Set type.

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

[3] We get the actual letters out of the Set object with the keys method. The rest of this line is just pretty printing, in sorted order.

Running it:

$ ./common-characters Perl Rust Raku
("r")

$ ./common-characters love live leave
("e", "l", "v")

Looking good.

With verbose mode, though it is not really helpful:

$ ./common-characters -v Perl Rust Raku
:Set: Set.new("r")
("r")

$ ./common-characters -v love live leave
:Set: Set.new("e","v","l")
("e", "l", "v")

Challenge #220.2: Squareful

You are given an array of integers, @ints.

An array is squareful if the sum of every pair of adjacent elements is a perfect square.

Write a script to find all the permutations of the given array that are squareful.

Example 1:
Input: @ints = (1, 17, 8)
Output: (1, 8, 17), (17, 8, 1)

(1, 8, 17) since 1 + 8 => 9, a perfect square and also 8 + 17 => 25
  is perfect square too.
(17, 8, 1) since 17 + 8 => 25, a perfect square and also 8 + 1 => 9
  is perfect square too.
Example 2:
Input: @ints = (2, 2, 2)
Output: (2, 2, 2)

There is only one permutation possible.
File: squareful
#! /usr/bin/env raku

unit sub MAIN (*@ints where @ints.elems > 1 && all(@ints) ~~ Int);  # [1]

my $length = @ints.elems;                                           # [2]
my @squareful;                                                      # [3]

PERM-LOOP:                                                          # [4]
for @ints.permutations.unique(:with(&[eqv])) -> @permutation        # [5]
{
  for 0 .. $length -2 -> $index                                     # [6]
  {
    my $sum  = @permutation[$index] + @permutation[$index +1];      # [7]
    my $root = $sum.sqrt;                                           # [8]
    next PERM-LOOP unless $root.Int == $root;                       # [9]
  }

  push(@squareful, @permutation);                                   # [10]
}

say @squareful;                                                     # [11]

[1] At least two values, all of which must be integers.

[2] Get the number of values.

[3] The result (the squareful permutations) will end up here.

[4] We attach a label to this outer loop, as we need to reference it in [9].

[5] Iterate over the permutations (with permutations), and then we get rid of duplicates (caused by duplicates in the input list) with unique. We have to tell it how to compare values, as the default behaviour does not cope with data structures, and :with(&[eqv]) does just that; specifying the Equivalence Operator eqv.

See docs.raku.org/routine/eqv for more information about the Equivalence Operator eqv.

[6] Iterate over the indices of the array, stopping at the last but one.

[7] Get the sum of values at the current index and the next one (index +1).

[8] Compute the square root.

[9] Skip this iteration of the outer loop (set up in [4], with next), if the square root is not an integer (i.e. the number is not a perfect square).

[10] The inner loop completed without failing (in [9]) if we get here. Add the value to the result (as failure to fail is success, in this case).

[11] Print the result.

Running it:

$ ./squareful 1 17 8
[(1 8 17) (17 8 1)]

$ ./squareful 2 2 2
[(2 2 2)]

Looking good, except the formatting. But this is good enough, as I am short of time this week.

And that's it.