Stringed Array
with Raku

by Arne Sommer

Stringed Array with Raku

[344] Published 21. May 2025.

This is my response to The Weekly Challenge #322.

Challenge #322.1: String Format

You are given a string and a positive integer.

Write a script to format the string, removing any dashes, in groups of size given by the integer. The first group can be smaller than the integer but should have at least one character. Groups should be separated by dashes.

Example 1:
Input: $str = "ABC-D-E-F", $i = 3
Output: "ABC-DEF"
Example 2:
Input: $str = "A-BC-D-E", $i = 2
Output: "A-BC-DE"
Example 3:
Input: $str = "-A-B-CD-E", $i = 4
Output: "A-BCDE"

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

unit sub MAIN ($str is copy where $str.chars > 0,  # [1]
               Int $i where $i > 0,                # [2]
               :v(:$verbose));

$str ~~ s:g/"-"//;                                 # [3]

say ": Dash free: $str" if $verbose;

my @new;                                           # [4]

my $size = $str.chars % $i;                        # [5]

if $size                                           # [6]
{
  my $add = $str.substr(0, $size);                 # [7]
  $str = $str.substr($size);                       # [8]

  @new.push: $add;                                 # [9]
  say ": Short part: $add" if $verbose;
}

while $str.chars                                   # [10]
{
  my $add = $str.substr(0, $i);                    # [10a]
  $str = $str.substr($i);                          # [10b]

  @new.push: $add;                                 # [10c3]
  say ": Full size part: $add" if $verbose;
}

say @new.join("-");                                # [11]

[1] A string with at least one character. Note the is copy trait, so that we can change the value later on (in [8] and {12]).

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

[2] Ensure a positive integer.

[3] Get rid of any dashes with the in-place substitution operator s///.

See docs.raku.org/language/operators#s///_in-place_substitution for more information about s///.

[4] The partial strings, that we are to glue together with dashes later on, will end up here.

[5] How many characters do we have left when we remove $int characters as many times as possible?

[6] If any

[7] • Get (copy) that number of character from the start of the string.

[8] • And remove them from the string itself.

[9] • Add the partial string toi the result.

[10] Now the same (as [7,7,9]), in a loop, as long as we have more characters to do. But this time, we take $int characters at a time.

[11] Join the partial strings up with dashes, and print the result.

Running it:

$ ./string-format "A-BC-D-E" 2
A-BC-DE

$ ./string-format "A-BCDE" 4
A-BCDE

$ ./string-format "A-BCDEF" 4
AB-CDEF

Looking good.

With verbose mode:

$ ./string-format -v "A-BC-D-E" 2
: Dash free: ABCDE
: Short part: A
: Full size part: BC
: Full size part: DE
A-BC-DE

$  ./string-format -v "A-BCDE" 4
: Dash free: ABCDE
: Short part: A
: Full size part: BCDE
A-BCDE

$ ./string-format -v "A-BCDEF" 4
: Dash free: ABCDEF
: Short part: AB
: Full size part: CDEF
AB-CDEF

The program works, but is a bit cumbersome. We can make it much neater if we start at the end (pun intended), gobbling up $int number of characters at a time, and finally adding the remainder - if any.

File: string-format-reverse
#! /usr/bin/env raku

unit sub MAIN ($str is copy where $str.chars > 0,
               Int $i where $i > 0,
               :v(:$verbose));

$str ~~ s:g/"-"//;

say ": Dash free: $str" if $verbose;

my @new;

while $str.chars >= $i                        # [1]
{
  my $add = $str.substr($str.chars - $i);     # [2]
  $str = $str.substr(0, $str.chars - $i);     # [3]

  @new.unshift: $add;                         # [4]
  say ": Full size part: $add" if $verbose;
}

if $str.chars                                 # [5]
{
  @new.unshift: $str;                         # [5a]
  say ": Short part: $str" if $verbose;
}

say @new.join("-");                           # [6]

[1] As long as we $int or more characters left,

[2] • Get $int number of characters from the end of the string.

[3] • Remove those characters from the string.

[4] • Add the substring to the front (or left hand side) of the array, with unshift.

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

[5] The final part is either non-existent or has fever character than $int. Add that string, also to the front of the array, if it exists.

[6] Note that the array order is ok, as we used unshift instead of push. So we can just join them without further ado. Or rather with dashes. (Much ado about dashes. Now that could have been the title for this artoicle. Alas, Yorick...)

Running it gives the expected result, but verbose mode has the substring order reversed.

$ ./string-format-reverse -v "A-BC-D-E" 2
: Dash free: ABCDE
: Full size part: DE
: Full size part: BC
: Short part: A
A-BC-DE

$ ./string-format-reverse -v "A-BCDE" 4
: Dash free: ABCDE
: Full size part: BCDE
: Short part: A
A-BCDE

$ ./string-format-reverse -v "A-BCDEF" 4
: Dash free: ABCDEF
: Full size part: CDEF
: Short part: AB
AB-CDEF

Challenge #322.2: Rank Array

You are given an array of integers.

Write a script to return an array of the ranks of each element: the lowest value has rank 1, next lowest rank 2, etc. If two elements are the same then they share the same rank.

Example 1:
Input: @ints = (55, 22, 44, 33)
Output: (4, 1, 3, 2)
Example 2:
Input: @ints = (10, 10, 10)
Output: (1, 1, 1)
Example 3:
Input: @ints = (5, 1, 1, 4, 3)
Output: (4, 1, 1, 3, 2)
File: rank-array
#! /usr/bin/env raku

unit sub MAIN (*@ints where @ints.elems > 1  # [1]
                  && all(@ints) ~~ Int,      # [1a]
               :v(:$verbose));

my @sorted = @ints.sort.squish;              # [2]

my %rank;                                    # [3]
my $rank = 0;                                # [5a]

for @sorted -> $val                          # [4]
{
  $rank++;                                   # [5]

  %rank{$val} = $rank;                       # [6]

  say ": Int $val has rank $rank" if $verbose;
}

my @rank = @ints.map({ %rank{$_} });         # [7]

say "({ @rank.join(", ") })";                # [8]

[1] A slurpy array with at least 2 elements, all of which must be integers.

[2] Get a sorted version of the input, without duplicates. We can use the more efficient squish instead of unqiue as the values are sorted.

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

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

[3] The rank mpping, from value to rank, will end up here.

[4] Iterate over the sorted (and unique) values.

[5] Increase the rank position, initialised in [5a].

[6] Note the rank for the current value.

[8] Use map to swap the values in the original array with the rank value for each value.

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

[8] Pretty print the result.

Running it:

$ ./rank-array 55 22 44 33
(4, 1, 3, 2)

$ ./rank-array 10 10 10
(1, 1, 1)

$ ./rank-array 5 1 1 4 3
(4, 1, 1, 3, 2)

Looking good.

With verbose mode:

$ ./rank-array -v 55 22 44 33
: Int 22 has rank 1
: Int 33 has rank 2
: Int 44 has rank 3
: Int 55 has rank 4
(4, 1, 3, 2)

$ ./rank-array -v 10 10 10
: Int 10 has rank 1
(1, 1, 1)

$ ./rank-array -v 5 1 1 4 3
: Int 1 has rank 1
: Int 3 has rank 2
: Int 4 has rank 3
: Int 5 has rank 4
(4, 1, 1, 3, 2)

And that's it.