Skip to content

Commit baabab5

Browse files
committed
Part 1 done. Part 2 does not yet scale.
1 parent 7dd2e1c commit baabab5

File tree

1 file changed

+174
-26
lines changed

1 file changed

+174
-26
lines changed

src/AoC_2024/Dazbo's_Advent_of_Code_2024.ipynb

+174-26
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
},
6565
{
6666
"cell_type": "code",
67-
"execution_count": 2,
67+
"execution_count": 17,
6868
"metadata": {
6969
"id": "p5Ki_HvOJUWk",
7070
"tags": []
@@ -254,7 +254,7 @@
254254
},
255255
{
256256
"cell_type": "code",
257-
"execution_count": 5,
257+
"execution_count": 20,
258258
"metadata": {
259259
"id": "lwP0r3BAaxjt",
260260
"tags": []
@@ -327,7 +327,7 @@
327327
},
328328
{
329329
"cell_type": "code",
330-
"execution_count": 6,
330+
"execution_count": 21,
331331
"metadata": {
332332
"id": "Y6nbd6WMryWi",
333333
"tags": []
@@ -355,7 +355,7 @@
355355
},
356356
{
357357
"cell_type": "code",
358-
"execution_count": 7,
358+
"execution_count": 22,
359359
"metadata": {
360360
"id": "A8sU4Ez_bBKl",
361361
"tags": []
@@ -525,7 +525,7 @@
525525
},
526526
{
527527
"cell_type": "code",
528-
"execution_count": 8,
528+
"execution_count": 23,
529529
"metadata": {
530530
"id": "DT5FSYliC9wp",
531531
"tags": []
@@ -7863,7 +7863,7 @@
78637863
"\n",
78647864
"We need to start with all shortest combinations, because we don't know which one will be optimal for the next remote.\n",
78657865
"\n",
7866-
"But now, to get from these directions to the next remote, we need new mappings for each pair, e.g.\n",
7866+
"Now, to get from these directions from the next remote we need new mappings for each pair, e.g.\n",
78677867
"\n",
78687868
"```text\n",
78697869
"A -> >: vA\n",
@@ -7874,35 +7874,178 @@
78747874
"^ -> >: >vA, v>A\n",
78757875
"```\n",
78767876
"\n",
7877-
"Now, these mappings are repeatable onto the next remote, and the remote after that. So we can definitely cache these and reuse them between robots.\n",
7877+
"These mappings are repeatable onto the next remote, and the remote after that. So we can definitely cache these and reuse them between robots.\n",
78787878
"\n",
7879-
"Now if we require a movement like `2 -> 9`, we can see that:\n",
7879+
"An example: if we're at `2` and we want to press `9`, these are the keypresses that are required on each upstream keypad:\n",
78807880
"\n",
78817881
"```text\n",
7882-
"<vA<AA>>^AvAA<^A>A<v<A>>^AvA^A<vA>^A<v<A>^A>AAvA^A<v<A>A>^AAAvA<^A>A\n",
7883-
"<A>AvA<^AA>A<vAAA>^A\n",
7884-
"^A>^^AvvvA\n",
7885-
"29A\n",
7882+
"Start\n",
7883+
"Numberic keypad: (2) 9 \n",
7884+
"Direction remote 1: (A) > ^ ^ A \n",
7885+
"Direction remote 2: (A) v A < ^ A A > A \n",
7886+
"Direction remote 3: (A) <vA >^A <v<A >^A >A A vA ^A \n",
78867887
"```\n",
78877888
"\n",
7888-
"```text\n",
7889-
"Numeric presses: (2) 9\n",
7890-
"Direction 1 presses: > ^ ^ A\n",
7891-
"```\n",
7889+
"- So we can map all `numeric keypad` pairs to `direction remote 1` presses. \n",
7890+
"- And we can map all `remote n` pairs to `remote n+1` presses.\n",
7891+
"- All upstream mappings will end in an `A`, i.e. the button press on the remote that causes the button press on the previous.\n"
7892+
]
7893+
},
7894+
{
7895+
"cell_type": "code",
7896+
"execution_count": 26,
7897+
"metadata": {},
7898+
"outputs": [],
7899+
"source": [
7900+
"class KeypadMapping():\n",
7901+
" POINTS_FOR_DIRECTIONS = { \"^\": Point(0, -1), \n",
7902+
" \"v\": Point(0, 1), \n",
7903+
" \"<\": Point(-1, 0), \n",
7904+
" \">\": Point(1, 0) }\n",
7905+
" \n",
7906+
" DIRECTIONS_FOR_POINTS = {v: k for k, v in POINTS_FOR_DIRECTIONS.items()}\n",
7907+
" \n",
7908+
" def __init__(self, keypad: list[list[str]]):\n",
7909+
" self._keypad = keypad\n",
7910+
" self._width = len(keypad[0])\n",
7911+
" self._height = len(keypad)\n",
7912+
" self._point_to_button: dict[Point, str] = {} # E.g. {P(0,0): '7', P(1,0): '8',..., P(2,3): 'A'}\n",
7913+
" self._button_to_point: dict[str, Point] = {} # E.g. {'7': P(0,0), '8': P(1,0), ..., 'A': P(2,3)}\n",
7914+
" self._build_keypad_dict()\n",
7915+
" self._paths_for_pair = self._compute_paths_for_pair()\n",
78927916
"\n",
7893-
"- Each of these combinations will result in combinations from _direction remote 3_, and so on.\n",
7894-
"- So ultimately, we can map each successive keypress - which is the movement from one location to the next - to the final keypresses.\n",
7895-
"\n"
7917+
" def _build_keypad_dict(self):\n",
7918+
" \"\"\" Build a dictionary of keypad points and their associated keys. \"\"\"\n",
7919+
" \n",
7920+
" for r, row in enumerate(self._keypad):\n",
7921+
" for c, key in enumerate(row):\n",
7922+
" if key:\n",
7923+
" self._point_to_button[Point(c, r)] = key\n",
7924+
" self._button_to_point[key] = Point(c, r)\n",
7925+
" \n",
7926+
" def _compute_paths_for_pair(self):\n",
7927+
" \"\"\" Precompute shortest set of paths of directions that take us from one point to all others.\n",
7928+
" E.g. {('7', '6'): {'>>vA', '>v>A', 'v>>A'}, ... }\n",
7929+
" \"\"\" \n",
7930+
" paths = {} # We will build our map, e\n",
7931+
" for start in self._button_to_point: # E.g. 7\n",
7932+
" for end in self._button_to_point: # E.g. 6\n",
7933+
" if start == end:\n",
7934+
" paths[start, end] = { \"A\" } # No need to move from a point to itself\n",
7935+
" continue # Go to next end\n",
7936+
" \n",
7937+
" # Now BFS to get all paths from start to end\n",
7938+
" all_paths = []\n",
7939+
" queue = deque([(self._button_to_point[start], [])]) # E.g. P(2,3), []\n",
7940+
" best_path_len = self._width + self._height + 1 # start with a high value\n",
7941+
" while queue:\n",
7942+
" current, path = queue.popleft() # E.g. P(2,3), []\n",
7943+
" \n",
7944+
" for direction, point in self.POINTS_FOR_DIRECTIONS.items(): # E.g. '^', P(0, -1)\n",
7945+
" new_point = current + point # adjacent point\n",
7946+
" if new_point not in self._point_to_button: # Avoids None\n",
7947+
" continue\n",
7948+
" \n",
7949+
" new_path = path + [direction] # E.g. ['^']\n",
7950+
" if self._point_to_button[new_point] == end: # We've reached the end of the path\n",
7951+
" if best_path_len < len(new_path): # If we've already found a shorter path\n",
7952+
" break # And break from outer loop\n",
7953+
" \n",
7954+
" # Otherwise we've found a new (or same length) best path\n",
7955+
" best_path_len = len(new_path)\n",
7956+
" all_paths.append(new_path) # Add best (or same length) path\n",
7957+
" else:\n",
7958+
" queue.append((new_point, new_path))\n",
7959+
" else: # only executed if the inner loop did NOT break\n",
7960+
" continue\n",
7961+
" \n",
7962+
" break # only executed if the inner loop DID break\n",
7963+
" \n",
7964+
" # Convert path lists to strings ending with \"A\"\n",
7965+
" paths[start, end] = {\"\".join(path)+\"A\" for path in all_paths\n",
7966+
" if len(path) == best_path_len}\n",
7967+
" \n",
7968+
" logger.debug(f\"{paths=}\")\n",
7969+
" return paths\n",
7970+
" \n",
7971+
" @cache\n",
7972+
" def moves_for_sequence(self, code: str) -> tuple[str]:\n",
7973+
" \"\"\" Determine the shortest set of move sequences to get from one button to another. \n",
7974+
" E.g. with door code 029A. But remember that we start pointing at A. \n",
7975+
" Output looks like ('<A^A^^>AvvvA', ... )\n",
7976+
" \"\"\"\n",
7977+
" \n",
7978+
" sequential_paths = []\n",
7979+
" for (start, end) in zip(\"A\" + code, code): # E.g. ('A', '0'), ('0', '2'), ('2', '9'), ('9', 'A')\n",
7980+
" sequential_paths.append(self._paths_for_pair[start, end])\n",
7981+
" \n",
7982+
" # Now we need the cartesian product of all the paths, and flattened into single strings\n",
7983+
" moves_for_seq = tuple(\"\".join(path) for path in product(*sequential_paths))\n",
7984+
" return moves_for_seq\n",
7985+
" \n",
7986+
" def __str__(self):\n",
7987+
" return \"\\n\".join(\"\".join(str(row)) for row in self._keypad)\n",
7988+
" \n",
7989+
" def __repr__(self):\n",
7990+
" return f\"{self.__class__.__name__}({self._keypad})\""
78967991
]
78977992
},
78987993
{
78997994
"cell_type": "code",
7900-
"execution_count": 12,
7995+
"execution_count": 27,
79017996
"metadata": {},
79027997
"outputs": [],
79037998
"source": [
7904-
"def solve_part1(data):\n",
7905-
" pass"
7999+
"def complexity(code: str, seq_len: int) -> int:\n",
8000+
" return int(code[:-1]) * seq_len\n",
8001+
"\n",
8002+
"def solve(data, robot_directional_keypads=2) -> int:\n",
8003+
" door_codes = data\n",
8004+
" logger.debug(f\"{door_codes=}\")\n",
8005+
" \n",
8006+
" NUMERIC_KEYPAD = [\n",
8007+
" [\"7\", \"8\", \"9\"],\n",
8008+
" [\"4\", \"5\", \"6\"],\n",
8009+
" [\"1\", \"2\", \"3\"],\n",
8010+
" [None, \"0\", \"A\"]]\n",
8011+
"\n",
8012+
" DIRECTION_KEYPAD = [\n",
8013+
" [None, \"^\", \"A\"],\n",
8014+
" [\"<\", \"v\", \">\"]]\n",
8015+
" \n",
8016+
" numeric_keypad = KeypadMapping(NUMERIC_KEYPAD)\n",
8017+
" direction_keypad = KeypadMapping(DIRECTION_KEYPAD)\n",
8018+
" \n",
8019+
" logger.debug(f\"\\n{numeric_keypad}\")\n",
8020+
" logger.debug(f\"\\n{direction_keypad}\")\n",
8021+
" \n",
8022+
" total_complexity = 0\n",
8023+
" for door_code in door_codes:\n",
8024+
" logger.debug(f\"{door_code=}\")\n",
8025+
" moves_for_robot_1 = numeric_keypad.moves_for_sequence(door_code)\n",
8026+
" logger.debug(f\"{moves_for_robot_1=}\")\n",
8027+
" \n",
8028+
" # From now on, all mappings are between direction keypads,\n",
8029+
" # so the mappings for direction buttons are always the same\n",
8030+
" next = moves_for_robot_1\n",
8031+
" \n",
8032+
" for _ in range(0, robot_directional_keypads):\n",
8033+
" moves_for_next_robot = [] \n",
8034+
" for move_seq in next: # E.g. '<A^A^^>AvvvA'\n",
8035+
" moves_for_next_robot += direction_keypad.moves_for_sequence(move_seq) \n",
8036+
" \n",
8037+
" # Each block of input move sequences can give us moves of different lengths\n",
8038+
" # We want only the shortest sequences\n",
8039+
" min_len = min(map(len, moves_for_next_robot))\n",
8040+
" moves_for_next_robot = [move for move in moves_for_next_robot if len(move) == min_len]\n",
8041+
" next = moves_for_next_robot\n",
8042+
" \n",
8043+
" logger.debug(f\"{len(moves_for_next_robot[0])=}\")\n",
8044+
" \n",
8045+
" total_complexity += complexity(door_code, min_len)\n",
8046+
" \n",
8047+
" return total_complexity\n",
8048+
" "
79068049
]
79078050
},
79088051
{
@@ -7913,18 +8056,23 @@
79138056
"source": [
79148057
"%%time\n",
79158058
"sample_inputs = []\n",
7916-
"sample_inputs.append(\"\"\"abcdef\"\"\")\n",
7917-
"sample_answers = [\"uvwxyz\"]\n",
8059+
"sample_inputs.append(\"\"\"\\\n",
8060+
"029A\n",
8061+
"980A\n",
8062+
"179A\n",
8063+
"456A\n",
8064+
"379A\"\"\")\n",
8065+
"sample_answers = [126384]\n",
79188066
"\n",
79198067
"logger.setLevel(logging.DEBUG)\n",
79208068
"for curr_input, curr_ans in zip(sample_inputs, sample_answers):\n",
7921-
" validate(solve_part1(curr_input), curr_ans) # test with sample data\n",
8069+
" validate(solve(curr_input.splitlines()), curr_ans) # test with sample data\n",
79228070
" logger.info(\"Test passed\")\n",
79238071
"\n",
79248072
"logger.info(\"All tests passed!\")\n",
79258073
"\n",
79268074
"logger.setLevel(logging.INFO)\n",
7927-
"soln = solve_part1(input_data)\n",
8075+
"soln = solve(input_data)\n",
79288076
"logger.info(f\"Part 1 soln={soln}\")"
79298077
]
79308078
},

0 commit comments

Comments
 (0)