Perl Weekly Challenge: Week 278

Challenge 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"

First the input string is split into words with .words() and each word s further split into the word itself and its' position with a regular expression. The position becomes a key in a hash, %order, and the word is the value.

my %order;

for $str.words -> $w {
    my ($word, $pos) = $w.match(/ (.+)(\d+) /).List;
    %order{$pos} = $word;
}

Now we just need to sort the keys of the hash, replace the sorted keys with their values, join them back together into a string with .join() and print.

%order.keys.sort.map({ %order{$_} }).join(q{ }).say;

(Full code on Github.)

The Perl version works the same way.

my %order;

for my $w (split /\s+/, $str) {
    my ($word, $pos) = $w =~ /(.+)(\d+)/;
    $order{$pos} = $word;
}

say join q{ }, map { $order{$_} } sort keys %order;

(Full code on Github.)

Challenge 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 1$char doesn’t exist then DON'T do anything.

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

This can be solved in one line with a regular expression. I'll show the Perl version first.

$ARGV[0] =~ s{(.+$ARGV[1])(.+)}{(join q{}, sort split//, $1) . $2}e; say $ARGV[0]

(Full code on Github.)

What the spec calls $str and $char are brought in from the command-line. The s/// operator is applied to the first command-line argument ($ARGV[0] or $word.) Actually {} are used as delimiters instead of //. as the regular expression // is used within it. In the first half of the s///, the word is split into two groups of characters; from the beginning of the word to the second command-line argument ($ARGV[1] or $char.) and from the character after $ARGV[1] to the end of the word. In the second part of the s///, the first group is split into individual characters with split(), sorted with sort() and put back together with join(). The second group is then appended to it The whole thing then replaces the original value of @ARGV[0] and is printed.

The Raku version gave me some problems. One is that command-line arguments are immutable so s cannot be used directly on them. I discovered that Raku also has an S operator which works on a copy of its input. Also, as you can see, || is being used as delimiters instead of //. Another is how Raku deals with a regular expression that fails to match. Perl will return an empty string in match variables ($1, $2 etc.) Raku returns Nil which is a separate datatype. This causes an unsightly warning message when you use a string method like .split() on it. The solution is to replace any Nil with an empty string using the logical defined-or operator //. Incidently this is why || is being used as delimiters for S instead of //.

All this makes the Raku version uncharacteristically longer than Perl but still one line.

say S| (.+$(@*ARGS[1]))? (.+) | $( ($0 // q{}).split(q{}).sort.join ~ ($1 // q{}) ) | with @*ARGS[0]

(Full code on Github.)