Coverage for Day8 / part2.py: 92%
58 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 09:47 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 09:47 +0000
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
4"""
5Advent Of Code 2025
6===================
7Day : 8
8Part : 2
10Ce script poursuit la logique de la Part 1 mais cette fois :
11------------------------------------------------------------
12On connecte les points dans l’ordre des distances croissantes,
13comme dans Kruskal, jusqu'à ce que le graphe devienne entièrement
14connecté (une seule composante).
16L'arête qui réalise la connexion finale est la plus éloignée
17dans l'arbre couvrant minimal.
19Le résultat demandé est :
20 produit des abscisses (x) des deux points reliés par cette
21 dernière arête.
23Concept :
24----------
25- calcul de toutes les distances au carré,
26- tri des arêtes,
27- union-find pour fusionner les composantes,
28- quand il ne reste plus qu’une composante,
29 on retourne x_i * x_j pour cette arête.
31La logique est identique à ton implémentation originale,
32seule la documentation est améliorée.
34.. codeauthor:: Alexandre Condette <alexandre.condette@wanadoo.fr>
35"""
37# %% ========================================================================
38# Lecture de l’input
39def get_input(day: int = 1, example: bool = False) -> list:
40 """
41 Lit le fichier d'entrée pour le jour demandé.
43 :param day: numéro du jour AoC
44 :param example: True → example.txt, False → input.txt
45 :return: liste des lignes sans fin de ligne
46 """
47 filename = 'example.txt' if example else 'input.txt'
48 with open(f"./Day{day}/{filename}", 'r', encoding='utf-8') as f:
49 return [line.rstrip('\n') for line in f]
51# ===========================================================================
53# ===========================================================================
54# Structures et fonctions utilitaires
55class UnionFind:
56 """
57 Structure Union-Find (Disjoint Set Union).
58 Fonctionne avec :
59 - compression de chemin,
60 - union par taille,
61 - fusion des composantes.
62 """
64 def __init__(self, n: int):
65 self.parent = list(range(n))
66 self.size = [1] * n
68 def find(self, x: int) -> int:
69 """Retourne le représentant de la composante contenant x."""
70 while self.parent[x] != x:
71 self.parent[x] = self.parent[self.parent[x]]
72 x = self.parent[x]
73 return x
75 def union(self, a: int, b: int) -> bool:
76 """
77 Fusionne les composantes de a et b.
78 Retourne True si une fusion a eu lieu.
79 """
80 ra = self.find(a)
81 rb = self.find(b)
83 if ra == rb:
84 return False
86 if self.size[ra] < self.size[rb]:
87 ra, rb = rb, ra
89 self.parent[rb] = ra
90 self.size[ra] += self.size[rb]
91 return True
93# ---------------------------------------------------------------------------
94def parse_coords(lines: list) -> list:
95 """
96 Convertit des lignes "x,y,z" en tuples (x, y, z).
98 :param lines: lignes de texte
99 :return: liste de tuples
100 """
101 pts = []
102 for line in lines:
103 s = line.strip()
104 if not s: 104 ↛ 105line 104 didn't jump to line 105 because the condition on line 104 was never true
105 continue
106 x, y, z = s.split(",")
107 pts.append((int(x), int(y), int(z)))
108 return pts
111# ===========================================================================
112# Résolution
113def solve(lines: list) -> int:
114 """
115 Partie 2 : connexion complète du graphe.
117 On :
118 - génère toutes les arêtes avec distance,
119 - trie les arêtes,
120 - applique Kruskal,
121 - quand il reste une seule composante,
122 on retourne le produit des abscisses des deux points
123 connectés par la dernière arête.
125 :param lines: input brut
126 :return: produit x_i * x_j de la dernière connexion
127 """
129 pts = parse_coords(lines)
130 n = len(pts)
132 if n <= 1: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true
133 return 0
135 # Construction de toutes les arêtes (dist², i, j)
136 edges = []
137 for i in range(n):
138 xi, yi, zi = pts[i]
139 for j in range(i + 1, n):
140 xj, yj, zj = pts[j]
141 dx = xi - xj
142 dy = yi - yj
143 dz = zi - zj
144 dist2 = dx * dx + dy * dy + dz * dz
145 edges.append((dist2, i, j))
147 # tri des arêtes par distance croissante
148 edges.sort(key=lambda e: e[0])
150 uf = UnionFind(n)
151 components = n
153 # Kruskal : on fusionne jusqu'à une seule composante
154 for dist2, i, j in edges: 154 ↛ 164line 154 didn't jump to line 164 because the loop on line 154 didn't complete
155 if uf.union(i, j):
156 components -= 1
158 # dernière arête → graphe connecté
159 if components == 1:
160 x1 = pts[i][0]
161 x2 = pts[j][0]
162 return x1 * x2
164 return 0
166# ===========================================================================
167if __name__ == "__main__":
168 RESULT = solve(get_input(8, False))
170 print("\n" + "═" * 60)
171 print(" 🔐 Advent of Code 2025 — Day 8 | Part 2".center(60))
172 print("═" * 60)
173 print(f"Résultat : \033[96m{RESULT}\033[0m")
174 print("═" * 60 + "\n")