Jesper Dramsch , , read in 3 minutes

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 to max in range(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.