This is my response to The Weekly Challenge #342.
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].
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
$str
, containing 0
and 1
only.
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
#! /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.