2023.10
This commit is contained in:
parent
68a68ac0da
commit
c0b90ab337
3 changed files with 183 additions and 0 deletions
1
challenges/2023/10-pipeMaze/README.md
Normal file
1
challenges/2023/10-pipeMaze/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# [Day 10: Pipe Maze](https://adventofcode.com/2023/day/10)
|
152
challenges/2023/10-pipeMaze/main.py
Normal file
152
challenges/2023/10-pipeMaze/main.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
import sys
|
||||
import time
|
||||
|
||||
|
||||
Coordinate = tuple[int, int]
|
||||
|
||||
|
||||
def parse(instr: str) -> tuple[dict[Coordinate, str], Coordinate]:
|
||||
s_loc = None
|
||||
res = {}
|
||||
|
||||
for y, line in enumerate(instr.splitlines()):
|
||||
for x, char in enumerate(line):
|
||||
coord = (x, y)
|
||||
|
||||
if char == "S":
|
||||
s_loc = coord
|
||||
|
||||
res[coord] = char
|
||||
|
||||
assert s_loc is not None
|
||||
return res, s_loc
|
||||
|
||||
|
||||
acceptable_moves = {
|
||||
"-": {
|
||||
(-1, 0): ["L", "F", "-"],
|
||||
(1, 0): ["7", "J", "-"],
|
||||
},
|
||||
"|": {
|
||||
(0, -1): ["7", "F", "|"],
|
||||
(0, 1): ["J", "L", "|"],
|
||||
},
|
||||
}
|
||||
|
||||
acceptable_moves["L"] = {
|
||||
(c := (0, -1)): acceptable_moves["|"][c],
|
||||
(c := (1, 0)): acceptable_moves["-"][c],
|
||||
}
|
||||
|
||||
acceptable_moves["J"] = {
|
||||
(c := (0, -1)): acceptable_moves["|"][c],
|
||||
(c := (-1, 0)): acceptable_moves["-"][c],
|
||||
}
|
||||
|
||||
acceptable_moves["F"] = {
|
||||
(c := (0, 1)): acceptable_moves["|"][c],
|
||||
(c := (1, 0)): acceptable_moves["-"][c],
|
||||
}
|
||||
|
||||
acceptable_moves["7"] = {
|
||||
(c := (0, 1)): acceptable_moves["|"][c],
|
||||
(c := (-1, 0)): acceptable_moves["-"][c],
|
||||
}
|
||||
|
||||
acceptable_moves["S"] = {
|
||||
**acceptable_moves["|"], **acceptable_moves["-"]
|
||||
}
|
||||
|
||||
|
||||
def apply_coord_delta(a: Coordinate, b: Coordinate) -> Coordinate:
|
||||
aa, ab = a
|
||||
ba, bb = b
|
||||
return aa + ba, ab + bb
|
||||
|
||||
|
||||
def check_coord(grid: dict[Coordinate, str], coord: Coordinate, vals: list[str]) -> bool:
|
||||
v = grid.get(coord)
|
||||
if v is None:
|
||||
return False
|
||||
|
||||
if v in vals:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_loop_boundary(grid: dict[Coordinate, str], start: Coordinate) -> dict[Coordinate, int]:
|
||||
visited = {start: 0}
|
||||
frontier = [start]
|
||||
|
||||
while frontier:
|
||||
coord = frontier.pop(0)
|
||||
char = grid[coord]
|
||||
|
||||
for c in acceptable_moves.get(char, {}):
|
||||
c_must_be = acceptable_moves[char][c]
|
||||
c = apply_coord_delta(coord, c)
|
||||
if c not in grid or c in visited:
|
||||
continue
|
||||
if check_coord(grid, c, c_must_be):
|
||||
frontier.append(c)
|
||||
visited[c] = visited[coord] + 1
|
||||
|
||||
return visited
|
||||
|
||||
|
||||
def one(instr: str):
|
||||
loop_boundary = get_loop_boundary(*parse(instr))
|
||||
return max(loop_boundary.values())
|
||||
|
||||
|
||||
def area(p):
|
||||
# https://stackoverflow.com/a/451482
|
||||
# HMM. I BARELY UNDERSTAND THIS.
|
||||
return 0.5 * abs(sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in zip(p, p[1:] + [p[0]])))
|
||||
|
||||
|
||||
def two(instr: str):
|
||||
grid, start_coord = parse(instr)
|
||||
loop_boundary = get_loop_boundary(grid, start_coord)
|
||||
|
||||
# This hilarious thing turns the loop boundary (dict of points with their
|
||||
# distances away from the origin) and orders them into a contiguous
|
||||
# sequence of points that represents the geometry of the shape.
|
||||
|
||||
cands = {}
|
||||
|
||||
for key in loop_boundary:
|
||||
cands[loop_boundary[key]] = cands.get(loop_boundary[key], []) + [key]
|
||||
|
||||
keys = cands.keys()
|
||||
res = []
|
||||
|
||||
for key in sorted(keys):
|
||||
res.append(cands[key][0])
|
||||
|
||||
for key in reversed(sorted(keys)):
|
||||
if len(cands[key]) < 2:
|
||||
continue
|
||||
res.append(cands[key][1])
|
||||
|
||||
# The area includes the size of the line itself, which we don't want. And
|
||||
# an extra one that I don't understand the source of.
|
||||
|
||||
return int(area(res) - max(loop_boundary.values()) + 1)
|
||||
|
||||
|
||||
def _debug(*args, **kwargs):
|
||||
kwargs["file"] = sys.stderr
|
||||
print(*args, **kwargs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in ["1", "2"]:
|
||||
print("Missing day argument", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
inp = sys.stdin.read().strip()
|
||||
if sys.argv[1] == "1":
|
||||
print(one(inp))
|
||||
else:
|
||||
print(two(inp))
|
30
challenges/2023/10-pipeMaze/tests.json
Normal file
30
challenges/2023/10-pipeMaze/tests.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"1": [
|
||||
{
|
||||
"is": "4",
|
||||
"input": ".....\n.S-7.\n.|.|.\n.L-J.\n.....\n\n"
|
||||
},
|
||||
{
|
||||
"is": "8",
|
||||
"input": "..F7.\n.FJ|.\nSJ.L7\n|F--J\nLJ...\n"
|
||||
}
|
||||
],
|
||||
"2": [
|
||||
{
|
||||
"is": "4",
|
||||
"input": "..........\n.S------7.\n.|F----7|.\n.||....||.\n.||....||.\n.|L-7F-J|.\n.|..||..|.\n.L--JL--J.\n..........\n"
|
||||
},
|
||||
{
|
||||
"is": "4",
|
||||
"input": "...........\n.S-------7.\n.|F-----7|.\n.||.....||.\n.||.....||.\n.|L-7.F-J|.\n.|..|.|..|.\n.L--J.L--J.\n...........\n\n"
|
||||
},
|
||||
{
|
||||
"is": "10",
|
||||
"input": "FF7FSF7F7F7F7F7F---7\nL|LJ||||||||||||F--J\nFL-7LJLJ||||||LJL-77\nF--JF--7||LJLJ7F7FJ-\nL---JF-JLJ.||-FJLJJ7\n|F|F-JF---7F7-L7L|7|\n|FFJF7L7F-JF7|JL---7\n7-L-JL7||F7|L7F-7F7|\nL.L7LFJ|||||FJL7||LJ\nL7JLJL-JLJLJL--JLJ.L\n\n"
|
||||
},
|
||||
{
|
||||
"is": "8",
|
||||
"input": ".F----7F7F7F7F-7....\n.|F--7||||||||FJ....\n.||.FJ||||||||L7....\nFJL7L7LJLJ||LJ.L-7..\nL--J.L7...LJS7F-7L7.\n....F-J..F7FJ|L7L7L7\n....L7.F7||L7|.L7L7|\n.....|FJLJ|FJ|F7|.LJ\n....FJL-7.||.||||...\n....L---J.LJ.LJLJ...\n\n"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue