Perl Weekly Challenge: Week 255

Challenge 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 = "Weekly" $t = "Weeakly"
Output: "a"
Example 3
Input: $s = "Box" $t = "Boxy"
Output: "y"

Raku makes short work of this problem using its' extensive support for sets.

say q{"}, (@*ARGS[0].comb.Mix ∖ @*ARGS[1].comb.Mix).keys.join, q{"}

(Full code on Github.)

This one-liner accepts two command-line arguments. The first is the string $s in the examples and the second is $c. The first string is split into individual characters with .comb() and cast to a Mix. This Raku data type is equivalent to a C++ multiset. i.e. it is a set that can hold more than one element with the same value. This is necessary because a letter might occur multiple times in our input. The second string is treated in the same way. The operator returns the difference between the two Mixes as another Mix whose .keys() method provides the elements in it. (There should only be one.) The rest of the code is to make the output look like that in the examples.

Perl doesn't have sets or set operations built in. I could have used a module or recycle some of the replacement code I've used in previous challenges but I chose a different approach.

As in Raku, $s and $t are picked up from the command-line arguments.

A hash is created to hold characters.

my %chars;

$t is split() into individual characters and they are added into the hash. The keyd are the character themselves and the values are the number of times each character occurs.

for my $c (split //, $t) {
    $chars{$c}++;
}

Now we split and iterate through $s but this time every time a character occurs, one is subtracted from its value in the hash.

for my $c (split //, $s) {
    $chars{$c}--;
}

By the end, most keys in the hash will have a value of 0; one will have a value of 1. This is the target of our search so we find it with grep() and print it out in a nice format.

say q{"}, (join q{}, grep { $chars{$_} > 0 } keys %chars), q{"};

(Full code on Github.)

Challenge 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.

$p and $w are taken from the command-line arguments.

A hash will hold how many times each word in the paragraph occurs.

my %count;

A word is defined as a sequence of characters separated by white-space, commas or periods. Words are extracted from $p with .split() and a regular expression.

my @words = $p.split(/<[\s \, \.]>+/);

The words are added to the hash along with the number of times they occur.

for @words -> $word {
    %count{$word}++;
}

The count for the "banned" word, $w, is removed from the hash.

%count{$w}:delete;

The keys of the hash (i.e. the words) are sorted by frequency; most frequent to least. The first is printed out.

%count.keys.sort({ %count{$^b} <=> %count{$^a} }).first.say;

(Full code on Github.)

This is the Perl version which works the same way.

my %count;
my @words = split /[\s\,\.]+/, $p;

for my $word (@words) {
    $count{$word}++;
}
delete %count{$w};

say [sort { $count{$b} <=> $count{$a} } keys %count]->[0];

(Full code on Github.)