Perl Weekly Challenge: Week 279

Challenge 1:

Sort Letters

You are given two arrays, @letters and @weights.

Write a script to sort the given array @letters based on the @weights.

Example 1
Input: @letters = ('R', 'E', 'P', 'L')
       @weights = (3, 2, 1, 4)
Output: PERL
Example 2
Input: @letters = ('A', 'U', 'R', 'K')
       @weights = (2, 4, 1, 3)
Output: RAKU
Example 3
Input: @letters = ('O', 'H', 'Y', 'N', 'P', 'T')
       @weights = (5, 4, 2, 6, 1, 3)
Output: PYTHON

I did manage to get this into one line but I'm not completely satisfied as I think it should be possible to get it a little bit shorter.

%(@*ARGS[1].words Z=> @*ARGS[0].words).sort.map({ $_.value }).join.say

(Full code on Github.)

The input is taken from two command-line parameters; what the spec calls @letters and @weights are @*ARGS[0] and @*ARGS[1] respectively. Each parameter is a string of letters or numbers separated by spaces.

The first step is to convert each string into an array with .words(). Then the Z=> operator takes consecutive elements from both arrays and makes them into a List of key-value Pairs. %( ... ) around this operation turns the List into a Hash. We could also have used .Hash but this is a little shorter.

Once we have this hash, we sort it and get the sorted values with .map(). I was hoping there would be a more direct way of doing this but I was unable to figure it out. These sorted values are then concatenated with .join() and the resulting string is printed out with .say().

The Perl version is a lot longer mainly because Perl doesn't have all the shortcuts Raku has for assembling the hash. The six lines below do just that.

my @letters = split /\s+/, $ARGV[0];
my @weights = split /\s+/, $ARGV[1];
my %h;

for my $key (keys @weights) {
    $h{$weights[$key]} = $letters[$key];
}

Once we have a hash though we can do everything else in one line.

say join q{}, map { $h{$_} } sort { $a <=> $b } keys %h;

(Full code on Github.)

Challenge 2:

Reverse Word

You are given a string, $str.

Write a script to split the given string into two containing exactly same number of vowels and return true if you can otherwise false.

Example 1
Input: $str = "perl"
Output: false
Example 2
Input: $str = "book"
Output: true

Two possible strings "bo" and "ok" containing exactly one vowel each.
Example 3
Input: $str = "good morning"
Output: true

Two possible strings "good " and "morning" containing two vowels each or "good m" and "orning" containing two vowels each.

The spec talks about splitting strings but this is misleading. You will only get the exactly same number of vowels if the total number of vowels in the string is divisible by two. So all you need to is count them and see if the total is even (true) or odd (false.)

This can be done with regular expressions but a Perl trick I learned a long long time ago is that tr/// which is for replacing or deleting characters returns the count of characters replaced (or deleted). You can even save one character by using the synonym y///. So all we need to do is get the input string as the first command-line parameter and run it through an invocation of y/// that replaces all vowels (ny code doesn't deal with upper-case but nor do any of the examples.) by nothing. The result of this is tested for evenness with the modulo operator % and false or true is printed accordingly.

say $ARGV[0] =~ y/aeiou// % 2 ? "false" : "true"

(Full code on Github.)

The Raku version is a little bit longer. tr/// works differently in Raku so I used the regular expression method instead. .match() captures all (with the :g or global flag) instances of the class a, e, i, o, and u. .elems() counts how many captures and the integer modulo operator %% is used to determine if that count is divisible by tw0 (true) or not (false) and say() prints the answer out.

say @*ARGS[0].match(/(<[aeiou]>)/, :g).elems %% 2

(Full code on Github.)