Josiah Winslow solves Advent of Code

Lens Library

Published: 2025-10-27 Original Prompt

Part 1

So we’re doing some math on ASCII values. We’ll need to use ord to get the ASCII value of a character; with this in hand, we can just do exactly what it says in the prompt.

2023\day15\solution.py
def holiday_hash(st: str) -> int:
result = 0
for c in st:
result += ord(c)
result *= 17
result %= 256
return result
class Solution(StrSplitSolution):
separator = ","
def part_1(self) -> int:
return sum(map(holiday_hash, self.input))

Tip

Instead of using a generator expression within the sum, I use the map function.

map(function, iterable) will take each item from iterable, and yield function applied to the item one by one. It’d be the same as writing (function(item) for item in iterable), and it’s sometimes more convenient.

Part 2

This puzzle really reminded me of my Data Structures and Programming course in college. If you’re not aware, the HASHMAP described by the prompt is one way to implement a hash map1 — the Python equivalent of which would be a dict — and the HASH algorithm from Part 1 is its hash function!

And in fact, specifically because we’re using Python, it’s convenient here to use dicts to represent our boxes of lenses. As of Python 3.7, dicts preserve insertion order2 — which is exactly the behavior we want our boxes of lenses to have.

The order of the boxes themselves within the line of boxes doesn’t matter for the answer, so we can use a defaultdict instead of a list to make our lives a bit easier. So our line of lens boxes will be a defaultdict of dicts (which themselves map str labels to int focal lengths).

The code for parsing the input and updating the lenses is simple from here.

2023\day15\solution.py
from collections import defaultdict
class Solution(StrSplitSolution):
...
def part_2(self) -> int:
boxes: dict[int, dict[str, int]] = defaultdict(dict)
for step in self.input:
# If step looks like "abc-"
if "-" in step:
label = step[:-1]
boxes[holiday_hash(label)].pop(label, None)
# If step looks like "abc=6"
elif "=" in step:
label, val_str = step.split("=")
boxes[holiday_hash(label)][label] = int(val_str)
else:
raise ValueError(f"unrecognized step: {step}")

Doing a sum over a nested generator gives us our answer. (Note that I add 1 to the box_id and lens_pos, because I want their indices starting from 1 instead of 0.)

2023\day15\solution.py
...
class Solution(StrSplitSolution):
...
def part_2(self) -> int:
...
return sum(
(box_id + 1) * (lens_pos + 1) * focal_length
for box_id, box in boxes.items()
for lens_pos, focal_length in enumerate(box.values())
)

Today definitely wasn’t as hard as I thought it’d be, but that’s probably because of Python’s dict behavior being so convenient. I would imagine that some more code would be required in any other language.

Footnotes

  1. Specifically, it describes a hash map where collision resolution is done by separate chaining. Look into it, if you’re into that sort of thing!

  2. Technically this behavior existed in Python 3.6, but only as an implementation detail of CPython 3.6 — the default and most widely used implementation of Python. Python 3.7 made this behavior official.