Perl Weekly Challenge: Week 334
Challenge 1:
Range Sum
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)
Range Sum: (-2) + 0 + 3 => 1
Example 2
Input: @ints = (1, -2, 3, -4, 5), $x = 1, $y = 3
Output: -3
Elements between indices (1, 3) => (-2, 3, -4)
Range Sum: (-2) + 3 + (-4) => -3
Example 3
Input: @ints = (1, 0, 2, -1, 3), $x = 3, $y = 4
Output: 2
Elements between indices (3, 4) => (-1, 3)
Range Sum: (-1) + 3 => 2
Example 4
Input: @ints = (-5, 4, -3, 2, -1, 0), $x = 0, $y = 3
Output: -2
Elements between indices (0, 3) => (-5, 4, -3, 2)
Range Sum: (-5) + 4 + (-3) + 2 => -2
Example 5
Input: @ints = (-1, 0, 2, -3, -2, 1), $x = 0, $y = 2
Output: 1
Elements between indices (0, 2) => (-1, 0, 2)
Range Sum: (-1) + 0 + 2 => 1
This problem can be solved as a one-liner; here is the Raku version first. The code consists of two statements.
The first is merely an alias so we don;t have to waste space writing @*ARGS
several times. While we are at it,
we also use ».Int
to convert the command-line arguments (which are String
s) to numbers.
The input is taken from @*ARGS
in the format @ints... $x $y
So example 3 for instance would be
1 0 2 -1 3 3 4
. The shell is a pain in the neck; you have to quote arguments like -1
or they are treated as
switches. Instead of assigning to $x
and $y
we use the positional indices *-2
(second to last) and *-1
(last) directly to form a Range
of elements in @a
. This range is then .sum()
ed and the result is printed
with .say()
.
my @a = @*ARGS».Int; (@a[@a[*-2] .. @a[*-1]]).sum.say
The Perl version is also one line but a little bit longer because we don't have .sum()
so we have to use a
for-loop and accumulator, $s
, instead.
my $s = 0, @a = @ARGV; for (@a[$a[-2] .. $a[-1]]) { $s+=$_ }; say $s
Challenge 2:
Nearest Valid Point
You are given current location as two integers:
x
andy
. 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 samey-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 thelowest 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
Input: $x = 3, $y = 4, @points ([1, 2], [3, 1], [2, 4], [2, 3])
Output: 2
Valid points: [3, 1] (same x), [2, 4] (same y)
Manhattan distances:
[3, 1] => |3-3| + |4-1| = 3
[2, 4] => |3-2| + |4-4| = 1
Closest valid point is [2, 4] at index 2.
Example 2
Input: $x = 2, $y = 5, @points ([3, 4], [2, 3], [1, 5], [2, 5])
Output: 3
Valid points: [2, 3], [1, 5], [2, 5]
Manhattan distances:
[2, 3] => 2
[1, 5] => 1
[2, 5] => 0
Closest valid point is [2, 5] at index 3.
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).
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:
[0, 1] => 1
[1, 0] => 1
[0, 2] => 2
[2, 0] => 2
Tie between index 0 and 1, pick the smaller index: 0
Example 5
Input: $x = 5, $y = 5, @points ([5, 6], [6, 5], [5, 4], [4, 5])
Output: 0
Valid points: all of them
[5, 6] => 1
[6, 5] => 1
[5, 4] => 1
[4, 5] => 1
All tie, return the one with the lowest index: 0
Within a few minutes, I had a really nice one line solution in Raku. The only problem is it was solving a different problem to the one actually being posed. People, you have to actually read the spec carefully before you start coding!
So the actual solution is a bit bigger but still quite traightforward I think. We take input
from the command-line with $x
and $y
first followed by the points in the format x,y
.
First we define two variables to hold the information we are going to need along the way; the smallest
Manhattan distance seen so far and the (index of the) point with the smallest Manhattan distance.
$smallestDistance
is initialized to the largest possible value, namely infinity. $smallestPoint
is initialized
to -1
because without evidence to the contrary, we are going to assume a valid point has not been found.
my $smallestDistance = ∞;
my $smallestPoint = -1;
Now iterating through @points
by index with .keys()
...
for @points.keys -> $point {
We split each point that we got from the command-line into its x and y coordinates and convert them into integers.
my ($x1, $y1) = @points[$point].split(',')».Int;
If either the x coordinate equals $x
or the y coordinate equals $y
, we have a valid point.
if $x1 == $x || $y1 == $y {
We find the Manhattand distance between this poin and the one specified by $x,$y
.
my $distance = manhattanDistance($x, $y, $x1, $y1);
The Manhattan Distance is very simple to calculate. I could have done it inline but chose to make it into a separate function that looks like this, a straightforward translation of the formula given in the spec.
sub manhattanDistance($x, $y, $x1, $y1) {
return ($x1 - $x).abs + ($y1 - $y).abs;
}
Back to MAIN()
, if this Manhattan distance was smaller than the current smallest one, it becomes the
new $smallestDistance
. I originally had <=
but this is wrong because the spec says if two points are
equally distant, the earlier one is to be preferred. Also, the index of the current point becomes the new
$smallestPoint
.
if $distance < $smallestDistance {
$smallestDistance = $distance;
$smallestPoint = $point;
}
}
}
Finally, we print the value of $smallestPoint
.
say $smallestPoint;
This is the Perl version. It is pretty much the same as Raku.
sub manhattanDistance($x, $y, $x1, $y1) {
return abs($x1 - $x) + abs($y1 - $y);
}
my ($x, $y, @points) = @ARGV;
my $smallestDistance = 'Inf';
my $smallestPoint = -1;
for my $point (keys @points) {
my ($x1, $y1) = split /,/, $points[$point];
if ($x1 == $x || $y1 == $y) {
my $distance = manhattanDistance($x, $y, $x1, $y1);
if ($distance < $smallestDistance) {
$smallestDistance = $distance;
$smallestPoint = $point;
}
}
}
say $smallestPoint;