Lens Library
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.
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.
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.)
...
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
-
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! ↩
-
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. ↩