Just some grain rain.
What is Advent of Code?
In this little series, I want to write about the thought processes I have behind solving the puzzles presented in Advent of Code.
During the Advent of Code small coding puzzles are presented with varying levels of difficulty. They're always Christmas-themed text puzzles where you help elves out of some mess to save the holidays. The puzzles are deliberately ambiguous to not bias puzzlers into a certain solution, they always have an example input in the text to explain some details and an input file for each day that comes in the form of a text file you can download. The solution is a simple number or a string you enter, which is checked by the website automatically. After you finish part 1, part 2 unlocks, which is a variation on part 2. This can be a little twist on part 1 or a large twist that changes the problem formulation into a problem that would take brute force solutions to compute longer than the heat death of the universe would take.
Find my solutions here: github.com/JesperDramsch/advent-of-code
These descriptions won't be a perfect narrative, but rather how I think about these problems and where I see different solutions. I apologise for the possibility of rambling!
The example input
When I saw today's example input, I was worried!
498,4 -> 498,6 -> 496,6 503,4 -> 502,4 -> 502,9 -> 494,9
But my parser of "lists of lists" already worked on this and then I just needed to split and convert the coordinates to integers.
The implementation
This hoenstly felt pretty straight-forward.
I implemented a dictionary with the given values, but ignored air. In the same step I also got the maximum depth.
Then I just used a while loop to drop the sand grains down to their position with three if statements checking for the middle, left and right.
Then I just repeated that until the first grain dropped below the maximum depth.
def sand_grain(grid, max_depth): loc = complex(500) while loc.imag < max_depth + 1: if loc + 1j not in grid: loc += 1j elif loc -1 + 1j not in grid: loc += -1 +1j elif loc + 1 + 1j not in grid: loc += 1 + 1j else: grid.add(loc) return grid return False
Part Deux
In part 2, I extended this logic with a floor:
def sand_grain(grid, max_depth, floor=None): loc = complex(500) if floor is not None: max_depth = floor while loc.imag < max_depth + 1: if loc.imag + 1 == floor: grid.add(loc) return grid ...
This allowed me to not assume a width of the stone floor, but simply used an additional logic step.
Refactor
I wasn't happy with the dictionary implementation, the deep if structure and using x,y when complex numbers would work.
The refactor is already live and a little cleaner.
The part 2 is still fairly slow, and there is a way to calculate it as triangles, but we have to subtract the negative space under horizontal parts and that's a bit of work.
Error Log
Well... that worked out alright!
- Not an error, but I did get a wrong intermittent result, when I forgot to change
min
tomax
inrange(min(b,y), max(b,y) + 1):
if not grid and start in grid: TypeError: argument of type 'bool' is not iterable
want to make it work with the existing function, but messed up one return.- Wrong answer for example part 2, but that was just a off-by-one error in the floor.