Perl Weekly Challenge: Week 322

Challenge 1:

String Format

You are given a string and a positive integer.

Write a script to format the string, removing any dashes, in groups of size given by the integer. The first group can be smaller than the integer but should have at least one character. Groups should be separated by dashes.

Example 1
Input: $str = "ABC-D-E-F", $i = 3
Output: "ABC-DEF"
Example 2
Input: $str = "A-BC-D-E", $i = 2
Output: "A-BC-DE"
Example 3
Input: $str = "-A-B-CD-E", $i = 4
Output: "A-BCDE"

Perl first.

The first step is to get rid of the dashes which we do with a simple regular expression.

$str =~ s/-//g;

We will need to know the position in the string at which each group starts. Initially, this will be the start of the string i.e. character 0.

my $pos = 0;

We will also need to know how long the string is which we can find with length().

my $len = length $str;

For some reason I decided to call the list of groups found @segments. The first group (or segment) is special because it may be less than $i is $len is not an even multiple of $i. So it's length will actually be $len % $1. This value will be 0 In the case that $len actually is an even multiple of $1 as in example 1. In that case we explicitly set @segments to () otherwise we will get an unsightly extra dash in the output. (i.e. -ABC-DEF instead of ABC-DEF.)

my @segments = substr($str, $pos, $len % $i) || ();

We then advance $pos by the length of the first segment.

$pos += $len % $i;

We know the rest of the segments are going to be $i character long so as long as $pos is less than $len...

while ($pos < $len) {

...We add a segment to the list...

    push @segments, (substr($str, $pos, $i));

...and advance $pos by $i.

    $pos += $i;
}

Finally, we readd dashes between all the segments and print the result.

say join q{-}, @segments;

(Full code on Github.)

In Raku, $str.comb($1) could have eliminated a lot of actual code but if a string does evenly split into groups, .comb() leaves the remainder at the end of the list of groups whereas we want it at the beginning. So we have to follow the same method as our Perl version instead.

Another difference is that, function parameters being immutable, we have to operate on a copy of $str I called $dashless instead.

my $dashless = $str.subst(q{-}, :g);
my $pos = 0;
my $len = $dashless.chars;

my @segments = $dashless.substr($pos, $len % $i) || ();
$pos += $len % $i;

while $pos < $len {
    @segments.push($dashless.substr($pos, $i));
    $pos += $i;
}

@segments.join(q{-}).say;

(Full code on Github.)

Challenge 2:

Rank Array

You are given an array of integers.

Write a script to return an array of the ranks of each element: the lowest value has rank 1, next lowest rank 2, etc. If two elements are the same then they share the same rank.

Example 1
Input: @ints = (55, 22, 44, 33)
Output: (4, 1, 3, 2)
Example 2
Input: @ints = (10, 10, 10)
Output: (1, 1, 1)
Example 3
Input: @ints = (5, 1, 1, 4, 3)
Output: (4, 1, 1, 3, 2)

We create a Hash to map the integers to their ranks.

my %ranked;

The initial rank is 1 not 0 as we are normally accustomed to.

my $rank = 1;

Now for each integer sorted in ascending numeric order...

for @ints.sort({$^a <=> $^b}) -> $int {

..we make it a key in %ranked with $rank as its' value. Then if the key did not previously exist, (i.e if this is the first time we have seen this integer,) $rank is incremented. If the integer was a duplicate, nothing needs to be added to %ranked.

    %ranked{$int} //= $rank++;
}

Finally, we .map() the ranks to their respective integers (in the original order this time) and print them. The rest of the code on this line is just to make the output format the same as in the examples.

say q{(}, @ints.map({ %ranked{$_} }).join(q{, }), q{)};

(Full code on Github.)

The Perl version works the same as in Raku.

my %ranked;
my $rank = 1;

for my $int (sort {$a <=> $b} @ints) {
    $ranked{$int} //= $rank++;
}

say q{(}, (join q{, }, map { $ranked{$_} } @ints), q{)};

(Full code on Github.)