Perl Weekly Challenge: Week 264

Challenge 1:

Greatest English Letter

You are given a string, $str, made up of only alphabetic characters [a..zA..Z].

Write a script to return the greatest english letter in the given string.

A letter is greatest if it occurs as lower and upper case. Also letter ‘b’ is greater than ‘a’ if ‘b’ appears after ‘a’ in the English alphabet.

Example 1
Input: $str = 'PeRlwEeKLy'
Output: L

There are two letters E and L that appears as lower and upper.
The letter L appears after E, so the L is the greatest english letter.
Example 2
Input: $str = 'ChaLlenge'
Output: L
Example 3
Input: $str = 'The'
Output: ''

No one-liners this week but the Raku solution is still pretty concise.

We start by taking the input and splitting it into individual characters with .comb(). Then we use .classify() as we did last week to put the upper and lower case characters into separate keys ('upper' and 'lower') of a hash called %chars.

$str.comb.classify({ $_ ~~ 'A' .. 'Z' ?? 'upper' !! 'lower'}, :into(my %chars));

Treating the values of %chars<upper> and %chars<lower> as Sets, the letters that they have in common will be the intersection of those sets. We can find the intersection with the operator but there is one additional step we must take first; The two sets must contain the same types of elements so we transform the elements of the lower case set to upper case with .map() and .uc().

The intersection is also a Set so we need the .keys() method to extract its' elements (i.e. the letters). .sort[*-1] gives the he greatest (i.e. highest alphabetical value) letter. One last scenario to consider is if the intersection is empty which means there were no great letters in the string. // q{''} prints empty quotes if that is the case.

say (%chars<upper>.values ∩ %chars<lower>.values.map({ .uc })).keys.sort[*-1] // q{''};

(Full code on Github.)

In Perl we don't have .classify() so we have to recreate it ourselves. First, I declared two hashes to hold the upper and lower case characters.

my %upper;
my %lower;

Then after splitting the input into individual characters, for each character...

for my $char (split //, $str) {

...If it is upper case, it is added as a key into the %upper hash with the arbitrary value of 1. The value doesn't matter, we just need the key to exist.

    if ($char =~ /[[:upper:]]/) {
        $upper{$char} = 1;

If the character wasn't upper case, it is added to %lower.

    } else {
        $lower{$char} = 1;
    }
}

A variable is reserved to hold the current greatest letter.

my $greatest = undef;

Then we iterate through the sorted keys of %upper.

for my $char (sort keys %upper) {

For each one, we convert it to lower case and see if a key with that value exists in %lower. The method I used to convert to lower case using ord() and chr() relies on the fact that the numeric values of lower case ASCII characters are exactly 32 (the numeric values of the space character) more than their upper-case counterparts. It is needlessly complicated and honestly I don't know what I was thinking. I could have just used lc(). In fact in hindsight I see I could have just had upper-case keys in %lower.

    if (exists $lower{chr((ord $char) + (ord ' '))}) {

Anyway if the upper-case letter has a lower-case counterpart, it becomes the new current greatest letter. $greatest = $char; } }

Whatever is the $greatest after we've gone through all the letters in %upper is the final answer and is printed out. If it was still undef that means we didn't find a greatest letter so empty quotes are printed.

say $greatest // q{''};

(Full code on Github.)

Challenge 2:

Target Array

You are given two arrays of integers, @source and @indices. The @indices can only contains integers 0 <= i < size of @source.

Write a script to create target array by insert at index $indices[i] the value $source[i].

Example 1
Input: @source  = (0, 1, 2, 3, 4)
    @indices = (0, 1, 2, 2, 1)
Output: (0, 4, 1, 3, 2)

@source  @indices  @target
0        0         (0)
1        1         (0, 1)
2        2         (0, 1, 2)
3        2         (0, 1, 3, 2)
4        1         (0, 4, 1, 3, 2)
Example 2
Input: @source  = (1, 2, 3, 4, 0)
    @indices = (0, 1, 2, 3, 0)
Output: (0, 1, 2, 3, 4)

@source  @indices  @target
1        0         (1)
2        1         (1, 2)
3        2         (1, 2, 3)
4        3         (1, 2, 3, 4)
0        0         (0, 1, 2, 3, 4)
Example 3
Input: @source  = (1)
    @indices = (0)
Output: (1)

This one was really easy because once we have imported the sources and indices from command-line parameters (they look like e.g. "0 1 2 3 4" "0 1 2 2 1" for example 1.)...

my @sources = $s.split(/\s+/).map({ .Int });
my @indices = $i.split(/\s+/).map({ .Int });

...And declared a variable to hold the output...

my @output;

...we can traverse through @sources by index and use it together with the equivalent index in @indices as parameters to .splice() which does all the work of inserting the number in the right place in @output.

for @sources.keys -> $i {
    @output.splice(@indices[$i], 0,  @sources[$i]);
}

And then we print @output formatted in the style of the example output.

 say q{(}, @output.join(q{, }), q{)};

(Full code on Github.)

The Perl version is almost exactly equivalent.

my @sources = split /\s+/, $ARGV[0];
my @indices = split /\s+/, $ARGV[1];
my @output;

for my $i (keys @sources) {
    splice @output, $indices[$i], 0, $sources[$i];
}

say q{(}, (join q{, }, @output), q{)};

(Full code on Github.)