Sort of Reverse
with Raku

by Arne Sommer

Sort of Reverse with Raku

[298] Published 20. July 2024.

This is my response to The Weekly Challenge #278.

Challenge #278.1: Sort String

You are given a shuffle string, $str.

Write a script to return the sorted string.

A string is shuffled by appending word position to each word.

Example 1:
Input: $str = "and2 Raku3 cousins5 Perl1 are4"
Output: "Perl and Raku are cousins"
Example 2:
Input: $str = "guest6 Python1 most4 the3 popular5 is2 language7"
Output: "Python is the most popular guest language"
Example 3:
Input: $str = "Challenge3 The1 Weekly2"
Output: "The Weekly Challenge"
File: sort-string
#! /usr/bin/env raku

unit sub MAIN ($str, :v(:$verbose));    # [1]

my @new;                                # [2]

for $str.words -> $shuffle              # [3]
{
  $shuffle ~~ /(.*?)(\d+)$/;            # [4]
  my $word  = $0;                       # [4a]
  my $index = $1;                       # [4b]

  say ":Word '$word' at index $index \
    { @new[$index].defined ?? 'REDEFINED' !! ''}"
      if $verbose;

  @new[$index] = $word;                 # [5]
}

say @new[1 .. *].join(" ");             # [6]
#say @new.grep( *.defined ).join(" ");  # [7]

[1] A string, without any constraints.

[2] The new (sorted) list of words will end up here.

[3] Iterate over the words in the sentence.

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

[4] Grab anything before one or more trailing digits (note the non-greedy pattern .*? so that all the trailing digits are gobbled up by the greedy digit pattern), and save it [4a]. Then save that number [4b].

[5] Insert the word at the given position (index) in the new array.

[6] Print the content of the array, excluding the undefined value with index zero, with spaces between them.

[7] I'll get back to this later.

Running it:

$ ./sort-string "and2 Raku3 cousins5 Perl1 are4"
Perl and Raku are cousins

$ ./sort-string "guest6 Python1 most4 the3 popular5 is2 language7"
Python is the most popular guest language

$ ./sort-string "Challenge3 The1 Weekly2"
The Weekly Challenge

Looking good.

With verbose mode:

$ ./sort-string -v "and2 Raku3 cousins5 Perl1 are4"
:Word 'and' at index 2 
:Word 'Raku' at index 3 
:Word 'cousins' at index 5 
:Word 'Perl' at index 1 
:Word 'are' at index 4 
Perl and Raku are cousins

$ ./sort-string  -v "guest6 Python1 most4 the3 popular5 is2 language7"
:Word 'guest' at index 6 
:Word 'Python' at index 1 
:Word 'most' at index 4 
:Word 'the' at index 3 
:Word 'popular' at index 5 
:Word 'is' at index 2 
:Word 'language' at index 7 
Python is the most popular guest language

$ ./sort-string -v "Challenge3 The1 Weekly2"
:Word 'Challenge' at index 3 
:Word 'The' at index 1 
:Word 'Weekly' at index 2 
The Weekly Challenge

Let us have a go at garbage in:

$ ./sort-string -v "Challenge4 The1 Weekly2"
:Word 'Challenge' at index 4 
:Word 'The' at index 1 
:Word 'Weekly' at index 2 
The Weekly

That was perhaps not obvious. The [1 .. *] array slice stops at the first undefined value, i.e. @new[3].

The alternate version in [7] will ignore (skip) any undefined entries, and print all the words. You may prefer [6], as in «garbage in, constipation out».

Reuse of word positions is not prevented, but verbose mode will notify you:

$ ./sort-string -v "and2 Raku3 cousins5 Perl1 are4 Ronny3"
:Word 'and' at index 2 
:Word 'Raku' at index 3 
:Word 'cousins' at index 5 
:Word 'Perl' at index 1 
:Word 'are' at index 4 
:Word 'Ronny' at index 3 REDEFINED
Perl and Ronny are cousins

Challenge #278.2: Reverse Word

You are given a word, $word and a character, $char.

Write a script to replace the substring up to and including $char with its characters sorted alphabetically. If the $char doesn't exist then DON'T do anything.

Example 1:
Input: $str = "challenge", $char = "e"
Ouput: "acehllnge"
Example 2:
Input: $str = "programming", $char = "a"
Ouput: "agoprrmming"
Example 3:
Input: $str = "champion", $char = "b"
Ouput: "champion"

Neither the challenge nor the examples say what to do if the character uccurs more than once. I have decided to stop after the very first one.

File: reverse-word
#! /usr/bin/env raku

unit sub MAIN ($word is copy, $char where $char.chars == 1);    # [1]

$word = $0.comb.sort.join ~ $1 if $word ~~ /(.*? $char) (.*)/;  # [2]

say $word;                                                      # [3]

[1] The word, followed by a single character. Note the is copy trait, so that we can change the variable in [2].

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

[2] First the regexp (the second half): Here we look for the specified character, in the non-greedy pattern .*?. This will match up to and including the first occurence of the character. The greedy pattern .* will gobble up the rest of the string. The if executes the first part (the assignment) if we did get a match. The assignment is the first part sorted alphabetically (or rather by Unicode codepoint value) by splitting the string into separate characters (comb), sorting them (sort) and gluing them together again join). Then we slap on the second part.

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

[3] Print the result.

Running it:

$ ./reverse-word challenge e
acehllnge

$ ./reverse-word programming a
agoprrmming

$ ./reverse-word champion b
champion
./

Looking good.

Some examples where the character occurs more than once:

$ ./reverse-word programming r
programming

$ ./reverse-word language a
alnguage

$ ./reverse-word challenge l
achllenge

And that's it.