Skip to content

Commit 3f9585f

Browse files
authored
Solutions days 23, 25 from 2023 (#20)
* Solution day 23 * Solve day 25
1 parent 557c0fb commit 3f9585f

5 files changed

Lines changed: 195 additions & 0 deletions

File tree

src/2023/23/2023_23.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import itertools
2+
from typing import Tuple
3+
4+
import networkx as nx
5+
import numpy as np
6+
from tqdm import tqdm
7+
8+
from src.utils.data import load_data
9+
from src.utils.submission import submit_or_print
10+
11+
ROCK = "#"
12+
13+
N = (-1, 0)
14+
S = (1, 0)
15+
W = (0, -1)
16+
E = (0, 1)
17+
18+
19+
def main(debug: bool) -> None:
20+
input_data = load_data(debug)
21+
22+
grid, start, end = parse_grid(input_data)
23+
24+
result_part1 = solve_part1(grid, start, end)
25+
result_part2 = solve_part2(grid, start, end)
26+
27+
submit_or_print(result_part1, result_part2, debug)
28+
29+
30+
def parse_grid(input_data: str) -> Tuple[np.array, Tuple[int, int], Tuple[int, int]]:
31+
rows = []
32+
for line in input_data.strip().splitlines():
33+
rows.append(list(line))
34+
35+
# borders
36+
rows.append(["#" for _ in rows[0]])
37+
rows.insert(0, ["#" for _ in rows[0]])
38+
39+
grid = np.array(rows)
40+
41+
# search for start and end positions
42+
start = 1, [y for y in range(grid.shape[1]) if grid[1, y] == "."][0]
43+
end = (
44+
grid.shape[0] - 2,
45+
[y for y in range(grid.shape[1]) if grid[grid.shape[0] - 2, y] == "."][0],
46+
)
47+
48+
return grid, start, end
49+
50+
51+
def solve_part1(grid: np.array, start: Tuple[int, int], end: Tuple[int, int]) -> int:
52+
graph = create_graph(grid)
53+
return max(map(len, nx.all_simple_paths(graph, start, end))) - 1
54+
55+
56+
def solve_part2(grid: np.array, start: Tuple[int, int], end: Tuple[int, int]) -> int:
57+
graph = create_graph(grid, part2=True)
58+
59+
print("Compressing graph...")
60+
compressed_graph = compress(graph)
61+
print("Initial graph: ", graph)
62+
print("Compressed graph:", compressed_graph)
63+
64+
viz_path = "graph.png"
65+
pos = nx.planar_layout(compressed_graph)
66+
nx.draw(compressed_graph, pos, with_labels=True)
67+
import matplotlib.pyplot as plt
68+
69+
plt.savefig(viz_path)
70+
print(f"Saved compressed graph visualization to: {viz_path}")
71+
72+
print("Searching for longest path in compressed graph...")
73+
return max(
74+
map(
75+
lambda path: nx.path_weight(compressed_graph, path, "weight"),
76+
nx.all_simple_paths(compressed_graph, start, end),
77+
)
78+
)
79+
80+
81+
def create_graph(grid: np.array, part2: bool = False) -> nx.Graph:
82+
graph = nx.Graph() if part2 else nx.DiGraph()
83+
for x, y in np.ndindex(grid.shape):
84+
if grid[x, y] != ROCK:
85+
graph.add_node((x, y))
86+
for x, y in np.ndindex(grid.shape):
87+
p = grid[x, y]
88+
if p != ROCK:
89+
if part2:
90+
p = "."
91+
92+
match p:
93+
case ">":
94+
dirs = [E]
95+
case "<":
96+
dirs = [W]
97+
case "^":
98+
dirs = [N]
99+
case "v":
100+
dirs = [S]
101+
case _:
102+
dirs = [N, S, W, E]
103+
104+
for d in dirs:
105+
n_pos = d[0] + x, d[1] + y
106+
n = grid[n_pos]
107+
if n != ROCK:
108+
graph.add_edge((x, y), n_pos, weight=1)
109+
return graph
110+
111+
112+
def compress(graph: nx.Graph) -> nx.Graph:
113+
crossroads = {node for node in graph.nodes if len(graph.edges(node)) != 2}
114+
115+
compressed_graph = nx.Graph(graph)
116+
for p1, p2 in tqdm(list(itertools.combinations(crossroads, 2))):
117+
# simplified graph without crossroads
118+
graph_copy = nx.Graph(graph)
119+
for c in crossroads:
120+
if c not in {p1, p2}:
121+
graph_copy.remove_node(c)
122+
123+
# find all paths
124+
for path in nx.all_simple_paths(graph_copy, p1, p2):
125+
if len(path) < 3:
126+
continue
127+
weight = len(path) - 1
128+
compressed_graph.add_edge(p1, p2, weight=weight)
129+
for n in path[1:-1]:
130+
compressed_graph.remove_node(n)
131+
return compressed_graph
132+
133+
134+
if __name__ == "__main__":
135+
debug_mode = True
136+
# debug_mode = False
137+
main(debug_mode)

src/2023/23/sample_input.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#.#######
2+
#>......#
3+
#.###.#.#
4+
#....>..#
5+
#######.#

src/2023/25/2023_25.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import math
2+
import re
3+
4+
import matplotlib.pyplot as plt
5+
import networkx as nx
6+
7+
from src.utils.data import load_data
8+
from src.utils.submission import submit_or_print
9+
10+
11+
def main(debug: bool) -> None:
12+
input_data = load_data(debug)
13+
14+
graph = parse_graph(input_data)
15+
print(graph)
16+
17+
viz_path = "graph.png"
18+
pos = nx.spring_layout(graph)
19+
nx.draw(graph, pos, with_labels=True)
20+
plt.savefig(viz_path)
21+
print(f"Saved graph visualization to: {viz_path}")
22+
23+
edges = nx.edge_betweenness_centrality(graph)
24+
top3 = sorted(edges.items(), key=lambda k: k[1], reverse=True)[:3]
25+
for e, _ in top3:
26+
graph.remove_edge(*e)
27+
print(f"Removed 3 key edges: {[t[0] for t in top3]}")
28+
29+
result_part1 = math.prod([len(c) for c in nx.connected_components(graph)])
30+
result_part2 = None
31+
32+
submit_or_print(result_part1, result_part2, debug)
33+
34+
35+
def parse_graph(input_data: str) -> nx.Graph:
36+
graph = nx.Graph()
37+
for line in input_data.strip().splitlines():
38+
nodes = re.findall(r"[a-z]+", line)
39+
for node in nodes[1:]:
40+
graph.add_edge(nodes[0], node)
41+
return graph
42+
43+
44+
if __name__ == "__main__":
45+
debug_mode = True
46+
# debug_mode = False
47+
main(debug_mode)

src/2023/25/graph.png

38.5 KB
Loading

src/2023/25/sample_input.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
a: b c d e
2+
e: f g h a
3+
f: b g h
4+
h: d g f
5+
b: f c d
6+
d: h b c

0 commit comments

Comments
 (0)