From bc9ece93a5b30711e4d7cbd11eb826a26f549cad Mon Sep 17 00:00:00 2001 From: derailed-dash Date: Wed, 27 Dec 2023 21:25:39 +0000 Subject: [PATCH] Making d23 BFS recursive --- .../Dazbo's_Advent_of_Code_2023.ipynb | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb b/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb index 6fdbb9d..61fac25 100644 --- a/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb +++ b/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb @@ -8918,7 +8918,8 @@ " return vertices\n", " \n", "def build_edges(grid, vertices: set[tuple[int,int]], part) -> dict[tuple, set]:\n", - " \"\"\" BFS from each vertex to find all connected vertices \"\"\"\n", + " \"\"\" BFS from each vertex to find all connected vertices. It returns an adjacency list \n", + " mapping each vertex to all adjacent vertices, along with distance. \"\"\"\n", " edges: dict[tuple, set] = defaultdict(set) # { { edge_1: { (edge_2, distance), (edge_3, distance), ... }, ... }\n", " \n", " for vertex in vertices:\n", @@ -8933,7 +8934,7 @@ " \n", " explored.add((x, y))\n", " \n", - " # for each direction arrow...\n", + " # for each direction arrow, return the zip, e.g. (`<`, (-1, 0))\n", " for arrow, (dx, dy) in zip(VectorDicts.ARROWS, VectorDicts.ARROWS.values()):\n", " next_x, next_y = x+dx, y+dy\n", " if (0 <= next_x < len(grid[0]) and 0 <= next_y < len(grid)\n", @@ -8955,7 +8956,8 @@ "\n", "def find_paths_dfs(grid, start: tuple, end: tuple, edges: dict[tuple, set]) -> list[int]:\n", " \"\"\" Find all valid paths by DFS, between start and end, \n", - " using a supplied dict of vertices mapped to edges and respective distances. \"\"\"\n", + " using a supplied dict of vertices mapped to edges and respective distances. \n", + " This is slower than doing the DFS recursively, so I'm not using this function anymore. \"\"\"\n", " assert start in edges, \"Start must be our first edge\"\n", "\n", " stack = deque() # vertices to visit\n", @@ -8977,6 +8979,36 @@ " \n", " return valid_paths_lengths \n", "\n", + "def dfs(grid, edges: dict[tuple, set], from_vertex: tuple, goal: tuple, seen: set=set()) -> list:\n", + " \"\"\" Recursive DFS to find all path lengths from from_vertex to goal. \n", + "\n", + " Args:\n", + " grid (_type_): _description_\n", + " edges (dict[tuple, set]): Adjacency dictionary mapping vertex to a set of (vertex, distance)\n", + " from_vertex (tuple): The current vertex\n", + " goal (tuple): The final vertex we want to reach\n", + " seen (set, optional): To track visisted vertices. First call will set it to empty.\n", + "\n", + " Returns:\n", + " list: lengths of all valid paths\n", + " \"\"\"\n", + " if from_vertex == goal:\n", + " return [0] # Found a path, return a list with length 0 (since no more distance is needed)\n", + " \n", + " seen.add(from_vertex) # tp prevent backtracking in THIS path\n", + " path_lengths = []\n", + " \n", + " # explore each connected vertex\n", + " for next_vertex, distance in edges[from_vertex]:\n", + " if next_vertex not in seen: # prevent backtracking for this path\n", + " # recursively call from the next vertex onwards\n", + " for path_len in dfs(grid, edges, next_vertex, goal, seen):\n", + " path_lengths.append(path_len + distance) # adjust each length by adding the current dist\n", + " \n", + " seen.remove(from_vertex) # to allow other paths to visit this vertex\n", + " \n", + " return path_lengths\n", + "\n", "def draw_graph(graph, start, end):\n", " # Create a list of colors, one for each node\n", " node_colors = ['blue' if node not in [start, end] else 'red' for node in graph.nodes()]\n", @@ -9020,7 +9052,8 @@ " \n", " draw_graph(graph, start, end) # let's have a look at it\n", " \n", - " valid_path_lengths = find_paths_dfs(grid, start, end, edges)\n", + " # valid_path_lengths = find_paths_dfs(grid, start, end, edges)\n", + " valid_path_lengths = dfs(grid, edges, start, end)\n", " \n", " return max(valid_path_lengths)" ] @@ -9074,13 +9107,13 @@ "source": [ "### Day 23 Part 2\n", "\n", - "Now we can ignore the arrows. They just behave like normal path `.`.\n", + "Now we can ignore the arrows. They just behave like normal path `.` locations.\n", "\n", "**Find the longest hike you can take through the surprisingly dry hiking trails listed on your map. How many steps long is the longest hike?**\n", "\n", - "Here, all we need to do is removed the check for the direction arrow. It does make the resulting graph significantly more complicated, and makes the number of valid paths huge.\n", + "Here, all we need to do is remove the check for the direction arrow. It does make the resulting graph significantly more complicated, and makes the number of valid paths huge.\n", "\n", - "But the solution still runs in a reasonable amount of time." + "But the solution still runs in a reasonable amount of time. This is because we've already applied the edge contraction, so the number of paths we have to test is significantly smaller than it would have been." ] }, {