Day 3: Gear Ratios
Puzzle
Part 1
Today’s puzzle involved some annoying parsing and string
/char list
traversals. Given a grid of numbers, symbols, and empty spots .
, we’re asked to find the sum of all numbers that are adjacent to at least one symbol. Adjacent, in this case, includes diagonals. As an example, in the following grid, all numbers except 114
and 58
should be included in the final sum.
There’re several ways to approach this problem.
- Two pass approach. First pass: find the locations of all symbols. Second pass: build all distinct numbers and add them to a collective sum if the number is adjacent to a symbol.
- “One” pass approach. Build all distinct numbers and add them to a collective sum if the number is adjacent to a symbol.
- This is slightly more performant for sparse graphs since empty spots (
.
) won’t need their adjacent cells searched. This results in overall fewer cells traversed.
- This is slightly more performant for sparse graphs since empty spots (
For simplicity of reasoning, this article will follow the first two-pass approach, which can be further broken down into distinct functions:
-
Compute the symbol positions
- I chose to store this in a
Set
for quick & easy lookup times. There’s some wonky OCaml syntax in order to work with theSet
module, including a custom moduleTup
(for tuples) and functors. Readers can largely ignore this syntax for the purposes of this solution.
- I chose to store this in a
-
Building a number from a “sequence”
- Working with a
char list
, a key part of the solution is extracting whole numbers (as opposed to digits). As we traverse the list, we keep track of the current “number-in-progress”. If we come across another digit, we know to multiply our current “number-in-progress” by 10 (left shift for base 10) and add the current digit. Otherwise, we’ve come to the end of the number. - Determining whether we should add this value to our sum or not is simple. We’ve already built a
Set
of symbol positions – we can directly lookup the 8 surrounding positions; if any of them match, we should add the number to the final sum.
- Working with a
Part 2
Part 2’s follow-up is an example of how a small change to the prompt can widely change your implementation (although in an ideal world, it wouldn’t. My code for today’s puzzle isn’t elegant).
Instead of finding the sum of all numbers adjacent to any symbol, we care only about those numbers that are adjacent to a gear. A gear is defined as a *
adjacent to exactly 2 numbers.
My first instinct (which ended up being the path I followed) is to flag all distinct numbers with an ID. If I can build a mapping from position to number, then the solution would be as simple as finding all *
’s and searching the adjacent 8 cells for 2 unique numbers.
Implementation wise, this part was very similar to Part 1. Some more refactoring can be done to clean up the code, but that’s for another day 😛. The new function num_positions
here maps grid positions (r,c)
to a tuple representing a unique id per number (used later to determine the count of unique numbers adjacent to a symbol) and the value itself.
Clearly, today’s solution was not the cleanest. Each function was heavily parameterized and overall difficult to read (it even took me some time to refamiliarize myself with my own solution). I’m going to leave this as is for now though: I’d like to keep a record of my old code for AoC solutions and compare them amongst different puzzles / years.