The Bear's Den

Enter at your own risk

Regular Sub-Gaps

Task 1: Rearrange Spaces

Submitted by: Mohammad Sajid Anwar


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.

Solution

Two basic cases can be differentiated:

Perl

Use split to extract the words into @words and tr to extract all spaces into $sp. Then use the regex engine to distribute the spaces.

There is a “happy case” with \(n \gt 1\) words resulting in one or more gaps. With \(r = n - 2\) the spaces in $sp can be distributed with this regex:

$sp =~ /^( +)\1{$r}( *)$/

It selects and captures one or more spaces, tries to match the captured string $r more times and captures all remaining spaces in a second capture group. As + operates greedy, $1 will contain the uniform inter-word space and $2 the remaining trailing space after a successful match.

This is problematic in the general case in two aspects:

With

$sp =~ /^(?(?{$n > 1})( +)\1{$r})( *)$/

and \(n > 1\) it acts as before, whereas for \(n \le 1\), $1 will be undef and $2 will contain all spaces.

The last regex can be used in a substitute operation with an evaluated right operand to produce the result as:

join($1, @words) . $2

With less than two words, actually there is nothing to join. However, $1 is still used as an argument to join and is undef. The emitted warning needs to be suppressed.

use strict;
use warnings;
use experimental 'signatures';

sub rearrange_spaces ($str) {
    my @words = split ' ', $str;
    my $rep = @words > 1 ? @words - 2: 0;

    no warnings 'uninitialized';
    $str =~ tr/ //cdr =~ s{^(?(?{@words > 1})( +)\1{$rep})( *)$}
        {join($1, @words) . $2}er;
}

See the full solution to task 1.

J

Naming some supplementary verbs to make the solution better readable.

These verbs are assembled to the resulting verb rearrange:

   rearrange =: cutopen ([ insert [: spbx"0 gaps@[ divmod spaces@]) f. ] 

   rearrange&.> '  challenge  ';'coding  is  fun';'a b c  d';'  team      pwc  ';'   the  weekly  challenge  '
┌─────────────┬───────────────┬────────┬─────────────────┬───────────────────────────┐
│challenge    │coding  is  fun│a b c d │team          pwc│the    weekly    challenge │
└─────────────┴───────────────┴────────┴─────────────────┴───────────────────────────┘

See the full solution.

Task 2: Largest Substring

Submitted by: Mohammad Sajid Anwar


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

Solution

Perl

In the regular expression qr{(.)(.*)\1} the second capture group matches any substring enclosed within two instances of the same character. Recording the maximum length of all such substrings by forcing the match to fail after each substring match reveals the solution to this task.

use strict;
use warnings;
use List::Util 'max';

sub largest_substring {
    my $l = -1;
    shift =~ /(.)(.*)\1(?{$l = max $l, length $2})(*FAIL)/;

    $l;
}

See the full solution to task 2.

J

Following a different approach in J:

   largest_substring =: ] ([: <: [: >./ i: - i.) ~.

   largest_substring&.> 'aaaaa';'abcdeba';'abbc';'abcaacbc';'laptop'
┌─┬─┬─┬─┬─┐
│3│5│0│4│2│
└─┴─┴─┴─┴─┘

See the full solution.