From 6f399dbbf3c65576d2a2515223bc48c450875296 Mon Sep 17 00:00:00 2001 From: derailed-dash Date: Sun, 28 Jan 2024 16:44:57 +0000 Subject: [PATCH] Improving d17 comments --- .../Dazbo's_Advent_of_Code_2023.ipynb | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 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 519fc4f..ecf8a0e 100644 --- a/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb +++ b/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb @@ -6655,7 +6655,9 @@ "\n", "We keep popping next states until we reach the goal. To determine valid next states:\n", "\n", - "- We create `set` to store states that we've already explored.\n", + "- We create `dict` to store states that we've already seen. \n", + " - Here, we only want to store `(current position, direction, staight_steps)` because we may have entered a cycle and re-entered the same configuration. In this case, we don't want to continue if the cost is higher, because this means we've entered a cycle. I.e. we've repeated a previous state, but with a higher cost. But we can't include the cost in the state, because then the state would always be different if we see a cycle.\n", + " - We could use a `set` to store the seen states. But by using a `dict`, we can also store the cumulative cost to reach this state. This way, we can ignore any previously seen states if the cumulative cost is not less than a cost we've already stored.\n", "- If we're in our initial position, we don't have a direction yet. So we can try all adjacent squares, and with each, we set `straight steps` to 1.\n", "- If we're not on the first step, then our valid next moves are left, right, or straight on. We can't go backwards.\n", " - We can move left relative to our current direction by adding the vector `Point(-dirn.y, dirn.x)`.\n", @@ -6700,7 +6702,11 @@ " straight_steps:int = 0 # number of steps taken in one direction without turning\n", " heapq.heappush(queue, (cost, current_posn, dirn, straight_steps)) # cost must come first for our priority queue\n", " \n", - " seen = set()\n", + " # Store initial state in the dict, with the cheapest cost to this state.\n", + " # We want to track states we've seen as (current_posn, dirn, straight_steps)\n", + " # But we want to ignore total cost. Because we need to detect \n", + " # if we've entered a configuration we've seen before. E.g. into a cycle.\n", + " seen = { (current_posn, dirn, straight_steps): 0 } # { state: cumulative cost }\n", " \n", " while queue:\n", " cost, current_posn, dirn, straight_steps = heapq.heappop(queue)\n", @@ -6709,12 +6715,6 @@ " if current_posn == goal and straight_steps >= pre_turn:\n", " return cost\n", " \n", - " # check if we've been in this configuration before\n", - " if (current_posn, dirn, straight_steps) in seen: \n", - " continue\n", - " \n", - " seen.add((current_posn, dirn, straight_steps)) # explored\n", - " \n", " next_states = [] # point, dirn, steps\n", " if dirn is None: # we're at the start, so we can go in any direction\n", " for dir_x, dir_y in ((0, 1), (1, 0), (0, 1), (-1, 0)):\n", @@ -6729,13 +6729,17 @@ " if straight_steps < max_straight: # we can move straight ahead. \n", " next_states.append(((current_posn + dirn), dirn, straight_steps+1))\n", " \n", - " for neighbour, dirn, new_steps in next_states:\n", + " for next_state in next_states:\n", + " neighbour, dirn, new_steps = next_state\n", " if (0 <= neighbour.x < len(grid[0]) and 0 <= neighbour.y < len(grid)):\n", " new_cost = cost + grid[neighbour.y][neighbour.x]\n", - " heapq.heappush(queue, (new_cost, \n", - " neighbour, \n", - " dirn, \n", - " new_steps))\n", + " # enqueue only if it's a new state, or a previous state with better cost\n", + " if next_state not in seen or new_cost < seen[next_state]:\n", + " seen[next_state] = new_cost\n", + " heapq.heappush(queue, (new_cost, \n", + " neighbour, \n", + " dirn, \n", + " new_steps))\n", " \n", " raise ValueError(\"No solution found\")" ] @@ -6796,21 +6800,21 @@ "\n", "We're upgrading to _ultra crucibles_. These:\n", "\n", - "- Require a minimum of 4 blocks before it can turn or stop\n", - "- But can move 10 blocks before turning\n", + "- Require a minimum of 4 blocks before it can turn or stop.\n", + "- But can move 10 blocks before turning.\n", "\n", "**Directing the ultra crucible from the lava pool to the machine parts factory, what is the least heat loss it can incur?**\n", "\n", "**My solution:**\n", "\n", "- Change the `max_straight` value from 3 to 10. So I've parameterised this.\n", - "- Add a new parameter called `pre_turn`, which determines how many `max_straight` is required before we're allowed to turn. So now I perform this test before turning left or right:\n", + "- Add a new parameter called `pre_turn`, which determines the minimum of `straight_steps` before we're allowed to turn. So now I perform this test before turning left or right:\n", "\n", "```python\n", - "if straight_steps >= pre_turn\n", + "if straight_steps >= pre_turn:\n", "```\n", "\n", - "- Finally, we also need to ensure that we've taken at least four straight steps when we arrive at the goal. So we simply amend our exit condition check like this:\n", + "Finally, we also need to ensure that we've taken at least four straight steps when we arrive at the goal. So we simply amend our exit condition check like this:\n", "\n", "```python\n", " if current_posn == goal and straight_steps >= pre_turn:\n",