Skip to content

Commit

Permalink
2023d17 documented
Browse files Browse the repository at this point in the history
  • Loading branch information
derailed-dash committed Dec 17, 2023
1 parent 7fcebfe commit 1f6984a
Showing 1 changed file with 78 additions and 55 deletions.
133 changes: 78 additions & 55 deletions src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -680,13 +680,9 @@
" return\n",
" \n",
" # axes, mkr_size = self._plot_info\n",
" \n",
" # axes.clear()\n",
" # min_x, max_x = -0.5, self.width - 0.5\n",
" # min_y, max_y = -0.5, self.height - 0.5\n",
" # axes.set_xlim(min_x, max_x)\n",
" # axes.set_ylim(max_y, min_y)\n",
" \n",
" # plot stuff\n",
" \n",
" # # save the plot as a frame; store the frame in-memory, using a BytesIO buffer\n",
" # frame = BytesIO()\n",
" # plt.savefig(frame, format='png') # save to memory, rather than file\n",
Expand Down Expand Up @@ -5658,54 +5654,40 @@
" return\n",
" \n",
" axes, mkr_size = self._plot_info\n",
" axes.clear()\n",
" min_x, max_x = -0.5, self.width - 0.5\n",
" min_y, max_y = -0.5, self.height - 0.5\n",
" axes.set_xlim(min_x, max_x)\n",
" axes.set_ylim(max_y, min_y)\n",
" \n",
" dir_sets = []\n",
" for _ in range(4):\n",
" dir_sets.append(set())\n",
" \n",
" dir_sets = [set() for _ in range(4)]\n",
" \n",
" vert_splitters = set()\n",
" horz_splitters = set()\n",
" forw_mirrors = set()\n",
" back_mirrors = set()\n",
" vert_splitters, horz_splitters, forw_mirrors, back_mirrors = set(), set(), set(), set()\n",
" splitter_mappings = {\n",
" '|': vert_splitters, \n",
" '-': horz_splitters, \n",
" '/': forw_mirrors, \n",
" '\\\\': back_mirrors\n",
" }\n",
" \n",
" for row_num, row in enumerate(self.array):\n",
" for char_num, char in enumerate(row):\n",
" point = Point(char_num, row_num)\n",
" if char in splitter_mappings:\n",
" splitter_mappings[char].add(point)\n",
" \n",
" # todo: just pull out the infra from the array, rather than the path\n",
" for point, dirn in self.path_taken:\n",
" value = self.value_at_point(point)\n",
" if value in (\"|\", \"-\", \"/\", \"\\\\\"):\n",
" if value == \"|\":\n",
" vert_splitters.add(point)\n",
" elif value == \"-\":\n",
" horz_splitters.add(point)\n",
" elif value == \"/\":\n",
" forw_mirrors.add(point)\n",
" else:\n",
" back_mirrors.add(point)\n",
" else:\n",
" if value not in splitter_mappings:\n",
" for dir_set, arrow in zip(dir_sets, (\"^\", \">\", \"v\", \"<\")):\n",
" if LightGrid.VECTORS_TO_ARROWS[dirn] == arrow:\n",
" dir_set.add(point)\n",
" continue\n",
" \n",
" if vert_splitters:\n",
" x, y = zip(*((point.x, point.y) for point in vert_splitters))\n",
" axes.scatter(x, y, marker=\"|\", s=mkr_size, color=\"blue\")\n",
" \n",
" if horz_splitters:\n",
" x, y = zip(*((point.x, point.y) for point in horz_splitters))\n",
" axes.scatter(x, y, marker=\"x\", s=mkr_size, color=\"blue\")\n",
" \n",
" if forw_mirrors:\n",
" x, y = zip(*((point.x, point.y) for point in forw_mirrors))\n",
" axes.scatter(x, y, marker=\"o\", s=mkr_size, color=\"blue\")\n",
"\n",
" # Batched scatter operations\n",
" for splitter_type, marker, color in [(vert_splitters, '|', 'blue'), \n",
" (horz_splitters, 'x', 'blue'), \n",
" (forw_mirrors, 'o', 'blue'), \n",
" (back_mirrors, '*', 'blue')]:\n",
" \n",
" if back_mirrors:\n",
" x, y = zip(*((point.x, point.y) for point in back_mirrors))\n",
" axes.scatter(x, y, marker=\"*\", s=mkr_size, color=\"blue\") \n",
" if splitter_type: # check not empty\n",
" x, y = zip(*((point.x, point.y) for point in splitter_type))\n",
" axes.scatter(x, y, marker=marker, s=mkr_size, color=color) \n",
" \n",
" for dir_set, arrow in zip(dir_sets, (\"^\", \">\", \"v\", \"<\")):\n",
" if dir_set:\n",
Expand Down Expand Up @@ -5743,7 +5725,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 165,
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -5925,7 +5907,28 @@
"\n",
"**My solution:**\n",
"\n",
"I think this probably needs to use the [A* algorithm](https://aoc.just2good.co.uk/python/shortest_paths)."
"I found this one pretty tough.\n",
"\n",
"It was pretty obvious from the start that I needed either [Dijkstra’s or A* algorithm][https://aoc.just2good.co.uk/python/shortest_paths] to find the path with the lowest cumulative cost. But the actual implementation took me ages to get right.\n",
"\n",
"So... We need to find a path from a start to a goal, where the cost varies depending on the path taken. As usual for a Dijkstra, we need to use a [priority queue](https://aoc.just2good.co.uk/python/priority_queues). First, we need to define some way to represent the state of our journey through the map. I'll represent state in a tuple that stores `(cost, current position, direction, straight steps taken)`. It's important that `cost` comes first, because our priority queue pops the next state based on the lowest value of the first item in the tuple.\n",
"\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",
"- 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",
" - We can move right relative to our current direction by adding the vector `Point(dirn.y, -dirn.x)`.\n",
" - We can only go straight if `straight steps` is less than `3`.\n",
"- Now we've got our new position and new direction, we can determine the cost of the next move, and add that cost to our current cumulative cost.\n",
"\n",
"Finally, we return the total cumulative cost, when we reach the goal.\n",
"\n",
"And that's basically all there is to it for the Dijkstra solution!\n",
"\n",
"I was interested to see what would happen if I changed to an A* implementation. I did this by creating a `heuristic` variable that stores the combination of `path cost` with the _Manhattan distance_ between start and goal. The idea here is that by using the heuristic, we will favour paths that take us towards the goal. But when I added this, the solution took about twice as long to compute. Oh well... It was worth a try! \n",
"\n"
]
},
{
Expand Down Expand Up @@ -5953,6 +5956,7 @@
" queue = [] # priority queue to store current state\n",
" \n",
" cost:int = 0 # cumulative cost of all moves, based on value of each entered location\n",
" # heuristic:int = 0 + start.manhattan_distance_from(goal)\n",
" current_posn:Point = start\n",
" dirn:Optional[Point] = None # Current direction. (None when we start.)\n",
" straight_steps:int = 0 # number of steps taken in one direction without turning\n",
Expand Down Expand Up @@ -5989,11 +5993,12 @@
" \n",
" for neighbour, dirn, new_steps in next_states:\n",
" if (0 <= neighbour.x < len(grid[0]) and 0 <= neighbour.y < len(grid)):\n",
" \n",
" heapq.heappush(queue, (cost+grid[neighbour.y][neighbour.x], \n",
" neighbour, \n",
" dirn, \n",
" new_steps))\n",
" new_cost = cost + grid[neighbour.y][neighbour.x]\n",
" # heuristic = new_cost + neighbour.manhattan_distance_from(goal)\n",
" heapq.heappush(queue, (new_cost, \n",
" neighbour, \n",
" dirn, \n",
" new_steps))\n",
" \n",
" raise ValueError(\"No solution found\")"
]
Expand Down Expand Up @@ -6052,12 +6057,30 @@
"source": [
"### Day 17 Part 2\n",
"\n",
"We're upgrading to _ultra crucibles_:\n",
"We're upgrading to _ultra crucibles_. These:\n",
"\n",
"- Requires a minimum of 4 blocks before it can turn or stop\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"
"**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",
"\n",
"```python\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",
"\n",
"```python\n",
" if current_posn == goal and straight_steps >= pre_turn:\n",
" return cost\n",
"```\n",
"\n",
"And that's it!"
]
},
{
Expand Down

0 comments on commit 1f6984a

Please sign in to comment.