Coverage for Day11 / part2.py: 100%
23 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 : 11
8Part : 2
10Résolution du problème : compter le nombre total de chemins allant de 'svr' vers 'out'
11tout en imposant que chaque chemin passe obligatoirement par *dac* ET *fft*.
12Le graphe est dirigé, potentiellement très ramifié, et l'exploration naïve serait
13explosive. On utilise donc une recherche en profondeur combinée à un mécanisme
14de mémoïsation afin d'éviter toute recomputation inutile.
16L'état de recherche inclut le nœud courant ainsi que deux indicateurs booléens
17permettant de savoir si 'dac' et/ou 'fft' ont déjà été visités.
18"""
20# ===========================================================================
22# %% ========================================================================
23# LECTURE DE L’INPUT
24def get_input(day: int = 1, example: bool = False) -> list:
25 """
26 Lit le fichier d'entrée associé au jour donné.
28 :param day: Numéro du jour AoC.
29 :param example: True → example.txt, False → input.txt.
30 :return: Liste de lignes (chaînes de caractères) sans retour à la ligne final.
31 """
32 # /!\ Jour11 la part2 à son propre fichier d'exemple
33 file = "example2.txt" if example else "input.txt"
34 with open(f"./Day{day}/{file}", "r", encoding="utf-8") as f:
35 return [line.rstrip("\n") for line in f]
37# ===========================================================================
40# %% ========================================================================
41# Résolution
42def solve(data: list) -> int:
43 """
44 Compte le nombre total de chemins allant de 'svr' à 'out' et passant
45 obligatoirement par les nœuds 'dac' et 'fft'.
47 Le graphe est décrit sous la forme :
48 device: a b c
49 signifiant que `device` pointe vers les nœuds a, b, c.
51 La fonction réalise une exploration DFS annotée :
52 - Chaque appel de dfs(device, seen_dac, seen_fft) représente un état unique
53 associé au nœud courant et aux flags déjà rencontrés.
54 - Un cache (memo) évite de recalculer les sous-chemins identiques.
55 - Lorsqu'on atteint 'out', on ne valide le chemin que si DAC ET FFT ont
56 effectivement été rencontrés.
58 :param data: Liste de lignes représentant le graphe.
59 :return: Nombre total de chemins valides.
60 """
61 # Construction du graphe sous forme de dictionnaire { noeud: [sorties...] }
62 server = {}
63 for line in data:
64 device, outputs = line.split(": ")
65 server[device] = outputs.split()
67 # Mémoïsation :
68 # (device, seen_dac, seen_fft) → nombre de chemins valides depuis cet état.
69 memo = {}
71 def dfs(device: str, seen_dac: bool, seen_fft: bool) -> int:
72 """
73 Explore récursivement tous les chemins depuis 'device'.
74 Les indicateurs seen_dac et seen_fft suivent l'état du chemin courant.
75 """
76 key = (device, seen_dac, seen_fft)
78 # Résultat déjà calculé → accélération massive sur les grands graphes
79 if key in memo:
80 return memo[key]
82 # Cas terminal : arrivée sur 'out'
83 if device == "out":
84 # Le chemin est valide uniquement si les deux nœuds obligatoires ont été vus
85 memo[key] = 1 if (seen_dac and seen_fft) else 0
86 return memo[key]
88 total = 0
90 # Exploration des successeurs
91 for nxt in server.get(device, []):
92 total += dfs(
93 nxt,
94 seen_dac or (nxt == "dac"), # Mémoire de passage par 'dac'
95 seen_fft or (nxt == "fft") # Mémoire de passage par 'fft'
96 )
98 memo[key] = total
99 return total
101 # Lancement depuis la racine
102 return dfs("svr", False, False)
104# ===========================================================================
107# %% ========================================================================
108# MAIN
109if __name__ == "__main__":
110 RESULT = solve(get_input(11, False))
112 print("\n" + "═" * 60)
113 print(" 🔐 Advent of Code 2025 — Day 11 | Part 2".center(60))
114 print("═" * 60)
115 print(f"Résultat : \033[96m{RESULT}\033[0m")
116 print("═" * 60 + "\n")
118# ===========================================================================