Skip to content

Commit

Permalink
d10 expansion and plots working
Browse files Browse the repository at this point in the history
  • Loading branch information
derailed-dash committed Jan 3, 2024
1 parent 68447bb commit 42ca393
Showing 1 changed file with 131 additions and 24 deletions.
155 changes: 131 additions & 24 deletions src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4052,7 +4052,7 @@
" curr_pt1_soln, curr_furthest, curr_from = solve_part1(curr_grid, show_plot=False)\n",
" curr_pt2_soln = solve_part2(curr_grid, curr_furthest, curr_from)\n",
" validate(curr_pt2_soln, curr_ans) # test with sample data\n",
" logger.info(\"Test passed.\")\n",
" logger.debug(\"Test passed\")\n",
"\n",
"logger.info(\"Tests passed!\")\n",
"\n",
Expand All @@ -4068,12 +4068,50 @@
"\n",
"There are a few other ways to solve this problem. \n",
"\n",
"- Horizontal [ray casting algorithm](https://www.youtube.com/watch?v=RSXM9bgqxJM): extend a virtual ray from left to right, and count how many times it intersects with the polygon. If the number of intersections is odd, then the line is inside the polygon. If it is even, it is outside of the polygon.\n",
"- Diagonal ray casting, which works the same way as horizontal, but avoids the need for handling the special case of \"walking along\" a channel.\n",
"- Another approach is to use the [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule) to determine whether a point falls within an enclosed curve. This approach requires tracking the current direction.\n",
"- **Use the [Shoelace formula](https://en.wikipedia.org/wiki/Shoelace_formula) in conjunction with [Pick's theorem](https://en.wikipedia.org/wiki/Pick%27s_theorem)** to determine all the interior points of any polygon. This is probably the simplest and fastest solution.\n",
"\n",
"- **Horizontal [ray casting algorithm](https://www.youtube.com/watch?v=RSXM9bgqxJM):**\n",
" - Extend a virtual ray from left to right, for each row.\n",
" - Count how many times it intersects with the polygon. If the number of intersections is odd, then the line is inside the polygon. If it is even, it is outside of the polygon.\n",
" - If we have a row edge of type L---J or F---7, then this should not toggle.\n",
" - Because we care about the type of pipe we're hitting, we need to determine what sort of pipe `S` is. We can do this by assessing the two adjcaent loop pipe components.\n",
"\n",
"- **Diagonal ray casting.**\n",
" - Works the same way as horizontal, but avoids the need for handling the special case of \"walking along\" a channel.\n",
" - Cast a diagonal line from EVERY empty (`.`) point in the grid.\n",
" - If the line crosses an odd number of intersections, then the point is inside the loop.\n",
" - If the line is moving diagonal down+right, then it will be blocked by all pipe types, except for `7` and `L`.\n",
" - Again, we need to determine the pipe type of `S`.\n",
"\n",
"- You can **scale-up the entire grid by 3**. \n",
" - Every square is replaced by a 3x3 group of squares. \n",
" - The result is that loops that were adjacent now have a channel between them. \n",
" - We can represent the resulting expanded grid as either containing spaces of wall. \n",
" - Now we can flood fill from the outside.\n",
"\n",
"```text\n",
".|.\n",
"-J.\n",
"...\n",
"```\n",
"\n",
"Would become:\n",
"\n",
"```text\n",
"....#....\n",
"....#....\n",
"....#....\n",
"....#....\n",
"#####....\n",
".........\n",
".........\n",
".........\n",
".........\n",
"```\n",
"\n",
"- **[Non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule)** to determine whether a point falls within an enclosed curve. This approach requires tracking the current direction.\n",
"\n",
"- This [Reddit post](https://www.reddit.com/r/adventofcode/comments/18fgddy/2023_day_10_part_2_using_a_rendering_algorithm_to/) from `tomi901` provides a **nice visual** to explain how to determine whether a point is in or out.\n",
"- You can use the [Shoelace formula](https://en.wikipedia.org/wiki/Shoelace_formula) in conjunction with [Pick's theorem](https://en.wikipedia.org/wiki/Pick%27s_theorem) to determine all the interior points of any polygon.\n",
"- You can **scale-up the entire grid by 3**. Every square is replaced by a 3x3 group of squares. The result is that loops that were adjacent now have a channel between them. This allows you to always flood fill to the outside, which makes elimination of _external_ tiles much easier.\n",
"\n",
"Additionally:\n",
"\n",
Expand Down Expand Up @@ -4105,7 +4143,16 @@
"def remove_junk_parts(grid, loop_points) -> PipeGrid:\n",
" \"\"\" Replaces any pipe components with `.`, if the component is not part of the main loop. \"\"\"\n",
" return PipeGrid([\"\".join(char if Point(x,y) in loop_points else \".\" for x, char in enumerate(row))\n",
" for y, row in enumerate(grid.array)])"
" for y, row in enumerate(grid.array)])\n",
" \n",
"def plot_grid(grid):\n",
" \"\"\" Take a 2D grid with spaces and #, and plot visually \"\"\"\n",
" num_grid = [[1 if cell == '#' else 0 for cell in row] for row in grid]\n",
" num_array = np.array(num_grid) # Convert to a NumPy array\n",
" \n",
" plt.figure(figsize=(10, 10), dpi=100)\n",
" plt.imshow(num_array, cmap='Greys', interpolation='nearest')\n",
" plt.show()"
]
},
{
Expand All @@ -4126,8 +4173,9 @@
"\n",
"def d10_with_horizontal_ray_casting(grid: PipeGrid, furthest: Point, came_from: dict[Point, tuple]):\n",
" \"\"\" Determine number of tiles (which can be empty or non-loop pipe components) that are internal\n",
" to the main loop. Use ray casting lines. When they intersect the edge of the polygon, we're inside.\n",
" When they intersect again, we're outside. \"\"\"\n",
" to the main loop. Use ray casting lines. Ray cast from every point in the array.\n",
" When a ray intersects the edge of the polygon, we're inside. When they intersect again, we're outside. \n",
" So odd intersections means the point is inside. \"\"\"\n",
" loop_path = get_loop_path(grid, furthest, came_from) # get complete enclosed main loop\n",
" loop_points = set(loop_path)\n",
" cleaned_grid = remove_junk_parts(grid, loop_points) \n",
Expand All @@ -4138,23 +4186,23 @@
" cleaned_grid.set_value_at_point(loop_path[len(loop_path)//2], start_type)\n",
" \n",
" inside = 0\n",
" # sweep each low, from left to right\n",
" # sweep each row, from left to right. Flip when we hit an edge.\n",
" for row in cleaned_grid.array:\n",
" row = re.sub(\"L-*J|F-*7\", \"\", row) # ignore horizontal edge with no turn\n",
" internal_point = False\n",
" for char in row:\n",
" if char in \"|FL\": # toggle left edges only, if we have a turn\n",
" for char in row: # test each point in the row\n",
" if char in \"|FL\": # toggle wall and left edges only\n",
" internal_point = not internal_point # invert\n",
" if internal_point and char == \".\": # we want to ignore the loop itself\n",
" if internal_point and char == \".\":\n",
" inside += 1\n",
" \n",
" return inside\n",
"\n",
"def d10_with_diagonal_ray_casting(grid: PipeGrid, furthest: Point, came_from: dict[Point, tuple]):\n",
" \"\"\" Determine number of tiles (which can be empty or non-loop pipe components) that are internal\n",
" to the main loop. Use ray casting lines, moving down and right. \n",
" When they intersect the edge of the polygon, we're inside.\n",
" When they intersect again, we're outside. \"\"\"\n",
" to the main loop. Use ray casting lines, moving down and right. Ray cast from every point in the array.\n",
" When a ray intersects the edge of the polygon, we're inside. When they intersect again, we're outside. \n",
" So odd intersections means the point is inside. \"\"\"\n",
" loop_path = get_loop_path(grid, furthest, came_from) # get complete enclosed main loop\n",
" loop_points = set(loop_path)\n",
" cleaned_grid = remove_junk_parts(grid, loop_points)\n",
Expand Down Expand Up @@ -4185,13 +4233,74 @@
"\n",
"def d10_with_scale_up(grid: PipeGrid, furthest: Point, came_from: dict[Point, tuple]):\n",
" \"\"\" Determine number of tiles (which can be empty or non-loop pipe components) that are internal\n",
" to the main loop. \"\"\"\n",
" to the main loop. We will scale-up the entire grid by 3x. So each point becomes 3x3. \n",
" We no longer need to care about pipe types, since each point can be represented by a wall. \"\"\"\n",
" loop_path = get_loop_path(grid, furthest, came_from) # get complete enclosed main loop\n",
" loop_points = set(loop_path)\n",
" cleaned_grid = remove_junk_parts(grid, loop_points)\n",
" # logger.debug(f\"\\n{cleaned_grid}\")\n",
" \n",
" return None"
" # update \"S\" to be actual pipe type\n",
" start_type = grid.infer_start_type(loop_path)\n",
" cleaned_grid.set_value_at_point(loop_path[len(loop_path)//2], start_type)\n",
" \n",
" expanded_grid = [] # build grid that is 3x larger\n",
" for row in cleaned_grid.array:\n",
" new_rows = [[] for _ in range(3)] # create 3 empty rows\n",
" for char in row:\n",
" subgrid = [[\" \"]*3 for _ in range(3)] # create empty 3x3\n",
" # change parts of the 3x3 into wall\n",
" if char != \".\": # middle element must be a wall\n",
" subgrid[1][1] = \"#\"\n",
" if \"N\" in PipeGrid.directions_for_pipe[char]: # e.g. |\n",
" subgrid[0][1] = \"#\"\n",
" if \"E\" in PipeGrid.directions_for_pipe[char]: # e.g. -\n",
" subgrid[1][2] = \"#\"\n",
" if \"S\" in PipeGrid.directions_for_pipe[char]: # e.g. 7\n",
" subgrid[2][1] = \"#\"\n",
" if \"W\" in PipeGrid.directions_for_pipe[char]: # e.g. 7\n",
" subgrid[1][0] = \"#\"\n",
" \n",
" for i in range(3): # build out the triple-row horizontally, by appending 3x3 at a time\n",
" new_rows[i] += subgrid[i]\n",
" \n",
" for new_row in new_rows: # add the triple row\n",
" expanded_grid.append(\"\".join(new_row))\n",
" \n",
" # Visualise the expanded grid\n",
" # logger.debug(f\"\\n\" + \"\\n\".join(expanded_grid))\n",
" plot_grid(expanded_grid)\n",
" \n",
" # now we can flood fill from the outside\n",
" start_locn = (0, 0)\n",
" assert cleaned_grid.value_at_point(Point(*start_locn)) == \".\", \"Top left should be empty and outside\"\n",
" \n",
" outside = {start_locn}\n",
" queue = deque([start_locn])\n",
" while queue:\n",
" y, x = queue.popleft()\n",
" for dx, dy in VectorDicts.DIRS.values(): # for N, E, S, W\n",
" next_y, next_x = y + dy, x + dx\n",
" if (next_y, next_x) in outside: # already seen\n",
" continue\n",
" \n",
" if 0 <= next_y < len(expanded_grid) and 0 <= next_x < len(expanded_grid[0]):\n",
" if expanded_grid[next_y][next_x] != \"#\": # stop at a wall\n",
" outside.add((next_y, next_x))\n",
" queue.append((next_y, next_x))\n",
" \n",
" # inside = (all - (loop points + outside points))\n",
" inside = 0\n",
" for y, row in enumerate(cleaned_grid.array):\n",
" for x, char in enumerate(row):\n",
" if Point(x, y) in loop_points:\n",
" continue\n",
" # convert normal grid to expanded grid. Add 1 because we want the one in the middle.\n",
" if (y*3+1, x*3+1) in outside: \n",
" continue\n",
" inside += 1\n",
"\n",
" return inside"
]
},
{
Expand All @@ -4203,7 +4312,6 @@
"%%time\n",
"\n",
"sample_inputs = []\n",
"\n",
"sample_inputs.append(\"\"\"\\\n",
"...........\n",
".S-------7.\n",
Expand Down Expand Up @@ -4261,7 +4369,7 @@
" curr_pt1_soln, curr_furthest, curr_from = solve_part1(curr_grid, show_plot=False)\n",
" curr_pt2_soln = func(curr_grid, curr_furthest, curr_from)\n",
" validate(curr_pt2_soln, curr_ans) # test with sample data\n",
" logger.info(\"Test passed\")\n",
" logger.debug(\"Test passed\")\n",
"\n",
" logger.info(\"All tests passed!\")\n",
"\n",
Expand All @@ -4275,8 +4383,7 @@
" d10_with_diagonal_ray_casting,\n",
" d10_with_scale_up):\n",
" logger.info(f\"Solving with {func.__name__}() ...\")\n",
" test_and_run_with_solve(func)\n",
"\n"
" test_and_run_with_solve(func)\n"
]
},
{
Expand Down Expand Up @@ -10115,9 +10222,9 @@
"toc_visible": true
},
"kernelspec": {
"display_name": "ana-aoc",
"display_name": "aca_aoc",
"language": "python",
"name": "python3"
"name": "aca_aoc"
},
"language_info": {
"codemirror_mode": {
Expand Down

0 comments on commit 42ca393

Please sign in to comment.