my advent of code solutions for twenty-twenty-five, written (badly) in python
advent-of-code python
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Solve day 8

+114 -1
+92
day8.py
··· 1 + import math 2 + 3 + from util import Vector3 4 + 5 + 6 + def parse_data(input_data: str) -> list[Vector3]: 7 + lines = input_data.splitlines() 8 + 9 + junctions = [] 10 + for line in lines: 11 + coords = [int(x) for x in line.split(",")] 12 + junctions.append(Vector3(*coords)) 13 + 14 + return junctions 15 + 16 + 17 + def part_1(junctions: list[Vector3], connection_count: int) -> str | None: 18 + distances = Vector3.calculate_distances(junctions) 19 + distances.sort(key=lambda x: x[2]) # sort from shortest to longest 20 + 21 + circuits = [] 22 + for a, b, _ in distances[:connection_count]: 23 + add_connection(a, b, circuits) 24 + 25 + circuit_sizes = sorted(list(len(c) for c in circuits), reverse=True) 26 + return str(math.prod(circuit_sizes[:3])) 27 + 28 + 29 + def part_2(junctions: list[Vector3]) -> str | None: 30 + distances = Vector3.calculate_distances(junctions) 31 + distances.sort(key=lambda x: x[2]) # sort from shortest to longest 32 + 33 + circuits = [] 34 + visited = set() 35 + while len(visited) < len(junctions) or len(circuits) > 1: 36 + a, b, _ = distances.pop(0) 37 + add_connection(a, b, circuits) 38 + visited.update((a, b)) 39 + 40 + return str(a.x * b.x) 41 + 42 + 43 + def remove_circuit_containing( 44 + v: Vector3, *, circuits: list[set[Vector3]] 45 + ) -> set[Vector3] | None: 46 + index = next((i for i, elem in enumerate(circuits) if v in elem), -1) 47 + 48 + if index == -1: 49 + return None 50 + 51 + return circuits.pop(index) 52 + 53 + 54 + def add_connection(v1: Vector3, v2: Vector3, circuits: list[set[Vector3]]): 55 + circuit_a = remove_circuit_containing(v1, circuits=circuits) or {v1} 56 + circuit_b = remove_circuit_containing(v2, circuits=circuits) or {v2} 57 + 58 + circuits.append(circuit_a | circuit_b) 59 + 60 + 61 + def solve(input_data: str, use_example: bool = False) -> tuple[str | None, str | None]: 62 + puzzle_data = parse_data(input_data) 63 + 64 + return part_1(puzzle_data, 10 if use_example else 1000), part_2(puzzle_data) 65 + 66 + 67 + examples = [ 68 + { 69 + "input": """162,817,812 70 + 57,618,57 71 + 906,360,560 72 + 592,479,940 73 + 352,342,300 74 + 466,668,158 75 + 542,29,236 76 + 431,825,988 77 + 739,650,466 78 + 52,470,668 79 + 216,146,977 80 + 819,987,18 81 + 117,168,530 82 + 805,96,715 83 + 346,949,466 84 + 970,615,88 85 + 941,993,340 86 + 862,61,35 87 + 984,92,344 88 + 425,690,689""", 89 + "part_1": "40", 90 + "part_2": "25272", 91 + } 92 + ]
+22 -1
util.py
··· 1 1 from __future__ import annotations 2 2 3 3 import enum 4 - from typing import Any, Iterator, NamedTuple, Self 4 + import math 5 + from dataclasses import dataclass 6 + from itertools import combinations 7 + from typing import Any, Iterable, Iterator, NamedTuple, Self 5 8 6 9 7 10 class Tree[T]: ··· 229 232 if isinstance(other, Direction): 230 233 return move_pos(self, other) 231 234 raise NotImplementedError() 235 + 236 + 237 + @dataclass(frozen=True) 238 + class Vector3: 239 + x: int 240 + y: int 241 + z: int 242 + 243 + def distance_to(self, other: Vector3) -> float: 244 + return math.sqrt( 245 + (other.x - self.x) ** 2 + (other.y - self.y) ** 2 + (other.z - self.z) ** 2 246 + ) 247 + 248 + @staticmethod 249 + def calculate_distances( 250 + vectors: Iterable[Vector3], 251 + ) -> list[tuple[Vector3, Vector3, float]]: 252 + return [(v1, v2, v1.distance_to(v2)) for v1, v2 in combinations(vectors, 2)]