Advent of Code 2021: Day 5 - Hydrothermal Venture

Advent of Code 2021 - Day 5 - Hydrothermal Venture

Day 5 puzzle description

Day 5 puzzle description can be found on my Github.

My solution in Python

from collections import Counter
from dataclasses import dataclass
from typing import List

class Point:
    x: int
    y: int

    def from_string(cls, s: str):
        x_str, y_str = s.split(",")
        return cls(x=int(x_str), y=int(y_str))

class Line:
    start: Point
    end: Point

    def from_string(cls, s: str):
        start, end = s.split(" -> ")
        return cls(start=Point.from_string(start), end=Point.from_string(end))

    def is_horizontal(self) -> bool:
        return self.start.y == self.end.y

    def is_vertical(self) -> bool:
        return self.start.x == self.end.x

    def is_diagonal(self) -> bool:
        return abs(self.start.x - self.end.x) == abs(self.start.y - self.end.y)

    def all_points(self) -> List[Point]:
        if self.is_vertical:
            step = 1 if self.start.y < self.end.y else -1
            return [
                Point(x=self.start.x, y=i)
                for i in range(self.start.y, self.end.y + step, step)
        elif self.is_horizontal:
            step = 1 if self.start.x < self.end.x else -1
            return [
                Point(x=i, y=self.start.y)
                for i in range(self.start.x, self.end.x + step, step)
        elif self.is_diagonal:
            # TODO: Can it be somehow simplified?
            x_step = 1 if self.start.x < self.end.x else -1
            y_step = 1 if self.start.y < self.end.y else -1
            x_coords = (x for x in range(self.start.x, self.end.x + x_step, x_step))
            y_coords = (y for y in range(self.start.y, self.end.y + y_step, y_step))
            return [
                Point(x=coords[0], y=coords[1]) for coords in zip(x_coords, y_coords)

def get_solution(lines: List[Line], condition) -> int:
    filtered_lines = list(filter(condition, lines))
    points = [point for line in filtered_lines for point in line.all_points]
    return sum(1 for v in Counter(points).values() if v >= 2)

def get_data_from_text_file(path: str) -> List[Line]:
    with open(path, "rt") as f:
        data = f.readlines()

    return list(map(lambda line: Line.from_string(line), data))

if __name__ == "__main__":
    part_1_condition = lambda line: line.is_vertical or line.is_horizontal  # noqa
    part_2_condition = (
        lambda line: line.is_vertical or line.is_horizontal or line.is_diagonal
    )  # noqa
    lines_from_file = get_data_from_text_file("input.txt")
    print(get_solution(lines_from_file, part_1_condition))
    print(get_solution(lines_from_file, part_2_condition))

Quick recap

Heroes of this puzzle's solution: dataclasses and collections.Counter.

I modelled each point as a frozen dataclass with x and y coordinates. frozen=True for dataclass makes its instances hashable. When they are hashable, then they can be counted with Counter data structure. Having count of all points, getting final puzzle solution was trivial - you just needed to compute amount of points with count >= 2.

And that's it for day 5 of Advent of Code 2021 :-).

Take care,