Perl Weekly Challenge: Week 372

Challenge 1:

Rearrange Spaces

You are given a string text of words that are placed among number of spaces.

Write a script to rearrange the spaces so that there is an equal number of spaces between every pair of adjacent words and that number is maximised. If you can’t distribute, place the extra spaces at the end. Finally return the string.

Example 1
Input: $str = "  challenge  "
Output: "challenge    "

We have 4 spaces and 1 word. So all spaces go to the end.
Example 2
Input: $str = "coding  is  fun"
Output: "coding  is  fun"

We have 4 spaces and 3 words (2 gaps). So 2 spaces per gap.
Example 3
Input: $str = "a b c  d"
Output: "a b c d "

We have 4 spaces and 4 words (3 gaps). So 1 space per gap and 1 remainder.
Example 4
Input: $str = "  team      pwc  "
Output: "team          pwc"

We have 10 spaces and 2 words (1 gap). So 10 spaces per gap.
Example 5
Input: $str = "   the  weekly  challenge  "
Output: "the    weekly    challenge "

We have 9 spaces and 3 words (2 gaps). So 4 spaces per gap and 1 remainder.

First we split $str into a list of words with .words().

my @words = $str.words;

While we could count each space, an easier way is to subtract the sum of the length of the words from the total length of $str.

my $spaces = $str.chars - @words».chars.sum;

The last piece of information neeeded is the number of gaps; which will always be one less than the number of worfd.

my $gaps = @words.elems - 1;

If there are gaps...

if $gaps {

... we divide the number of spaces by the number of gaps and add that many spaces to every word except the last one.

    for 0 ..^ $gaps -> $i {
        @words[$i] ~= q{ } x ($spaces / $gaps);
    }

If the number of spaces did not divide into the number of gaps evenly, we add the remainder to the end of the @words list.

    @words.push(q{ } x ($spaces % $gaps));        

If there were no gaps (i.e. there is only one word in @words,) we append all the spaces to @words.

} else {
    @words.push(q{ } x $spaces);
}

Now all the spaces will be properly distributed. We .join() all of @words back to together into one string and wrap it in quotes for output.

say q{"}, @words.join, q{"};

(Full code on Github.)

The only replacement we need for the Perl version is sum(); other than that it is a direct translation from Raku.

my @words = $str =~/(\w+)/g;
my $spaces = (length $str) - sum(map { length $_ } @words);
my $gaps = scalar @words - 1;

if ($gaps) {
    for my $i (0 .. $gaps - 1) {
        $words[$i] .= q{ } x ($spaces / $gaps);
    }
    push @words, q{ } x ($spaces % $gaps);        
} else {
    push @words, q{ } x $spaces;
}

say q{"}, (join q{}, @words), q{"};

(Full code on Github.)

Challenge 2:

Largest Substring

You are given a string.

Write a script to return the length of the largest substring between two equal characters excluding the two characters. Return -1 if there is no such substring.

Example 1
Input: $str = "aaaaa"
Output: 3

For character "a", we have substring "aaa".
Example 2
Input: $str = "abcdeba"
Output: 5

For character "a", we have substring "bcdeb".
Example 3
Input: $str = "abbc"
Output: 0

For character "b", we have substring "".
Example 4
Input: $str = "abcaacbc"
Output: 4

For character "a", we have substring "bca".
For character "b", we have substring "caac".
For character "c", we have substring "aacb".
Example 5
Input: $str = "laptop"
Output: 2

For character "p", we have substring "to".

Because this challenge wants the length of the largest substring, the first order of business is to record the position of each character in $str.

my %positions;

for $str.comb.kv -> $index, $char {
    %positions{$char}.push($index);
}

We assign storage for the maximum substring length and set it to -1 by default.

my $max = -1;

Now For each character that appears more than once...

for %positions.values -> @indices {
    if @indices.elems > 1 {

...we calculate the distance between the first and last occurrences. We have to substract from the distance to exclude the two equal characters at each end.

        my $distance = @indices[*-1] - @indices[0] - 1;

If the distance is greater than the current value of $max, it becomes the new value of it.

        if $distance > $max {
            $max = $distance;
        }
    }
}

Finally, we just print $max. If we had failed to find an appropriate substring, this would still equal -1.

say $max;

(Full code on Github.)

This time, the Perl translation doesn't require any extra code; everything can be done with core Perl itself.

my %positions;

Note here, to substitute for Raku's .kv(), I'm using indexed() which is now a standard builtin in the most recent releases of Perl.

for my ($index, $char) (indexed split //, $str) {
    push @{$positions{$char}}, $index;
}

my $max = -1;

for my $indices (values %positions) {
    if (scalar @{$indices} > 1) {
        my $distance = $indices->[-1] - $indices->[0] - 1;
        if ($distance > $max) {
            $max = $distance 
        }
    }
}

say $max;

(Full code on Github.)