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.
- more-itertools provides functions for chunking and windowing collections.
- pyparsing provides PEG parser functionality with intuitive BNF syntax using Python's syntactic-sugar magic.
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!