All Good
with Raku

by Arne Sommer

All Good with Raku

[350] Published 6. July 2025.

This is my response to The Weekly Challenge #328.

Challenge #328.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"
File: replace-all-qm
#! /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

Challenge #328.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"
File: good-string
#! /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.