04 July 2025 | Challenge 328 |
Replace and Remove
Task 1: Replace all ?
Submitted by: Mohammad Sajid Anwar
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"
Solution
Search for a question mark in the string and capture zero or one non question marks preceding and following the question mark. Create a hash with all English lower case letters and delete the captured ones. Then pick a random key as the substitution for the found question mark. Repeat until there is no question mark left.
use v5.30;
use warnings;
use experimental qw(signatures vlb);
sub rc {
(\my %c)->@{'a' .. 'z'} = ();
delete @c{@_};
scalar each %c;
}
sub replace_all ($str) {
1 while $str =~ s/(?<=([^?]?))\?(?=([^?]?))/rc($1, $2)/e;
$str;
}
See the full solution to task 1.
Task 2: Good String
Submitted by: Mohammad Sajid Anwar
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"
Solution
Preliminary Note
This task was not clear to me at first. Removing a complete upper/lower case pair doesn’t appear to be necessary, as removing one of the characters would be sufficient. The examples suggest removing pairs only, though.
As I didn’t want to exclude either of the these interpretations, I implemented both. When Mohammad clarified the task in the examples’ sense, I already had an implementation for both interpretations and for the sake of completeness I present the failed interpretation here, too.
Remove Pairs
The aim of this task is the removal of adjacent letter pairs in mixed upper/lower case. Though the resulting string will be “good”, I’d like to call this interpretation remove pairs.
We may match an uppercase letter followed by the the same letter in lower case or a lowercase letter followed by the same letter in upper case and then remove both until no more such pairs can be found.
This can be accomplished with a s///
operation in a while
loop.
As I’m not aware of an escape sequence that would flip the case of a letter,
this operation is performed within a
“postponed” regular subexpression.
There is no access to outer capture groups via \n
and so $n
has to be used instead.
use v5.20;
use warnings;
use experimental 'signatures';
sub remove_pairs ($str) {
1 while $str =~ s/(\p{Lu})(??{lc $1})|(\p{Ll})(??{uc $2})//;
$str;
}
Make Good
From the original task description I got the impression, the string should simply be made “good”. Unless explicitly forbidden, the removal of single letters results in a “good” string, too. Finding the minimal set of letters to be removed is a completely different task.
In example 1 the string 'WeEeekly'
can be made “good” by removing the letter 'E'
.
Generally, a substring consisting of a letter repeated one or more times ignoring case can be made “good” by removing all cased instances of this letter that appear fewer times.
Examples:
'Aaaa'
-> 'aaa'
, 'AAaA'
-> 'AAA'
etc.
Suppose the variable $str
consists of two or more a
’s or A
’s.
It can be made “good” with the expression:
2 * ($str =~ tr/a//) < length($str) ? $str =~ tr/a//dr : $str =~ tr/A//dr;
Earlier this year I wrote the module
String::Compile::Tr
that performs the functionality of the
tr///
operator, uses run time arguments and does not
eval
the content of its arguments.
The misinterpreted task turned out to be my first actual use case for this module.
Now the expressions
$str =~ tr/a//
and
$str =~ tr/a//dr
can be rewritten as
gentr('a')->($str)
and
gentr('a', '', 'dr')->($str)
.
As the operands of the tr///
operator now have become function arguments, they may be given at run time.
The substrings of case-insensitive repeated letters can be found with the regex /(\p{LC})\1+/i
as the first operand of an s///
operation.
The implementation then may look like:
use String::Compile::Tr;
sub make_good {
shift =~ s/(\p{LC})\1+/
trgen(2 * trgen(lc($1))->($&) < length($&) ? lc($1) : uc($1),
'', 'dr')->($&);
/iegr;
}
See the full solution to task 2.
If you have a question about this post or if you like to comment on it, feel free to open an issue in my github repository.