Strong Count
with Raku

by Arne Sommer

Strong Count with Raku

[297] Published 12. July 2024.

This is my response to The Weekly Challenge #277.

Challenge #277.1: Count Common

You are given two array of strings, @words1 and @words2.

Write a script to return the count of words that appears in both arrays exactly once.

Example 1:
Input: @words1 = ("Perl", "is", "my", "friend")
       @words2 = ("Perl", "and", "Raku", "are", "friend")
Output: 2

The words "Perl" and "friend" appear once in each array.
Example 2:
Input: @words1 = ("Perl", "and", "Python", "are", "very", "similar")
       @words2 = ("Python", "is", "top", "in", "guest", "languages")
Output: 1
Example 3:
Input: @words1 = ("Perl", "is", "imperative", "Lisp", "is", "functional")
       @words2 = ("Crystal", "is", "similar", "to", "Ruby")
Output: 0

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

unit sub MAIN ($sentence1, $sentence2, :v(:$verbose));  # [1]

my @words1 = $sentence1.words;                          # [2]
my @words2 = $sentence2.words;                          # [2a]

my $once1  = (@words1.Bag.grep: *.value == 1).Bag;      # [3]
my $once2  = (@words2.Bag.grep: *.value == 1).Bag;      # [3a]

my $common = $once1 (&) $once2;                         # [4]

if $verbose
{
  say ": Once1: { $once1.keys.sort.join(", ") }";
  say ": Once2: { $once2.keys.sort.join(", ") }";
  say ": Common: { $common.keys.sort.join(", ") }";
}

say $common.elems;                                      # [5]

[1] Two sentences, without any restrictions on length or content.

[2] Turn the sentences into arrays of words.

[3] Coerce the array into a Bag, a hash like structure that counts the frequency, and keep only those that appear once. Ensure that the result is also a Bag.

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

[4] Get the intersection of the two Bags, with the (&) operator.

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

[5] Print the number of elements in the Bag.

Running it:

$ ./count-common "Perl is my friend" "Perl and Raku are friend"
2

$ ./count-common "Perl and Python are very similar" \
  "Python is top in guest langages"
1

$ ./count-common "Perl is imperative Lisp is functional" "\
  Crystal is similar to Ruby"
0

Looking good.

With verbose mode:

$ ./count-common -v "Perl is my friend" "Perl and Raku are friend"
: Once1: Perl, friend, is, my
: Once2: Perl, Raku, and, are, friend
: Common: Perl, friend
2

$ ./count-common -v "Perl and Python are very similar" \
  "Python is top in guest langages"
: Once1: Perl, Python, and, are, similar, very
: Once2: Python, guest, in, is, langages, top
: Common: Python
1

$ ./count-common -v "Perl is imperative Lisp is functional" \
  "Crystal is similar to Ruby"
: Once1: Lisp, Perl, functional, imperative
: Once2: Crystal, Ruby, is, similar, to
: Common: 
0

Challenge #277.2: Strong Pair

You are given an array of integers, @ints.

Write a script to return the count of all strong pairs in the given array.

A pair of integers x and y is called strong pair if it satisfies: 0 < |x - y| < min(x, y).

Example 1:
Input: @ints = (1, 2, 3, 4, 5)
Ouput: 4

Strong Pairs: (2, 3), (3, 4), (3, 5), (4, 5)
Example 2:
Input: @ints = (5, 7, 1, 7)
Ouput: 1

Strong Pairs: (5, 7)

We can deduce quite a lot about the input from the given rule:

  • x and y must be distinct, as 0 < |x - y| would give 0 < 0 if they were equal
  • x and y must be positive (and greater than zero), as 0 < min(x, y) will only hold if the lowest value is 1
We can decude some more from the examples:
  • The first example gives e.g. (2,3) but not (3,2), so the pairs are Sets (without order). That means that we can use combinations
  • The second example has a duplicate value (7), and this will be ignored because of the distinct deduction above

File: strong-pair
#! /usr/bin/env raku

subset PosInt of Int where * > 0;

unit sub MAIN (*@ints where all(@ints) ~~ PosInt && @ints.elems > 1,  # [1]
               :v(:$verbose));

my @unique = @ints.unique;                                            # [2]
my $count  = 0;                                                       # [3]

for @unique.combinations(2) -> ($left, $right)                        # [4]
{
  my $is-strong = 0 < abs($left - $right) < min($left, $right);       # [5]

  say ": Pair ($left, $right) { $is-strong ?? "strong" !! "-" }" if $verbose;

  $count++ if $is-strong;                                             # [6]
}

say $count;                                                           # [7]

[1] Ensure at least two elements, all of which must be of the UInt type (Unsigned Integer). Note that this does not prevent zeroes in the input.

[2] Get rid of duplicates, as they only lead to extra work.

[3] The result will end up here.

[4] Iterate over all the combinations of two values.

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

[5] Do the two values satisfy the given rule?

[6] If so, increase the count.

[7] Print the count.

Running it:

$ ./strong-pair 1 2 3 4 5
4

$ ./strong-pair 5 7 1 7
1

Looking good.

With verbose mode:

$ ./strong-pair -v 1 2 3 4 5
: Pair (1, 2) -
: Pair (1, 3) -
: Pair (1, 4) -
: Pair (1, 5) -
: Pair (2, 3) strong
: Pair (2, 4) -
: Pair (2, 5) -
: Pair (3, 4) strong
: Pair (3, 5) strong
: Pair (4, 5) strong
4

$ ./strong-pair -v 5 7 1 7
: Pair (5, 7) strong
: Pair (5, 1) -
: Pair (7, 1) -
1

And that's it.