Advent Time Again

Advent of Code
Author

Ryan Heslin

Published

December 1, 2023

It’s Advent of Code time again!

Once again, thousands of programmers around the world are rearranging their lives to solve a series of Christmas-themed puzzles. As I write this, only one puzzle has been released, but this already promises to be an interesting year.

This year, I’ll write a brief post explaining how I solved each puzzle. We start with day 1.

This year’s first puzzle was unexpectedly hard. Part 1 is a simple string manipulation problem. The input consists of a file where each line has lowercase letters and single digits. You just have to read the first and last integers on each line of a file, combine them into a string, read the resulting integer, and sum them.

```{python}
import re

from utils.utils import split_lines

raw = split_lines("inputs/day1.txt")
# No zeroes
naturals = r"[1-9]"
stripped = [re.sub("[a-z]+", "", line) for line in raw]
part1 = sum(int(line[0] + line[-1]) for line in stripped)
print(part1)
```

Part 2 reveals a nasty surprise. You no longer have to just search for digits, but written numbers. one and 1 now both mean 1. I created an awkward regular expression to handle this, then confidently submitted my answer. Wrong. I checked my code on the provided test case, and it was correct.

This happens to me several times each year, but rarely on day 1! I racked my mind for a pathological edge case that would break my code. Immediately, I thought of overlapping matches. By the rules, a string like eightwo should be parsed as 82, but a conventional pattern would match eight, move on to w, and fail to match any more substrings from the string. I needed an overlapping pattern to get the correct answer.

As with most problems, the answer to this one turned out to lie in a purple StackOverflow link. I tweaked my pattern, reran my code, and submitted a new, slightly higher answer. Correct!

```{python}
nums = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
n = len(nums)
parts = "|".join(nums + [naturals])
pattern = f"(?=({parts}))"
chars = list(map(str, range(1, 1 + n)))
keys = dict(zip(nums, chars))

matches = [re.findall(pattern, line) for line in raw]
part2 = sum(
    int(keys.get(r[0], r[0]) + keys.get(r[-1], r[-1])) if r else 0 for r in matches
)
print(part2)
```

All in all, it was a typical Day 7 or 8 puzzle, which made it a nasty surprise as a Day 1 puzzle. So Day 2 should be easy by comparison - right?