Nous sommes toujours sur l'île du désert. Grâce aux pièces détachées reçues de l'île du métal, triées avec notre aide, les lutins ont pu réparer leurs machines et cherchent maintenant à les démarrer.
Première partie
Les machines sont commandées par un système de communication très lutinesque, c'est à dire complexe à souhait : il est constitué de modules reliés les uns aux autres, et qui fonctionnent un peu comme des portes logiques électroniques qui s'envoient des signaux bas ou hauts.
On a une description du système de communication, par exemple :
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a
Ou encore :
broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output
On appuie sur un bouton, qui envoie un signal bas au diffuseur (le broadcaster
), qui le transmet à toutes ses sorties. Ensuite, les signaux progressent de module en module avec des règles spécifiques pour chaque type de module (le symbole avant le nom de chaque module indique son type). Les règles sont un peu longues à expliquer ici, cf. la page du problème.
On appuie 1000 fois sur le bouton, et on veut savoir combien de signaux bas et combien de signaux hauts ont été transmis. En fait on nous demande le produit de ces deux comptages.
Deuxième partie
Après cet échauffement, il est temps d'allumer le convoyeur de sable principal. Pour ça, il faut lui transmettre un signal haut. Le problème, c'est que quand on appuie une seule fois sur le bouton, il reçoit un signal bas. Quand on appuie une seconde fois, il reçoit un signal bas. Et ainsi de suite, vous pouvez bien appuyer des milliards de fois, il recevra toujours un signal bas.
Il finira bien par recevoir un signal haut, mais ça risque de prendre longtemps. Très longtemps. Il faut appuyer quelque chose comme plusieurs centaines de milliers de milliards de fois sur ce satané bouton. Appuyer avec précaution, parce qu'après un tel usage, il risquerait d'être dans un sale état. Mais combien de fois exactement, d'ailleurs ? Bonne chance !
# Les données imposent la méthode
Posté par Yth (Mastodon) . É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
, lerx
de sortie n'existe pas dans les données en entrée, et qu'il est uniquement relié à une boîte à conjonction, l'équivalent duinv
du second exemple.rx
recevra un low pulse ssiinv
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 :
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)…
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…
[^] # Re: Les données imposent la méthode
Posté par Yth (Mastodon) . Évalué à 2.
Ah oui, le
deque()
permet de faire une FIFO de messages, on les empile à droite, on les dépile à gauche, on a fini quand c'est vide, d'où un code d'itération assez simple dans la fonctionpush()
.C'était la minute structure de données pas encore vue cette année. Le
deque()
c'est unelist()
optimisée pour faire des ajouts/suppressions aux bouts, donc idéal pour des FIFO, LIFO et trucs du genre.[^] # Re: Les données imposent la méthode
Posté par Guillaume.B . Évalué à 1.
En Haskell, on la structure
Seq
. C'est des structures immuables permettant d'ajouter ou d'enlever un élément en début ou fin en temps constant. Quand je dis enlever ou ajouter, je veux dire créer une nouvelle séquence en se basant sur l'existante mais sans la modifier.Sous le capot, c'est basé sur les finger trees.
https://en.wikipedia.org/wiki/Finger_tree
# Solution en Haskell
Posté par Guillaume.B . Évalué à 2.
Je peux dire que j'ai galéré sur celui là. Tout ça parce que j'ai mal lu l'énoncé.
Je n'avais pas vu qu'il fallait traiter les gestions de pulsations sous forme de file.
Je les traitais sous forme de pile et bizarrement ça m'a quand même donné la bonne réponse pour la première partie.
Pour la deuxième partie, c'est comme pour le jour 8, c'est très compliqué dans le cas général mais facile parce que l'instance a de bonnes propriétés (je n'aime pas trop ce genre de journée).
Tout d'abord, on remarque que rx a un seul prédécesseur qui est de type Conjonction et que celui a 4 prédécesseurs (que je vais appeler a, b, d) chacun de type Conjonction.
Ensuite, si on enlève broadcaster, rx et son prédécesseur, on se retrouve avec 4 composantes connexes et donc les pulsations de chacune vont vivre leur vie indépendamment des autres.
Si, on regarde quand a, b, c ou d envoie une pulsation forte, on remarque que ça forme un cycle sans prépériode et qu'une pulsation forte n'apparait qu'une seule fois durant un cycle.
Il suffit donc de repérer pour a, b, c et d la première fois qu'il y a une pulsation forte et faire le PPCM entre les différentes valeurs trouvées.
Pour le code en Haskell, j'utilise une monade State et des Lens, ce qui me permet de simplifier l'écriture.
[^] # Re: Solution en Haskell
Posté par Yth (Mastodon) . Évalué à 2.
Ah, oui, c'est vrai ça, le résultat c'est le PPCM des 4 cycles, sauf qu'en l'occurrence ils sont premiers entre eux (enfin, chez moi en tout cas…), encore une propriété cachée dans les données, d'où la multiplication annoncée dans mon message !
J'ai testé vite fait la multiplication sur le site avant de lancer le PPCM, et ça a fonctionné, donc j'ai complètement oublié qu'il fallait faire ça pour être corrects.
# Pas d'exemple
Posté par 🚲 Tanguy Ortolo (site web personnel) . Évalué à 3.
À noter que les exemples fournis en première partie ne s'appliquaient pas à la deuxième partie, et que cette dernière ne fournissait aucun exemple utilisable. J'ai trouvé ça vache.
[^] # Re: Pas d'exemple
Posté par Ysabeau 🧶 (site web personnel, Mastodon) . Évalué à 3.
Y'a moyen de voir ce que fait le code au fait ? Le code ça ne me dit rien.
« Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.
[^] # Re: Pas d'exemple
Posté par Yth (Mastodon) . Évalué à 3.
C'est pas hyper simple à représenter, mais je vais essayer.
On va dire qu'un « low pulse » est un 1, et un « high pulse » un 0.
C'est plus simple pour comprendre.
Le button envoie un 1 quand on appuie dessus.
Le broadcaster transmet ce qu'il a reçu à toutes ses destinations.
Les noms simples, en deux lettres, sans cadre, sont des alternateurs, s'ils reçoivent un 1, ils envoient une fois un 1, et une fois un 0. Mais s'ils reçoivent 0 ils bougent pas.
À l'état initial ils enverront un 0 au premier 1 reçu.
Donc notre broadcaster étant relié à des alternateurs, au repos il ne se passe rien, mais quand on appuie sur le gros bouton rouge, il active les quatre alternateurs devant lui.
Les noms dans les cadres sont des boîtes de remise à zéro, en gros.
Au début elles sont configurées sur 1 à toutes leurs entrées, mais c'est mis à jour dès qu'elles reçoivent un truc.
Si toutes leurs entrées sont à 0, alors elles envoient un 1 à toutes leurs sorties ! Sinon, c'est un 0, qui sera ignoré par tous les alternateurs connectés. D'où le nom de boîte de remise à zéro, ça ne va envoyer 1 aux alternateurs que quand ça reçoit du 0 de partout à la fois !
Quand ça arrive, les boîtes à droites reçoivent un 1 et donc envoient un 0, sinon elles reçoivent un 0 et envoie un 1.
Avec une seule entrée, ce sont des inverseurs.
Donc quand nos grosses boîtes s'activent et réussissent à envoyer un 1, le signal est inversé en 0 vers la boîte finale.
Si toutes les quatre grosses boîtes envoient un 1 en même temps, alors la boîte finale reçoit quatre 0.
À ce moment là elle va envoyer un 1 vers rx, qui est notre module de sortie et qui n'attends que ça !
Franchement, une fois représenté comme ça, le problème il est hyper clair hein ?
Faut trouver le moment où chacune des grosses boîte s'active.
Là, on a grosso-modo tous fait une simulation : on part de l'état initial, chaque alternateur va envoyer un 0, et les grosses boîtes sont sur 1 dans toutes leurs entrées, et on appuie sur le gros bouton rouge, jusqu'à ce que les boîtes s'activent.
Là on note quand c'est arrivé.
Typiquement on va continuer pour voir quand ça va se produire à nouveau, pour chaque boîte, et essayer de déduire un schéma.
Ici c'est facile, en fait, dès la première fois que ça s'active, on a fait un cycle et c'est retour à l'état initial, en fait pas tout à fait, mais au moins ça boucle sur la même durée.
On a donc 4 cycles, il ne reste plus qu'à les coordonner, savoir quand est-ce qu'ils vont s'activer tous ensemble.
Et le plus petit multiple commun aux quatre valeurs mesurées est notre réponse.
Maintenant en poussant l'analyse.
Notre série par exemple de la première boîte, consiste en 12 alternateurs, tous éteints donc prêts à envoyer 0.
On va noter ça
000000000000
.La boîte n'enverra absolument rien tant qu'on ne l'aura pas activée (enfin, elle envoie des 0 qui sont ignorés par les alternateurs).
Si on appuie, le premier va s'allumer, et envoyer 0, qui va être ignoré, on est donc dans cette situation :
100000000000
.Au second coup, notre premier alternateur va s'éteindre, repasser à 0, mais aura envoyé un 1 à droite, sur le second, qui va s'allumer et envoyer 0 qui sera ignoré.
01000000000
Au 3ème coup, on rallume le premier, rien d'autre ne bouge :
110000000000
.4 :
0010000000
5 :
1010000000
6 :
0110000000
7 :
1110000000
Ok peut continuer la simulation, mais il apparaît comme évident qu'on est en train de compter. On a exactement la représentation binaire, mais de gauche à droite, des numéros des étapes.
Là on va regarder notre boîte nl, et constater qu'elle va s'activer dès qu'elle reçoit 0 en même temps de la part de hd, rh, tg, qz, rl, pf et pr, ce qui va arriver quand il vont tous s'être allumés (passer de 0 à 1) comme dernière action.
Ça arrive dès qu'on a 1 sur chacun d'entre eux, et 0 sur les autres, c'est la première fois où ça va se produire et c'est cette configuration :
101001001111
qu'on retourne en111100100101
pour avoir le chiffre binaire 3877, qu'on retrouve bien dans mes résultats plus haut.Pour les autres, on va simplement lire
111000001111
soit 3847,110010111111
soit 4051, et enfin101010110111
soit 3797.Diantre c'est juste.
Là nos nombres sont premiers entre eux, donc le PPCM vaut la multiplication des 4 soit… beaucoup.
Mais bref, voilà le problème entièrement décortiqué, et finalement une lecture directe des chiffre à trouver, sans aucun calcul, sans besoin d'ordinateur, sauf pour le PPCM (c'est un peu misérable à la main avec des chiffres aussi gros quand même).
Merci de ta question Ysabeau, je n'aurais pas poussé autant sinon :)
Pour continuer un petit peu, sur notre exemple 1.
On active sur
101001001111
mais à ce moment là notre boîte ne se contente pas d'envoyer un 1 vers la sortie, elle envoie aussi un 1 vers tous nos 0 !En effet, tous les alternateurs qui n'envoient pas vers la boîte, reçoivent de la boîte.
Ils sont éteints, ils vont donc s'allumer, envoyer des 0 qui seront ignorés et passer tous à 1 :
111111111111
= 4095.Sauf que le tout premier reçoit lui aussi un 1, exactement comme si on avait appuyé sur le bouton une fois tout passé à 1 !
C'est
hd
, et il est bien en dernier de la liste des alternateurs activés parnl
. On lui envoie un 1 qui va faire s'éteindre tous les alternateurs en chaîne, puisqu'ils envoient un 1 en s'éteignant. Ils vont donc tous avoir envoyé un 1 vers la boîte (ceux qui sont connectés) qui verra bien son état retourner à celui d'origine.D'où la boucle et le cycle qui recommence exactement à zéro, on est effectivement retourné précisément à l'état initial.
On a compté de 0 à 3877, et au moment d'arriver à 3877, on a une remise à 0 avec un signal envoyé vers la sortie.
[^] # Re: Pas d'exemple
Posté par Ysabeau 🧶 (site web personnel, Mastodon) . Évalué à 3.
Merci pour les explications (et je comprend qu'il n'y à rien à voir en fait).
« Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.
[^] # Re: Pas d'exemple
Posté par Yth (Mastodon) . Évalué à 2.
Ah non, on manipule des chiffres ou des lettres, réunis en ensembles, en listes, en dictionnaires.
Parfois on peut représenter ce qu'il se passe, parfois pas trop.
Et on doit faire gaffe à pas trop demander de calculs à la machine, sinon ça peut prendre des mois.
# Enfin...
Posté par syj . Évalué à 1.
J'ai enfin ma solution pour le jour 20.
1) J'ai exploré plusieurs solutions comme laisser mon PC calculé à l'aveugle pendant 1j de travail. Je chauffe en partie à l'électrique. Donc, cela ne me coutait pas de laisser mon PC cramer des Watt :).
2) J’ai tenté de simplifier les cycles des flip-flop sans grand succès. Car je pensais que les conjuctions serait beaucoup plus complexe avec leurs multiples false, true qu’elles crachent
3) Via récursivité de trouver une méthode pour factoriser les circuits. Je n’ai pas vu la solution suivante toute suite, car ma fonction recursive allé jusqu’au flip / flop. Mais au final,c’était trop complexe pour trouver une simplification évidente.
4) Finalement, j’ai tenté juste de regarder les cycles des trues sur les 4 conjonctions qui sont avant RX { "hz", "pv", "qh", "xm" }
Et là, c’était évident. J’avais des cycles réguliers pour ces 4 conjonctions. Il ne me reste plus qu’à trouver quand elles sont à true toutes les 4 en même temps. Ce qui revient à trouver le PPCM des 4 cycles.
J’ai demandé à chatgpt de me fournir le calcul d’un PPCM (çà se reconnait au style).
En prenant, le problème dans le bon sens, j’aurai pu le résoudre en 1h partie 1 et partie 2…. J’ai mis encore bien 5h cumulé.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.