This is my response to The Weekly Challenge #364.
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
This task is suitable for gather/take,
where we use gather to collect the parts (the numeric values),
the first - and hardest - part of this task.
See my Raku Gather, I Take article or docs.raku.org/language/control#gather/take for more information about gather/take.
#! /usr/bin/env raku
unit sub MAIN ($string where $string.chars > 0, :v(:$verbose)); # [1]
my $tokens := gather # [2]
{
my @chars = $string.comb; # [3]
while @chars.elems # [4]
{
if @chars.elems > 2 && @chars[2] eq '#' # [5]
{
say ": '{ @chars.join }' -> take '{ @chars[0..2].join }'" if $verbose;
take (@chars.shift ~ @chars.shift ~ @chars.shift).substr(0,2); # [5a]
}
else # [6]
{
say ": '{ @chars.join }' -> take '@chars[0]'" if $verbose;
take @chars.shift; # [6a]
}
}
}
say $tokens.map({ .&decrypt }).join; # [7]
sub decrypt ($key) # [8]
{
constant base = 'a'.ord -1; # [9]
return (base + $key).chr; # [10]
}
[1] A string with at least 1 character.
[2] Collect the numeric values (here called tokens).
[3] Split the string into an array of individual characters.
[4] As long as we have unprocessed input.
[5] Do we have at least three characters left, and the third one is
#? If so, get the three characters from the left of the array
(with shift) and return the first two joined together (with
substr and take [5a]).
[6] If not, get the first character and return it [6a].
[7] Apply the «decrypt» procedure on each element, join the resulting values to a string and print that.
[8] The procedure doing the decryption.
[9] The ascii value of 'a' (courtesy of ord), less one.
To be used as offset.
See docs.raku.org/routine/ord for more information about ord.
[10] Convert the ascii value of the alphabetic number (e.g.
1 => a, 2 => b, and so on) back to a character (with chr) and
return it.
See docs.raku.org/routine/chr for more information about chr.
Running it:
$ ./decrypt-string "10#11#12"
jkab
$ ./decrypt-string "1326#"
acz
$ ./decrypt-string "25#24#123"
yxabc
$ ./decrypt-string "20#5"
te
$ ./decrypt-string "1910#26#"
aijz
Looking good.
With verbose mode:
$ ./decrypt-string -v "10#11#12"
: '10#11#12' -> take '10#'
: '11#12' -> take '11#'
: '12' -> take '1'
: '2' -> take '2'
jkab
$ ./decrypt-string -v "1326#"
: '1326#' -> take '1'
: '326#' -> take '3'
: '26#' -> take '26#'
acz
$ ./decrypt-string -v "25#24#123"
: '25#24#123' -> take '25#'
: '24#123' -> take '24#'
: '123' -> take '1'
: '23' -> take '2'
: '3' -> take '3'
yxabc
$ ./decrypt-string -v "20#5"
: '20#5' -> take '20#'
: '5' -> take '5'
te
$ ./decrypt-string -v "1910#26#"
: '1910#26#' -> take '1'
: '910#26#' -> take '9'
: '10#26#' -> take '10#'
: '26#' -> take '26#'
aijz
$str.
Goal Parser.
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"
#! /usr/bin/env raku
unit sub MAIN ($string is copy where $string.chars > 0, # [1]
:v(:$verbose));
my $tokens := gather # [2]
{
while $string.chars # [3]
{
if $string ~~ /^([ "G" | "\(\)" | "\(al\)" ]) (.*)/ # [4]
{
say ": '$string' -> '$0' + '$1'" if $verbose;
given $0.Str # [5]
{
when "G" { take "G" } # [5G]
when "()" { take "o" } # [5o]
when "(al)" { take "al" } # [5al]
}
$string = $1.Str // ""; # [6]
}
else
{
die "Illegal input; not a goal"; # [7]
}
}
}
say $tokens.join; # [8]
[1] Ensure a string with at least 1 character. The is_copy
adverb makes it possible to change the value later on (in [6]).
See docs.raku.org/type/Parameter#method_copy for more information about is copy.
[2] Collect the tokens with gather.
[3] As long as we have more characters to process.
[4] This regexp looks for the three "characters".
[5] Use given/when to check the regexp match,
and take to return the translation.
See docs.raku.org/syntax/default when for more information about given/when/default.
[6] Update the string (by removing the part we just translated), ready for the next iteration.
[7] No regexp match? Report the error and terminate.
[8] Print the result, as a string.
Running it:
$ ./goal-parser "G()(al)"
Goal
$ ./goal-parser "G()()()()(al)"
Gooooal
$ ./goal-parser "(al)G(al)()()"
alGaloo
$ ./goal-parser "()G()G"
oGoG
$ ./goal-parser "(al)(al)G()()"
alalGoo
Looking good.
With verbose mode:
$ ./goal-parser -v "G()(al)"
: 'G()(al)' -> 'G' + '()(al)'
: '()(al)' -> '()' + '(al)'
: '(al)' -> '(al)' + ''
Goal
$ ./goal-parser -v "G()()()()(al)"
: 'G()()()()(al)' -> 'G' + '()()()()(al)'
: '()()()()(al)' -> '()' + '()()()(al)'
: '()()()(al)' -> '()' + '()()(al)'
: '()()(al)' -> '()' + '()(al)'
: '()(al)' -> '()' + '(al)'
: '(al)' -> '(al)' + ''
Gooooal
$ ./goal-parser -v "(al)G(al)()()"
: '(al)G(al)()()' -> '(al)' + 'G(al)()()'
: 'G(al)()()' -> 'G' + '(al)()()'
: '(al)()()' -> '(al)' + '()()'
: '()()' -> '()' + '()'
: '()' -> '()' + ''
alGaloo
$ ./goal-parser -v "()G()G"
: '()G()G' -> '()' + 'G()G'
: 'G()G' -> 'G' + '()G'
: '()G' -> '()' + 'G'
: 'G' -> 'G' + ''
oGoG
$ ./goal-parser -v "(al)(al)G()()"
: '(al)(al)G()()' -> '(al)' + '(al)G()()'
: '(al)G()()' -> '(al)' + 'G()()'
: 'G()()' -> 'G' + '()()'
: '()()' -> '()' + '()'
: '()' -> '()' + ''
alalGoo
The else part is there to take care of non-goal input, like e.g.
$ ./goal-parser -v "G()(al)!"
: 'G()(al)!' -> 'G' + '()(al)!'
: '()(al)!' -> '()' + '(al)!'
: '(al)!' -> '(al)' + '!'
Illegal input; not a goal
in block at ./goal-parser line 22
in sub MAIN at ./goal-parser line 26
in block <unit> at ./goal-parser line 1
Without this else block, the program would go on in an infinite loop.
An alternative could be to let non-goal characters pass through unchanged into the output.
File: goal-parser-allow
#! /usr/bin/env raku
unit sub MAIN ($string is copy where $string.chars > 0, :v(:$verbose));
my $tokens := gather
{
while $string.chars
{
if $string ~~ /^([ "G" | "\(\)" | "\(al\)" ] | . ) (.*)/ # [1]
{
say ": '$string' -> '$0' + '$1'" if $verbose;
given $0.Str
{
when "G" { take "G" }
when "()" { take "o" }
when "(al)" { take "al" }
default { take $_ } # [2]
}
$string = $1.Str // "";
} # [3]
}
}
say $tokens.join;
[1] Note the single period, that matches a single character.
[2] We use default to catch the non-goal match.
[3] The else block has gone.
Running it:
$ ./goal-parser-allow -v "G()(al)!"
: 'G()(al)!' -> 'G' + '()(al)!'
: '()(al)!' -> '()' + '(al)!'
: '(al)!' -> '(al)' + '!'
: '!' -> '!' + ''
Goal!
$ ./goal-parser-allow -v "foo()bar"
: 'foo()bar' -> 'f' + 'oo()bar'
: 'oo()bar' -> 'o' + 'o()bar'
: 'o()bar' -> 'o' + '()bar'
: '()bar' -> '()' + 'bar'
: 'bar' -> 'b' + 'ar'
: 'ar' -> 'a' + 'r'
: 'r' -> 'r' + ''
fooobar
The fact that «G» is translated to «G» is a strong indication that this
version of the program gets it wrong (as the default block makes that rule
redundant). So stick with the first version.
And that's it.