def load_data(file): with open(file) as f: data = f.read() return [d for d in data.split("\n") if len(d) > 0] def sign(x): return 1 if abs(x) == x else -1 class Keypad: def __init__(self): keypad = self.build_keypad() self.M = len(keypad) self.N = len(keypad[0]) self.keypad = keypad def build_keypad(self): raise NotImplementedError() def get_pos(self, key: str): for i in range(self.M): for j in range(self.N): if self.keypad[i][j] == key: return (i, j) raise ValueError(f"Key {key} not found") def get_distance(self, key_a: str, key_b: str): pos_a = self.get_pos(key_a) pos_b = self.get_pos(key_b) dx = pos_b[0] - pos_a[0] dy = pos_b[1] - pos_a[1] return (dx, dy) def __repr__(self): return "\n".join([str(k) for k in self.keypad]) def pprint(self): print("\n".join([str(k) for k in self.keypad])) def find_sequence(self, key, next_key): """Finds the valid sequence between 2 key presses. Sequence returend is a string of button pushes.""" start_pos = self.get_pos(key) if key == next_key: return "" distance = self.get_distance(key, next_key) X = distance[0] Y = distance[1] # Strategy is to always try going >>>>, ^^^^^ until reaching, or other way around # Keep track of the things you pass by, if you pass by an illegal move, then no good seq_1 = [] # Actual button presses pass_by_1 = [] # Keeps track of keys visited in sequence i, j = start_pos for dx in range(abs(X)): i += 1*sign(X) pass_by_1.append(self.keypad[i][j]) seq_1.append("v" if sign(X) == 1 else "^") for dy in range(abs(Y)): j += 1*sign(Y) pass_by_1.append(self.keypad[i][j]) seq_1.append(">" if sign(Y) == 1 else "<") # if not None in pass_by: # return "".join(sequence) seq_2 = [] pass_by_2 = [] i, j = start_pos for dy in range(abs(Y)): j += 1*sign(Y) pass_by_2.append(self.keypad[i][j]) seq_2.append(">" if sign(Y) == 1 else "<") for dx in range(abs(X)): i += 1*sign(X) pass_by_2.append(self.keypad[i][j]) seq_2.append("v" if sign(X) == 1 else "^") if None in pass_by_1: return "".join(seq_2) elif None in pass_by_2: return "".join(seq_1) # We have a tie-break, so compute the cost of pressing the first key and tie-break based on that cost = { "^": 0, ">": 0, "v": 1, "<": 2, } cost_1 = cost[seq_1[0]] cost_2 = cost[seq_2[0]] # I swear i thought this should be <= but can't figure out why its >= # seq = seq_1 if cost_1 <= cost_2 else seq_2 seq = seq_1 if cost_1 >= cost_2 else seq_2 # if cost_1 != cost_2: # print(seq_1) # print(seq_2) # print(seq) # print() return "".join(seq) def find_full_sequence(self, sequence: str): # A robot always points at the A key first start_key = "A" full_sequence = [] key = start_key for next_key in sequence: full_sequence.append(self.find_sequence(key, next_key)) key = next_key return "A".join(full_sequence) + "A" # Account for each button press needed and final button press class NumericKeypad(Keypad): def build_keypad(self): return [ ["7", "8", "9"], ["4", "5", "6"], ["1", "2", "3"], [None, "0", "A"], ] class DirectionalKeypad(Keypad): def build_keypad(self): return [ [None, "^", "A"], ["<", "v", ">"], ] def complexity(code, sequence): l = len(sequence) n = int(code.split("A")[0]) # print(l, n) return n*l file = "input.txt" # file = "test.txt" data = load_data(file) dir_keypad = DirectionalKeypad() num_keypad = NumericKeypad() total = 0 for code in data: seq = num_keypad.find_full_sequence(code) # print(code) # print(seq) for _ in range(2): seq = dir_keypad.find_full_sequence(seq) # print(seq) c = complexity(code, seq) total += c # print(seq) print(total) ## Part 2 # Simulating the whole process won't work, grows exponentially, must be more clever than that