Perl Weekly Challenge: Week 380

Challenge 1:

Sum of Frequencies

You are given a string consisting of English letters.

Write a script to find the vowel and consonant with maximum frequency. Return the sum of two frequencies.

Example 1
Input: $str = "banana"
Output: 5

Vowel: "a" appears 3 times.
Consonant: "n" appears 2 times, "b" appears 1 time.

Max frequency of vowel: 3
Max frequency of consonant: 2
Example 2
Input: $str = "teestett"
Output: 7

Vowel: "e" appears 3 times.
Consonant: "t" appears 4 times, "s" appears 1 time.

Max frequency of vowel: 3
Max frequency of consonant: 4
Example 3
Input: $str = "aeiouuaa"
Output: 3

Vowel: "a" appears 3 times, "u" 2 times, "e", "i", "o" 1 time each.
Consonant: None.

Max frequency of vowel: 3
Max frequency of consonant: 0
Example 4
Input: $str = "rhythm"
Output: 2

Vowel: None
Consonant: "h" appears 2 times, "r", "y", "t", "m" 1 time each.

Max frequency of vowel: 0
Max frequency of consonant: 2
Example 5
Input: $str = "x"
Output: 1

Vowel: None
Consonant: "x" appears 1 time.

Max frequency of vowel: 0
Max frequency of consonant: 1

This time I'm showing the Perl solution first.

A hash will store the frequency of each letter. The keys are the letters and the values are the number of times each appears. The values are initialized to 0.

my %freq = map { $_ => 0 } 'a'..'z';

Rather than a for-loop or map(), I chose to count the frequences via the s/// operator. In the first half, individual characters are matched and captured. The /g flag ensures that every character is matched. The second half is usually for substitutions but with the /e we can execute code instead. The code in questions increments the key of %freq corresponding to the character matched in the first half. Finally, the /r flag is used to keep $str unmodified.

$str =~ s/(.)/$freq{$1}++/ger;

We need to find the maximum frequencies of vowels and consonants separately. As the vowels are spread through the alphabet haphazardly, there isn't any easy way to separate them. What I did was to create another hash for the vowels and...

my %vowels;

...for each vowel...

for my $vowel (qw/ a e i o u/) {

...delete its' key from %freq. As delete() returns the the element that was deleted, it can be added onto %vowels effectively transferring it.

    $vowels{$vowel} = delete $freq{$vowel};
}

Now we can sort %vowels and %freq in desscending numerical order and take the first element which will be the key with the maximum frequency for each. We add the values of these keys and output the result.

say $vowels{ (sort { $vowels{$b} <=> $vowels{$a}} keys %vowels)[0] } +
    $freq{ (sort { $freq{$b} <=> $freq{$a} } keys %freq)[0] };

(Full code on Github.)

The Raku version is similar.

my %freq = <a .. z>.map({ $_ => 0 });

Instead of the /r flaf to s///, Raku has a separate S/// operator for non- destructive substitutions.

S:g/(.)/ { %freq{$0}++ }/ given $str;

my %vowels;
for <a e i o u> -> $vowel {

If a key doesn't exist Perls delete() returns undef which is ok because it is effectively 0 in this case. Raku on the other hand returns Any which is a totally different value type and cannot automatically be converted to 0. So in Raku, we have to use the defined-or operator // to explicitly set the result to 0.

    my $deleted = %freq{$vowel}:delete // 0;
    %vowels{$vowel} = $deleted;
}

say %vowels.values.sort({ $^b <=> $^a }).first +
    %freq.values.sort({ $^b <=> $^a }).first;

(Full code on Github.)

Challenge 2:

Reverse Degree

You are given a string.

Write a script to find the reverse degree of the given string.

For each character, multiply its position in the reversed alphabet (‘a’ = 26, ‘b’ = 25, …, ‘z’ = 1) with its position in the string. Sum these products for all characters in the string to get the reverse degree.

Example 1
Input: $str = "z"
Output: 1

Reverse alphabet value of "z" is 1.
Position 1: 1 x 1
Sum of product: 1
Example 2
Input: $str = "a"
Output: 26

Reverse alphabet value of "a" is 26.
Position 1: 1 x 26
Sum of product: 26
Example 3
Input: $str = "bbc"
Output: 147

Reverse alphabet value of "b" is 25 and "c" is 24.
Position 1: 1 x 25
Position 2: 2 x 25
Position 3: 3 x 24
Sum of product: 25 + 50 + 72 => 147
Example 4
Input: $str = "racecar"
Output: 560

Reverse alphabet value of "r" is 9, "a" is 26, "c" is 24 and "e" is 24.
Position 1: 1 x 9
Position 2: 2 x 26
Position 3: 3 x 24
Position 4: 4 x 22
Position 5: 5 x 24
Position 6: 6 x 26
Position 7: 7 x 9
Sum of product: 9 + 52 + 72 + 88 + 120 + 156 + 63
Example 5
Input: $str = "zyx"
Output: 14

Reverse alphabet value of "z" is 1, "y" is 2 and "x" is 3.
Position 1: 1 x 1
Position 2: 2 x 2
Position 3: 3 x 3
Sum of product: 1 + 4 + 9

In my Raku solution I began by creating a Hash whose keys are the letters from a to z and keys are numbers from 26 to 1. It can easily be done in one line with the Z=> operator.

my %reverse = 'a' .. 'z' Z=> (1 .. 26).reverse;

We also need to store the result.

my $result = 0;

We break up $str into individual characters with .comb(). .kv in this context returns a Pair of the position of each character and the character itself. We loop through these and use them to calculate the postion in the string multiplled by the position in the reversed alphabet. This amount is added to $result.

for $str.comb.kv -> $pos, $char {
    $result += ($pos + 1) * %reverse{$char};
}

Finally $result is printed with say().

say $result;

(Full code on Github.)

This is the Perl version.

Unfortunately Perl can't create %reverse as elegantly as Raku.

my $reversed = 26;
my %reverse = map { $_ => $reversed-- }'a' .. 'z';

my $result = 0;

indexed() is one of the newest core Perl features. (See the builtin perldoc.) It acts the same as Rakus' .kv().

for my ($pos, $char) (indexed split //, $str) {
    $result += ($pos + 1) * $reverse{$char};
}

say $result;

(Full code on Github.)