Perl Weekly Challenge: Week 364

Challenge 1:

Decrypt String

You are given a string formed by digits and ‘#'.

Write a script to map the given string to English lowercase characters following the given rules.

- Characters 'a' to 'i' are represented by '1' to '9' respectively.
- Characters 'j' to 'z' are represented by '10#' to '26#' respectively.
Example 1
Input: $str = "10#11#12"
Output: "jkab"

10# -> j
11# -> k
1   -> a
2   -> b
Example 2
Input: $str = "1326#"
Output: "acz"

1   -> a
3   -> c
26# -> z
Example 3
Input: $str = "25#24#123"
Output: "yxabc"

25# -> y
24# -> x
1   -> a
2   -> b
3   -> c
Example 4
Input: $str = "20#5"
Output: "te"

20# -> t
5   -> e
Example 5
Input: $str = "1910#26#"
Output: "aijz"

1   -> a
9   -> i
10# -> j
26# -> z

We can parse this format in one line with a regular expression and a little bit of post processing.

We get the input string from the first command-line argument, @*ARGS[0]. then we .match() that string against a regular expressiion that identifies either two digits followed by a '#' or a single digit. Each choice is wrapped in <( ... )> to "group" it for further use. Notice that in the first group, the '#' is outside; this is because we want to match it but not include it in the results. .match() will return a List of all groups found, each of which will be a number from 1 to 26. We traverse through this list with .map() and add each number to 0x60 (decimal 96) which is the ASCII code for '`' the character before 'a'. This code is converted back to a character from 'a' to 'z' with chr(). The list of characters is then .join()ed back into a string and output with .say().

@*ARGS[0].match(/ <(\d\d)>\# || <(\d)> /,:g).map({ chr(0x60 + $_) }).join(q{}).say

(Full code on Github.)

We can do the same thing just as compactly in Perl.

say join q{}, map { chr(0x60 + $_) } grep {$_} shift =~ /(\d\d)\#|(\d)/g

(Full code on Github.)

Challenge 2:

Goal Parser

You are given a string, $str.

Write a script to interpret the given string using Goal Parser.

The Goal Parser interprets “G” as the string “G”, “()” as the string “o”, and “(al)” as the string “al”. The interpreted strings are then concatenated in the original order.

Example 1
Input: $str = "G()(al)"
Output: "Goal"

G    -> "G"
()   -> "o"
(al) -> "al"
Example 2
Input: $str = "G()()()()(al)"
Output: "Gooooal"

G       -> "G"
four () -> "oooo"
(al)    -> "al"
Example 3
Input: $str = "(al)G(al)()()"
Output: "alGaloo"

(al) -> "al"
G    -> "G"
(al) -> "al"
()   -> "o"
()   -> "o"
Example 4
Input: $str = "()G()G"
Output: "oGoG"

() -> "o"
G  -> "G"
() -> "o"
G  -> "G"
Example 5
Input: $str = "(al)(al)G()()"
Output: "alalGoo"

(al) -> "al"
(al) -> "al"
G    -> "G"
()   -> "o"
()   -> "o"

This is another simple application of regular expressions.

First we have to make a copy of the input because it is immutable in Raku.

my $result = $str;

Then we successively apply a substitution to $result for all instances of each of the tokens mentioned in the spec. Well, we can skip "G" because its' input is the same as it's output.

$result ~~ s:g/\(\)/o/;
$result ~~ s:g/\(al\)/al/;

The spec does not clarify what should be done if a character sequence other than these are encountered so we will just leave them as-is and print $result.

$result.say;

This works and is quick and easy but I wondered if I could avoid making multiple passes over $result. I remembered that Raku also has S/// which performs substitutions on a copy of the string and returns the result. This makes it possible to chain together multiple substitutions via given like this. The second (and subsequent) calls to S/// have to be wrapped in parantheses.

say S:g/\(al\)/al/ given (S:g/\(\)/o/ given @*ARGS[0])

(Full code on Github.)

Later it struck me that I could also chain the .subst() method with the same effect and much better readability. So my final version looks like this:

@*ARGS[0].subst(/\(al\)/, "al", :g).subst(/\(\)/, "o", :g).say

(Full code on Github.)

For Perl I didn't know if I could create a one-liner. It doesn't have .subst() and s/// returns the number of substitutions made in scalar context so chaining substitutions would not be possible. Or would it? Rummaging through the documentation revealed that s/// has an /r flag I wasn't aware of which "Return[s] substitution and leave the original string untouched." In other words, it acts like Rakus' S/// operator. So we can meka a translation of my second Raku version like thia:

say shift =~ s/\(\)/o/gr =~ s/\(al\)/al/gr

(Full code on Github.)