Learning Python with Advent of Code Walkthroughs

Dazbo's Advent of Code solutions, written in Python

Bathroom Security

Advent of Code 2016 - Day 2

Day 2: Bathroom Security

Useful Links

Concepts and Packages Demonstrated

NumPyLoggingTiming and ProgressReading files

Problem Intro

The elves are having trouble with a keypad in a bathroom. They need your help to figure out the correct code. The keypad is a 3x3 grid of numbers:

1 2 3
4 5 6
7 8 9

You are given a series of instructions, where each instruction is a sequence of movements (U, D, L, R). ‘U’ means move up, ‘D’ means move down, ‘L’ means move left, and ‘R’ means move right. You start at the ‘5’ button. If a movement would take you off the keypad, you stay in your current position. At the end of each line of instructions, you record the number of the button you are currently on. The final code is the sequence of recorded button presses.

For example, the input data might look like this:

ULL
RRDDD
LURDL
UUUUD

Part 1

What is the bathroom code?

My approach to solving this problem involves representing the keypad as a 2D NumPy array. This allows for easy navigation and boundary checking.

Keypad Representation

keypad = np.arange(1, 10).reshape(3, 3)

This creates a 3x3 NumPy array:

[[1 2 3]
 [4 5 6]
 [7 8 9]]

The core logic for navigating the keypad and recording button presses is encapsulated in the get_combo function.

def get_combo(keypad: np.ndarray, instructions: list, row: int, col: int) -> list:
    rows, cols = keypad.shape
    keypresses = []

    for line in instructions:
        for char in line:
            # Apply movement
            col += NavigationConstants.VECTORS[char][0]
            row += NavigationConstants.VECTORS[char][1]

            # Boundary checking: if off the edge, revert to edge
            if col < 0:
                col = 0
            if col > cols - 1:
                col = cols - 1
            if row < 0:
                row = 0
            if row > rows - 1:
                row = rows - 1

        keypresses.append(str(keypad[row][col]))

    return keypresses

Part 1 Solution

    # Part 1
    # initialise the array in the form of a numeric keypad, with digits 1-9
    keypad = np.arange(1, 10).reshape(3, 3)

    # set starting position to be digit 5 (row 1, col 1 in 0-indexed array)
    row = col = 1
    keypresses = get_combo(keypad, data, row, col)
    logging.info("".join(keypresses))

Part 2

The elves have a new, more complex keypad:

    1
  2 3 4
5 6 7 8 9
  A B C
    D

The rules for navigation are the same, but now some positions are empty. If a movement would take you to an empty position, you stay in your current position.

What is the new bathroom code?

Keypad Representation (Part 2)

For Part 2, the keypad is represented as a 5x5 NumPy array, with empty spaces represented by " ":

keypad = np.array([[" ", " ", "1", " ", " "],
                   [" ", "2", "3", "4", " "],
                   ["5", "6", "7", "8", "9"],
                   [" ", "A", "B", "C", " "],
                   [" ", " ", "D", " ", " "]])

Modified Navigation Logic

The get_combo function is reused, but with an additional check to handle empty spaces:

            # if current array position is ' ', then reverse (ignore) last instruction
            if str(keypad[row][col]) == ' ':
                col -= NavigationConstants.VECTORS[char][0]
                row -= NavigationConstants.VECTORS[char][1]

If a movement leads to an empty space, the row and col are reverted to their previous values, effectively ignoring the invalid move.

Part 2 Solution

    # Part 2
    # initialise the array with the weird keypad
    keypad = np.array([[" ", " ", "1", " ", " "],
                       [" ", "2", "3", "4", " "],
                       ["5", "6", "7", "8", "9"],
                       [" ", "A", "B", "C", " "],
                       [" ", " ", "D", " ", " "]])
    # set starting position to be '5' (row 2, col 0 in 0-indexed array)
    row = 2
    col = 0
    keypresses = get_combo(keypad, data, row, col)
    logging.info("".join(keypresses))

The starting position for Part 2 is ‘5’, which corresponds to (row=2, col=0) in the 0-indexed NumPy array.

Conclusion

This problem effectively demonstrates the utility of NumPy arrays for grid-based problems, especially when combined with clear navigation logic and boundary/validity checks. The solution is concise and handles both parts of the puzzle by simply changing the keypad representation and initial position.