A Case of Vowels
with Raku

by Arne Sommer

A Case of Vowels with Raku

[419] Published 11. October 2026.

This is my response to The Weekly Challenge #394.

#394.1 Alternate Case You are given a string containing an equal number of uppercase and lowercase English letters.

Write a script to the minimum number of adjacent character swaps needed to turn the given string into an alternate case string.

Example 1:
Input: $str = "aAbB"
Output: 0
Example 2:
Input: $str = "AAbb"
Output: 1

Swap 1: "AbAb"
Example 3:
Input: $str = "AAAbbb"
Output: 3

Swap 1: "AAbAbb"
Swap 2: "AbAAbb"
Swap 3: "AbAbAb"
Example 4:
Input: $str = "aABb"
Output: 1

Swap 1: "aAbB"
Example 5:
Input: $str = "bBBAaa"
Output: 2

Swap 1: "BbBAaa"
Swap 2: "BbBaAa"
File: alternate-case
#! /usr/bin/env raku

unit sub MAIN ($str where ($str ~~ /^ <[a..zA..Z]>+ $/)
                 && ($str.comb.grep(/<[a..z]>/).elems * 2
                    == $str.chars),
               :v(:$verbose));

sub is-lower(Str $c) { $c.lc eq $c }

my $best-count = Inf;
my @best-steps;
my $best-start;

for 0, 1 -> $start
{
  my @chars = $str.comb;
  my $count = 0;
  my @steps;

  for ^@chars.elems -> $i
  {
    my $want-lower = ($i % 2) == $start;

    next if is-lower(@chars[$i]) == $want-lower;

    my $j = $i + 1;

    $j++ while is-lower(@chars[$j]) != $want-lower;

    while $j > $i
    {
      my $tmp  = @chars[$j - 1];
      @chars[$j - 1] = @chars[$j];
      @chars[$j] = $tmp;
      $j--;
      $count++;
      @steps.push: @chars.join;
    }
  }

  if $count < $best-count
  {
    $best-count = $count;
    @best-steps = @steps;
    $best-start = $start;
  }
}

if $verbose
{
  my @pattern = map { ($_ % 2) == $best-start ?? "L" !! "U" },
                 ^$str.chars;

  say ": Target: " ~ @pattern.join(" ");

  for 1 .. @best-steps.elems -> $n
  {
    say ": Swap $n: { @best-steps[$n - 1] }";
  }
}

say $best-count;

[3] English characters only, and exactly half of them lowercase.

[8] Helper procedure telling us if the input is lowercase (with lc).

See docs.raku.org/routine/lc for more information about lc.

[10] The lowest number of swaps for the best one.

[11] The actual swaps.

[12] The best start (see [14]).

[14] We can start the result with either a lowercase or uppcase letter, so have to check both cases (pun intended). 0=lowercase (index 0,2,4,..) and 1=uppercase (index 1,3,5,..).

[16] The number of characters.

[17] The number of swaps.

[18] The actual swaps.

[20] Iterate over all the character indices.

[22] Do we want a lowercase letter here?

[24] Do nothing if we have what we want.

[26] Start looking at the next index.

[27] Look for the first character of the correct case, saving the index.

[30] As long as we have not swaped that character back to where we need it.

[32-34] Swap that character one position to the left.

[36] Count the swap.

[37] The swap itself, for verbose mode.

[41] Do we have a winner? Note that the first iteration (start with lowercase) will always give a yes. The second one (start with uppercase) may or may not.

[43-45] If so, set the number of swaps, the swaps themselves, and the lc/uc starting status.

[62] Print the result.

Running it:

$ ./alternate-case aAbB
0

$ ./alternate-case AAbb
1

$ ./alternate-case AAAbbb
3

$ ./alternate-case aABb
1

$ ./alternate-case bBBAaa
2

Looking good.

With verbose mode:

$ ./alternate-case -v aAbB
: Target: L U L U
0

$ ./alternate-case -v AAbb
: Target: U L U L
: Swap 1: AbAb
1

$ ./alternate-case -v AAAbbb
: Target: U L U L U L
: Swap 1: AAbAbb
: Swap 2: AbAAbb
: Swap 3: AbAbAb
3

$ ./alternate-case -v aABb
: Target: L U L U
: Swap 1: aAbB
1

$ ./alternate-case -v bBBAaa
: Target: U L U L U L
: Swap 1: BbBAaa
: Swap 2: BbBaAa
2

#394.2 Alternating Vowels Consonants You are given three strings containing English alphabetic characters.

Find all the longest contiguous substrings common to all three strings that strictly alternate between vowels and consonants.

Example 1:
Input: @str = ("relocate", "delocate", "allocate")
Output: ("locate")
Example 2:
Input: @str = ("apple", "banana", "cherry")
Output: ()
Example 3:
Input: @str = ("navigate", "cavity", "gravity")
Output: ("avi")
Example 4:
Input: @str = ("pedalgia", "pedalboard", "pedantic")
Output: ("peda")
Example 5:
Input: @strings = ("schoolmaster", "schoolhouse", "schooling")
Output: ("ho", "ol")
File: alternating-vowels-consonants
#! /usr/bin/env raku

subset English where * ~~  /^ <[a..zA..Z]>+ $/;

unit sub MAIN (English $s1, English $s2, English $s3,
               :v(:$verbose));

sub is-vowel(Str $c) { $c.lc ∈ <a e i o u> }

sub alternating-runs(Str $s)
{
  my @runs;
  my $n = $s.chars;

  my $i = 0;
  while $i < $n
  {
    my $j = $i;
    $j++ while $j + 1 < $n
      && is-vowel($s.substr($j, 1))
           != is-vowel($s.substr($j + 1, 1));

    @runs.push: $s.substr($i, $j - $i + 1);
    $i = $j + 1;
  }

  @runs;
}

sub alternating-substrings(Str $s)
{
  my @subs;

  for alternating-runs($s) -> $run
  {
    for 0 ..^ $run.chars -> $a
    {
      for 1 .. $run.chars - $a -> $len
      {
        @subs.push: $run.substr($a, $len);
      }
    }
  }

  @subs;
}

my @sets   = map { alternating-substrings($_).Set },
               $s1, $s2, $s3;

my @common = (@sets[0] ∩ @sets[1] ∩ @sets[2]).keys;
my $max    = @common.elems ?? @common.map(*.chars).max !! 0;
my @result = @common.grep(*.chars == $max).sort;

if $verbose
{
  for $s1, $s2, $s3 -> $s
  {
    say ": $s -> " ~ alternating-runs($s).join(", ");
  }
  say ": Common: " ~ @common.sort.join(", ") if @common.elems;
  say ": Longest: $max" if $max;
}

say "({ @result.map( '"' ~ * ~ '"' ).join(", ") })";

[3] A custom type with subset for an English "word".

See docs.raku.org/language/typesystem#subset for more information about subset.

[5] Apply the custom type on the three input strings.

[8] Helper procedure telling us if the character is a vowel. I have excluded "y" here.

[10] The procedure finding the maximal alternating runs in a string.

[12] The runs we find.

[13] The number of characters.

[16] While there are characters left.

[18] The end of the current run.

[19-21] Extend the run as long as the next two characters have different vowel/consonant status (is-vowel).

[23] Save the run.

[24] Continue right after the run.

[27] Return the runs.

[30] The procedure finding all the alternating substrings in a string. (Every substring of an alternating run is alternating itself.)

[32] The substrings we find.

[34] For each maximal run.

[36] For each starting position in the run.

[38] For each possible length from that position.

[40] Save the substring.

[45] Return the substrings.

Running it:

$ ./alternating-vowels-consonants relocate delocate allocate
("locate")

$ ./alternating-vowels-consonants apple banana cherry
()

$ ./alternating-vowels-consonants navigate cavity gravity
("avi")

$ ./alternating-vowels-consonants pedalgia pedalboard pedantic
("peda")

$ ./alternating-vowels-consonants schoolmaster schoolhouse schooling
("ho", "ol")

Looking good.

With verbose mode:

$ ./alternating-vowels-consonants -v relocate delocate allocate
: relocate -> relocate
: delocate -> delocate
: allocate -> al, locate
: Common: a, at, ate, c, ca, cat, cate, e, l, lo, loc, loca, locat, locate, \
    o, oc, oca, ocat, ocate, t, te
: Longest: 6
("locate")

$ ./alternating-vowels-consonants -v apple banana cherry
: apple -> ap, p, le
: banana -> banana
: cherry -> c, her, r, y
()

$ ./alternating-vowels-consonants -v navigate cavity gravity
: navigate -> navigate
: cavity -> cavit, y
: gravity -> g, ravit, y
: Common: a, av, avi, i, t, v, vi
: Longest: 3
("avi")

$ ./alternating-vowels-consonants -v pedalgia pedalboard pedantic
: pedalgia -> pedal, gi, a
: pedalboard -> pedal, bo, ar, d
: pedantic -> pedan, tic
: Common: a, d, da, e, ed, eda, p, pe, ped, peda
: Longest: 4
("peda")

$ ./alternating-vowels-consonants -v schoolmaster schoolhouse schooling
: schoolmaster -> s, c, ho, ol, mas, ter
: schoolhouse -> s, c, ho, ol, ho, use
: schooling -> s, c, ho, olin, g
: Common: c, h, ho, l, o, ol, s
: Longest: 2
("ho", "ol")

And that's it.