Advent of Code’s inputs are usually provided in a standardised format where the input file can be processed line by line. The last few years I’ve used a custom built function that reads the input file and transforms its lines into a usable format. This allows me to focus on solving the puzzle and not worry about how to read the input each morning.

import os
import sys
 
 
def read_input(day, map_fn=str, example=False):
    """
    Read puzzle input for file `/inputs/day_{day}.txt'
    and apply transformer function to each line.
 
    :param int day: number of the day
    :param func map_fn: a function that is run for each line in the input
    :param boolean example: if True, read example input instead
    """
 
    try:
        if example:
            filename = f'day_{day}_example.txt'
        else:
            filename = f'day_{day}.txt'
        with open(os.path.join('..', 'inputs', filename)) as input_file:
            return [map_fn(line.strip()) for line in input_file]
    except FileNotFoundError as e:
        print(e)
        sys.exit(1)

My read_input function takes in two three arguments:

  • day is the number of the day which is used to define which input file is read. A side effect of this is that it documents at the start of the code file which day’s solution it is.
  • map_fn is a function (defaulting to str which is effectively a no-op) that accepts a line of input as a string and returns a desired data format.
  • example is a flag that allows me to easily switch between reading the provided example input and the actual puzzle input.

For example, if the input for day 1 is a list of numbers per line like this:

10
15
20
22
15
17
23

I would read it in as

from utils import read_input
 
numbers = read_input(1, int)
print(numbers)
# [10, 15, 20, 22, 15, 17, 23]

I can use built-in functions or write my own as long. I often use named tuples as my data structure in these puzzles so for a more complex example, I could do the following:

a,5
b,7
c,1
d,5
from utils import read_input
from collections import namedtuple
 
Entry = namedtuple('Entry', ['id', 'value'])
 
def map_fn(line):
  id, value = line.split(',')
  return Entry(id=id, value=value)
 
entries = read_input(2, map_fn)
print(entries)
# [Entry(id='a', value='5'), 
#  Entry(id='b', value='7'),
#  Entry(id='c', value='1'),
#  Entry(id='d', value='5')]

Writing these input mapper functions as the first thing of the day gives me structure in designing how to map the input and puzzle description into a data structure that would work best. I often go back and change things around as I learn more about the solution once I actually start writing the code.