Perl Weekly Challenge: Week 328
Challenge 1:
Replace All ?
You are given a string containing only lower case English letters and
?
.Write a script to replace all
?
in the given string so that the string doesn’t contain consecutive repeating characters.
Example 1
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"
We take the input string from the command-line and as command-line arguments. Using the .subst()
method,
we find all instances of a ?
character in the string. We also need to know the characters on either side of the ?
. The enclosing characters are passed to a function newChar()
which will be explained below. The return values of this function are substituted for the matched characters. The modified string is printed out with
.say()
.
$str.subst(/(.) \? (.)/, { newChar($0, $1) }, :g ).say;
The spec just says we need to replace ?
with any lower-case letter except the ones around it.
I decided to make it a random letter just because. The newChar()
function makes it all happen.
It takes two parameters $left
and $right
which are the two characters adjoining the ?
.
sub newChar($left, $right) {
The replacement character is set to the left one so the following loop will run at least once. I
could have used $right
too.
my $replacement = $left;
While the replacement character is equal to the left or right one...
while $replacement eq $left || $replacement eq $right {
...we randomly select a lower-case letter using the .pick()
method.
$replacement = ('a' .. 'z').pick;
}
When we have one which is not $left
or $right
, the loop ends and we return a string
concatenating $left
, $replacement
and $right
which will be substituted into the result.
return "$left$replacement$right";
}
For the Perl version, we have to provide a replacement for .pick()
. My implementation looks
like this:
sub pick(@array) {
return $array[int(rand(@array))];
}
Other than that, the rest is pretty much a straight translation of the Raku code.
sub newChar($left, $right) {
my $replacement = $left;
while ($replacement eq $left || $replacement eq $right) {
$replacement = pick('a' .. 'z');
}
return "$left$replacement$right";
}
$_ = shift; s/(.) \? (.)/newChar($1, $2)/egx; say;
Challenge 2:
Good String
You are given a string made up of lower and upper case English letters only.
Write a script to return the good string of the given string. A string is called
good string
if it doesn’t have two adjacent same characters, one in upper case and other is lower case.
UPDATE [2025-07-01]
: Just to be explicit, you can only remove pair if they are same characters, one in lower case and other in upper case, order is not important.
Example 1
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"
We can do this task as a one-liner in both Raku and Perl.
I'll start with Perl. We get the input from the first command-line argument with shift()
. It
is assigned to $_
so we can shave off a few characters.
In a while
loop, we use s///
to look for instances
of a lower-case character followed by the same character but upper-case or vice-versa and remove it.
I feel my regular expression could be optimized but what I have works. Why a loop and not s///g
?
Because that would have failed for example 2.
When all such substitutions have been made, we have a good string so we print it with say()
.
$_ = shift; while ( s/([a-z])(??{uc $1}) | ([A-Z])(??{lc $2})// ) {} say
The Raku version is for once less compact. One thing which always trips me up is that if you have a capture group reference like $0
and you want to use it within the regular expression itself, you need to insert a code block
(even an empty one) behind the match. So annoying.
$_ = @*ARGS[0]; while s/( (<[a..z]>) {} $($0.uc) ) || ( (<[A..Z]>) {} $($0.lc) ) // {}; .say