This is my response to The Weekly Challenge #328.
?
.
?
in the given string so that the string
doesn’t contain consecutive repeating characters.
Input: $str = "a?z"
Output: "abz"
There can be many strings, one of them is "abz".
The choices are 'a' to 'z' but we can't use either 'a' or 'z' to replace
the '?'.
Example 2:
Input: $str = "pe?k"
Output: "peak"
Example 3:
Input: $str = "gra?te"
Output: "grabte"
#! /usr/bin/env raku
unit sub MAIN ($str where $str ~~ /^<[a..z \?]>+$/, # [1]
:v(:$verbose));
my @str = $str.comb; # [2]
my $prev = ''; # [3]
my $result = ''; # [4]
my $end = @str.end; # [5]
for 0 .. $end -> $index # [6]
{
my $current = @str[$index]; # [7]
my $next = $index >= $end ?? "" !! @str[$index +1]; # [8]
if $current eq '?' # [9]
{
say ": Replace ? with anything != ($prev, $next)" if $verbose;
my $s = ('a' .. 'z') (-) ($prev, $next); # [10]
my $pick = $s.pick; # [11]
say ": From: { $s.keys.sort.join(",") } -> $pick" if $verbose;
$result ~= $pick; # [12]
$prev = $pick; # [13]
}
else # [14]
{
say ": Added normal letter $current" if $verbose;
$result ~= $current; # [15]
$prev = $current; # [16]
}
}
say $result; # [17]
[1] Ensure lowercase letters and question marks only. At least one character.
[2] Split the string into an array of characters with
comb
.
See docs.raku.org/routine/comb for more information about comb
.
[3] The previous character (passed on to the result), initially nothing.
[4] The result will end up here (as a string).
[5] The index of the last character.
[6] Iterate over all the indices of the characters.
[7] Get the current character.
[8] Get the next character. This defaults to ""
if we are at
the very end (and thus no next character).
[9] Is the current character ?
. If so,
[10] • Set up a Set of characters to pick from. Where the
previous and next characters are excluded with the set difference operator
(-)
.
See
docs.raku.org/language/setbagmix#Set_operators_that_return_a_QuantHash and scroll
down to the set difference operator (-)
.
[11] • Randomly pick one of the possible characters with
pick
.
See docs.raku.org/routine/pick for more information about pick
.
[12] • Add the pick'ed character to the result.
[13] • Set it as the previous one, ready for the next iteration.
[14] If not,
[15] • Add the character itself to the result.
[16] • As [13].
[17] Print the result.
Running it:
$ ./replace-all-qm 'a?z'
ajz
$ ./replace-all-qm 'pe?k'
pebk
$ ./replace-all-qm 'gra?te'
gradte
Looking good.
With verbose mode, running it twice for each example for the fun of it:
$ ./replace-all-qm -v 'a?z'
: Added normal letter a
: Replace ? with anything != (a, z)
: From: b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y -> l
: Added normal letter z
alz
$ ./replace-all-qm -v 'a?z'
: Added normal letter a
: Replace ? with anything != (a, z)
: From: b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y -> t
: Added normal letter z
atz
$ ./replace-all-qm -v 'pe?k'
: Added normal letter p
: Added normal letter e
: Replace ? with anything != (e, k)
: From: a,b,c,d,f,g,h,i,j,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z -> n
: Added normal letter k
penk
$ ./replace-all-qm -v 'pe?k'
: Added normal letter p
: Added normal letter e
: Replace ? with anything != (e, k)
: From: a,b,c,d,f,g,h,i,j,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z -> n
: Added normal letter k
penk
$ ./replace-all-qm -v 'gra?te'
: Added normal letter g
: Added normal letter r
: Added normal letter a
: Replace ? with anything != (a, t)
: From: b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,u,v,w,x,y,z -> k
: Added normal letter t
: Added normal letter e
grakte
$ ./replace-all-qm -v 'gra?te'
: Added normal letter g
: Added normal letter r
: Added normal letter a
: Replace ? with anything != (a, t)
: From: b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,u,v,w,x,y,z -> n
: Added normal letter t
: Added normal letter e
grante
Input: $str = "WeEeekly"
Output: "Weekly"
We can remove either, "eE" or "Ee" to make it good.
Example 2:
Input: $str = "abBAdD"
Output: ""
We remove "bB" first: "aAdD"
Then we remove "aA": "dD"
Finally remove "dD".
Example 3:
Input: $str = "abc"
Output: "abc"
#! /usr/bin/env raku
unit sub MAIN ($str is copy where $str ~~ /^<[ a..z A..Z ]>+$/, # [1]
:v(:$verbose));
my $index = 0; # [2]
loop # [3]
{
my $end = $str.chars -1; # [4]
my $current = $str.substr($index, 1); # [5]
last if $end == -1; # [6]
last if $index == $end; # [7]
my $next = $str.substr($index +1, 1); # [8]
print ": Checking '$current$next' (index $index)" if $verbose;
if $current.lc eq $next.lc && $current ne $next # [9]
{
$str.substr-rw($index,2) = ""; # [10]
say " - replace with nothing -> $str" if $verbose;
$index-- unless $index == 0; # [11]
}
else # [12]
{
say "" if $verbose;
$index++; # [12a]
}
}
say $str; # [13]
[1] Ensure a string containing lower- and uppercase letters only.
The is copy
is there to enable us to change the variable
(in [10]).
[2] We start at index 0, the very first character.
[3] An eternal loop, but see the exit strategies at [6] and [7].
[4] The index of the last character.
[5] Get the current character.
[6] Exit the loop if we do not have any (more) characters in the string (i.e. removed them all).
[7] Exit the loop if we have passed the last character.
[8] Get the next character. Note that [7] ensures that we do have a next character.
[9] This is an easy way to check that we have a lowercase and uppercase version of the same character, in any order.
[10] Use substr-rw
to replace those two
characters with nothing, i.e. remove them.
See docs.raku.org/routine/substr-rw for more information about substr-rw
.
[11] We have removed the characters, so have to backtrack for the next iteration. This is demonstrated by e.g. 'aBbA', after removing 'Bb'. That would leave 'aA' but with the index still at 1 (the 'A') and thus not cause the removal of anything more.
[12] Not removeable? Move the index to the right.
[13] Print the result.
Running it:
$ ./good-string -v WeEeekly
Weekly
$ ./good-string -v abBAdD
$ ./good-string -v abc
abc
Looking good.
With verbose mode:
$ ./good-string -v WeEeekly
: Checking 'We' (index 0)
: Checking 'eE' (index 1) - replace with nothing -> Weekly
: Checking 'We' (index 0)
: Checking 'ee' (index 1)
: Checking 'ek' (index 2)
: Checking 'kl' (index 3)
: Checking 'ly' (index 4)
Weekly
$ ./good-string -v abBAdD
: Checking 'ab' (index 0)
: Checking 'bB' (index 1) - replace with nothing -> aAdD
: Checking 'aA' (index 0) - replace with nothing -> dD
: Checking 'dD' (index 0) - replace with nothing ->
$ ./good-string -v abc
: Checking 'ab' (index 0)
: Checking 'bc' (index 1)
abc
And that's it.