Weekly Challenge: Perl has classes now 👍




Weekly Challenge 334

Each week Mohammad S. Anwar sends out The Weekly Challenge, a chance for all of us to come up with solutions to two weekly tasks. My solutions are written in Python first, and then converted to Perl. It’s a great way for us all to practice some coding.

Challenge, My solutions



Task 1: Range Sum



Task

You are given a list integers and pair of indices..

Write a script to return the sum of integers between the given indices (inclusive).



My solution

This is relatively straight forward, so doesn’t require too much explanation. For the input from the command line, I take the x and y values from the last two numbers, and the rest is the list of integers.

I first check that the x and y values are valid (between 0 and one less than the length of the integers) and that x is less than or equal to y.

I then use the sum function and list slicing to get the sum of the integers. Since the second value in Python is the stop value, I add one to achieve the desired result.

def range_sum(ints: list, x: int, y: int) -> int:
    if x < 0 or x >= len(ints):
        raise ValueError(f"x must be between 0 and len(ints) - 1, got x")
    if y < 0 or y >= len(ints):
        raise ValueError(f"y must be between 0 and len(ints) - 1, got y")
    if x > y:
        raise ValueError(
            f"x must be less than or equal to y, got x=x, y=y")

    return sum(ints[x:y + 1])
Enter fullscreen mode

Exit fullscreen mode

The Perl solution follows the same logic, slightly different syntax

use List::Util 'sum';

sub main (@ints) 
    my $y = pop @ints;    # Last element is y
    my $x = pop @ints;    # Second last element is x

    if ( $x < 0 or $x > $#ints ) 
        die "x must be between 0 and " . $#ints . ", got $x\n";
    
    if ( $y < 0 or $y > $#ints ) 
        die "y must be between 0 and " . $#ints . ", got $y\n";
    
    if ( $x > $y ) 
        die "x must be less than or equal to y, got x=$x, y=$y\n";
    

    return sum( @ints[ $x .. $y ] );

Enter fullscreen mode

Exit fullscreen mode



Examples

$ ./ch-1.py -2 0 3 -5 2 -1 0 2
1

$ ./ch-1.py 1 -2 3 -4 5 1 3
-3

$ ./ch-1.py 1 0 2 -1 3 3 4
2

$ ./ch-1.py -5 4 -3 2 -1 0 0 3
-2

$ ./ch-1.py -1 0 2 -3 -2 1 0 2
1
Enter fullscreen mode

Exit fullscreen mode



Task 2:



Task

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|.



My solution

For input from the command line, I take the first two values as x and y and the remaining values as the x and y for each point.

Python 3.7 introduced dataclasses which are a special class that are designed to hold data. For this task, I create a Point class that hold values for x and y. I also create a magic method for subtracting two points to get the Manhattan distance.

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

    def __sub__(self, other: 'Point') -> int:
        return abs(self.x - other.x) + abs(self.y - other.y)
Enter fullscreen mode

Exit fullscreen mode

This then makes the function a lot more straight forward. I convert x and y into a Point object called starting_point. I also convert the points_list into a list of Point objects called points.

I set the value min_distance to None, and min_index to -1.

I then iterate through the points. If they don’t share the x and y with the starting_point, I skip it. I calculate the Manhattan distance in a variable called distance. If this is less than the current min_distance value, I update the min_distance and min_index values.

def shortest_index(x: int, y: int, points_list: list[list[int]]) -> int:
    starting_point = Point(x, y)
    points = [Point(*point) for point in points_list]

    min_distance = None
    min_index = -1

    for index, point in enumerate(points):
        if point.x != starting_point.x and point.y != starting_point.y:
            continue

        distance = starting_point - point
        if min_distance is None or distance < min_distance:
            min_distance = distance
            min_index = index

    return min_index
Enter fullscreen mode

Exit fullscreen mode

For the Perl solution, I spent a bit of time looking at the new class statement that has been introduced in Perl 5.38. Yes, Perl has had OOP class since Perl 5.4 (using the bless statement) and more recently Moose/Moo, but this task gave me time to look at native classes.

I create a class called Point. Unsurprisingly, Copilot got a little confused and tried to tried write Moose-style classes.

use feature 'class';
no warnings 'experimental::class';

class Point 
    field $x : param;
    field $y : param;

    method x  return $x; 
    method y  return $y; 

    method distance_to($other) 
        return abs( $x - $other->x ) + abs( $y - $other->y );
    

Enter fullscreen mode

Exit fullscreen mode

The main function follows the same logic as the Python code.

sub main (@ints) 
    my $x           = shift @ints;    # First element is x
    my $y           = shift @ints;    # Second element is y
    my @points_list = ();
    for ( my $i = 0 ; $i < $#ints ; $i += 2 ) 
        push @points_list, [ $ints[$i], $ints[ $i + 1 ] ];
    

    my $starting_point = Point->new( x => $x, y => $y );
    my @points = map  Point->new( x => $_->[0], y => $_->[1] )  @points_list;

    my $min_distance = undef;
    my $min_index    = -1;

    for my $index ( 0 .. $#points ) 

    say $min_index;

Enter fullscreen mode

Exit fullscreen mode

Classes are still experimental in Perl 5.38 and 5.40, and it doesn’t seem possible to overload functions like you can with the old blessed functions. But definitely a nice addition for programmers of other languages.



Examples

$ ./ch-2.py 3 4 1 2 3 1 2 4 2 3
2

$ ./ch-2.py 2 5 3 4 2 3 1 5 2 5
3

$ ./ch-2.py 1 1 2 2 3 3 4 4
-1

$ ./ch-2.py 0 0 0 1 1 0 0 2 2 0
0

$ ./ch-2.py 5 5 5 6 6 5 5 4 4 5
0
Enter fullscreen mode

Exit fullscreen mode



Source link