Perl Weekly Challenge: Week 202

Challenge 1:

Consecutive Odds

You are given an array of integers.

Write a script to print 1 if there are THREE consecutive odds in the given array otherwise print 0.

Example 1
Input: @array = (1,5,3,6)
Output: 1
Example 2
Input: @array = (2,6,3,5)
Output: 0
Example 3
Input: @array = (1,2,3,4)
Output: 0
Example 4
Input: @array = (2,3,5,7)
Output: 1

I initialized a variable for the result with the value of 0.

my $result = 0;

Now for every consecutive group of three elements in the input (which we got from the command line and placed in @args.)

for 0 .. @args.end - 2 -> $i {

We check if the number is odd by taking it modulo 2. If the answer is 0, it is even and if it is 1 it is even. Conincidentally, 1 and 0 are true and false values so we can chain the tests for all three numbers nicely with logical ANDs.

If they are all odd numbers...

    if @args[$i] % 2 && @args[$i + 1] % 2 && @args[$i + 2] % 2 {

...We change $result to 1 and stop processing. There may be other runs of three (or more) odd numbers but the spec says we only need to care if there is atleast one.

        $result = 1;
        last;
    }
}

If we had reached the end of the @args and there had not been three consecutive odd numbers, $result would still be 0 otherwise it is 1. In any case we print it out and then we are done.

say $result;

(Full code on Github.)

This is the Perl version.

my $result = 0;

for my $i (0 .. scalar @ARGV - 3) {
    if ($ARGV[$i] % 2 && $ARGV[$i + 1] % 2 && $ARGV[$i + 2] % 2) {
        $result = 1;
        last;
    }
}

say $result;

(Full code on Github.)

Challenge 2:

Widest Valley

Given a profile as a list of altitudes, return the leftmost widest valley. A valley is defined as a subarray of the profile consisting of two parts: the first part is non-increasing and the second part is non-decreasing. Either part can be empty.

Example 1
Input: 1, 5, 5, 2, 8
Output: 5, 5, 2, 8
Example 2
Input: 2, 6, 8, 5
Output: 2, 6, 8
Example 3
Input: 9, 8, 13, 13, 2, 2, 15, 17
Output: 13, 13, 2, 2, 15, 17
Example 4
Input: 2, 1, 2, 1, 3
Output: 2, 1, 2
Example 5
Input: 1, 3, 3, 2, 1, 2, 3, 3, 2
Output: 3, 3, 2, 1, 2, 3, 3

It took me a little while to get exactly what was being asked for here but eventually I gathered it meant a sequence that was ascending then descending then ascending again, or just ascending then descending or just descending than ascending. It helps if you draw it out on graph paper.

First storage for any vallies we might find.

my @vallies;

Then for each element in the input array (actually the index of each element)...

for 0 .. @args.end -> $i {

Storage is set up for the current valley.

    my @valley;

We need to keep track of whether we are going up or down. I chose to use ascending but we could just as easily have used descending.

    my Bool $ascending = False;

Then we look at the elements of each subarray from our current location in the input to the end.

    for $i .. @args.end -> $j {

If we are currently ascending but the current element we are looking at is less than the last one we looked at, we have stopped ascending and in fact are descending. Also we have reached the end of this valley so we can stop processing this subarray.

        if $ascending && @valley.elems && @args[$j] < @valley[*-1] {
            $ascending = False;
            last;

On the other hand if we were descending and the current element is greater than the last one we looked at, we are ascending.

        } elsif !$ascending && @valley.elems && @args[$j] > @valley[*-1] {
            $ascending = True;
        }

Whatever the case was, we add the current element to the current valley.

        @valley.push(@args[$j]);
    }

When we have reached the end of the subarray, we take whatever sized valley we found (perhaps we didn't find one at all) and add it to the list of vallies.

    @vallies.push(@valley);
}

Once we have all vallies, we sort them from widest to narrowest and print the widest in a nicely formatted way.

@vallies.sort({ @$^b.elems <=> @$^a.elems })[0].join(q{, }).say;

(Full code on Github.)

This we the Perl version. The only annoyance I had in translating it from Raku was...

my @vallies;

for my $i (0 .. scalar @ARGV - 1) {
    my @valley;
    my $ascending = undef;

    for my $j ($i .. scalar @ARGV - 1) {
        if ($ascending && scalar @valley && $ARGV[$j] < $valley[-1]) {
            $ascending = undef;
            last;
        } elsif (!$ascending && scalar @valley && $ARGV[$j] > $valley[-1]) {
            $ascending = 1;
        }
        push @valley, $ARGV[$j];
    }

...Here. We have to store arrays into other arrays by reference...

    push @vallies, [ @valley ];
}

...which means in order to use them we need to dereference them and the Perl syntax for this is very awkward IMO.

say join q{, }, @{ (sort { scalar @{$b} <=> scalar @{$a} } @vallies)[0] };

(Full code on Github.)