Last Peak
with Raku

by Arne Sommer

Last Peak with Raku

[369] Published 1. November 2025.

This is my response to The Weekly Challenge #345.

Challenge #345.1: Peak Positions

You are given an array of integers, @ints.

Find all the peaks in the array, a peak is an element that is strictly greater than its left or right neighbours. Return the indices of all such peak positions.

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

It follows from the 4th example that the very first and last values should be considered, even if they are not strictly (or otherwise) higher then their left (for the first one) and right (for the last one) non-existent neighbour.

I have decided to support the challenge text as well, with the aptly named «strictly» command line argument.

File: peak-positions
#! /usr/bin/env raku

unit sub MAIN (*@ints where @ints.elems > 0 && all(@ints) ~~ Int,  # [1]
		:s(:$strictly),                                    # [2]
		:v(:$verbose));

my $i-offset = 0;                                                  # [3]

unless $strictly                                                   # [4]
{
  @ints.unshift: -Inf;                                             # [4a]
  @ints.push: -Inf;                                                # [4b]
  $i-offset = -1;                                                  # [4c]
}

my @indices;                                                       # [5]

for 1 .. @ints.end - 1 -> $i                                       # [6]
{
  my $peak = @ints[$i -1] < @ints[$i] > @ints[$i +1];              # [7]

  say ": index { $i + $i-offset } (@ints[$i -1] < @ints[$i] > \
    @ints[$i +1]) : $peak" if $verbose;

  @indices.push($i + $i-offset) if $peak;                          # [8]
}

say "({ @indices.join(",") })";                                    # [9]

[1] A slurpy array of integers, with at least one element.

[2] Enable strict mode if you want the fourth example to fail.

[3] Index offset. Used to compensate for [4a] and [4b].

[4] Not strict mode? If so, add a very negative number at the front (unshift) [4a] and end (push) [4b] of the array, and adjust the index offset [4c] to compensate for the index shift (pun intended) caused by the first one (i.e. [4a].

See docs.raku.org/routine/unshift for more information about unshift.

See docs.raku.org/routine/push for more information about push.

[5] The result will end up here.

[6] Iterate over the indices, except the very first and last ones. Note that this will actually iterate over all the original indices (before the infinitely negative additions), thus checking all the values. Except in strict mode, where the first and last elements will not be considered for inclusion in the result. end gives the index of the last element, and is shorter (to write) than elems -1.

See docs.raku.org/routine/end for more information about end.

[7] Is the value with the current index a peak value?

[8] If so, add it to the result. (After adjusting the index, if we have used strict mode.)

[9] Pretty print the result.

Running it:

$ ./peak-positions 1 3 2
(1)

5 ./peak-positions 2 4 6 5 3
(2)

$ ./peak-positions 1 2 3 2 4 1
(2,4)

$ ./peak-positions 5 3 1
(0)

$ ./peak-positions 1 5 1 5 1 5 1
(1,3,5)

Looking good.

With verbose mode:

$ ./peak-positions -v 1 3 2
: index 0 (-Inf < 1 > 3) : False
: index 1 (1 < 3 > 2) : True
: index 2 (3 < 2 > -Inf) : False
(1)

$ ./peak-positions -v 2 4 6 5 3
: index 0 (-Inf < 2 > 4) : False
: index 1 (2 < 4 > 6) : False
: index 2 (4 < 6 > 5) : True
: index 3 (6 < 5 > 3) : False
: index 4 (5 < 3 > -Inf) : False
(2)

$ ./peak-positions -v 1 2 3 2 4 1
: index 0 (-Inf < 1 > 2) : False
: index 1 (1 < 2 > 3) : False
: index 2 (2 < 3 > 2) : True
: index 3 (3 < 2 > 4) : False
: index 4 (2 < 4 > 1) : True
: index 5 (4 < 1 > -Inf) : False
(2,4)

$ ./peak-positions -v 5 3 1
: index 0 (-Inf < 5 > 3) : True
: index 1 (5 < 3 > 1) : False
: index 2 (3 < 1 > -Inf) : False
(0)

$ ./peak-positions -v 1 5 1 5 1 5 1
: index 0 (-Inf < 1 > 5) : False
: index 1 (1 < 5 > 1) : True
: index 2 (5 < 1 > 5) : False
: index 3 (1 < 5 > 1) : True
: index 4 (5 < 1 > 5) : False
: index 5 (1 < 5 > 1) : True
: index 6 (5 < 1 > -Inf) : False
(1,3,5)

The fourth example with strict mode:

$ ./peak-positions -v -s 5 3 1
: index 1 (5 < 3 > 1) : False
()

Challenge #345.2: Last Visitor

You are given an integer array @ints where each element is either a positive integer or -1.

We process the array from left to right while maintaining two lists:

@seen
stores previously seen positive integers (newest at the front)

@ans
stores the answers for each -1

Rules:
  • If $ints[i] is a positive number -> insert it at the front of @seen [5,5b]
  • If $ints[i] is -1: [6]
    Let $x be how many -1s in a row we’ve seen before this one [9].
    • If $x < len(@seen) -> append seen[x] to @ans [7,7a]
    • Else -> append -1 to @ans [8,8a]
At the end, return @ans. [12]

Example 1:
Input: @ints = (5, -1, -1)
Output: (5, -1)

@seen = (5)
First  -1: @ans = (5)
Second -1: @ans = (5, -1)
Example 2:
Input: @ints = (3, 7, -1, -1, -1)
Output: (7, 3, -1)

@seen = (3, 7)
First  -1: @ans = (7)
Second -1: @ans = (7, 3)
Third  -1: @ans = (7, 3, -1)
Example 3:
Input: @ints = (2, -1, 4, -1, -1)
Output: (2, 4, 2)
Example 4:
Input: @ints = (10, 20, -1, 30, -1, -1)
Output: (20, 30, 20)
Example 5:
Input: @ints = (-1, -1, 5, -1)
Output: (-1, -1, 5)
File: last-visitor
#! /usr/bin/env raku

unit sub MAIN (*@ints where @ints.elems > 0 && all(@ints) ~~ Int,  # [1]
		:v(:$verbose));

my @seen;                                                          # [2]
my @ans;                                                           # [2a]

my $x = 0;                                                         # [3]

for @ints -> $int                                                  # [4]
{
  if $int > 0                                                      # [5]
  {
    $x = 0;                                                        # [5a]  
    @seen.unshift: $int;                                           # [5b]
  }
  elsif $int == -1                                                 # [6]
  {
    if $x < @seen.elems                                            # [7]
    {
      @ans.push: @seen[$x];                                        # [7a]
    }
    else                                                           # [8]
    {
      @ans.push: -1;                                               # [8a]
    }
    $x++;                                                          # [9]
  }
  else                                                             # [10]
  {
    die "Illegal value $int (legal values: -1,1,2,3,..)";          # [11]
  }

  say ": $int -> seen: { @seen.join(",") } ans: { @ans.join(",") }"
    if $verbose;
}

say "({ @ans.join(",") })";                                        # [12]

[1] A slurpy arrat of integers, with at least one item. The other restrictions on the input is handled in [5], [6] and [11].

[2] The seen and ans arrays.

[3] The number of already encountered -1s.

[4] Iterate over the input values.

[5] Is it positive? If so, reset the x counter [5a] and add the value to the front of seen (with unshift).

The rest of the code is just the challenge text translated from English to Raku. I have added code line references in the former.

Running it:

$ ./last-visitor 5 -1 -1
(5,-1)

$ ./last-visitor 3 7 -1 -1 -1
(7,3,-1)

$ ./last-visitor 2 -1 4 -1 -1
(2,4,2)

$ ./last-visitor 10 20 -1 30 -1 -1
(20,30,20)

$ ./last-visitor -- -1 -1 5 -1

Looking good.

With verbose mode:

$ ./last-visitor -v 5 -1 -1
: 5 -> seen: 5 ans: 
: -1 -> seen: 5 ans: 5
: -1 -> seen: 5 ans: 5,-1
(5,-1)

$ ./last-visitor -v 3 7 -1 -1 -1
: 3 -> seen: 3 ans: 
: 7 -> seen: 7,3 ans: 
: -1 -> seen: 7,3 ans: 7
: -1 -> seen: 7,3 ans: 7,3
: -1 -> seen: 7,3 ans: 7,3,-1
(7,3,-1)

$ ./last-visitor -v 2 -1 4 -1 -1
: 2 -> seen: 2 ans: 
: -1 -> seen: 2 ans: 2
: 4 -> seen: 4,2 ans: 2
: -1 -> seen: 4,2 ans: 2,4
: -1 -> seen: 4,2 ans: 2,4,2
(2,4,2)

$ ./last-visitor -v 10 20 -1 30 -1 -1
: 10 -> seen: 10 ans: 
: 20 -> seen: 20,10 ans: 
: -1 -> seen: 20,10 ans: 20
: 30 -> seen: 30,20,10 ans: 20
: -1 -> seen: 30,20,10 ans: 20,30
: -1 -> seen: 30,20,10 ans: 20,30,20
(20,30,20)

$ ./last-visitor -v -- -1 -1 5 -1
: -1 -> seen:  ans: -1
: -1 -> seen:  ans: -1,-1
: 5 -> seen: 5 ans: -1,-1
: -1 -> seen: 5 ans: -1,-1,5
(-1,-1,5)

And that's it.