Coverage for Day9 / part2.py: 100%

32 statements  

« 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 -*- 

3 

4""" 

5Advent of Code 2025 

6=================== 

7Day : 9 

8Part : 2 

9 

10Ce script calcule la plus grande aire d’un rectangle axis-aligné totalement 

11contenu dans une boucle décrite par une liste de points (x,y). 

12 

13L’algorithme utilisé est une version optimisée (O(N²)) : 

14- on parcourt toutes les paires de points, 

15- on calcule le rectangle correspondant, 

16- on vérifie rapidement s’il est entièrement contenu dans la boucle, 

17- on garde le maximum. 

18 

19La vérification s’appuie sur les segments de la boucle (edges), ce qui évite 

20les bibliothèques lourdes comme shapely tout en restant efficace. 

21 

22.. codeauthor:: Alexandre Condette <alexandre.condette@wanadoo.fr> 

23""" 

24 

25# %% ========================================================================  

26# Import 

27from itertools import combinations 

28 

29# =========================================================================== 

30 

31# %% ======================================================================== 

32# Lecture de l’input 

33def get_input(day: int = 1, example: bool = False) -> list: 

34 """ 

35 Lit le fichier d'entrée pour le jour demandé. 

36 

37 :param day: numéro du jour AoC 

38 :param example: True → example.txt, False → input.txt 

39 :return: liste de lignes sans fin de ligne 

40 """ 

41 file = 'example.txt' if example else 'input.txt' 

42 with open(f"./Day{day}/{file}", 'r', encoding='utf-8') as f: 

43 return f.read().strip() 

44# =========================================================================== 

45 

46# %% ======================================================================== 

47# Fonctions utilitaires 

48def calculate_area(p1: tuple[int, int], p2: tuple[int, int]) -> int: 

49 """ 

50 Calcule l’aire d’un rectangle défini par deux coins. 

51 

52 Les deux coins sont inclus → +1 sur chaque dimension. 

53 

54 :param p1: premier point (x, y) 

55 :param p2: second point (x, y) 

56 :return: aire du rectangle 

57 :rtype: int 

58 """ 

59 return (abs(p1[0] - p2[0]) + 1) * (abs(p1[1] - p2[1]) + 1) 

60 

61# --------------------------------------------------------------------------- 

62def is_fully_contained( 

63 edges: list[tuple[int, int, int, int]], 

64 min_x: int, 

65 min_y: int, 

66 max_x: int, 

67 max_y: int, 

68) -> bool: 

69 """ 

70 Vérifie si un rectangle est entièrement contenu dans la boucle. 

71 

72 On teste si le rectangle coupe un des segments : 

73 - si oui → il n’est pas contenu, 

74 - sinon → il est contenu. 

75 

76 :param edges: segments de la boucle (min_x, min_y, max_x, max_y) 

77 :param min_x: borne gauche du rectangle 

78 :param min_y: borne basse 

79 :param max_x: borne droite 

80 :param max_y: borne haute 

81 :return: True si contenu, False sinon 

82 :rtype: bool 

83 """ 

84 for e_min_x, e_min_y, e_max_x, e_max_y in edges: 

85 # intersection stricte avec un segment → rectangle "sort" 

86 if min_x < e_max_x and max_x > e_min_x and min_y < e_max_y and max_y > e_min_y: 

87 return False 

88 

89 return True 

90 

91# =========================================================================== 

92 

93# %% ======================================================================== 

94# Résolution — Partie 2 optimisée 

95def solve(data: str) -> int: 

96 """ 

97 Calcule la plus grande aire contenue dans la boucle. 

98 

99 Étapes : 

100 1. Parser les points, 

101 2. Construire les segments, 

102 3. Tester toutes les paires, 

103 4. Vérifier la containment, 

104 5. Garder le maximum. 

105 

106 :param data: texte brut des points "x,y" 

107 :return: aire maximale trouvée 

108 :rtype: int 

109 """ 

110 # parse des points 

111 tiles = [ 

112 (int(x), int(y)) 

113 for line in data.splitlines() 

114 for (x, y) in [line.split(",")] 

115 ] 

116 

117 n = len(tiles) 

118 

119 # construction des segments (edges) 

120 edges = [] 

121 for i in range(n - 1): 

122 p1, p2 = tiles[i], tiles[i + 1] 

123 edges.append( 

124 (min(p1[0], p2[0]), min(p1[1], p2[1]), max(p1[0], p2[0]), max(p1[1], p2[1])) 

125 ) 

126 

127 # segment final (boucle) 

128 p_last = tiles[-1] 

129 p_first = tiles[0] 

130 edges.append( 

131 ( 

132 min(p_last[0], p_first[0]), 

133 min(p_last[1], p_first[1]), 

134 max(p_last[0], p_first[0]), 

135 max(p_last[1], p_first[1]), 

136 ) 

137 ) 

138 

139 best = 0 

140 

141 # test toutes les paires 

142 for p1, p2 in combinations(tiles, 2): 

143 

144 area = calculate_area(p1, p2) 

145 

146 # prune → inutile de tester les petites aires 

147 if area <= best: 

148 continue 

149 

150 min_x, max_x = sorted((p1[0], p2[0])) 

151 min_y, max_y = sorted((p1[1], p2[1])) 

152 

153 # containment 

154 if is_fully_contained(edges, min_x, min_y, max_x, max_y): 

155 best = area 

156 

157 return best 

158 

159# ====================================================================== 

160 

161# %% ======================================================================== 

162# Programme principal 

163if __name__ == "__main__": 

164 INPUT = get_input(9, False) 

165 RESULT = solve(INPUT) 

166 

167 print("\n" + "═" * 60) 

168 print(" 🔐 Advent of Code 2025 — Day 9 | Part 2".center(60)) 

169 print("═" * 60) 

170 print(f"Résultat : \033[96m{RESULT}\033[0m") 

171 print("═" * 60 + "\n") 

172 

173# ===========================================================================