Perl Weekly Challenge 364‘s tasks are “Decrypt String” and “Goal Parser”. Because task 2’s second example is decidedly a tribute to Andrés Cantor, that meant I needed to think “soccer” when I was thinking about the music this week, and suddenly I heard Arsenal fans singing in my head.
Task 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.
Input: $str = "10#11#12"
Output: "jkab"
10# -> j
11# -> k
1 -> a
2 -> b
Input: $str = "1326#"
Output: "acz"
1 -> a
3 -> c
26# -> z
Input: $str = "25#24#123"
Output: "yxabc"
25# -> y
24# -> x
1 -> a
2 -> b
3 -> c
Input: $str = "20#5"
Output: "te"
20# -> t
5 -> e
Input: $str = "1910#26#"
Output: "aijz"
1 -> a
9 -> i
10# -> j
26# -> z
Approach
There are a couple ways I could approach this. I could write a parser that processed the string once from left to right, and maintaining a two-character buffer of previously read characters. If the third character read is a #, then the previous two characters must map to a single output character from j-z, and the buffer would be cleared. If the third character is numeric, then the first character in the buffer maps to a single output character from a-i, and the first character is shifted off the buffer and the new character is pushed onto the end. Once we reach the end of the string, any remaining characters in the buffer are output characters from a-i.
Or I could take two passes through the string with regular expressions: first, match all /(\d\d\#)/ and replace them with the appropriate output characters from j-z, and then match all /(\d)/ and replace them with the appropriate output characters from a-i.
Raku
In Raku, the .subst method on the Str class is called on a Str object with a matcher and a replacement (where the matched is replaced by the replacement), and returns a new Str object. The matcher can be either a string or a regular expression, and the replacement can be either a string or a Callable in which the current Match object will be placed in the $/ variable, as well as the $_ topic variable. We’re using a callable where we add 96 to the numeric value extracted and convert it back to a character. Because the output of the method is a Str object, we can just invoke .subst again on that to do the second replacement, and then the resulting object is just returned from our function.
I could have embedded the regular expressions from lines 4 & 5 directly in the .subst calls on lines 8 & 9, but compiling them into variables makes the code marginally easier to read.
my $jz_re = rx/(\d\d)(\#)/;
my $ai_re = rx/(\d)/;
sub decrypt($str is copy) {
$str.subst($jz_re, { chr( $0 + 96 ) }, :g)
.subst($ai_re, { chr( $0 + 96 ) }, :g);
}View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: $str = "10#11#12"
Output: "jkab"
Example 2:
Input: $str = "1326#"
Output: "acz"
Example 3:
Input: $str = "25#24#123"
Output: "yxabc"
Example 4:
Input: $str = "20#5"
Output: "te"
Example 5:
Input: $str = "1910#26#"
Output: "aijz"Perl
As always, the Perl solution looks like the Raku solution. The big difference is that regular expression substitution occurs in place in the string, and the result is the success/failure of the substitution, so I have to do the replacements in separate statements.
my $jz_re = qr/(\d\d)(\#)/;
my $ai_re = qr/(\d)/;
sub decrypt($str) {
$str =~ s/$jz_re/chr( $1 + 96 )/eg;
$str =~ s/$ai_re/chr( $1 + 96 )/eg;
$str;
}View the entire Perl script for this task on GitHub.
Python
Python may or may not need a defined function, I might have been able to replace the calls to to_char with a lambda, but it was late and I was tired.
import re
jz_re = re.compile(r'(\d\d)(\#)')
ai_re = re.compile(r'(\d)')
def to_char(match):
return chr( int(match.group(1)) + 96 )
def derypt(string):
string = jz_re.sub(to_char, string)
return ai_re.sub(to_char, string)View the entire Python script for this task on GitHub.
Elixir
While Regex.replace/4 let me specify a function that had multiple captures, String.replace/4 only allowed me to pass a single capture value to the function. Since I wanted to be able to pipe the output of the first replacement into the second replacement, I opted to filter out the # character in the to_char/1 function.
I was looking for a way to take the numeric value yielded by line 8 and convert it into a character. I knew List.to_charlist/1 would do it if I could get the integer into a list, so I started looking in the Kernel module looking for a function that could take a single value and make it a list. It looked like then/2 would fit the bill, but when I tried it out in iex, I discovered that a list of integers is a charlist:
iex(1)> 107 |> then(fn x -> [x] end)
~c"k"def to_char(match), do:
match
|> String.replace("#", "")
|> String.to_integer
|> Kernel.+(96)
|> then(fn x -> [x] end)
def decrypt(str) do
str
|> String.replace(~r/\d\d\#/, fn x -> to_char(x) end)
|> String.replace(~r/\d/, fn x -> to_char(x) end)
endView the entire Elixir script for this task on GitHub.
Task 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.
Input: $str = "G()(al)"
Output: "Goal"
G -> "G"
() -> "o"
(al) -> "al"
Input: $str = "G()()()()(al)"
Output: "Gooooal"
G -> "G"
four () -> "oooo"
(al) -> "al"
Input: $str = "(al)G(al)()()"
Output: "alGaloo"
(al) -> "al"
G -> "G"
(al) -> "al"
() -> "o"
() -> "o"
Input: $str = "()G()G"
Output: "oGoG"
() -> "o"
G -> "G"
() -> "o"
G -> "G"
Input: $str = "(al)(al)G()()"
Output: "alalGoo"
(al) -> "al"
(al) -> "al"
G -> "G"
() -> "o"
() -> "o"
Approach
This problem has the same approach. I could parse it character by character, or I could just do two passes with regular expressions.
Raku
In fact, I was able to use the EXACT SAME CODE, only swapping out the regular expressions and the replacement strings.
my $al_re = rx/\(al\)/;
my $o_re = rx/\(\)/;
sub parser($str is copy) {
$str.subst($al_re, "al", :g)
.subst($o_re, "o", :g);
}View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: $str = "G()(al)"
Output: "Goal"
Example 2:
Input: $str = "G()()()()(al)"
Output: "Gooooal"
Example 3:
Input: $str = "(al)G(al)()()"
Output: "alGaloo"
Example 4:
Input: $str = "()G()G"
Output: "oGoG"
Example 5:
Input: $str = "(al)(al)G()()"
Output: "alalGoo"Perl
my $al_re = qr/\(al\)/;
my $o_re = qr/\(\)/;
sub parser($str) {
$str =~ s/$al_re/al/g;
$str =~ s/$o_re/o/g;
$str;
}View the entire Perl script for this task on GitHub.
Python
import re
al_re = re.compile(r'\(al\)')
o_re = re.compile(r'\(\)')
def parser(string):
string = al_re.sub("al", string)
return o_re.sub("o", string)View the entire Python script for this task on GitHub.
Elixir
def parser(str) do
str
|> String.replace(~r/\(al\)/, "al")
|> String.replace(~r/\(\)/, "o")
endView the entire Elixir script for this task on GitHub.
Here’s all my solutions in GitHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-364/packy-anderson