Upper Group
with Raku

by Arne Sommer

Upper Group with Raku

[332] Published 8. March 2025.

This is my response to The Weekly Challenge #311.

Challenge #311.1: Upper Lower

You are given a string consists of english letters only.

Write a script to convert lower case to upper and upper case to lower in the given string.

Example 1:
Input: $str = "pERl"
Output: "PerL"
Example 2:
Input: $str = "rakU"
Output: "RAKu"
Example 3:
Input: $str = "PyThOn"
Output: "pYtHoN"
File: upper-lower
#! /usr/bin/env raku

unit sub MAIN ($str where $str ~~ /^<[a..z A..Z]>+/);  # [1]

say $str.comb.map({ /<[a..z]>/ ?? .uc !! .lc }).join;  # [2]

[1] Ensure that we only get lower- and uppercase English letters. I have chosen to assume that this means the plain old ascii letters only, and not other things that may pop up in English dictionaries. See e.g. en.wiktionary.org/wiki/Category:English_terms_spelled_with_Œ.

[2] Split the word into a list of characters (with comb), then use map to apply uc on the lowercase letters (i.e. those matching the regex) to uppercase them and lc on the rest (i.e. the upercase letters) to lowercase them. Then we join the characters together to string, and print it.

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

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

Running it:

$ ./upper-lower pERl
PerL

$ ./upper-lower rakU
RAKu

$ ./upper-lower PyThOn
pYtHoN

Looking good.

This is basically a oneliner, so verbose mode did not make sense.

So let us try something different instead...

... Unicode support.

I have chosen the more catchy «Flip Case» name for this version of the program. The flipping case, so to speak, has been delegated to a «flc» function, making it easy to add it to a module of string functions - should anybody want to do so.

File: flip-case
#! /usr/bin/env raku

unit sub MAIN ($str where $str.chars > 0);  # [1]

sub flc ($str)                              # [2]
{
  return $str.comb.map({
    if    /<lower>/ { .uc }                 # [3]
    elsif /<upper>/ { .lc }                 # {3a]
    else { $_ }                             # [3b]
  }).join; 
}

say $str.&flc;                              # [4]

[1] At least one character. Anything Unicode goes.

[2] The function doing the job.

[3] Does the character have the unicode «lower case letter» property? If so, uppercase it. Does it have the «upper case letter» property? If so lowercase it [3a]. If none of the above, keep the character as is [3b].

[4] Apply the function with the handy .& «function-as-a-method» syntax.

See docs.raku.org/language/operators#methodop_.& for more information about the special procedure invocation syntax .&.

Running it with some non-English letters:

$ ./flip-case "This is funny!"
tHIS IS FUNNY!

$ ./flip-case "Jordbærsyltetøy"
jORDBÆRSYLTETØY

$ ./flip-case "JordbærsyltetØy"
jORDBÆRSYLTETøY

$ ./flip-case "Die Große Welt"
dIE gROSSE wELT

$ ./flip-case "dIE gROSSE wELT"
Die Grosse Welt

Note that the German (lowercase) letter «ß» is uppercased to «SS». This obviously does not round trip, as shown in the last example.

Challenge #311.2: Group Digit Sum

You are given a string, $str, made up of digits, and an integer, $int, which is less than the length of the given string.

Write a script to divide the given string into consecutive groups of size $int (plus one for leftovers if any). Then sum the digits of each group, and concatenate all group sums to create a new string. If the length of the new string is less than or equal to the given integer then return the new string, otherwise continue the process.

Example 1:
Input: $str = "111122333", $int = 3
Output: "359"

Step 1: "111", "122", "333" => "359"
Example 2:
Input: $str = "1222312", $int = 2
Output: "76"

Step 1: "12", "22", "31", "2" => "3442"
Step 2: "34", "42" => "76"
Example 3:
Input: $str = "100012121001", $int = 4
Output: "162"

Step 1: "1000", "1212", "1001" => "162"

This task can be divided in two:

  • Split the string into chunks of $int length
  • The other stuff

The first part is eminently suited for gather/take.

File: group-digit-sum
#! /usr/bin/env raku

unit sub MAIN ($str is copy where $str ~~ /^<[0..9]>+/,  # [1]
               Int $int where $int > 1                   # [2a]
                && $str.chars > $int,                    # [2]
	       :v($verbose));

while $str.chars > $int                                  # [3]
{
  my @parts = partition($str, $int);                     # [4]
  my @sums  = @parts>>.comb>>.sum;                       # [5]
  my $sum   = @sums.join;                                # [6]

  say ": String: $str Parts: { @parts.join(", ") } -> { @sums.join(", ") }
    -> $sum" if $verbose;

  $str      = $sum;                                      # [7]
}

say $str;                                                # [8]

sub partition ($str is copy, $length)                    # [9]
{
  my @parts = gather                                     # [10]
  {
    while $str.chars                                     # [11]
    {
      my $size = min($length, $str.chars);               # [12]
      take $str.substr(0, $size);                        # [13]
      $str = $str.substr($size);                         # [14]
    }
  } 

  return @parts;                                         # [15]
}

[1] A number, consisting of at least one digit. The is copy trait is there so that we can edit the local copy (which we do in [7]).

See docs.raku.org/type/Parameter#method_copy for more information about is copy.

[2] The integer, which must be lower than the length of the number in [1]. I have added the «larger than 1» rule [2a]; see the end of the article for why.

[3] As long as the exit condition is not met.

[4] Get the parts of the requested length.

[5] Convert each part of the array into a sum, giving an array of sums.

[6] Join the sums together.

[7] Reset the input string, ready for further iterations of the loop, if required.

[8] Print the result.

[9] The procedure doing the heavy lifting.

[10] We use gather to collect the return array.

See my Raku Gather, I Take article or docs.raku.org/language/control#gather/take for more information about gather/take.

[11] As long as we have unfinished business.

[12] Get the number of characters to grab. The user specified length, or whatever is left if we have not got that many.

[13] Return (to the array) the requested number of digits (with substr).

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

[14] Remove the returned part of the string.

[15] Return the result.

Running it:

$ ./group-digit-sum 111122333 3
359

$ ./group-digit-sum 1222312 2
76

$ ./group-digit-sum 100012121001 4
162

Looking good.

With verbose mode:

$ ./group-digit-sum -v 111122333 3
: String: 111122333 Parts: 111, 122, 333 -> 3, 5, 9 -> 359
359

$ ./group-digit-sum -v 1222312 2
: String: 1222312 Parts: 12, 22, 31, 2 -> 3, 4, 4, 2 -> 3442
: String: 3442 Parts: 34, 42 -> 7, 6 -> 76
76

$ ./group-digit-sum -v 100012121001 4
: String: 100012121001 Parts: 1000, 1212, 1001 -> 1, 6, 2 -> 162
162

A size of «1» is a bad idea, as it will give an eternal loop:

$ ./group-digit-sum -v 1001 1
: String: 1001 Parts: 1, 0, 0, 1 -> 1, 0, 0, 1 -> 1001
: String: 1001 Parts: 1, 0, 0, 1 -> 1, 0, 0, 1 -> 1001
: String: 1001 Parts: 1, 0, 0, 1 -> 1, 0, 0, 1 -> 1001
: String: 1001 Parts: 1, 0, 0, 1 -> 1, 0, 0, 1 -> 1001
^C

As will a size of «0». An eternal loop, but without any verbose output:

$ ./group-digit-sum -v 1001 0
^C

So I added the rule enforcing values greater than 1 (in [2a]).

And that's it.