Skip to content

Commit

Permalink
d21 pt2 done
Browse files Browse the repository at this point in the history
  • Loading branch information
derailed-dash committed Dec 22, 2024
1 parent 8f1d170 commit 53a080d
Showing 1 changed file with 200 additions and 49 deletions.
249 changes: 200 additions & 49 deletions src/AoC_2024/Dazbo's_Advent_of_Code_2024.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7924,28 +7924,50 @@
},
{
"cell_type": "code",
"execution_count": 65,
"execution_count": 136,
"metadata": {},
"outputs": [],
"source": [
"\n",
"class KeypadMapping():\n",
" NUMERIC = \"numeric\"\n",
" DIRECTION = \"direction\"\n",
" \n",
" POINTS_FOR_DIRECTIONS = { \"^\": Point(0, -1), \n",
" \"v\": Point(0, 1), \n",
" \"<\": Point(-1, 0), \n",
" \">\": Point(1, 0) }\n",
" \n",
" DIRECTIONS_FOR_POINTS = {v: k for k, v in POINTS_FOR_DIRECTIONS.items()}\n",
" \n",
"\n",
" NUMERIC_KEYPAD = [\n",
" [\"7\", \"8\", \"9\"],\n",
" [\"4\", \"5\", \"6\"],\n",
" [\"1\", \"2\", \"3\"],\n",
" [None, \"0\", \"A\"]]\n",
"\n",
" DIRECTION_KEYPAD = [\n",
" [None, \"^\", \"A\"],\n",
" [\"<\", \"v\", \">\"]]\n",
" \n",
" def __init__(self, keypad: list[list[str]]):\n",
" self._keypad = keypad\n",
" self._width = len(keypad[0])\n",
" self._height = len(keypad)\n",
" self._point_to_button: dict[Point, str] = {} # E.g. {P(0,0): '7', P(1,0): '8',..., P(2,3): 'A'}\n",
" self._button_to_point: dict[str, Point] = {} # E.g. {'7': P(0,0), '8': P(1,0), ..., 'A': P(2,3)}\n",
" self._build_keypad_dict()\n",
" self._paths_for_pair = self._compute_paths_for_pair()\n",
"\n",
" self._paths_for_pair = self._compute_paths_for_pair() # All paths to get from one button to another\n",
" self._path_lengths_for_pair = {pair: len(paths[0]) for pair, paths in self._paths_for_pair.items()}\n",
" \n",
" @classmethod\n",
" def from_type(cls, keypad_type: str):\n",
" \"\"\" Factory method to create a KeypadMapping instance with predefined keypads. \"\"\"\n",
" \n",
" match keypad_type:\n",
" case KeypadMapping.NUMERIC: return cls(KeypadMapping.NUMERIC_KEYPAD)\n",
" case KeypadMapping.DIRECTION: return cls(KeypadMapping.DIRECTION_KEYPAD)\n",
" case _: raise ValueError(f\"Unknown keypad type: {keypad_type}\")\n",
" \n",
" def _build_keypad_dict(self):\n",
" \"\"\" Build a dictionary of keypad points and their associated keys. \"\"\"\n",
" \n",
Expand All @@ -7955,15 +7977,15 @@
" self._point_to_button[Point(c, r)] = key\n",
" self._button_to_point[key] = Point(c, r)\n",
" \n",
" def _compute_paths_for_pair(self):\n",
" def _compute_paths_for_pair(self) -> dict[tuple[str, str], tuple[str]]:\n",
" \"\"\" Precompute shortest set of paths of directions that take us from one point to all others.\n",
" E.g. {('7', '6'): {'>>vA', '>v>A', 'v>>A'}, ... }\n",
" \"\"\" \n",
" paths = {} # We will build our map, e\n",
" for start in self._button_to_point: # E.g. 7\n",
" for end in self._button_to_point: # E.g. 6\n",
" if start == end:\n",
" paths[start, end] = [\"A\"] # No need to move from a point to itself\n",
" paths[start, end] = tuple(\"A\") # No need to move from a point to itself\n",
" continue # Go to next end\n",
" \n",
" # Now BFS to get all paths from start to end\n",
Expand Down Expand Up @@ -7994,17 +8016,17 @@
" break # only executed if the inner loop DID break\n",
" \n",
" # Convert path lists to strings ending with \"A\"\n",
" paths[start, end] = {\"\".join(path)+\"A\" for path in all_paths\n",
" if len(path) == best_path_len}\n",
" paths[start, end] = tuple(\"\".join(path)+\"A\" for path in all_paths\n",
" if len(path) == best_path_len)\n",
" \n",
" logger.debug(f\"{paths=}\")\n",
" return paths\n",
" \n",
" @cache\n",
" def moves_for_sequence(self, code: str) -> tuple[str]:\n",
" \"\"\" Determine the shortest set of move sequences to get from one button to another. \n",
" E.g. with door code 029A. But remember that we start pointing at A. \n",
" Output looks like ('<A^A^^>AvvvA', ... )\n",
" \"\"\" Determine the shortest set of move sequences on button n+1 to tap out a sequence on button n.\n",
" Remember that we start pointing at A. \n",
" E.g. which presses are required on navigation remote 1 to enter `029A` into the numeric keypad?\n",
" - Output looks like ('<A^A^^>AvvvA', ... )\n",
" \"\"\"\n",
" \n",
" sequential_paths: list[set] = []\n",
Expand All @@ -8017,6 +8039,16 @@
" moves_for_seq = tuple(\"\".join(path) for path in product(*sequential_paths))\n",
" return moves_for_seq\n",
" \n",
" @property\n",
" def paths_for_pair(self) -> dict[tuple[str, str], tuple]:\n",
" \"\"\" Return the paths for each button pair. \"\"\"\n",
" return self._paths_for_pair\n",
" \n",
" @property\n",
" def path_length_for_pair(self) -> int:\n",
" \"\"\" Return the length of the shortest path for each button pair. \"\"\"\n",
" return self._path_lengths_for_pair\n",
" \n",
" def __str__(self):\n",
" return \"\\n\".join(\"\".join(str(row)) for row in self._keypad)\n",
" \n",
Expand All @@ -8026,29 +8058,19 @@
},
{
"cell_type": "code",
"execution_count": 68,
"execution_count": 137,
"metadata": {},
"outputs": [],
"source": [
"def complexity(code: str, seq_len: int) -> int:\n",
" return int(code[:-1]) * seq_len\n",
"\n",
"def solve(data, robot_directional_keypads=2) -> int:\n",
"def solve_part1(data, robot_directional_keypads=2) -> int:\n",
" door_codes = data\n",
" logger.debug(f\"{door_codes=}\")\n",
" \n",
" NUMERIC_KEYPAD = [\n",
" [\"7\", \"8\", \"9\"],\n",
" [\"4\", \"5\", \"6\"],\n",
" [\"1\", \"2\", \"3\"],\n",
" [None, \"0\", \"A\"]]\n",
"\n",
" DIRECTION_KEYPAD = [\n",
" [None, \"^\", \"A\"],\n",
" [\"<\", \"v\", \">\"]]\n",
" \n",
" numeric_keypad = KeypadMapping(NUMERIC_KEYPAD)\n",
" direction_keypad = KeypadMapping(DIRECTION_KEYPAD)\n",
" numeric_keypad = KeypadMapping.from_type(KeypadMapping.NUMERIC)\n",
" direction_keypad = KeypadMapping.from_type(KeypadMapping.DIRECTION)\n",
" \n",
" logger.debug(f\"\\n{numeric_keypad}\")\n",
" logger.debug(f\"\\n{direction_keypad}\")\n",
Expand All @@ -8057,7 +8079,6 @@
" for door_code in door_codes:\n",
" logger.debug(f\"{door_code=}\")\n",
" moves_for_robot_1 = numeric_keypad.moves_for_sequence(door_code)\n",
" logger.debug(f\"{moves_for_robot_1=}\")\n",
" \n",
" # From now on, all mappings are between direction keypads,\n",
" # so the mappings for direction buttons are always the same\n",
Expand All @@ -8073,13 +8094,10 @@
" min_len = min(map(len, moves_for_next_robot))\n",
" moves_for_next_robot = [move for move in moves_for_next_robot if len(move) == min_len]\n",
" next = moves_for_next_robot\n",
" \n",
" logger.debug(f\"{len(moves_for_next_robot[0])=}\")\n",
" \n",
" total_complexity += complexity(door_code, min_len)\n",
" \n",
" return total_complexity\n",
" "
" return total_complexity"
]
},
{
Expand All @@ -8100,13 +8118,13 @@
"\n",
"logger.setLevel(logging.DEBUG)\n",
"for curr_input, curr_ans in zip(sample_inputs, sample_answers):\n",
" validate(solve(curr_input.splitlines()), curr_ans) # test with sample data\n",
" validate(solve_part1(curr_input.splitlines()), curr_ans) # test with sample data\n",
" logger.info(\"Test passed\")\n",
"\n",
"logger.info(\"All tests passed!\")\n",
"\n",
"logger.setLevel(logging.INFO)\n",
"soln = solve(input_data)\n",
"soln = solve_part1(input_data)\n",
"logger.info(f\"Part 1 soln={soln}\")"
]
},
Expand All @@ -8116,39 +8134,172 @@
"source": [
"### Day 21 Part 2\n",
"\n",
"Overview..."
"Now, instead of 2 robot directional keypads between us and the numeric keypad, there are 25!\n",
"\n",
"My solution above won't scale to this. I need to be a lot smarter!\n",
"\n",
"The problem:\n",
"\n",
"- My part 1 solution performs a cartesian product of paths for each button press for each keypad. The numbers grow exponentially at each layer.\n",
"\n",
"The solution:\n",
"\n",
"- I don't need to do this! I can just do one pair of buttons at a time, rather than combining all the paths breadth-first.\n",
"- We can determine the direction presses on button n+1 that is required for a given direction press on button n.\n",
"- We can do this recursively.\n",
"- And we can cache the results."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 168,
"metadata": {},
"outputs": [],
"source": [
"def solve_part2(data):\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"numeric_keypad = KeypadMapping.from_type(KeypadMapping.NUMERIC)\n",
"direction_keypad = KeypadMapping.from_type(KeypadMapping.DIRECTION)\n",
"\n",
"def solve_part2(data, robot_depth=2) -> int:\n",
" logger.debug(f\"\\n{numeric_keypad}\")\n",
" logger.debug(f\"\\n{direction_keypad}\")\n",
"\n",
" door_codes = data\n",
" logger.debug(f\"{door_codes=}\")\n",
" \n",
" # Check we have our paths and lengths for each pair in the direction keypad\n",
" logger.debug(f\"{direction_keypad.paths_for_pair=}\")\n",
" logger.debug(f\"{direction_keypad.path_length_for_pair=}\")\n",
" \n",
" total_complexity = 0\n",
" for door_code in door_codes:\n",
" logger.debug(f\"{door_code=}\")\n",
" \n",
" # Compute moves for Robot 1 to press the numeric keypad - there can be more than one sequence\n",
" seq_for_numeric_input = numeric_keypad.moves_for_sequence(door_code) # E.g. ('<A^A^^>AvvvA', ... )\n",
" logger.debug(f\"Moves for Robot 1: {seq_for_numeric_input}\")\n",
"\n",
" # Use precomputed mappings for any upstream robot direction keypads\n",
" shortest = float(\"inf\")\n",
" for i, move_seq in enumerate(seq_for_numeric_input):\n",
" logger.debug(f\"Processing {i} - {move_seq=}\")\n",
" seq_len = 0\n",
" for (first, second) in zip(\"A\" + move_seq, move_seq):\n",
" seq_len += process_robot_keypads((first, second), robot_depth=robot_depth)\n",
" shortest = min(shortest, seq_len)\n",
" \n",
" logger.debug(f\"{shortest=}\")\n",
"\n",
" # Calculate complexity for the current code\n",
" total_complexity += complexity(door_code, shortest)\n",
"\n",
" return total_complexity\n",
"\n",
"@cache\n",
"def process_robot_keypads(move_pair: tuple, robot_depth: int) -> int:\n",
" \"\"\" For each move pair, recursively determine the number of presses required, \n",
" for the given number of chained robot keypads. \n",
" \"\"\"\n",
" \n",
" if robot_depth == 1: # Base case\n",
" return direction_keypad.path_length_for_pair[move_pair]\n",
"\n",
" # Otherwise, we need to recurse into the next robot keypad\n",
" shortest = float(\"inf\")\n",
" for sequence in direction_keypad.paths_for_pair[move_pair]:\n",
" seq_len = 0\n",
" for (first, second) in zip(\"A\" + sequence, sequence):\n",
" seq_len += process_robot_keypads((first, second), robot_depth - 1)\n",
" shortest = min(shortest, seq_len)\n",
" \n",
" return shortest"
]
},
{
"cell_type": "code",
"execution_count": 169,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[34m22:30:16.456:aoc2024 - DBG: \n",
"['7', '8', '9']\n",
"['4', '5', '6']\n",
"['1', '2', '3']\n",
"[None, '0', 'A']\u001b[39m\n",
"\u001b[34m22:30:16.457:aoc2024 - DBG: \n",
"[None, '^', 'A']\n",
"['<', 'v', '>']\u001b[39m\n",
"\u001b[34m22:30:16.457:aoc2024 - DBG: door_codes=['029A', '980A', '179A', '456A', '379A']\u001b[39m\n",
"\u001b[34m22:30:16.458:aoc2024 - DBG: direction_keypad.paths_for_pair={('^', '^'): ('A',), ('^', 'A'): ('>A',), ('^', '<'): ('v<A',), ('^', 'v'): ('vA',), ('^', '>'): ('v>A', '>vA'), ('A', '^'): ('<A',), ('A', 'A'): ('A',), ('A', '<'): ('v<<A', '<v<A'), ('A', 'v'): ('v<A', '<vA'), ('A', '>'): ('vA',), ('<', '^'): ('>^A',), ('<', 'A'): ('>^>A', '>>^A'), ('<', '<'): ('A',), ('<', 'v'): ('>A',), ('<', '>'): ('>>A',), ('v', '^'): ('^A',), ('v', 'A'): ('^>A', '>^A'), ('v', '<'): ('<A',), ('v', 'v'): ('A',), ('v', '>'): ('>A',), ('>', '^'): ('^<A', '<^A'), ('>', 'A'): ('^A',), ('>', '<'): ('<<A',), ('>', 'v'): ('<A',), ('>', '>'): ('A',)}\u001b[39m\n",
"\u001b[34m22:30:16.458:aoc2024 - DBG: direction_keypad.path_length_for_pair={('^', '^'): 1, ('^', 'A'): 2, ('^', '<'): 3, ('^', 'v'): 2, ('^', '>'): 3, ('A', '^'): 2, ('A', 'A'): 1, ('A', '<'): 4, ('A', 'v'): 3, ('A', '>'): 2, ('<', '^'): 3, ('<', 'A'): 4, ('<', '<'): 1, ('<', 'v'): 2, ('<', '>'): 3, ('v', '^'): 2, ('v', 'A'): 3, ('v', '<'): 2, ('v', 'v'): 1, ('v', '>'): 2, ('>', '^'): 3, ('>', 'A'): 2, ('>', '<'): 3, ('>', 'v'): 2, ('>', '>'): 1}\u001b[39m\n",
"\u001b[34m22:30:16.459:aoc2024 - DBG: door_code='029A'\u001b[39m\n",
"\u001b[34m22:30:16.459:aoc2024 - DBG: Moves for Robot 1: ('<A^A^^>AvvvA', '<A^A^>^AvvvA', '<A^A>^^AvvvA')\u001b[39m\n",
"\u001b[34m22:30:16.460:aoc2024 - DBG: Processing 0 - move_seq='<A^A^^>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.460:aoc2024 - DBG: Processing 1 - move_seq='<A^A^>^AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.461:aoc2024 - DBG: Processing 2 - move_seq='<A^A>^^AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.461:aoc2024 - DBG: shortest=68\u001b[39m\n",
"\u001b[34m22:30:16.462:aoc2024 - DBG: door_code='980A'\u001b[39m\n",
"\u001b[34m22:30:16.462:aoc2024 - DBG: Moves for Robot 1: ('^^^A<AvvvA>A',)\u001b[39m\n",
"\u001b[34m22:30:16.463:aoc2024 - DBG: Processing 0 - move_seq='^^^A<AvvvA>A'\u001b[39m\n",
"\u001b[34m22:30:16.463:aoc2024 - DBG: shortest=60\u001b[39m\n",
"\u001b[34m22:30:16.464:aoc2024 - DBG: door_code='179A'\u001b[39m\n",
"\u001b[34m22:30:16.465:aoc2024 - DBG: Moves for Robot 1: ('^<<A^^A>>AvvvA', '<^<A^^A>>AvvvA')\u001b[39m\n",
"\u001b[34m22:30:16.465:aoc2024 - DBG: Processing 0 - move_seq='^<<A^^A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.466:aoc2024 - DBG: Processing 1 - move_seq='<^<A^^A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.466:aoc2024 - DBG: shortest=68\u001b[39m\n",
"\u001b[34m22:30:16.467:aoc2024 - DBG: door_code='456A'\u001b[39m\n",
"\u001b[34m22:30:16.467:aoc2024 - DBG: Moves for Robot 1: ('^^<<A>A>AvvA', '^<^<A>A>AvvA', '^<<^A>A>AvvA', '<^^<A>A>AvvA', '<^<^A>A>AvvA')\u001b[39m\n",
"\u001b[34m22:30:16.467:aoc2024 - DBG: Processing 0 - move_seq='^^<<A>A>AvvA'\u001b[39m\n",
"\u001b[34m22:30:16.468:aoc2024 - DBG: Processing 1 - move_seq='^<^<A>A>AvvA'\u001b[39m\n",
"\u001b[34m22:30:16.468:aoc2024 - DBG: Processing 2 - move_seq='^<<^A>A>AvvA'\u001b[39m\n",
"\u001b[34m22:30:16.469:aoc2024 - DBG: Processing 3 - move_seq='<^^<A>A>AvvA'\u001b[39m\n",
"\u001b[34m22:30:16.469:aoc2024 - DBG: Processing 4 - move_seq='<^<^A>A>AvvA'\u001b[39m\n",
"\u001b[34m22:30:16.470:aoc2024 - DBG: shortest=64\u001b[39m\n",
"\u001b[34m22:30:16.470:aoc2024 - DBG: door_code='379A'\u001b[39m\n",
"\u001b[34m22:30:16.471:aoc2024 - DBG: Moves for Robot 1: ('^A^^<<A>>AvvvA', '^A^<^<A>>AvvvA', '^A^<<^A>>AvvvA', '^A<^^<A>>AvvvA', '^A<^<^A>>AvvvA', '^A<<^^A>>AvvvA')\u001b[39m\n",
"\u001b[34m22:30:16.471:aoc2024 - DBG: Processing 0 - move_seq='^A^^<<A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.472:aoc2024 - DBG: Processing 1 - move_seq='^A^<^<A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.472:aoc2024 - DBG: Processing 2 - move_seq='^A^<<^A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.473:aoc2024 - DBG: Processing 3 - move_seq='^A<^^<A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.473:aoc2024 - DBG: Processing 4 - move_seq='^A<^<^A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.474:aoc2024 - DBG: Processing 5 - move_seq='^A<<^^A>>AvvvA'\u001b[39m\n",
"\u001b[34m22:30:16.474:aoc2024 - DBG: shortest=64\u001b[39m\n",
"\u001b[32m22:30:16.475:aoc2024 - INF: Test passed\u001b[39m\n",
"\u001b[32m22:30:16.475:aoc2024 - INF: Tests passed!\u001b[39m\n",
"\u001b[32m22:30:16.478:aoc2024 - INF: Part 2 soln=228800606998554\u001b[39m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: total: 0 ns\n",
"Wall time: 23 ms\n"
]
}
],
"source": [
"%%time\n",
"sample_inputs = []\n",
"sample_inputs.append(\"\"\"abcdef\"\"\")\n",
"sample_answers = [\"uvwxyz\"]\n",
"sample_inputs.append(\"\"\"\\\n",
"029A\n",
"980A\n",
"179A\n",
"456A\n",
"379A\"\"\")\n",
"sample_answers = [126384]\n",
"\n",
"logger.setLevel(logging.DEBUG)\n",
"for curr_input, curr_ans in zip(sample_inputs, sample_answers):\n",
" validate(solve_part2(curr_input), curr_ans) # test with sample data\n",
" validate(solve_part2(curr_input.splitlines()), curr_ans) # test with sample data\n",
" logger.info(\"Test passed\") \n",
"\n",
"logger.info(\"Tests passed!\")\n",
"\n",
"logger.setLevel(logging.INFO)\n",
"soln = solve_part2(input_data)\n",
"soln = solve_part2(input_data, robot_depth=25)\n",
"logger.info(f\"Part 2 soln={soln}\")"
]
},
Expand Down

0 comments on commit 53a080d

Please sign in to comment.