Ici aussi, l'énoncé ne suffit pas à résoudre le problème, les données imposent la méthode.
Il faut constater qu'à l'instar de l'exemple 2 avec output, le rx de sortie n'existe pas dans les données en entrée, et qu'il est uniquement relié à une boîte à conjonction, l'équivalent du inv du second exemple. rx recevra un low pulse ssi inv reçoit des high pulses de partout à la fois.
Et là on lance des simulations (ou on analyse les données en entrée ?), pour voir quand notre inv reçoit des high pulses, on va constater en environ 13000 itérations qu'on a vu passer 3 high pulses de chacun des 4 sources, et que chacun de ces évènement cycle.
Chez moi :
3797 HP depuis la 1ère entrée ;
3847 HP depuis la 3è entrée ;
3877 HP depuis la 4è entrée ;
4051 HP depuis la 2è entrée.
Et ça cycle, donc 1ère entrée en 3797, 7594, 11391, 2è entrée en 4051, 8102, 12153, 3è entrée en 3847, 7694, 11541, 4è entrée en 3877, 7754, 11631.
4 cycles, et qui commencent tous à 0 en plus, heureux hasard, n'est-ce pas ?
En analysant les données on doit réaliser qu'on a 4 parcours indépendants, depuis chacune des 4 entrées de notre broadcaster, qui mènent chacun à une entrée de notre boîte inv, donc les cycles sont bien indépendants.
Par contre, deviner qu'ils commencent à zéro ne me semble pas si évident, même si tout les FlipFlop commencent à off, et les Conjoncteurs à low partout, pourquoi une fois un low reçu à l'arrivée est-ce qu'on retomberait sur l'état initial ?
Bon, bah le résultat c'est de multiplier les longueurs de cycles entre eux.
- Parce qu'on a des cycles indépendants.
- Et qu'ils commencent tous à zéro.
Le résultat est dans les 256 billions (proche de 40004 ), donc on lance pas la force brute, quelle que soit la qualité de notre modélisation.
Côté code j'ai pas lourd à montrer, peut-être ma modélisation pour le 1, mignonne, que j'ai un peu pensée en amont pour pouvoir détecter des cycles, donc avec des frozen dataclass. Ça tourne pas trop mal avec 1 millions de cycles en 15 secondes et une RAM constante à 80Mo (c'est PyPy, ya un overhead de RAM de 70Mo, c'est comme ça).
Le problème réel (4051 cycles) prend moins d'une seconde en CPython, avec 10Mo de RAM.
L'état des Conjunctions est un binaire avec les bits à 0 ou 1 selon le dernier pulse reçu de chaque entrée, donc les entrées sont ordonnées. Et bien sûr, partout, low c'est 1 ou True, et high c'est 0 ou False, ça simplifie de le prendre comme ça !
L'état est dans boxes qui ne contient que des frozen dataclasses, donc en pratique quand une boîte change d'état, elle retourne une nouvelle boîte dans le nouvel état, je ne modifie jamais une boîte, ce n'est pas possible, j'en crée des nouvelles selon la situation.
Cette façon de faire permettrait de comparer deux états global du système.
On n'a pas à faire ça, donc probablement qu'il vaudrait mieux coder des boîtes dynamiques à états.
de toute façon le code final est un mélange de trucs fixes et de trucs qui bougent magiquement avec des variables globales, on a vu plus propre (je pense aux statistiques pour le premier problème)...
data=stdin.read().strip().splitlines()FINAL="ns"# manuellement extrait de mes donnéesfromdataclassesimportdataclassfromcollectionsimportdequefromfunctoolsimportcached_propertymessages=deque()stats=[0,0]# ModulesclassModule:defsend(self,src,low):# low if True, high if Falsefordstinself.destination:stats[notlow]+=1messages.append((src,dst,low))@dataclass(frozen=True)classBroadcaster(Module):destination:tuple=tuple()def__call__(self,src,name,low):self.send(name,low)returnself@dataclass(frozen=True)classFlipFlop(Module):on:bool=Falsedestination:tuple=tuple()def__call__(self,src,name,low):ifnotlow:returnselfself.send(name,self.on)# High if off, low if onreturnFlipFlop(notself.on,self.destination)@dataclass(frozen=True)classConjunction(Module):src:tuple=tuple()destination:tuple=tuple()state:int=0defaddsrc(self,*src):returnConjunction(self.src+src,self.destination,self.state+2**len(self.src))@cached_propertydeffull(self):return2**len(self.src)-1# low pulse for eachdef__call__(self,src,name,low):state=self.fullifself.state==-1elseself.statebit=2**self.src.index(src)iflow:state|=bitelse:state&=self.full-bitself.send(name,state==0)returnConjunction(self.src,self.destination,state)# data modelisationboxes={}conjunctions=set()forlineindata:name,_,*dst=line.replace(",","").split()ifname[0]=="b":boxes[name]=Broadcaster(tuple(dst))elifname[0]=="%":boxes[name[1:]]=FlipFlop(False,tuple(dst))else:boxes[name[1:]]=Conjunction(destination=tuple(dst))conjunctions.add(name[1:])# storing all conjunction names# Updating src for conjunctionsforname,boxinboxes.items():i=set(box.destination).intersection(conjunctions)ifi:forcini:boxes[c]=boxes[c].addsrc(name)# Red Button, DO NOT PUSH !button=Broadcaster(("broadcaster",))defpush():button.send("button",True)cstate=0whilemessages:src,dst,low=messages.popleft()ifdstinboxes:boxes[dst]=boxes[dst](src,dst,low)ifdst==FINALandnotlow:cstate=boxes[FINAL].statereturncstate# Cycling...cycles={}for_inrange(5000**4):if_==1000:stats1000=tuple(stats)state=push()ifstateandstatenotincycles:cycles[state]=_+1iflen(cycles)==len(boxes[FINAL].src):break# Resultsdefmul(i):returnreduce(lambdax,y:x*y,i)ex1=mul(stats1000)ex2=mul(cycles.values())
Le coût fixe de PyPy le rend inutile sur le problème réel, je monte de 0,55s à 0,65s, par contre si je cycle 1 million de fois, ça passe de 2min10 à 15s.
Ça doit être pour ça que je n'ai pas trouvé PyPy si pertinent cette année : l'an dernier je devais être un gros bourrin, cette année je suis tout en finesse (......).
Bon, c'était mignon, mais toujours un poil agaçant quand l'énoncé ne suffit pas à avoir la résolution, et qu'il faut compter sur des particularités des données.
Mais bon, si on nous expliquait dès le début qu'il y avait 4 cycles et qu'il fallait les coordonner, l'exercice serait assez trivial...
# Les données imposent la méthode
Posté par Yth (Mastodon) . En réponse au message Advent of Code 2023, jour 20. Évalué à 3.
Ici aussi, l'énoncé ne suffit pas à résoudre le problème, les données imposent la méthode.
Il faut constater qu'à l'instar de l'exemple 2 avec
output, lerxde sortie n'existe pas dans les données en entrée, et qu'il est uniquement relié à une boîte à conjonction, l'équivalent duinvdu second exemple.rxrecevra un low pulse ssiinvreçoit des high pulses de partout à la fois.Et là on lance des simulations (ou on analyse les données en entrée ?), pour voir quand notre
invreçoit des high pulses, on va constater en environ 13000 itérations qu'on a vu passer 3 high pulses de chacun des 4 sources, et que chacun de ces évènement cycle.Chez moi :
Et ça cycle, donc 1ère entrée en 3797, 7594, 11391, 2è entrée en 4051, 8102, 12153, 3è entrée en 3847, 7694, 11541, 4è entrée en 3877, 7754, 11631.
4 cycles, et qui commencent tous à 0 en plus, heureux hasard, n'est-ce pas ?
En analysant les données on doit réaliser qu'on a 4 parcours indépendants, depuis chacune des 4 entrées de notre broadcaster, qui mènent chacun à une entrée de notre boîte
inv, donc les cycles sont bien indépendants.Par contre, deviner qu'ils commencent à zéro ne me semble pas si évident, même si tout les FlipFlop commencent à off, et les Conjoncteurs à low partout, pourquoi une fois un low reçu à l'arrivée est-ce qu'on retomberait sur l'état initial ?
Bon, bah le résultat c'est de multiplier les longueurs de cycles entre eux.
- Parce qu'on a des cycles indépendants.
- Et qu'ils commencent tous à zéro.
Le résultat est dans les 256 billions (proche de 40004 ), donc on lance pas la force brute, quelle que soit la qualité de notre modélisation.
Côté code j'ai pas lourd à montrer, peut-être ma modélisation pour le 1, mignonne, que j'ai un peu pensée en amont pour pouvoir détecter des cycles, donc avec des frozen dataclass. Ça tourne pas trop mal avec 1 millions de cycles en 15 secondes et une RAM constante à 80Mo (c'est PyPy, ya un overhead de RAM de 70Mo, c'est comme ça).
Le problème réel (4051 cycles) prend moins d'une seconde en CPython, avec 10Mo de RAM.
L'état des Conjunctions est un binaire avec les bits à 0 ou 1 selon le dernier pulse reçu de chaque entrée, donc les entrées sont ordonnées. Et bien sûr, partout, low c'est 1 ou True, et high c'est 0 ou False, ça simplifie de le prendre comme ça !
L'état est dans
boxesqui ne contient que des frozen dataclasses, donc en pratique quand une boîte change d'état, elle retourne une nouvelle boîte dans le nouvel état, je ne modifie jamais une boîte, ce n'est pas possible, j'en crée des nouvelles selon la situation.Cette façon de faire permettrait de comparer deux états global du système.
On n'a pas à faire ça, donc probablement qu'il vaudrait mieux coder des boîtes dynamiques à états.
de toute façon le code final est un mélange de trucs fixes et de trucs qui bougent magiquement avec des variables globales, on a vu plus propre (je pense aux statistiques pour le premier problème)...
Le coût fixe de PyPy le rend inutile sur le problème réel, je monte de 0,55s à 0,65s, par contre si je cycle 1 million de fois, ça passe de 2min10 à 15s.
Ça doit être pour ça que je n'ai pas trouvé PyPy si pertinent cette année : l'an dernier je devais être un gros bourrin, cette année je suis tout en finesse (......).
Bon, c'était mignon, mais toujours un poil agaçant quand l'énoncé ne suffit pas à avoir la résolution, et qu'il faut compter sur des particularités des données.
Mais bon, si on nous expliquait dès le début qu'il y avait 4 cycles et qu'il fallait les coordonner, l'exercice serait assez trivial...