For your musical enjoyment, I recommend First We Take Manhattan (Leonard Cohen, 1988).
Task 1: Range Sum
Well, actually, first we do task 1, then we work on Manhattan.
You are given a list integers and pair of indices.
Write a script to return the sum of integers
between the given indices (inclusive).
-
Example 1:
-
Input:
@ints = (-2, 0, 3, -5, 2, -1), $x = 0, $y = 2
-
Output:
1
- Elements between indices (0, 2) => (-2, 0, 3)
-
Input:
-
Example 2:
-
Input:
@ints = (1, -2, 3, -4, 5), $x = 1, $y = 3
-
Output:
-3
- Elements between indices (1, 3) => (-2, 3, -4)
-
Input:
Knowing that array slices are a thing, and that Perl has a range operator (..
), this is almost trivial. The sum comes from the List::Util
module.
What happens if $x
or $y
is out of range? The array slice still yields values, but those values are undef
. When we pass undef
to sum0
, we get a warning for every undef
value. So untidy.
What happens if $x
is greater than $y
? The range operator in Perl doesn't do descending, for ... reasons. Although there's good logic why it's like that, it's one of the few places where Perl doesn't "do what I mean". Executive decision: swap the arguments to make it work.
sub rangeSum($x, $y, $ints)
{
use List::Util qw/sum0/;
if ( $x > $y ) { ($x,$y) = ($y, $x) }
return sum0 map { $_ // 0 } $ints->@[$x .. $y];
}
Notes:
- Swapping can be done without a temporary variable.
-
$ints->@[$x..$y]
is postfix syntax for an array slice, which I like. Traditional syntax would be@$ints[$x..$y]
. -
$_ // 0
uses the//
operator to yield 0 if$_
is undefined. This has been around quite a while now, but I still see a lot ofdefined($_)?$_:0
. So untidy. -
sum0
, notsum
because we want to handle the possibility of an empty list by returning 0.
Task 2: Nearest Valid Point
You are given current location as two integers: x and y.
You are also given a list of points on the grid.
A point is considered valid if it shares either
the same x-coordinate or the same y-coordinate
as the current location.
Write a script to return the index of the valid point
that has the smallest Manhattan distance to the current
location. If multiple valid points are tied for the
smallest distance, return the one with the lowest index.
If no valid points exist, return -1.
The Manhattan distance between two points (x1,y1) and (x2,y2)
is calculated as: |x1 - x2| + |y1 - y2|
-
Example 1:
-
Example 2:
-
Input:
$x = 2
,$y = 5
, @points([3,4], [2,3], [1,5], [2,5])
-
Output:
3
([2,5])
-
Input:
-
Example 3:
-
Input:
$x = 1
,$y = 1
, @points([2,2], [3,3], [4,4])
-
Output:
-1
- No point shares x or y with (1, 1).
-
Input:
-
Example 4:
-
Input:
$x = 0
,$y = 0
, @points([0,1], [1,0], [0,2], [2,0])
-
Output:
0
- Valid points: all of them
- Manhattan distances: Tie between index 0 and 1, pick the smaller index: 0
-
Input:
-
Example 5:
-
Input:
$x = 5
,$y = 5
, @points([5,6], [6,5], [5,4], [4,5])
-
Output:
0
- Valid points: all of them, same distance. All tie, return the one with the lowest index: 0
-
Input:
Manhattan distance is the result of moving on a grid only left-to-right or up-and-down. It comes up a lot in Advent of Code problems, which reminds me that I still have problems 21 through 25 of last year to finish. The constraint that either the x or y coordinate must match means that only vertical and horizontal destination points are valid. Weird flex, but OK.
I'm going to tackle this problem in a literal way by examining each point in a loop.
sub nvp($x, $y, $point)
{
my $minIndex;
my $minDistance;
for my ($i, $p) (indexed reverse @$point)
{
next unless $p->[0] == $x || $p->[1] == $y;
my $d = abs($x - $p->[0] + $y - $p->[1] );
$minDistance //= $d;
if ( $d <= $minDistance )
{
$minIndex = $i;
$minDistance = $d;
}
}
return $minIndex // -1;
}
Notes:
- The input parameter
$point
is a reference to an array of points, not the array itself. Passing a single reference is more efficient than passing an array, as it is in many languages. -
my $minIndex
is initially undefined. If it's still undefined at the end, it means we didn't find any valid points. -
for my $(i,$p) (indexed ...)
--indexed
conveniently gives us both the index and the value at the index. - The value at the index is a reference to a point, which is an [x,y] pair.
-
for ... (reverse @$point)
-- We want the smallest index, so working backwards ends at smaller values. -
next unless
-- convenient syntax for asserting what must be true -
$d = abs(...)
-- In the calculation of the distance, the literal formula has two absolute values, but we only need to do it once, because eitherx-p[0]
ory-p[1]
must be zero. -
$minDistance //= $d
-- Initialize$minDistance
the first time we see a valid point. -
return $minIndex // -1
-- If we never saw a valid point, we want to return -1 instead ofundef
.
Top comments (0)