Advent of Code 2022: Day 0

by Michael Welborn on 2022-12-17

Before we get started on daily puzzles, let's lay a little groundwork for solving problems in Advent of Code. First up, the language and runtime.

Python

I'll be using Python (CPython 3.11 specifically) to implement solutions for AoC puzzles. It has a strong standard library, healthy ecosystem of 3rd-party packages, and static type checking. Plus 3.11 has some great performance improvements. But most importantly, I just enjoy the language.

Most of the heavy lifting will be done by Python's native types and standard library, but there are a couple packages on PyPI that will come in handy.

We can get all those with pip:

$ pip install more-itertools pyparsing

Solver

With our language, runtime, and packages sorted, let's provide some structure for our problem solving.

Each day from December 1st through December 25th a new Advent of Code puzzle becomes available. Each puzzle has two parts that describe the input and desired output. Both parts share the same input. The input is usually line-based, where each line represents one "thing" in a collection of "things".

With most of the puzzles following this pattern, we can create some structure to reuse for solving puzzles. Let's implement this as an abstract base class that we'll extend for each day's solution.

At the highest level, we want a Solver class with a solve method. This method should read the puzzle input, parse it, optionally transform it, and print the solutions to parts one and two using it.

from abc import ABC, abstractmethod


class Solver(ABC):
    def solve(self) -> None:
        self.read_input()
        self.parse_input()
        self.transform_input()
        print("Part one: ", self.solve_part_one())
        print("Part two: ", self.solve_part_two())

For reading the puzzle input, we'll use the argparse module in the standard library. Let's define an optional positional file argument that defaults to standard input. We'll read the file into aninput_text attribute, and split that into an input_lines attribute for line-based puzzles. We'll make sure to remove the conventional Unix trailing newline.

from abc import ABC, abstractmethod
from argparse import ArgumentParser, FileType
from sys import stdin


class Solver(ABC):
    ...

    input_text: str
    input_lines: list[str]

    def read_input(self) -> None:
        parser = ArgumentParser()
        parser.add_argument(
            "input",
            help="Input file. Defaults to stdin if omitted.",
            type=FileType("r"),
            nargs="?",
            default=stdin,
        )
        args = parser.parse_args()

        with args.input:
            self.input_text = args.input.read()
            self.input_lines = self.input_text.removesuffix("\n").split("\n")

The other methods we'll make abstract to be filled in for each day's solution, except the transformation which is optional.

class Solver(ABC):
    ...

    @abstractmethod
    def parse_input(self) -> None:
        pass

    def transform_input(self) -> None:
        pass

    @abstractmethod
    def solve_part_one(self) -> object:
        pass

    @abstractmethod
    def solve_part_two(self) -> object:
        pass

And with that, we're ready to import our ABC and define how to parse input and solve parts one and two. Let's get solving!