Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f9c9e47

Browse files
committed
Year 20223 day 23
1 parent 7e33783 commit f9c9e47

2 files changed

Lines changed: 328 additions & 0 deletions

File tree

2023/day23.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env python3
2+
from collections import defaultdict
3+
from heapq import heappop, heappush
4+
from itertools import product
5+
from sys import maxsize
6+
7+
import matplotlib.pyplot as plt
8+
import networkx as nx
9+
10+
import adventofcode
11+
12+
t1 = [
13+
'#.#####################',
14+
'#.......#########...###',
15+
'#######.#########.#.###',
16+
'###.....#.>.>.###.#.###',
17+
'###v#####.#v#.###.#.###',
18+
'###.>...#.#.#.....#...#',
19+
'###v###.#.#.#########.#',
20+
'###...#.#.#.......#...#',
21+
'#####.#.#.#######.#.###',
22+
'#.....#.#.#.......#...#',
23+
'#.#####.#.#.#########v#',
24+
'#.#...#...#...###...>.#',
25+
'#.#.#v#######v###.###v#',
26+
'#...#.>.#...>.>.#.###.#',
27+
'#####v#.#.###v#.#.###.#',
28+
'#.....#...#...#.#.#...#',
29+
'#.#########.###.#.#.###',
30+
'#...###...#...#...#.###',
31+
'###.###.#.###v#####v###',
32+
'#...#...#.#.>.>.#.>.###',
33+
'#.###.###.#.###.#.#v###',
34+
'#.....###...###...#...#',
35+
'#####################.#',
36+
]
37+
38+
def parse(lines):
39+
grid = defaultdict(lambda: '#')
40+
for y, row in enumerate(lines):
41+
for x, c in enumerate(row):
42+
grid[(x, y)] = c
43+
return grid
44+
45+
def adjacent(pos):
46+
# clockwise, starting up, negative y is up
47+
for d in [(0, -1), (1, 0), (0, 1), (-1, 0)]:
48+
yield pos[0] + d[0], pos[1] + d[1]
49+
50+
def walkable(grid, icy, pos):
51+
c = grid[pos]
52+
dirs = {'^': (0, -1), '>': (1, 0), 'v': (0, 1), '<': (-1, 0)}
53+
if icy and c in dirs:
54+
d = dirs[c]
55+
adj = [(pos[0] + d[0], pos[1] + d[1])]
56+
else:
57+
adj = adjacent(pos)
58+
return adj
59+
60+
def find_route(lines, icy):
61+
grid = parse(lines)
62+
start = (1, 0)
63+
destination = (len(lines[0]) - 2, len(lines) - 1)
64+
65+
distances = {}
66+
q = [(maxsize, 0, start, {start})]
67+
while q:
68+
_, dist, pos, been = heappop(q)
69+
if pos not in distances or distances[pos] <= dist:
70+
distances[pos] = dist
71+
adj = walkable(grid, icy, pos)
72+
adj = [np for np in adj if grid[np] != '#' and np not in been]
73+
for np in adj:
74+
heappush(q, (maxsize - (dist + 1), dist + 1, np, been | {np}))
75+
76+
return distances[destination]
77+
78+
def part1(lines):
79+
"""
80+
# >>> part1(t1)
81+
94
82+
"""
83+
return find_route(lines, True)
84+
85+
def display(grid, been, w, h):
86+
for y in range(h):
87+
for x in range(w):
88+
ic = '+' if (x, y) in been else grid[(x, y)]
89+
if ic == '#':
90+
ic = ' '
91+
print(ic, end='')
92+
print()
93+
94+
def part2(lines):
95+
"""
96+
# >>> part2(t1)
97+
154
98+
"""
99+
return route_using_networkx(lines)
100+
101+
def find_graph(lines):
102+
grid = parse(lines)
103+
w, h = len(lines[0]), len(lines)
104+
start = (1, 0)
105+
destination = (len(lines[0]) - 2, len(lines) - 1)
106+
nodes = find_nodes(grid, w, h, start, destination)
107+
weights = find_weights(grid, nodes)
108+
return weights, start, destination
109+
110+
def find_weights(grid, nodes):
111+
weights = {}
112+
for node in nodes:
113+
clear = set(p for p in adjacent(node) if grid[p] != '#')
114+
for np in clear:
115+
been = {node, np}
116+
while np not in nodes:
117+
adj = walkable(grid, False, np)
118+
npl = [p for p in adj if grid[p] != '#' and p not in been]
119+
if not npl:
120+
break
121+
assert len(npl) == 1
122+
been.add(npl[0])
123+
np = npl[0]
124+
if np in nodes:
125+
edge = (min(node, np), max(node, np))
126+
weight = len(been) - 1
127+
weights[edge] = weight
128+
return weights
129+
130+
def find_nodes(grid, w, h, start, destination):
131+
nodes = {start, destination}
132+
for x, y in product(range(w), range(h)):
133+
if grid[(x, y)] != '#':
134+
adj = set(p for p in adjacent((x, y)) if grid[p] != '#')
135+
if len(adj) > 2:
136+
nodes.add((x, y))
137+
return nodes
138+
139+
def hike_length(weights, x):
140+
return sum(weights[(min(x[i], x[i + 1]), max(x[i], x[i + 1]))] for i in range(len(x) - 1))
141+
142+
def route_using_networkx(lines):
143+
weights, start, destination = find_graph(lines)
144+
145+
# g = nx.from_edgelist(weights.keys())
146+
g = nx.Graph()
147+
for e, w in weights.items():
148+
g.add_edge(e[0], e[1], weight=w)
149+
150+
longest = max(nx.all_simple_paths(g, start, destination), key=lambda a: hike_length(weights, a))
151+
pe = list(nx.utils.pairwise(longest))
152+
153+
visual = False
154+
if visual:
155+
plot_graph(g, pe)
156+
157+
ll = hike_length(weights, longest)
158+
return ll
159+
160+
def plot_graph(g, pe):
161+
# pos = nx.spring_layout(g, seed=seed)
162+
pos = nx.kamada_kawai_layout(g)
163+
nx.draw_networkx_nodes(g, pos)
164+
nx.draw_networkx_edges(g, pos, width=3)
165+
if pe:
166+
nx.draw_networkx_edges(g, pos, edgelist=pe, edge_color="red", width=6)
167+
nx.draw_networkx_labels(g, pos, font_size=20, font_family="sans-serif")
168+
edge_labels = nx.get_edge_attributes(g, "weight")
169+
nx.draw_networkx_edge_labels(g, pos, edge_labels)
170+
ax = plt.gca()
171+
ax.margins(0.08)
172+
plt.axis("off")
173+
plt.tight_layout()
174+
plt.show()
175+
176+
def main():
177+
puzzle_input = adventofcode.read_input(23)
178+
adventofcode.answer(0, 94, part1(t1))
179+
adventofcode.answer(1, 2310, part1(puzzle_input))
180+
adventofcode.answer(0, 154, part2(t1))
181+
adventofcode.answer(2, 6738, part2(puzzle_input))
182+
183+
if __name__ == '__main__':
184+
import doctest
185+
186+
doctest.testmod()
187+
main()

0 commit comments

Comments
 (0)