Puzzle #1

 9th July 2023 at 11:31am

First part

I struggled to solve the first puzzle in little time, which has definitely not been habitual for the last three years. I have never done the 2016 edition of the Advent of Code, but that year's was unusually hard for me. Only on Monday did I take on it again (and solve it), during the bus trip back to Maribor.

At first, I tried through cleverness to figure out some sort of pattern that would model the problem quickly: since there would only be consecutive perpendicular turns, the x and y coordinates would alternate in changing. This would be helpful in the case of having a for loop over each instruction of the input: for an even iteration, the y coordinate changes, and the x would otherwise. However, I had lots of trouble figuring out whether it should change in the positive or negative direction; I did not find any elegant/logical way to derive that information.

Thus, I remembered a question Tim Ferriss asks regularly: "if this were easy, how would it look like?" It's a great trigger for stepping back and trying to simplify the current situation. And so, I hardcoded some variables, which is not necessarily a bad thing; in fact, it should help external comprehension of my solution.

CARDINAL_SIGNS = ['N', 'E', 'S', 'W']

Based on this, I defined the initial state: INITIAL_STATE = ['N', 0, 0], and there's also the INPUT variable, a string containing the problem input (R1, L2, R12, L3,...), which is afterwards split on the , characters. The CARDINAL_SIGNS list should be traversed either in clockwise or counter-clockwise direction; this is determined by a lambda function, shift:

shift = lambda direction: 1 if direction == 'R' else -1

If we're turning right, direction is positive, thus N -> E -> ...; otherwise, N -> W -> ....

dirs = INPUT.split(', ')
cur_card, x, y = list(INITIAL_STATE)
for i in range(len(dirs)):
    # ARGH! amount can be more than one digit
    direction, amount = dirs[i][0], int(dirs[i][1:])
    # get new cardinal sign orientation index
    new_orientation_index = (CARDINAL_SIGNS.index(cur_card[0]) + shift(direction)) % 4
    cur_card = CARDINAL_SIGNS[new_orientation_index]
    # 'N' and 'E' shift the coordinate in the positive axis;
    # otherwise, the coordinates move negatively
    sign = 1 if cur_card in CARDINAL_SIGNS[:2] else -1
    if i % 2 == 0:
        y += sign * amount
    else:
        x += sign * amount

There are some things to point out:

  • I was getting the wrong answer because I assumed amount would only be one digit (in fact, I did not necessarily assume, so much as I just didn't account for any other possibility.
  • the code depends on a for loop; I'd like to do this more functionally.

I still haven't done any further puzzles, because I'd like to rework this solution into a more functional and clean solution. I've put the work in, but, of course, I refactored without any intermediate testing – and now it's broken.

def iterate_through_coordinates_input(
        state: list[str, int, int, str],
        coords: list[str],
        ) -> int:
    cur_dir, x, y, coord_to_change = state
    if coords:
        direction, amount = coords[0][0], int(coords[0][1:])
        sign = 1 if cur_dir in NESW[:2] else -1
        return iterate_through_coordinates_input(
                [
                    NESW[(NESW.index(cur_dir) + shift(direction)) % 4],
                    x + sign * amount if coord_to_change == 'x' else x,
                    y + sign * amount if coord_to_change == 'y' else y,
                    'x' if coord_to_change == 'y' else 'y',
                ],
                coords[1:]
                )
    else:
        print(abs(x) + abs(y))

I ran both scripts alongside one another, but the bug was quite tricky: the sign was being calculated before the new direction (NESW[(NESW.index(cur_dir) + shift(direction)) % 4], which is not too legible...) was updated. So, after some further refactoring,

shift = lambda direction: 1 if direction == 'R' else -1
sign = lambda cur_dir: 1 if cur_dir in NESW[:2] else -1

def iterate_through_coordinates_input(
        state: list[str, int, int, str],
        coords: list[str],
        ) -> int:
    cur_dir, x, y, axis = state
    if coords:
        direction, amount = coords[0][0], int(coords[0][1:])
        new_dir = NESW[(NESW.index(cur_dir) + shift(direction)) % 4]
        return iterate_through_coordinates_input(
                [
                    new_dir,
                    x + sign(new_dir) * amount if axis == 'y' else x,
                    y + sign(new_dir) * amount if axis == 'x' else y,
                    'y' if axis == 'x' else 'x',
                ],
                coords[1:]
                )
    else:
        print(abs(x) + abs(y))

Feels good! I might have to rework the solution for part 2, but I'm exhausted for today (it's already Wednesday...)

Second part

For the second part, it is necessary to get all the positions between any two starting and ending positions. On the first working version of the code, I had

        if i % 2 == 0:
            y += sign * amount
            new_positions = [[x, j] for j in range(old_y + sign, y + sign, sign)][::sign]
        else:
            x += sign * amount
            new_positions = [[j, y] for j in range(old_x + sign, x + sign, sign)]

This feels nice, but maybe a little redundant – it has the same vices as the other version of the code. I'd love to work on this further, but maybe some other time. Everyone else is already on Puzzle #3, so I'd rather just move on.