Sort of Again
with Raku

by Arne Sommer

Sort of Again with Raku

[299] Published 26. July 2024.

This is my response to The Weekly Challenge #279.

Challenge #279.1: Sort Letters

You are given two arrays, @letters and @weights.

Write a script to sort the given array @letters based on the @weights.

Example 1:
Input: @letters = ('R', 'E', 'P', 'L')
       @weights = (3, 2, 1, 4)
Output: PERL
Example 2:
Input: @letters = ('A', 'U', 'R', 'K')
       @weights = (2, 4, 1, 3)
Output: RAKU
Example 3:
Input: @letters = ('O', 'H', 'Y', 'N', 'P', 'T')
       @weights = (5, 4, 2, 6, 1, 3)
Output: PYTHON

File: sort-letters
#! /usr/bin/env raku

unit sub MAIN ($letters where $letters.chars > 0,              # [1]
               *@weight where @weight.elems eq $letters.chars  # [2]
                  && all(@weight) ~~ UInt);                    # [2a]

say ($letters.comb Z @weight)                                  # [3]
      .sort({ $^a.[1] <=> $^b.[1] })                           # [4]
      .map( *.[0] )                                            # [5]
      .join;                                                   # [6]

[1] Ensure at least one letter, all of them in a single string.

[2] A slurpy array of weights, with one weight for each letter, and all of them of the UInt (Unsigned Int) type [2a].

See docs.raku.org/type/UInt for more information about the UInt type.

[3] Combine each letter (as individual letters, courtsey of comb on the string) with its weight with the Z zip operator. The result is a list containing a sublist for each letter/weight combination.

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

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

[4] Sort the list, on the second element (with index 1) of each sublist - the weight.

[5] Then we take the sorted list and pick the first element (with index 0) of each sublist - the letter - with map. The result is a sorted list of letters.

[6] Join the list of letters into a single string, and print it (in [3]).

Running it:

$ ./sort-letters REPL 3 2 1 4
PERL

$ ./sort-letters AURK 2 4 1 3
RAKU

$ ./sort-letters OHYNPT 5 4 2 6 1 3
PYTHON

Looking good.

Note that a weight of zero is perfectly ok, as is gaps:

$ ./sort-letters AURK 2 4 0 3
RAKU

$ ./sort-letters AURK 200 400 100 300
RAKU

Duplicate weights are sorted in the original order, perhaps surprisingly:

$ ./sort-letters AURK 1 1 1 1
AURK

The order is the same if you run the program several times, at least on Rakudo v2024.02.

Challenge #279.2: Split String

You are given a string, $str.

Write a script to split the given string into two containing exactly same number of vowels and return true if you can otherwise false.

Example 1:
Input: $str = "perl"
Ouput: false
Example 2:
Input: $str = "book"
Ouput: true

Two possible strings "bo" and "ok" containing exactly one vowel each.
Example 3:
Input: $str = "good morning"
Ouput: true

Two possible strings "good " and "morning" containing two vowels each or "good m" and "orning" containing two vowels each.

We do not actually have to split the string at all, just count the vowels. If the number is even, report success, and report failure if it is odd.

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

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

say $str.comb.grep( * eq any(<a e i o u>) ).elems %% 2;
######## 2 ## # 3 ############################### # 4 ######

[1] Ensure at least one character in the string.

[2] Turn the string into a list of individual characters.

[3] Keep the vowels only, using grep on an any junction on a list of legal (i.e. english) vowels.

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

[4] Count the number of vowels. Are they divisible (the %% operator) by 2, thus an even number?

Running it:

$ ./split-string perl
False

$ ./split-string book
True

$ ./split-string "good morning"
True

Looking good.

But...

$ ./split-string bcd
True

Zero is divisible by 2. And that gives us a false positive.

Let us fix that.

File: split-string-zero
#! /usr/bin/env raku

unit sub MAIN ($str where $str.chars > 0);

my $elems = $str.comb.grep( * eq any() ).elems;

say so $elems && $elems %% 2;

Not quite as elegang as the first version, but the result is correct:

$ ./split-string-zero bcd
False

And that's it.