With Perl Weekly Challenge 345‘s tasks being “Peak Positions” and “Last Visitor”, I was somewhat at a loss for what to what music to pick. Then I thought about how I haven’t done musical theater in a while, and it came to me: “One Last Time” from Hamilton.
So let’s teach them how to say goodbye with PWC 345…
Task 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 and 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)
Approach
This is a single loop through the array, where we compare each element with the elements on either side and skip any that aren’t strictly greater than those elements:
for i := 0 .. length(ints) do
skip if not ints[i-1] <= ints[i]
skip if not ints[i+1] <= ints[i]
push i on result list
doneBut then we need to handle either end of the list:
for i := 0 .. length(ints) do
skip if not (ints[i-1] <= ints[i] or i == 0)
skip if not (ints[i+1] <= ints[i] or i == length(ints))
push i on result list
doneRaku
sub peakPositions(@ints) {
my @peaks;
for 0..@ints.end -> $i {
next unless $i == 0 || @ints[$i - 1] <= @ints[$i];
next unless $i == @ints.end || @ints[$i + 1] <= @ints[$i];
@peaks.push($i);
}
return @peaks;
}View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
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)Perl
sub peakPositions(@ints) {
my @peaks;
foreach my $i (0 .. $#ints) {
next unless $i == 0 || $ints[$i - 1] <= $ints[$i];
next unless $i == $#ints || $ints[$i + 1] <= $ints[$i];
push @peaks, $i;
}
return @peaks;
}View the entire Perl script for this task on GitHub.
Python
The thing I need to keep in mind with Python’s range() function is that the list it returns stops one before the end value…
def peak_positions(ints):
peaks = []
for i in range(len(ints)):
if not (i == 0 or ints[i - 1] <= ints[i]):
continue
if not (i == len(ints)-1 or ints[i + 1] <= ints[i]):
continue
peaks.append(i)
return peaksView the entire Python script for this task on GitHub.
Elixir
Elixir doesn’t really have loops, so we need to generate the range of indices (line 16), convert that to a list (line 17), then use the Enum.map_reduce/3 function (line 18) to process each of those indices.
def reduce_fn(i, {peaks, ints}) do
cond do
i != 0 && Enum.at(ints,i-1) > Enum.at(ints,i) ->
{i, {peaks, ints}}
i != length(ints) && Enum.at(ints,i+1) > Enum.at(ints,i) ->
{i, {peaks, ints}}
true ->
{i, {peaks ++ [i], ints}}
end
end
def peak_positions(ints) do
{_, {peaks, _}} = 0..length(ints)-1
|> Enum.to_list
|> Enum.map_reduce({[], ints}, &reduce_fn/2)
peaks
endView the entire Elixir script for this task on GitHub.
❝ Though, in reviewing the incidents of my administration, I am unconscious of intentional error, I am nevertheless too sensible of my defects not to think it probable that I may have committed many errors.❞
I’m hoping that I’m not making an error in task 2.
Task 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
If $ints[i] is -1:
Let $x be how many -1s in a row we’ve seen before this one.
If $x < len(@seen) -> append seen[x] to @ans
Else -> append -1 to @ans
At the end, return @ans.
Example 1
Input: @ints = (5, -1, -1)
Output: (5, -1)
@seen = (5)
First -1: @ans = (5)
Second -1: @ans = (5, -1)
My walk through of algorithm:
@seen = (5)
First -1: x = 0, push(@ans, @seen[0]), @ans = (5)
Second -1: x = 1, push(@ans, -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)
My walk through of algorithm:
@seen = (3)
@seen = (7, 3)
First -1: x = 0, push(@ans, @seen[0]), @ans = (7)
Second -1: x = 1, push(@ans, @seen[1]), @ans = (7, 3)
Third -1: x = 2, push(@ans, -1), @ans = (7, 3, -1)
Example 3
Input: @ints = (2, -1, 4, -1, -1)
Output: (2, 4, 2)
My walk through of algorithm:
@seen = (2)
First -1: x = 0, push(@ans, @seen[0]), @ans = (2)
@seen = (4, 2)
Second -1: x = 1, push(@ans, @seen[1]), @ans = (2, 2)
Third -1: x = 2, push(@ans, -1), @ans = (2, 2, -1)
Example 4
Input: @ints = (10, 20, -1, 30, -1, -1)
Output: (20, 30, 20)
My walk through of algorithm:
@seen = (10)
@seen = (20, 10)
First -1: x = 0, push(@ans, @seen[0]), @ans = (20)
@seen = (30, 20, 10)
Second -1: x = 1, push(@ans, @seen[1]), @ans = (20, 20)
Third -1: x = 2, push(@ans, @seen[2]), @ans = (20, 20, 10)
Example 5
Input: @ints = (-1, -1, 5, -1)
Output: (-1, -1, 5)
My walk through of algorithm:
@seen = ()
First -1: x = 0, push(@ans, -1), @ans = (-1)
Second -1: x = 1, push(@ans, -1), @ans = (-1, -1)
@seen = (5)
Third -1: x = 2, push(@ans, -1), @ans = (-1, -1, -1)
Approach
Ok, this challenge seems straightforward, but the provided output for the example doesn’t mesh with the way I’m reading the algorithm, so I’m pushing ahead with my understanding.
What we do is we walk through the values of @ints and build the arrays as directed.
Raku
sub lastVisitor(@ints) {
my (@seen, @ans);
my $x = 0;
for @ints -> $i {
if ($i > 0) {
@seen.unshift($i); # insert at front of @seen
}
else {
if ($x < @seen.elems) { # if $x < len(@seen)
@ans.push( @seen[$x] ); # append seen[x] to @ans
}
else {
@ans.push( -1 ); # append -1 to @ans
}
$x++;
}
}
return @ans;
}View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: @ints = (5, -1, -1)
Output: (5, -1)
Example 2:
Input: @ints = (3, 7, -1, -1, -1)
Output: (7, 3, -1)
Example 3:
Input: @ints = (2, -1, 4, -1, -1)
Output: (2, 2, -1)
Example 4:
Input: @ints = (10, 20, -1, 30, -1, -1)
Output: (20, 20, 10)
Example 5:
Input: @ints = (-1, -1, 5, -1)
Output: (-1, -1, -1)Perl
sub lastVisitor(@ints) {
my (@seen, @ans);
my $x = 0;
foreach my $i ( @ints ) {
if ($i > 0) {
unshift @seen, $i; # insert at front of @seen
}
else {
if ($x < @seen) { # if $x < len(@seen)
push @ans, $seen[$x]; # append seen[x] to @ans
}
else {
push @ans, -1; # append -1 to @ans
}
$x++;
}
}
return @ans;
}View the entire Perl script for this task on GitHub.
Python
def last_visitor(ints):
seen = []
ans = []
x = 0
for i in ints:
if i > 0:
seen.insert(0, i) # insert at front of @seen
else:
if x < len(seen): # if $x < len(@seen)
ans.append( seen[x] ) # append seen[x] to @ans
else:
ans.append( -1 ) # append -1 to @ans
x += 1
return ansView the entire Python script for this task on GitHub.
Elixir
And rather than a loop, in Elixir we’re using recursion to go through the list of integers.
def last_visitor([], _, ans, _), do: ans
def last_visitor([i | ints], seen, ans, x) do
{seen, ans, x} = if i > 0 do
{[i] ++ seen, ans, x} # insert at front of @seen
else
if x < length(seen) do # if $x < len(@seen)
# append seen[x] to @ans
{seen, ans ++ [Enum.at(seen, x)], x+1}
else
{seen, ans ++ [-1], x+1} # append -1 to @ans
end
end
last_visitor(ints, seen, ans, x)
endView the entire Elixir script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-345/packy-anderson