Home > Backend Development > Python Tutorial > How to trace steps in a map, Advent of Code ay 6

How to trace steps in a map, Advent of Code ay 6

DDD
Release: 2024-12-29 07:05:10
Original
163 people have browsed it

Back to my Advent of Code challenge, due to some unforeseeable issues, I am unable to complete the challenges in time and am currently about 5–6 days behind. However, I am still determined to complete the puzzles this year. Today, let’s discuss the 6th puzzle.

How to trace steps in a map, Advent of Code ay 6
Copilot generated image that somewhat suits the theme

Looking for things in a 2D plane seems to be a recurring theme for this year. Today, we are tracing the steps of a guard that has a clear, defined movement logic. The guard moves in a straight line and turns right when it is obstructed.

Assuming we are representing each step the guard takes as a point in a 2D plane, we can then represent each step in a different direction as vectors:

LEFT = (1, 0)
RIGHT = (-1, 0)
UP = (0, -1)
DOWN = (0, 1)

Copy after login
Copy after login

By performing some calculations as shown below, we obtain a matrix that represents the right rotation

How to trace steps in a map, Advent of Code ay 6
Deriving the rotation matrix

Originally, this was represented as a dictionary, since we will be heavily relying on it for numerous calculations. However, I wanted to ensure proper type annotations, hence this implementation for now.

class Rotation:
    c0r0: int
    c1r0: int
    c0r1: int
    c1r1: int

@dataclass(frozen=True)
class RotateRight(Rotation):
    c0r0: int = 0
    c1r0: int = 1
    c0r1: int = -1
    c1r1: int = 0
Copy after login
Copy after login

We also require a means to represent position and movement, along with methods to manipulate position and execute rotations:

from __future__ import annotations

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

    def __add__ (self, direction: Direction) -> Point:
        return Point(self.x + direction.x, self.y + direction.y)

@dataclass
class Direction:
    x: int
    y: int

    def __mul__ (self, rotation: Rotation) -> Direction:
        return Direction(
            self.x * rotation.c0r0 + self.y * rotation.c0r1,
            self.x * rotation.c1r0 + self.y * rotation.c1r1,
        )
Copy after login
Copy after login

Defining the dunder/magic methods __add__ and __mul__ enables both Point and Direction objects to be manipulated as if they were standard arithmetic operations within the code. The snippet also demonstrates how to effectively type-annotate the Rotation class.

Lastly, we define the models for our input:

class Symbol(Enum):
    Guard = "^"
    Obstruction = "#"

@dataclass
class Space:
    pass

@dataclass
class Guard:
    pass

@dataclass
class Obstruction:
    pass

@dataclass
class Board:
    tiles: dict[Point, Space | Guard | Obstruction]
    width: int
    height: int
Copy after login

Symbol is a standard Enum that encapsulates the meaning of various symbols within the input. Space, Guard, and Obstruction have self-explanatory meanings. Board is a representation of the map. My initial approach involved a more object-oriented design, but I ultimately opted for this implementation where each class simply maintains a state that can be readily inspected.

Let’s start by parsing the input:

def finder(board: tuple[str, ...], symbol: Symbol) -> Generator[Point, None, None]:
    return (
        Point(x, y)
        for y, row in enumerate(board)
        for x, item in enumerate(tuple(row))
        if item == symbol.value
    )

def parse(input: str) -> tuple[Board, Point]:
    board = tuple(input.strip().splitlines())

    return (
        Board(
            {point: Obstruction() for point in finder(board, Symbol.Obstruction)},
            len(board[0]),
            len(board),
        ),
        next(finder(board, Symbol.Guard)),
    )
Copy after login

The guard is represented by a Point object. The finder function is responsible for scanning the input and identifying the positions of the desired symbol. Similar solutions for 2D map parsing will continue to be explored in subsequent posts.

My solution for part 1 is relatively straightforward. To calculate the guard’s next step:

  1. Determine the desired destination directly in front of the guard, based on the current direction vector.
  2. Check if the desired destination is obstructed.
  3. If the desired destination is obstructed: We return the desired destination, and the current direction vector.
  4. If the desired destination is not obstructed: We return the current position, and the rotated direction vector.
LEFT = (1, 0)
RIGHT = (-1, 0)
UP = (0, -1)
DOWN = (0, 1)

Copy after login
Copy after login

Finally, we address part 1 of the challenge, which requires determining the number of unique tiles visited by the guard.

class Rotation:
    c0r0: int
    c1r0: int
    c0r1: int
    c1r1: int

@dataclass(frozen=True)
class RotateRight(Rotation):
    c0r0: int = 0
    c1r0: int = 1
    c0r1: int = -1
    c1r1: int = 0
Copy after login
Copy after login

Part 2 throws a curveball at us! We’re tasked with finding the perfect spot to place a new object on the map to make our robot patrol in a loop forever.

The good news is, finding the right spot isn’t rocket science. We know the robot’s initial path from Part 1, so we just need to place the object somewhere along that route.

Now, the tricky part: how do we know if we’ve successfully created a loop?

Here’s my approach:

  1. Track the robot’s moves: Instead of keeping track of its exact position, I focused on recording each “step” it takes. Each step is basically a move from one tile to the next.
  2. Look for repeating patterns: If the robot starts repeating the same sequence of steps, we’ve got a loop!
  3. Make sure it stays on the map: Crucially, we need to ensure the robot doesn’t wander off the map.
from __future__ import annotations

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

    def __add__ (self, direction: Direction) -> Point:
        return Point(self.x + direction.x, self.y + direction.y)

@dataclass
class Direction:
    x: int
    y: int

    def __mul__ (self, rotation: Rotation) -> Direction:
        return Direction(
            self.x * rotation.c0r0 + self.y * rotation.c0r1,
            self.x * rotation.c1r0 + self.y * rotation.c1r1,
        )
Copy after login
Copy after login

The reason I chose to store the steps in a dictionary is that the order of the steps doesn’t matter for this part of the problem (which hints at a similar concept in a later puzzle).

Since I needed to frequently check if a particular step had already occurred, storing the steps in a dictionary significantly improved performance. Dictionaries in Python are incredibly fast for lookups, making these checks much quicker compared to using a list or tuple.

My initial attempt, which used a different data structure, took around 70 seconds to run on all 8 cores of my machine. By switching to a dictionary, I was able to significantly reduce the runtime to just a few seconds. This demonstrates the power of choosing the right data structure for the task at hand!

That’s it for today. Considering the runtime improvement from 70 seconds on all cores to just a few seconds on a single core, I’m quite happy with the optimization, although I know further improvements are possible (ideally aiming for sub-second execution).

My previous attempt to score a job didn’t end well (yes, still #OpenToWork, ping me!), as I declined a request for additional assignment/test without compensation. Hopefully, things will improve significantly next year, and I shall write again next week.

The above is the detailed content of How to trace steps in a map, Advent of Code ay 6. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template