Maximum Balance
with Raku

by Arne Sommer

Maximum Balance with Raku

[366] Published 11. October 2025.

This is my response to The Weekly Challenge #342.

Challenge #342.1: Balance String

You are given a string made up of lowercase English letters and digits only.

Write a script to format the given string where no letter is followed by another letter and no digit is followed by another digit. If there are multiple valid rearrangements, always return the lexicographically smallest one. Return empty string if it is impossible to format the string.

Example 1:
Input: $str = "a0b1c2"
Output: "0a1b2c"
Example 2:
Input: $str = "abc12"
Output: "a1b2c"
Example 3:
Input: $str = "0a2b1c3"
Output: "0a1b2c3"
Example 4:
Input: $str = "1a23"
Output: ""
Example 5:
Input: $str = "ab123"
Output: "1a2b3"

It follows from the third example that the digit and letter parts should be internally lexicographically arranged (i.e. sorted).

File: balance-string
#! /usr/bin/env raku

unit sub MAIN ($str where $str ~~ /^ <[0..9 a..z]>+ $/,    # [1]
               :v(:$verbose));

my @letters = $str.comb.grep( * eq any("a" .. "z") ).sort; # [2]
my @digits  = $str.comb.grep( * eq any("0" .. "9") ).sort; # [3]

my $letters = @letters.elems;                              # [2a]
my $digits  = @digits.elems;                               # [3a]

if $verbose
{
  say ": $letters letters: { @letters.join(", ") } (sorted)";
  say ": $digits digits: { @digits.join(", ") } (sorted)";
}

if $digits == $letters || $digits == $letters +1           # [4]
{
  say roundrobin(@digits, @letters).flat.join;             # [6]
}
elsif $digits == $letters -1                               # [5]
{
  say roundrobin(@letters, @digits).flat.join;             # [6]
}
else                                                       # [7]
{
  say "";                                                  # [7a]
}

[1] A string containing english lowercase letters and digits only, with at least one character.

[2] Get the letters, with grep, and the number of them [2a].

[3] Get the digits, and the number of them [3a].

[4] The digits are lexicographically first, so start with them if there are equally many of each type (where we would otherwise have a choice), and if there is one more digit than letters.

[5] Start with the letters if there is one more of them than digits.

[6] Use roundrobin to merge the two lists. The result is a list of pairs, so we slap on a flat to flatten the thing, and a join to get a string ready for printing.

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

Note that the infix zip operator Z cannot be used if the size of the two to-be-zippered lists have different sizes. We could have rewritten the test in [6] as two tests, and used Z in the == case, but that would just be showing off. Showing off a longer program, actually. So not actually showing off... I'd better zip it now.

[7] The size difference is greater than 2, and we report failure [7a].

[3+4] A Case for Unzip

Raku does not have an unzip operator (possibly called «unZ» or «unrobin»), but this is an example of where it would have been useful. The current code iterates twice over the input, and does the check on each value in both iterations. That is not very elegant, though...

We could have done it like this:

my @letters;
my @digits;

$str.comb.map({ $_ eq any("0" .. "9")
  ?? @digits.push($_)
  !! @letters.push($_)
});
But with unzip, perhaps something like this:
my (@letters, @digits) <== $str.comb.unzip({ $_ eq any("0" .. "9"), * });
The comma is important, and we could have dropped the whatever part at the end. The default would be to put the non-matched parts in the last array, as we have one more array than patterns.
my (@lc, @uc, @digits, @whatever) <== str.comb.unzip( {
  $_ eq any("a" .. "z"),
  $_ eq any("A" .. "Z"),
  $_ eq any("0" .. "9")
});

Feel free to ignore this suggestion...

Running it:

$ ./balance-string a0b1c2
0a1b2c

$ ./balance-string abc12
a1b2c

$ ./balance-string 0a2b1c3
0a1b2c3

$ ./balance-string 1a23


$ ./balance-string ab123
1a2b3

Looking good.

With verbose mode:

$ ./balance-string -v a0b1c2
: 3 letters: a, b, c (sorted)
: 3 digits: 0, 1, 2 (sorted)
0a1b2c

$ ./balance-string -v abc12
: 3 letters: a, b, c (sorted)
: 2 digits: 1, 2 (sorted)
a1b2c

$ ./balance-string -v 0a2b1c3
: 3 letters: a, b, c (sorted)
: 4 digits: 0, 1, 2, 3 (sorted)
0a1b2c3

$ ./balance-string -v 1a23
: 1 letters: a (sorted)
: 3 digits: 1, 2, 3 (sorted)


$ ./balance-string -v ab123
: 2 letters: a, b (sorted)
: 3 digits: 1, 2, 3 (sorted)
1a2b3

Challenge #342.2: Max Score

You are given a string, $str, containing 0 and 1 only.

Write a script to return the max score after splitting the string into two non-empty substrings. The score after splitting a string is the number of zeros in the left substring plus the number of ones in the right substring.

Example 1:
Input: $str = "0011"
Output: 4

1: left = "0", right = "011" => 1 + 2 => 3
2: left = "00", right = "11" => 2 + 2 => 4
3: left = "001", right = "1" => 2 + 1 => 3
Example 2:
Input: $str = "0000"
Output: 3

1: left = "0", right = "000" => 1 + 0 => 1
2: left = "00", right = "00" => 2 + 0 => 2
3: left = "000", right = "0" => 3 + 0 => 3
Example 3:
Input: $str = "1111"
Output: 3

1: left = "1", right = "111" => 0 + 3 => 3
2: left = "11", right = "11" => 0 + 2 => 2
3: left = "111", right = "1" => 0 + 1 => 1
Example 4:
Input: $str = "0101"
Output: 3

1: left = "0", right = "101" => 1 + 2 => 3
2: left = "01", right = "01" => 1 + 1 => 2
3: left = "010", right = "1" => 2 + 1 => 3
Example 5:
Input: $str = "011101"
Output: 5

1: left = "0", right = "11101" => 1 + 4 => 5
2: left = "01", right = "1101" => 1 + 3 => 4
3: left = "011", right = "101" => 1 + 2 => 3
4: left = "0111", right = "01" => 1 + 1 => 2
5: left = "01110", right = "1" => 2 + 1 => 3
File: max-score
#! /usr/bin/env raku

unit sub MAIN ($str where $str ~~ /^ <[01]> ** 2..* $/,            # [1]
               :v(:$verbose));

my $max = -Inf;                                                    # [2]

for 1 .. $str.chars -1 -> $i                                       # [3]
{
  my $left        = $str.substr(0, $i);                            # [4]
  my $right       = $str.substr($i);                               # [5]
  my $left-score  = $left.comb.grep( * eq 0 ).elems;               # [6]
  my $right-score = $right.comb.sum;                               # [7]
  my $score       = $left-score + $right-score;                    # [8]

  say ": Index:$i: $left - $right | $left-score + $right-score = \
    $score { $score > $max ?? "max" !! ""}" if $verbose;

  $max = $score if $score > $max;                                  # [9]
}

say $max;                                                          # [10]

[1] A string containing zeroes and ones only, with at least two characters.

[2] The maximum number, starting off as low as possible.

[3] Iterate over the breaking point (as in how mnany characters to put in the left part).

[4] Get the left substring,

[5] and the right one.

[6] Count the number of zeroes, by grepping them out and counting the number of elements.

[7] Counting ones is much easier. Just add all the digits together. Adding up a bunch of zeroes does not affect the result.

[8] Get the current score.

[9] Do we have a new max score? If so we save it.

[10] Print the max score.

Running it:

$ ./max-score 0011
4

$ ./max-score 0000
3

$ ./max-score 1111
3

$ ./max-score 0101
3

$ ./max-score 011101
5

Looking good.

With verbose mode:

$ ./max-score -v 0011
: Index:1: 0 - 011 | 1 + 2 = 3 max
: Index:2: 00 - 11 | 2 + 2 = 4 max
: Index:3: 001 - 1 | 2 + 1 = 3 
4

$ ./max-score -v 0000
: Index:1: 0 - 000 | 1 + 0 = 1 max
: Index:2: 00 - 00 | 2 + 0 = 2 max
: Index:3: 000 - 0 | 3 + 0 = 3 max
3

$ ./max-score -v 1111
: Index:1: 1 - 111 | 0 + 3 = 3 max
: Index:2: 11 - 11 | 0 + 2 = 2 
: Index:3: 111 - 1 | 0 + 1 = 1 
3

$ ./max-score -v 0101
: Index:1: 0 - 101 | 1 + 2 = 3 max
: Index:2: 01 - 01 | 1 + 1 = 2 
: Index:3: 010 - 1 | 2 + 1 = 3 
3

$ ./max-score -v 011101
: Index:1: 0 - 11101 | 1 + 4 = 5 max
: Index:2: 01 - 1101 | 1 + 3 = 4 
: Index:3: 011 - 101 | 1 + 2 = 3 
: Index:4: 0111 - 01 | 1 + 1 = 2 
: Index:5: 01110 - 1 | 2 + 1 = 3 
5

And that's it.