Skip to content

Commit 743d077

Browse files
committed
08
1 parent 5a11d9d commit 743d077

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

2025/Day08/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ Visit the website for the full story and [full puzzle](https://adventofcode.com/
99

1010
I decided to go with two implementations of Kruskal's algorithm, as part one and two are different enough. I didn't use a
1111
disjoint set representation. It's fast enough this way as well, and switching to disjoint sets would just make the code longer.
12+
13+
This is how my graph looks with the spanning tree:
14+
15+
![graph.png](graph.png)
16+
17+
I created a small [renderer](graph.html) for this. (Well, ChatGPT created the render, with my input.)

2025/Day08/graph.html

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
2+
<!doctype html>
3+
<html>
4+
<head>
5+
<meta charset="utf-8" />
6+
<title>Kruskal MST Viewer</title>
7+
<style>
8+
html, body {
9+
margin: 0;
10+
height: 100%;
11+
background: #000;
12+
overflow: hidden;
13+
font-family: system-ui, sans-serif;
14+
}
15+
#wrap {
16+
display: grid;
17+
grid-template-columns: 360px 1fr;
18+
height: 100%;
19+
}
20+
#sidebar {
21+
padding: 12px;
22+
color: #ddd;
23+
background: rgba(0,0,0,0.65);
24+
border-right: 1px solid rgba(255,255,255,0.08);
25+
}
26+
textarea {
27+
width: 100%;
28+
height: 55vh;
29+
background: #111;
30+
color: #eee;
31+
border: 1px solid rgba(255,255,255,0.15);
32+
border-radius: 8px;
33+
padding: 8px;
34+
font-family: ui-monospace, monospace;
35+
font-size: 12px;
36+
}
37+
button {
38+
margin-top: 10px;
39+
padding: 8px 10px;
40+
background: rgba(255,255,255,0.12);
41+
border: 1px solid rgba(255,255,255,0.18);
42+
color: #eee;
43+
border-radius: 8px;
44+
cursor: pointer;
45+
}
46+
button:hover { background: rgba(255,255,255,0.18); }
47+
#graph { width: 100%; height: 100%; }
48+
.err { color: #ff8080; white-space: pre-wrap; margin-top: 8px; }
49+
</style>
50+
</head>
51+
<body>
52+
<div id="wrap">
53+
<div id="sidebar">
54+
<div style="font-weight:600">3D points</div>
55+
<textarea id="input">
56+
27279,20893,37416
57+
30000,21000,36000
58+
26000,20000,38000
59+
28000,19000,37000
60+
</textarea>
61+
<button id="render">Render MST</button>
62+
<button id="fit">Fit</button>
63+
<div id="err" class="err"></div>
64+
</div>
65+
<div id="graph"></div>
66+
</div>
67+
68+
<script type="module">
69+
import ForceGraph3D from "https://esm.sh/[email protected]";
70+
71+
const el = document.getElementById("graph");
72+
const ta = document.getElementById("input");
73+
const errEl = document.getElementById("err");
74+
const btn = document.getElementById("render");
75+
76+
/* ---------- Parsing ---------- */
77+
78+
function parsePoints(text) {
79+
const pts = [];
80+
const lines = text.split(/\r?\n/);
81+
for (let i = 0; i < lines.length; i++) {
82+
const line = lines[i].trim();
83+
if (!line) continue;
84+
const parts = line.split(",").map(Number);
85+
if (parts.length !== 3 || parts.some(Number.isNaN)) {
86+
throw new Error(`Line ${i+1}: invalid coordinate`);
87+
}
88+
pts.push({ id: i, x: parts[0], y: parts[1], z: parts[2] });
89+
}
90+
return pts;
91+
}
92+
93+
/* ---------- Kruskal (faithful to your PartTwo) ---------- */
94+
95+
function metric(a, b) {
96+
return (a.x-b.x)**2 + (a.y-b.y)**2 + (a.z-b.z)**2;
97+
}
98+
99+
function kruskal(points) {
100+
const parent = new Map();
101+
points.forEach(p => parent.set(p.id, p.id));
102+
103+
function find(x) {
104+
while (parent.get(x) !== x) {
105+
parent.set(x, parent.get(parent.get(x)));
106+
x = parent.get(x);
107+
}
108+
return x;
109+
}
110+
111+
function union(a, b) {
112+
parent.set(find(b), find(a));
113+
}
114+
115+
const edges = [];
116+
for (let i = 0; i < points.length; i++) {
117+
for (let j = i+1; j < points.length; j++) {
118+
edges.push({
119+
a: points[i],
120+
b: points[j],
121+
w: metric(points[i], points[j])
122+
});
123+
}
124+
}
125+
126+
edges.sort((e1, e2) => e1.w - e2.w);
127+
128+
const mst = [];
129+
for (const e of edges) {
130+
if (find(e.a.id) !== find(e.b.id)) {
131+
union(e.a.id, e.b.id);
132+
mst.push(e);
133+
if (mst.length === points.length - 1) break;
134+
}
135+
}
136+
return mst;
137+
}
138+
139+
/* ---------- Graph ---------- */
140+
141+
const Graph = ForceGraph3D()(el)
142+
.backgroundColor("#000")
143+
.nodeRelSize(1)
144+
.nodeVal(2)
145+
.nodeColor("#ffffff")
146+
.nodeOpacity(1)
147+
.linkColor("#ffeb3b") // yellow edges
148+
.linkWidth(0.7)
149+
.linkOpacity(1)
150+
.enableNodeDrag(false) // positions are fixed
151+
.onEngineStop(() => Graph.zoomToFit(600, 80));
152+
153+
function render() {
154+
errEl.textContent = "";
155+
try {
156+
const points = parsePoints(ta.value);
157+
const mst = kruskal(points);
158+
159+
const SCALE = 1000;
160+
161+
function computeCenter(points) {
162+
let sx = 0, sy = 0, sz = 0;
163+
for (const p of points) {
164+
sx += p.x;
165+
sy += p.y;
166+
sz += p.z;
167+
}
168+
const n = points.length || 1;
169+
return {
170+
x: sx / n,
171+
y: sy / n,
172+
z: sz / n
173+
};
174+
}
175+
176+
const center = computeCenter(points);
177+
178+
const nodes = points.map(p => ({
179+
id: p.id,
180+
181+
// center + scale for rendering
182+
x: (p.x - center.x) / SCALE,
183+
y: (p.y - center.y) / SCALE,
184+
z: (p.z - center.z) / SCALE,
185+
186+
fx: (p.x - center.x) / SCALE,
187+
fy: (p.y - center.y) / SCALE,
188+
fz: (p.z - center.z) / SCALE
189+
}));
190+
191+
192+
const links = mst.map(e => ({
193+
source: e.a.id,
194+
target: e.b.id
195+
}));
196+
197+
Graph.graphData({ nodes, links });
198+
setTimeout(() => Graph.zoomToFit(600, 80), 50);
199+
} catch (e) {
200+
errEl.textContent = String(e.message ?? e);
201+
}
202+
}
203+
204+
btn.addEventListener("click", render);
205+
render();
206+
207+
const btnFit = document.getElementById("fit");
208+
btnFit.addEventListener("click", () => {
209+
Graph.zoomToFit(300, 80);
210+
});
211+
212+
window.addEventListener("resize", () => {
213+
Graph.zoomToFit(300, 80);
214+
});
215+
</script>
216+
</body>
217+
</html>

2025/Day08/graph.png

686 KB
Loading

0 commit comments

Comments
 (0)