with Raku

This is my response to The Weekly Challenge #202.

You are given an array of integers.

Write a script to print

Example 1:

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 choose to interpretet «three consecutive» as
«**at least** three consecutive», and not as «**exactly** three consecutive».

#! /usr/bin/env raku
unit sub MAIN (*@array where @array.elems
&& all(@array) ~~ /^\-?<[0..9]>*$/, # [1]
:v(:$verbose));
my $consecutive = 0; # [2]
my $start_index = 0; # [3]
for ^@array.elems -> $i # [4]
{
if (@array[$i] %% 2) # [5]
{
$consecutive = 0; # [5a]
$start_index++; # [3a]
}
else # [6]
{
$consecutive++; # [6a]
}
if ($consecutive == 3) # [7]
{
say ":Consecutives: [{ @array[$start_index .. $start_index+2].join(",") \
}] starting at undex $start_index" if $verbose; # [3b]
say 1; # [7a]
exit; # [7b]
}
}
say 0; # [8]

[1] Alow negative integers as well (with an optional minus sign at the beginning of each value). Note that a negative integer as the very first argument will screw up the argument passing, so you should not do that.

[2] The number of consecutive odd values, initially none.

[3] The start index of where we found a match, set in [3a] and used by the verbose output in [3b] only. The algoritm as such does not need this variable.

[4] Iterate over the indices of the array.

[5] Do we have an even number (i.e. one that is divisible by 2),
courtesy of the divisibility operator `%%`

. If so reset the count of odd
values [5a].

See
docs.raku.org/routine/%%
for more information about the Divisibility Operator `%%`

.

[6] If not (i.e. it is odd), increase the count of consecutive odd values [6a].

[7] Do we have three consecutive odd values? if so, print «1» and exit.

[8] We have failed to find three consecutive odd values, if we get here. Print «0».

Running it gives the expected result:

$ ./consecutive-odds 1 5 3 6
1
$ ./consecutive-odds 2 6 3 5
0
$ ./consecutive-odds 1 2 3 4
0
$ ./consecutive-odds 2 3 5 7
1

Running it with verbose mode:

$ ./consecutive-odds -v 1 5 3 6
:Consecutives: [1,5,3] starting at undex 0
1
$ ./consecutive-odds -v 2 6 5 3
0
$ ./consecutive-odds -v 1 2 3 4
0
$ ./consecutive-odds -v 2 3 5 7
:Consecutives: [3,5,7] starting at undex 1
1

5 consecutive odd values are ok:

$ ./consecutive-odds -v 0 1 3 5 7 9
:Consecutives: [1,3,5] starting at undex 1
1

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:

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

This first version is slighly more rich of lines than strictly necessary. I'll get back to that later.

File: widest-valley#! /usr/bin/env raku
unit sub MAIN (*@array where @array.elems && all(@array) ~~ /^<[0..9]>*$/,
:v(:$verbose)); # [0]
my @valleys; # [1]
for ^@array.elems -> $start # [2]
{
my @c = @array[$start..Inf].clone; # [3]
say ":Starting at offset $start; values [ { @c.join(",") } ]" if $verbose;
my @current = (@c.shift.Int,); # [4]
say ":First: @current[0]" if $verbose;
my $non-inc = True; # [5]
while (@c.elems) # [6]
{
my $curr = @c.shift.Int; # [7]
if $non-inc # [8]
{
if $curr <= @current.tail # [8a]
{
; # [8b]
}
else
{
$non-inc = False; # [8c]
}
}
else # [9]
{
if $curr >= @current.tail # [9a]
{
; # [9b]
}
else
{
@valleys.push: @current.clone; # [10]
@current = (); # [10a]
last; # [10b]
}
}
say ":Add: $curr ({ $non-inc ?? "!inc" !! "!desc" })" if $verbose;
@current.push: $curr; # [11]
}
@valleys.push: @current if @current.elems; # [12]
}
say ":Valleys: { @valleys.raku; }" if $verbose;
say ":Widest: { @valleys>>.elems.max }" if $verbose;
say @valleys.grep({ $_.elems == @valleys>>.elems.max }).first.join(", ");
# [13]

[0] At least one element, and they must all be non-negative integers.

[1] All the possible valleys will end up here.

[2] Iterate over the indices (of the array), from left to right.

[3] Get a copy (with `clone`

) of the original array,
starting at the given index (from [2]). A copy, as we are changing it (in [4] and
[7]).

See
docs.raku.org/routine/clone for
more information about the `clone`

method.

[4] Get the first value, placed into the array used to build up the current
valley. There are two interesting features in play here. The first is the use
of `.Int`

to coerce the value from the `IntStr`

type,
courtesy of the command line, to a real (not in the matematical sense, but you
get the idea) integer. The second is the trailing comma after the value. The
comma is there to turn the single value into a list, which is what we want.
The parens are just for grouping in Raku (as opposed to Perl where they will
genereate a list).

[5] We are initially in the first part of the valley (the non-increasing part).

[6] As long as we have more elements to parse.

[7] Get the next one.

[8] If we are in the first part of the valley, and the next value is non-increasing [8a], all is well [8b] (as in we are still in the first part of the valley). If not, we have reached the second part of the valley [8c]; the non-decreasing part. In both cases, the next value will be added to the valley in [11].

[9] We are in the second part. If the next value is non-decreasing [9a], all is well [9b] (and the current value will be added to the valley in [11]).

[10] If not, we are done and add the valley (or rather, a copy of it) to the list of
valleys, then we reset the current valley [9b] (ready for the next iteration of [2]),
and exit the inner loop (with `last`

) [9c] as adding more values to the
currently illegal valley will not make it legal.

[11] Add the current value to the current valley.

[12] After going throgh the values of the array, add the resulting valley to the list of results - if it is non-empty.

[13] We are looking for the leftmost (or first) widest valley. We start with the widest,
which is the one with the higest number of elements. We get that size with
`>>.elems`

(which gets the size of each valley) and then `.max`

to get the highest one. Then we use `grep`

to get only the valleys with that
exact width. This is a list of all (1 or more) valleys with that size. Then we use
`first`

to get the first (leftmost) one. And finally we print that list
(i.e. valley) separated by commas.

Running it:

$ ./widest-valley 1 5 5 2 8
5, 5, 2, 8
$ ./widest-valley 2 6 8 5
2, 6, 8
$ ./widest-valley 9 8 13 13 2 2 15 17
13, 13, 2, 2, 15, 17
$ ./widest-valley 2 1 2 1 3
2, 1, 2
$ ./widest-valley 1 3 3 2 1 2 3 3 2
3, 3, 2, 1, 2, 3, 3

Looking good.

With verbose mode, just to show off:

$ ./widest-valley -v 1 5 5 2 8
:Starting at offset 0; values [ 1,5,5,2,8 ]
:First: 1
:Add: 5 (!desc)
:Add: 5 (!desc)
:Starting at offset 1; values [ 5,5,2,8 ]
:First: 5
:Add: 5 (!inc)
:Add: 2 (!inc)
:Add: 8 (!desc)
:Starting at offset 2; values [ 5,2,8 ]
:First: 5
:Add: 2 (!inc)
:Add: 8 (!desc)
:Starting at offset 3; values [ 2,8 ]
:First: 2
:Add: 8 (!desc)
:Starting at offset 4; values [ 8 ]
:First: 8
:Valleys: [[1, 5, 5], [5, 5, 2, 8], [5, 2, 8], [2, 8], [8]]
:Widest: 4
5, 5, 2, 8
$ ./widest-valley -v 2 6 8 5
:Starting at offset 0; values [ 2,6,8,5 ]
:First: 2
:Add: 6 (!desc)
:Add: 8 (!desc)
:Starting at offset 1; values [ 6,8,5 ]
:First: 6
:Add: 8 (!desc)
:Starting at offset 2; values [ 8,5 ]
:First: 8
:Add: 5 (!inc)
:Starting at offset 3; values [ 5 ]
:First: 5
:Valleys: [[2, 6, 8], [6, 8], [8, 5], [5]]
:Widest: 3
2, 6, 8
$ ./widest-valley -v 9 8 13 13 2 2 15 17
:Starting at offset 0; values [ 9,8,13,13,2,2,15,17 ]
:First: 9
:Add: 8 (!inc)
:Add: 13 (!desc)
:Add: 13 (!desc)
:Starting at offset 1; values [ 8,13,13,2,2,15,17 ]
:First: 8
:Add: 13 (!desc)
:Add: 13 (!desc)
:Starting at offset 2; values [ 13,13,2,2,15,17 ]
:First: 13
:Add: 13 (!inc)
:Add: 2 (!inc)
:Add: 2 (!inc)
:Add: 15 (!desc)
:Add: 17 (!desc)
:Starting at offset 3; values [ 13,2,2,15,17 ]
:First: 13
:Add: 2 (!inc)
:Add: 2 (!inc)
:Add: 15 (!desc)
:Add: 17 (!desc)
:Starting at offset 4; values [ 2,2,15,17 ]
:First: 2
:Add: 2 (!inc)
:Add: 15 (!desc)
:Add: 17 (!desc)
:Starting at offset 5; values [ 2,15,17 ]
:First: 2
:Add: 15 (!desc)
:Add: 17 (!desc)
:Starting at offset 6; values [ 15,17 ]
:First: 15
:Add: 17 (!desc)
:Starting at offset 7; values [ 17 ]
:First: 17
:Valleys: [[9, 8, 13, 13], [8, 13, 13], [13, 13, 2, 2, 15, 17], \
[13, 2, 2, 15, 17], [2, 2, 15, 17], [2, 15, 17], [15, 17], [17]]
:Widest: 6
13, 13, 2, 2, 15, 17
$ ./widest-valley -v 2 1 2 1 3
:Starting at offset 0; values [ 2,1,2,1,3 ]
:First: 2
:Add: 1 (!inc)
:Add: 2 (!desc)
:Starting at offset 1; values [ 1,2,1,3 ]
:First: 1
:Add: 2 (!desc)
:Starting at offset 2; values [ 2,1,3 ]
:First: 2
:Add: 1 (!inc)
:Add: 3 (!desc)
:Starting at offset 3; values [ 1,3 ]
:First: 1
:Add: 3 (!desc)
:Starting at offset 4; values [ 3 ]
:First: 3
:Valleys: [[2, 1, 2], [1, 2], [2, 1, 3], [1, 3], [3]]
:Widest: 3
2, 1, 2
$ ./widest-valley -v 1 3 3 2 1 2 3 3 2
:Starting at offset 0; values [ 1,3,3,2,1,2,3,3,2 ]
:First: 1
:Add: 3 (!desc)
:Add: 3 (!desc)
:Starting at offset 1; values [ 3,3,2,1,2,3,3,2 ]
:First: 3
:Add: 3 (!inc)
:Add: 2 (!inc)
:Add: 1 (!inc)
:Add: 2 (!desc)
:Add: 3 (!desc)
:Add: 3 (!desc)
:Starting at offset 2; values [ 3,2,1,2,3,3,2 ]
:First: 3
:Add: 2 (!inc)
:Add: 1 (!inc)
:Add: 2 (!desc)
:Add: 3 (!desc)
:Add: 3 (!desc)
:Starting at offset 3; values [ 2,1,2,3,3,2 ]
:First: 2
:Add: 1 (!inc)
:Add: 2 (!desc)
:Add: 3 (!desc)
:Add: 3 (!desc)
:Starting at offset 4; values [ 1,2,3,3,2 ]
:First: 1
:Add: 2 (!desc)
:Add: 3 (!desc)
:Add: 3 (!desc)
:Starting at offset 5; values [ 2,3,3,2 ]
:First: 2
:Add: 3 (!desc)
:Add: 3 (!desc)
:Starting at offset 6; values [ 3,3,2 ]
:First: 3
:Add: 3 (!inc)
:Add: 2 (!inc)
:Starting at offset 7; values [ 3,2 ]
:First: 3
:Add: 2 (!inc)
:Starting at offset 8; values [ 2 ]
:First: 2
:Valleys: [[1, 3, 3], [3, 3, 2, 1, 2, 3, 3], [3, 2, 1, 2, 3, 3], \
[2, 1, 2, 3, 3], [1, 2, 3, 3], [2, 3, 3], [3, 3, 2], [3, 2], [2]]
:Widest: 7
3, 3, 2, 1, 2, 3, 3

Let us have a go at a plain, a couple of slopes, and a mountain to top it all off:

$ ./widest-valley -v 1 1 1
:Starting at offset 0; values [ 1,1,1 ]
:First: 1
:Add: 1 (!inc)
:Add: 1 (!inc)
:Starting at offset 1; values [ 1,1 ]
:First: 1
:Add: 1 (!inc)
:Starting at offset 2; values [ 1 ]
:First: 1
:Valleys: [[1, 1, 1], [1, 1], [1]]
:Widest: 3
1, 1, 1
$ ./widest-valley -v 1 2 3
:Starting at offset 0; values [ 1,2,3 ]
:First: 1
:Add: 2 (!desc)
:Add: 3 (!desc)
:Starting at offset 1; values [ 2,3 ]
:First: 2
:Add: 3 (!desc)
:Starting at offset 2; values [ 3 ]
:First: 3
:Valleys: [[1, 2, 3], [2, 3], [3]]
:Widest: 3
1, 2, 3
$ ./widest-valley -v 3 2 1
:Starting at offset 0; values [ 3,2,1 ]
:First: 3
:Add: 2 (!inc)
:Add: 1 (!inc)
:Starting at offset 1; values [ 2,1 ]
:First: 2
:Add: 1 (!inc)
:Starting at offset 2; values [ 1 ]
:First: 1
:Valleys: [[3, 2, 1], [2, 1], [1]]
:Widest: 3
3, 2, 1
$ ./widest-valley -v 1 2 3 2 1
:Starting at offset 0; values [ 1,2,3,2,1 ]
:First: 1
:Add: 2 (!desc)
:Add: 3 (!desc)
:Starting at offset 1; values [ 2,3,2,1 ]
:First: 2
:Add: 3 (!desc)
:Starting at offset 2; values [ 3,2,1 ]
:First: 3
:Add: 2 (!inc)
:Add: 1 (!inc)
:Starting at offset 3; values [ 2,1 ]
:First: 2
:Add: 1 (!inc)
:Starting at offset 4; values [ 1 ]
:First: 1
:Valleys: [[1, 2, 3], [2, 3], [3, 2, 1], [2, 1], [1]]
:Widest: 3
1, 2, 3

Then a shorter version, with the replaced part highlighted in green:

File: widest-valley-shorter#! /usr/bin/env raku
unit sub MAIN (*@array where @array.elems && all(@array) ~~ /^<[0..9]>*$/,
:v(:$verbose));
my @valleys;
for ^@array.elems -> $start
{
my @c = @array[$start..Inf].clone;
say ":Starting at offset $start; values [ { @c.join(",") } ]" if $verbose;
my @current = (@c.shift.Int,);
say ":First: @current[0]" if $verbose;
my $non-inc = True;
while (@c.elems)
{
my $curr = @c.shift.Int;
if $non-inc && $curr > @current.tail
{
$non-inc = False;
}
elsif ! $non-inc && $curr < @current.tail
{
@valleys.push: @current.clone;
@current = ();
last;
}
say ":Add: $curr ({ $non-inc ?? "!inc" !! "!desc" })" if $verbose;
@current.push: $curr;
}
@valleys.push: @current if @current.elems;
}
say ":Valleys: { @valleys.raku; }" if $verbose;
say ":Widest: { @valleys>>.elems.max }" if $verbose;
say @valleys.grep({ $_.elems == @valleys>>.elems.max }).first.join(", ");

The result of running this version is as expected:

$ ./widest-valley-shorter 1 5 5 2 8
5, 5, 2, 8
$ ./widest-valley-shorter 2 6 8 5
2, 6, 8
$ ./widest-valley-shorter 9 8 13 13 2 2 15 17
13, 13, 2, 2, 15, 17
$ ./widest-valley-shorter 2 1 2 1 3
2, 1, 2
$ ./widest-valley-shorter 1 3 3 2 1 2 3 3 2
3, 3, 2, 1, 2, 3, 3

And that's it.