33% Word
with Raku

by Arne Sommer

33% Word with Raku

[285] Published 18. April 2024.

This is my response to The Weekly Challenge #265.

Challenge #265.1: 33% Appearance

You are given an array of integers, @ints.

Write a script to find an integer in the given array that appeared 33% or more. If more than one found, return the smallest. If none found then return undef.

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

1 appeared 1 times.
2 appeared 2 times.
3 appeared 4 times.

3 appeared 50% (>33%) in the given array.
Example 2:
Input: @ints = (1,1)
Output: 1

1 appeared 2 times.

1 appeared 100% (>33%) in the given array.
Example 3:
Input: @ints = (1,2,3)
Output: 1

1 appeared 1 times.
2 appeared 1 times.
3 appeared 1 times.

Since all three appeared 33.3% (>33%) in the given array.
We pick the smallest of all.

File: appearance
#! /usr/bin/env raku

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

my $limit  = @ints.elems * 0.33;                                   # [2]
my %freq   = @ints.Bag;                                            # [3]
my %ok     = %freq.grep({ $_.value >= $limit });                   # [4]
my @keys   = %ok.keys.sort;                                        # [5]

if $verbose
{
  say ": Frequencies: { %freq.raku }";
  say ": Limit: $limit";
  say ": OK: { %ok.raku }";
  say ": Sorted: { @keys.join(",") }";
}

say @keys.first;                                                   # [6]

[1] Ensure a lot of integers, at least one.

[2] The limit, as a real number instead of as a percent of the total.

[3] Get the frequency of the digits, with Bag. The assignment to a %-sigilled variable coerces that Bag into a hash.

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

[4] Use grep to retain only the values with a high enough frequency.

[5] Get the values themselves, in sorted order.

[6] Print the first value in the array (of values). This will give an undefined value if the array is empty, which is fine.

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

Running it:

$ ./appearance 1 2 3 3 3 3 4 2 5 6 7 8 9
Nil

$ ./appearance 1 1 
1

$ ./appearance 1 2 3 
3

Looking good(ish).

If you do not like the Raku Nil, but want an explicit undef (which in Raku we must specify as a string, as it is not a thing - so to speak), swap row [6] with something like this:

say @keys ?? @keys.first !! 'undef';

With verbose mode:

$ ./appearance -v 1 2 3 3 3 3 4 2 5 6 7 8 9
: Frequencies: {"1" => 1, "2" => 2, "3" => 4, "4" => 1, "5" => 1, "6" => 1,
    "7" => 1, "8" => 1, "9" => 1}
: Limit: 4.29
: OK: {}
: Sorted: 
Nil

$ ./appearance -v 1 1 
: Frequencies: {"1" => 2}
: Limit: 0.66
: OK: {"1" => 2}
: Sorted: 1
1

$ ./appearance -v 1 2 3 
: Frequencies: {"1" => 1, "2" => 1, "3" => 1}
: Limit: 0.99
: OK: {"1" => 1, "2" => 1, "3" => 1}
: Sorted: 1,2,3
3

Challenge #265.2: Completing Word

You are given a string, $str containing alphnumeric characters and array of strings (alphabetic characters only), @str.

Write a script to find the shortest completing word. If none found return empty string.

A completing word is a word that contains all the letters in the given string, ignoring space and number. If a letter appeared more than once in the given string then it must appear the same number or more in the word.

Example 1:
Input: $str = 'aBc 11c'
       @str = ('accbbb', 'abc', 'abbc')
Output: 'accbbb'

The given string contains following, ignoring case and number:
a 1 times
b 1 times
c 2 times

The only string in the given array that satisfies the condition is
'accbbb'.
Example 2:
Input: $str = 'Da2 abc'
       @str = ('abcm', 'baacd', 'abaadc')
Output: 'baacd'

The given string contains following, ignoring case and number:
a 2 times
b 1 times
c 1 times
d 1 times

The are 2 strings in the given array that satisfies the condition:
'baacd' and 'abaadc'.

Shortest of the two is 'baacd'
Example 3:
Input: $str = 'JB 007'
       @str = ('jj', 'bb', 'bjb')
Output: 'bjb'

The given string contains following, ignoring case and number:
j 1 times
b 1 times

The only string in the given array that satisfies the condition is 'bjb'.

File: completing-word
#! /usr/bin/env raku

unit sub MAIN ($str, *@str, :v(:$verbose));                  # [1]

my $str-b = $str.comb.grep( * ~~ /<[a..z A..Z]>/)>>.lc.Bag;  # [2]

say ": Str Bag: { $str-b.raku }" if $verbose;

for @str.sort: *.chars -> $candidate                         # [3]
{
  my $candidate-bag = $candidate.comb>>.lc.Bag;              # [4]
  say ": Checking $candidate (Bag: { $candidate-bag.raku })" if $verbose;

  if $str-b (<=) $candidate-bag                              # [5]
  {
    say $candidate;                                          # [5a]
    exit;                                                    # [5b]
  }
}

[1] First the string, followed by the array of strings. I have chosen not to add content checks this time.

[2] Extract all the letters (the grep) from the individual characters (comb), convert each one to lowercase (>>.lc) and turn the result into a Bag.

[3] We are looking for the shortest match, so we iterate over the candidates by string length, shortest first.

[4] Turn the candidate into a Bag, as done with the string (in [2]).

[5] Do we have a subset (or equal) set of characters (the (<=) operator) to the allowed characters? If so, print the matching candidate and exit.

See docs.raku.org/language/operators infix (<=) for more information about the subset or equal to operator (<=).

Running it:

$ ./completing-word "aBc 11c" accbbb abc abbc
accbbb

$ ./completing-word "Da2 abc" abcm baacd abaadc
baacd

$ ./completing-word "JB 007" jj bb bjb
bjb

Looking good.

With verbose mode:

$ ./completing-word -v "aBc 11c" accbbb abc abbc
: Str Bag: ("a"=>1,"c"=>2,"b"=>1).Bag
: Checking abc (Bag: ("a"=>1,"b"=>1,"c"=>1).Bag)
: Checking abbc (Bag: ("c"=>1,"b"=>2,"a"=>1).Bag)
: Checking accbbb (Bag: ("c"=>2,"a"=>1,"b"=>3).Bag)
accbbb

$ ./completing-word -v "Da2 abc" abcm baacd abaadc
: Str Bag: ("b"=>1,"c"=>1,"a"=>2,"d"=>1).Bag
: Checking abcm (Bag: ("c"=>1,"a"=>1,"m"=>1,"b"=>1).Bag)
: Checking baacd (Bag: ("a"=>2,"d"=>1,"c"=>1,"b"=>1).Bag)
baacd

$ ./completing-word -v "JB 007" jj bb bjb
: Str Bag: ("b"=>1,"j"=>1).Bag
: Checking jj (Bag: ("j"=>2).Bag)
: Checking bb (Bag: ("b"=>2).Bag)
: Checking bjb (Bag: ("b"=>2,"j"=>1).Bag)
bjb

And that's it.