feat: add solution 2025/08
This commit is contained in:
20
data/example/2025/08.txt
Normal file
20
data/example/2025/08.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
162,817,812
|
||||||
|
57,618,57
|
||||||
|
906,360,560
|
||||||
|
592,479,940
|
||||||
|
352,342,300
|
||||||
|
466,668,158
|
||||||
|
542,29,236
|
||||||
|
431,825,988
|
||||||
|
739,650,466
|
||||||
|
52,470,668
|
||||||
|
216,146,977
|
||||||
|
819,987,18
|
||||||
|
117,168,530
|
||||||
|
805,96,715
|
||||||
|
346,949,466
|
||||||
|
970,615,88
|
||||||
|
941,993,340
|
||||||
|
862,61,35
|
||||||
|
984,92,344
|
||||||
|
425,690,689
|
||||||
135
src/solution/year_2025/day_08.py
Normal file
135
src/solution/year_2025/day_08.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import math
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import Any, Self
|
||||||
|
|
||||||
|
Point = tuple[int, int, int]
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_input(input_data: str) -> list[Point]:
|
||||||
|
points = []
|
||||||
|
for line in input_data.splitlines():
|
||||||
|
a, b, c, *_ = list(map(int, line.split(",")))
|
||||||
|
points.append((a, b, c))
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sorted_pairs(points: list[Point]) -> list[tuple[int, Point, Point]]:
|
||||||
|
pairs: list[tuple[int, Point, Point]] = []
|
||||||
|
|
||||||
|
for idx, p1 in enumerate(points):
|
||||||
|
x1, y1, z1 = p1
|
||||||
|
for p2 in points[idx + 1 :]:
|
||||||
|
x2, y2, z2 = p2
|
||||||
|
dist = (x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2
|
||||||
|
pairs.append((dist, p1, p2))
|
||||||
|
|
||||||
|
pairs.sort()
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
|
class UnionFind:
|
||||||
|
def __init__(self):
|
||||||
|
# internal counter for ids
|
||||||
|
self._component_id = 0
|
||||||
|
# component id to size mapper
|
||||||
|
self.component_size: dict[int, int] = {}
|
||||||
|
# point to id mapper
|
||||||
|
self.points: dict[Point, int] = {}
|
||||||
|
# point to parent point mapper
|
||||||
|
self.parent: dict[Point, Point] = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_points(cls, points: Iterable[Point]) -> Self:
|
||||||
|
c = cls()
|
||||||
|
for p in points:
|
||||||
|
c.insert_point(p)
|
||||||
|
|
||||||
|
return c
|
||||||
|
|
||||||
|
def _issue_component_id(self) -> int:
|
||||||
|
self._component_id += 1
|
||||||
|
return self._component_id
|
||||||
|
|
||||||
|
def _get_parent(self, p: Point) -> Point:
|
||||||
|
if self.parent[p] == p:
|
||||||
|
return p
|
||||||
|
|
||||||
|
parent = self._get_parent(self.parent[p])
|
||||||
|
|
||||||
|
self.parent[p] = parent
|
||||||
|
self.points[p] = self.points[parent]
|
||||||
|
|
||||||
|
return parent
|
||||||
|
|
||||||
|
def _update_parent(self, point: Point, parent: Point):
|
||||||
|
ppoint = self._get_parent(point)
|
||||||
|
pparent = self._get_parent(parent)
|
||||||
|
|
||||||
|
self.parent[ppoint] = pparent
|
||||||
|
self.points[pparent] = self.points[pparent]
|
||||||
|
|
||||||
|
def insert_point(self, p: Point) -> None:
|
||||||
|
pid = self._issue_component_id()
|
||||||
|
|
||||||
|
assert p not in self.points
|
||||||
|
self.points[p] = pid
|
||||||
|
self.parent[p] = p
|
||||||
|
|
||||||
|
self.component_size[pid] = 1
|
||||||
|
|
||||||
|
def connect(self, p1: Point, p2: Point):
|
||||||
|
if p1 not in self.points:
|
||||||
|
self.insert_point(p1)
|
||||||
|
|
||||||
|
if p2 not in self.points:
|
||||||
|
self.insert_point(p2)
|
||||||
|
|
||||||
|
pid1 = self.points[self._get_parent(p1)]
|
||||||
|
pid2 = self.points[self._get_parent(p2)]
|
||||||
|
|
||||||
|
if pid1 == pid2:
|
||||||
|
return
|
||||||
|
|
||||||
|
# swap components so we can assume component pid1 is larger
|
||||||
|
if self.component_size[pid1] < self.component_size[pid2]:
|
||||||
|
p1, p2 = p2, p1
|
||||||
|
pid1, pid2 = pid2, pid1
|
||||||
|
|
||||||
|
self.component_size[pid1] += self.component_size[pid2]
|
||||||
|
self.component_size.pop(pid2)
|
||||||
|
|
||||||
|
self._update_parent(p2, p1)
|
||||||
|
|
||||||
|
|
||||||
|
def part_1(input_data: str) -> Any:
|
||||||
|
points = _parse_input(input_data)
|
||||||
|
pairs = _get_sorted_pairs(points)
|
||||||
|
|
||||||
|
g = UnionFind.from_points(points)
|
||||||
|
for _, p1, p2 in pairs[:1000]:
|
||||||
|
g.connect(p1, p2)
|
||||||
|
|
||||||
|
return math.prod(sorted(g.component_size.values())[-3:])
|
||||||
|
|
||||||
|
|
||||||
|
def part_2(input_data: str) -> Any:
|
||||||
|
points = _parse_input(input_data)
|
||||||
|
pairs = _get_sorted_pairs(points)
|
||||||
|
|
||||||
|
g = UnionFind.from_points(points)
|
||||||
|
for _, p1, p2 in pairs:
|
||||||
|
g.connect(p1, p2)
|
||||||
|
|
||||||
|
if len(g.component_size) == 1:
|
||||||
|
return p1[0] * p2[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def test_part_1(example_data):
|
||||||
|
assert part_1(example_data) == 20
|
||||||
|
|
||||||
|
|
||||||
|
def test_part_2(example_data):
|
||||||
|
assert part_2(example_data) == 25272
|
||||||
Reference in New Issue
Block a user