Perl Weekly Challenge: Week 355

Challenge 1:

Thousand Seperator

You are given a positive integer, $int.

Write a script to add thousand separator, , and return as string.

Example 1
Input: $int = 123
Output: "123"
Example 2
Input: $int = 1234
Output: "1,234"
Example 3
Input: $int = 1000000
Output: "1,000,000"
Example 4
Input: $int = 1
Output: "1"
Example 5
Input: $int = 12345
Output: "12,345"

I knew I could solve this one as a one-liner but it turned out to be a little bit more complicated than I originally anticipated.

We start by assigning the first command-line argument (our input) to $_. As well as making the length of the variable name shorter, $_ is default target of many methods. Both of these are helpful in reducing the length of a one-liner.

The key to this solution is the .comb(3) method which will split $_ into a sequence of three character strings. If we put a , between them we will be done right? Unfortunately, comb starts from the left of the string so for e.g. example 2, we would get "123,4" instead of "1,234" which is what we actually want.

The trick is to pad the string enough to make its' length an even multiple of three. That's what .subst(/^/, (0 x 3 - .chars % 3)) does; it adds zeros's as padding. If the string is already a multiple of 3, it will add three zeros which is unnecessary but harmless so to avoid complication I left it as is.

Now we can apply .comb() and then .join() the sub-lists with commas.

The last stage is to remove the leading zero padding (and a leading comma if there is one) with subst() and print the result with .say().

$_= @*ARGS[0]; .subst(/^/, (0 x 3 - .chars % 3)).comb(3).join(q{,}).subst(/^0+\,*/, "").say

(Full code on Github.)

The Perl version is not a one-liner but it is, I think, a little clearer to understand.

$int =~ s/^/0 x (3 - (length $int) % 3)/e;
$int = join q{,}, ($int =~ /(.{3})/g);
$int =~s/^0+,*//;
say $int;

(Full code on Github.)

Challenge 2:

Mountain Array

You are given an array of integers, @ints.

Write a script to return true if the given array is a valid mountain array.

An array is mountain if and only if:
1) arr.length >= 3
and
2) There exists some i with 0 < i < arr.length - 1 such that:
arr[0] < arr[1]     < ... < arr[i - 1] < arr[i]
arr[i] > arr[i + 1] > ... > arr[arr.length - 1]
Example 1
Input: @ints = (1, 2, 3, 4, 5)
Output: false
Example 2
Input: @ints = (0, 2, 4, 6, 4, 2, 0)
Output: true
Example 3
Input: @ints = (5, 4, 3, 2, 1)
Output: false
Example 4
Input: @ints = (1, 3, 5, 5, 4, 2)
Output: false
Example 5
Input: @ints = (1, 3, 2)
Output: true

The MAIN() function is very simple:

say isMountain(@ints);

The isMountain() function is where all the work happens. It takes a list of integers and returns True or False if they represent a mountain array or not.

sub isMountain(@ints) {

As the spec suggests, if @ints has less than 3 elements it is not a mountain array so we can immediately return False.

    if @ints.elems < 3 {
        return False;
    }

We will start from the beginning of @ints (index 0 which will be assigned to $i.)

    my $i = 0;

As we "climb up" @ints (meaning each succeeding value is greater than the preceding one,) we increment $1. We also check we are not at the end of @ints.

    while $i < @ints.end && @ints[$i] < @ints[$i + 1] {
        $i++;
    }

When we have stopped climbing, we are at the "peak" of the mountain. We check to see that this is not the end of @ints or the beginning (which could happen if the second element was smaller than the first so we didn't climb at all.) In those cases, this is not a mountain array so we return False.

    if $i == 0 || $i == @ints.end {
        return False;
    }

Now we begin "climbing down". Once again we should check we aren't at the end of @ints.

    while $i < @ints.end && @ints[$i] > @ints[$i + 1] {
        $i++;
    }

By now $i should have equalled the index of the last element in @ints. This might not be True if e.g. after climbing down we may have begun climbing up again.

    return $i == @ints.end;
}

(Full code on Github.)

This is the Perl version.

sub isMountain(@ints) {
    if (@ints < 3) {
        return false;
    }

    my $i = 0;

    while ($i < $#ints && $ints[$i] < $ints[$i + 1]) {
        $i++;
    }

    if ($i == 0 || $i == $#ints) {
        return false;
    }

    while ($i < $#ints && $ints[$i] > $ints[$i + 1]) {
        $i++;
    }

    return $i == $#ints;
}

say isMountain(@ARGV) ? 'true' : 'false';

(Full code on Github.)