Perl Weekly Challenge: Week 313

Challenge 1:

Broken Keys

You have a broken keyboard which sometimes type a character more than once.

You are given a string and actual typed string.

Write a script to find out if the actual typed string is meant for the given string.

Example 1
Input: $name = "perl", $typed = "perrrl"
Output: true

Here "r" is pressed 3 times instead of 1 time.
Example 2
Input: $name = "raku", $typed = "rrakuuuu"
Output: true
Example 3
Input: $name = "python", $typed = "perl"
Output: false
Example 4

I have a sneaking suspicion that this is not the ideal way to solve this challenge but it seems to work.

I implemented the Perl solution first so I shall begin with that. As an initiial step we need to break up each of the input strings into runs of the same character In Example 1 for instance, $name would become ('p', 'e', 'r', 'l') whereas $typed would become ('p', 'e', 'rrr', 'l' ). To avoid code duplication, I split this out into a function.

sub breakup($str) {

I'll skip over this line for now.

    my $i = 0;

Going from right to left, we first have a regular expression that looks for all groups of a character followed by 0 or more instances of the same character. Unfortunately due to the global nature of Perl's capture groups, what we get for e.g. $typed in example 1 is ('p', 'p', 'e', 'e' 'rrr', 'r', 'l', 'l') It is only the even-indexed elements that we are interested in. Perhaps some cleverer regex tricks could have surpressed the extras but I was unable to think of any. Anyway, the correct elements can be extracted with grep() and that's why we needed to have the previous line, $i acts as the running index of the array,

    return grep { $_ if $i++ % 2 == 0 } ($str =~ /((.)\2*)/g);
}

Back to MAIN(), we use breakup() on our two input strings:

my @nameParts = breakup($name);
my @typedParts = breakup($typed);

We also create a variable to hold the result and initialize it to true. (Once again, we are using the new, experimental builtin true and false values in modern varieties of Perl.)

my $result = true;

Now we compare the elements of @nameParts and @typedParts pairwise, and if the element from @nameParts fails to match the corresponding element from @typedParts, we set the $result to false and stop comparison. Otherwise we continue to the next pair.

for my $i (0 .. scalar @nameParts - 1) {
    unless ($typedParts[$i] =~ /$nameParts[$i]/) {
        $result = false;
        last;
    } 
}

Finally we print the result. As true and false are not fully implemented yet (or atleast not in my version of Perl) we have' to explicitly print true or false ourselves.

say $result ? "true" : "false";

(Full code on Github.)

Input: $name = "coffeescript", $typed = "cofffeescccript"
Output: true

The Raku version is the same length in lines but has some features that make the code simpler and easier to understand.

sub breakup($str) {

Raku has nested capture groups so we can dispense with grep().

    return $str.match(/( (.) $0* )/, :g).List;
}

my @nameParts = breakup($name);
my @typedParts = breakup($typed);
my $result = True;

Raku's Z operator makes "zipping" two arrays easy.

for @typedParts Z @nameParts -> ($t, $n) {
    unless $t.match(/$n/) {
        $result = False;
        last;
    } 
}

Raku has print support for Booleans.

say $result;

(Full code on Github.)

Challenge 2:

Reverse Letters

You are given a string.

Write a script to reverse only the alphabetic characters in the string.

Example 1
Input: $str = "p-er?l"
Output: "l-re?p"
Example 2
Input: $str = "wee-k!L-y"
Output: "yLk-e!e-w"
Example 3
Input: $str = "_c-!h_all-en!g_e"
Output: "_e-!g_nel-la!h_c"

First we make an array of reversed chaaracters by splitting $str into characters with .comb(), finding the alphabetic characters with .grep() and then reversing it with .reverse().

my @reversed = $str.comb.grep({ /<alpha>/ }).reverse;

We will also need to keep track of the current index in that array.

my $index = 0;

Now we can just .subst()itute any alphabetic character in $str with its' equivalent in @reversed. Finally, we print the result with .say().

($str.subst(/<alpha>/, { @reversed[$index++] }, :g)).say;

(Full code on Github.)

The Perl version is slightly more verbose but works the same way.

my @reversed = reverse grep { /[[:alpha:]]/ } split //, $str;
my $index = 0;

$str =~ s/[[:alpha:]]/$reversed[$index++]/g;
say $str;

(Full code on Github.)