Mostly Odd
with Raku

by Arne Sommer

Mostly Odd with Raku

[275] Published 9. February 2024.

This is my response to The Weekly Challenge #255.

Challenge #255.1: Odd Character

You are given two strings, $s and $t. The string $t is generated using the shuffled characters of the string $s with an additional character.

Write a script to find the additional character in the string $t.

Example 1:
Input: $s = "Perl" $t = "Preel"
Output: "e"
Example 2:
Input: $s = "Box" $t = "Boxy"
Output: "y"
File: odd-character
#! /usr/bin/env raku

unit sub MAIN ($s where $s.chars > 0,              # [1]
               $t where $t.chars == $s.chars + 1,  # [1a]
               :v(:$verbose));

my $t-bag = $t.comb.Bag;                           # [2]
my $s-bag = $s.comb.Bag;                           # [2a]
my $add   = $t-bag (^) $s-bag;                     # [3]

if $verbose
{
  say ":s: { $s-bag.raku }";
  say ":t: { $t-bag.raku }";
  say ":addition: { $add.raku }";
}

die "Not one additional letter, but { $add.elems } ({ $add.keys.join(",") })"
  unless $add.elems == 1;                          # [4]

say $add.keys.first;                               # [5]

[1] The first string must have at least one character, and the second string must have one character more than the first one.

[2] Turn the strings into Bags of individual characters. (The keys will be the original characters (without duplicates), and the values are the frequency.)

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

[3] Apply the symmetric set difference operator (^) to get the difference between the two Bags, i.e. the added letter(s).

See docs.raku.org/language/setbagmix#Set_operators_that_return_a_QuantHash and scroll down to the symmetric set difference operator (^).

[4] Protest if the difference has more than one character.

[5] We have Bag with one element. Using keys will return a list of one key, and first will give that one. This is the added character, and we print it

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

Running it:

$ ./odd-character Perl Preel
e

$ ./odd-character Box Boxy
y

Looking good.

With verbose mode:

$ ./odd-character -v "Perl" "Preel"
:s: ("P"=>1,"e"=>1,"r"=>1,"l"=>1).Bag
:t: ("e"=>2,"r"=>1,"l"=>1,"P"=>1).Bag
:addition: ("e"=>1).Bag
e

$ ./odd-character -v Box Boxy
:s: ("o"=>1,"x"=>1,"B"=>1).Bag
:t: ("y"=>1,"o"=>1,"B"=>1,"x"=>1).Bag
:addition: ("y"=>1).Bag
y

Challenge #255.2: Most Frequent Word

You are given a paragraph $p and a banned word $w.

Write a script to return the most frequent word that is not banned.

Example 1:
Input: $p = "Joe hit a ball, the hit ball flew far after it was hit."
       $w = "hit"
Output: "ball"

The banned word "hit" occurs 3 times.
The other word "ball" occurs 2 times.
Example 2:
Input: $p = "Perl and Raku belong to the same family. Perl is the most \
popular language in the weekly challenge."
       $w = "the"
Output: "Perl"

The banned word "the" occurs 3 times.
The other word "Perl" occurs 2 times
File: most-frequent-word
#! /usr/bin/env raku

unit sub MAIN ($p where $p.chars > 1,                      # [1]
               $w where $w ~~ /^\w+$/,                     # [2]
               :v(:$verbose));

my $p2      = $p.trans({ '.' => ' ', ',' => ' ' });        # [3]
my $words   = $p2.words.Bag;                               # [4]
my $count   = $words{$w};                                  # [5]
my $without = $words (-) $w xx $count;                     # [6]
my @sorted  = $without.sort({ $^b.value <=> $^a.value });  # [7]

if $verbose
{
  say ":p: '$p'";
  say ":p: '$p2' (modified)";
  say ":word '$w' found $count times";
  say ":Words: { $words.raku }";
  say ":Without: { $without.raku }";
  say ":Sorted: { @sorted.raku }";
}

say @sorted.first.key;                                     # [8]

[1] Ensure that the paragraph has at least one character.

[2] Ensure that the banned word contains word characters only (the \w+ regex), one or more of them.

[3] Use trans to translate commas and periods (as two pairs) to spaces, as the words method (see [4]) does not consider those characters as word separators.

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

[4] Use words to split the sentence into separate words. Then apply Bag to turn that list of words into a Bag, thus counting the words for us.

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

[5] Get the number of times the word occurs in the sentence (by looking it up in the Bag).

[6] Remove the banned word, exactly as many times as it occurs in the sentence, using the list repetition operator xx and the set difference operator (-).

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

See docs.raku.org/language/setbagmix#Set_operators_that_return_a_QuantHash and scroll down to the set difference operator (-).

[7] Sort the Bag of non-banned words on the frequency (the value part of the Pairs), with the largest first. The result is a list of Pair objects.

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

[8] Take the first Pair object (with first, and print the value (i.e. the word), with value).

Running it:

$ ./most-frequent-word "Joe hit a ball, the hit ball flew far after it was \
hit." hit
ball

$ ./most-frequent-word "Perl and Raku belong to the same family. Perl is \
the most popular language in the weekly challenge." the
Perl

Looking good.

With verbose mode:

$ ./most-frequent-word -v "Joe hit a ball, the hit ball flew far after it \
was hit." hit
:p: 'Joe hit a ball, the hit ball flew far after it was hit.'
:p: 'Joe hit a ball  the hit ball flew far after it was hit ' (modified)
:word 'hit' found 3 times
:Words: ("the"=>1,"after"=>1,"was"=>1,"it"=>1,"far"=>1,"hit"=>3,"Joe"=>1,
         "flew"=>1,"ball"=>2,"a"=>1).Bag
:Without: ("flew"=>1,"ball"=>2,"Joe"=>1,"the"=>1,"was"=>1,"a"=>1,"far"=>1,
           "it"=>1,"after"=>1).Bag
:Sorted: [:ball(2), :flew(1), :Joe(1), :the(1), :was(1), :a(1), :far(1),
          :it(1), :after(1)]
ball

$ ./most-frequent-word -v "Perl and Raku belong to the same family. Perl \
is the most popular language in the weekly challenge." the
:p: 'Perl and Raku belong to the same family. Perl is the most popular \
language in the weekly challenge.'
:p: 'Perl and Raku belong to the same family  Perl is the most popular \
language in the weekly challenge ' (modified)
:word 'the' found 3 times
:Words: ("family"=>1,"to"=>1,"popular"=>1,"language"=>1,"Perl"=>2,
         "belong"=>1,"most"=>1,"the"=>3,"and"=>1,"in"=>1,"is"=>1,
         "Raku"=>1,"same"=>1,"weekly"=>1,"challenge"=>1).Bag
:Without: ("popular"=>1,"same"=>1,"is"=>1,"Perl"=>2,"challenge"=>1,
           "family"=>1,"most"=>1,"to"=>1,"belong"=>1,"and"=>1,"in"=>1,
           "weekly"=>1,"Raku"=>1,"language"=>1).Bag
:Sorted: [:Perl(2), :popular(1), :same(1), :is(1), :challenge(1),
          :family(1), :most(1), :to(1), :belong(1), :and(1), :in(1),
          :weekly(1), :Raku(1), :language(1)]
Perl

And that's it.