Jour 14, tout en haut des nuages
À partir de demain nous allons redescendre, il n'y a plus d'île au-dessus de l'île de lave, donc une fois que la lave se remettra à couler, on va retourner en arrière pour tout remettre en marche.
On imagine déjà devoir faire s'écouler la lave vers les bonnes sources chaudes, et activer les bonnes machines pour fabriquer les bonnes pièces, pour réparer les autres machines pour envoyer du sable pour filtrer l'eau, pour la refroidir et faire de la neige.
Trêve digressions, aujourd'hui on constate que les miroirs concaves sont désalignés, et ne concentrent donc plus efficacement la lumière du soleil pour faire fondre la montagne et s'écouler le magma.
Alors on va remettre tout ça en place.
Et pour ça on a des manettes pour faire pivoter la plateforme du miroir, ce qui va faire rouler des pierres, au nord, au sud, à l'est, à l'ouest.
On va devoir compte la mousse amassée, comme pierre qui roule n'amasse pas mousse, le résultat est zéro, exercice suivant.
Ah, pardon, non, c'est pas ça : on a peur que la rambarde de sécurité ne supporte pas le poids des pierres qui roulent, donc on a mesurer le poids supporté par la rambarde nord, après avoir fait glisser les pierres au nord.
Un dessin valant mieux qu'un long discours, parait-il, voici des dessins, avec notre plateforme d'entraînement, et son état une fois tout basculé au nord. On a besoin de comptabilisé le poids des pierres-qui-roulent sur la rambarde nord, et ça dépend de leur distance, c'est le chiffre tout à droite. La somme pour chaque pierre donne le poids total, ici de 136 :
O....#.... -> OOOO.#.O.. 10
O.OO#....# -> OO..#....# 9
.....##... -> OO..O##..O 8
OO.#O....O -> O..#.OO... 7
.O.....O#. -> ........#. 6
O.#..O.#.# -> ..#....#.# 5
..O..#O..O -> ..O..#.O.O 4
.......O.. -> ..O....... 3
#....###.. -> #....###.. 2
#OO..#.... -> #....#.... 1
Maintenant qu'on s'est entraîner, on va jouer !
C'est rigolo de mettre les pierres au nord, mais on va tourner : nord, ouest, sud, est, et on tourne, et on secoue, et on mélange, et on spirale, et on danse, puis, enfin, on pèse.
On pèse la même chose après un milliard de tours, et le poids final est de 64, croyez-moi sur parole.
Un milliard de tours de manivelle, allez, hophophop, faut commencer tôt, parce qu'on n'a pas fini hein !
Bon, par contre après on va redescendre, peut-être faire une pause aux bains chauds, et calculer le massage optimal pour se détendre…
- Yth.
# Impossible de se cacher, va falloir tourner.
Posté par Yth (Mastodon) . Évalué à 2. Dernière modification le 14 décembre 2023 à 12:42.
Comme dans tout les films avec une action répétées pleins de fois, comme de lustrer et frotter, ou peindre des palissades, on a une option pointillés, qui permet de sauter des deux-trois premières itérations directement à la dernière, et de constater, ébahis, les résultats de l'entraînement des padawans, devenu des adultes.
Tout ça pour dire que même avec un cache efficace, tester le cache un milliard de fois, même si on ne calcule vraiment que quelques centaines, ou milliers, d'opérations, ça va être très, très, très long.
Ça ne veut pas dire que le cache est inutile, mais totalement insuffisant.
En pratique, que je le mette ou pas, et ce coup-ci j'ai absolument pensé ma modélisation et toutes mes fonctions, pour avoir un cache le plus efficace possible, ben on gagne quedalle en vitesse, et on sagouine même pas sa RAM, en fait, comme j'ai fait ça super bien, le cache, les caches, ben ils utilisent rien en mémoire.
En fait, tout ce qui compte, c'est de faire des cycles, et de voir quand est-ce qu'on retombe sur un état vu précédemment.
Là on a deux informations : les étapes préalables au démarrage d'un cycle de cycles, et la longueur de ce cycle de cycles.
Si quelqu'un pense, là, maintenant, que j'ai mal choisi mes appellations, il a sans doute raison, c'est bien toute la difficulté du choix d'un bon nom de variable, choix qui n'est pas toujours en
O(temps disponible)
.Chez moi, on calcule 160 cycles, on boucle sur 77 cycles après 83 cycles de mise en jambe.
Donc avec (1 000 000 000 - 83) modulo 77 = 70, on va chercher le 70ème cycle de notre cycle de cycles, soit le 83+70=153ème cycle calculé, c'est l'état final, on le pèse.
Et avec les données de test, on retombe bien sur le 64 prévu, avec un cycle de 7 cycles, et 3 cycles de mise en route, donc 10 cycles calculés, et le milliardième est le 6ème, youpi !
Mon code est pas super joli, je n'ai pas cherché à factoriser mes fonctions pour glisser vers le nord, sud, est et ouest, donc j'en ai quatre, presque identiques.
Ce qui est plus intéressant, ce sont mes efforts (inutile, mais jolis) pour préparer un cache super efficace et peu gourmand, comme quoi j'ai à la fois appris de mes erreurs passées, et pas du tout appris à anticiper où la difficulté se trouve dans un exercice.
Par contre ça résout l'exercice 1 fastoche avec le code du 2.
Pour comprendre, je considère les problèmes remis à plat, donc une seule chaîne de caractères et on navigue en connaissant la largeur et la hauteur.
Et je stocke ces problèmes dans une dictionnaire avec le
hash()
en clé. Donc j'appelle mes fonctions avec la valeur de platform qui est unhash()
, donc un entier.Ça veut dire que chaque fonction qui fait glisser les pierres prend en entrée un unique entier, et sort un autre entier.
Autant dire que le cache il pèse pas lourd, il est optimisé, il est efficace (et il est inutile).
Allez, souffrez lecteurs :
Bilan : 1,6 secondes et 15Mo de RAM, avec ou sans le cache, c'est pareil, et pas de mauvaise surprise, quand ça a validé les données de test, ça a validé le problème complet.
PS: pour faire plaisir à Tanguy, mon premier jet pour passer l'étape 1 modélisait avec un Enum Rock.RND/SQR/NOP, et ma fonction, unique, était un mix de mes méthodes north et load, tout en une passe, rapide, efficace, pour aller vite fait à l'étape 2.
PPS: je suis resté sur des str au bout du compte, parce qu'une list c'est unhashable, donc je pouvais pas utiliser hash() pour optimiser mes caches (inutiles). Je suppose qu'en revenant à un truc qui fait moins d'explosion de str en list et vice-versa ça doit pouvoir marcher, voire en utilisant un tuple plutôt qu'une liste pour le cache, et une transformation tuple->list->roule_tes_pierres->tuple pour conserver le cache.
[^] # Re: Impossible de se cacher, va falloir tourner.
Posté par vicnet . Évalué à 1.
Tu as eu de la chance que le 1er nombre de la liste soit unique dans la boucle sinon tu avais les mauvaises valeurs…
Par exemple:
Les 2e 10 ou 11 sont bien déjà dans la liste states mais c'est pas une boucle !1 2 3 10 5 6 11 10 11 12 13 14 10 11 12 13 14 10 11 12 13 14
[^] # Re: Impossible de se cacher, va falloir tourner.
Posté par Yth (Mastodon) . Évalué à 3. Dernière modification le 14 décembre 2023 à 13:56.
Non, non, j'enregistre un état après un tour complet nord, ouest sud, et est.
Si on retombe sur un état rigoureusement identique, alors on a forcément bouclé, on ne conserve pas d'information d'un tour à l'autre, un tour de manivelle ne dépend que de l'état des pierres-qui-roulent au début du tour.
Ça veut dire qu'après 10 on a forcément 11.
Ya pas possible de se tromper là…
[^] # Re: Impossible de se cacher, va falloir tourner.
Posté par Yth (Mastodon) . Évalué à 2.
Par contre, on peut noter qu'ici PyPy fait descendre de 1,6s à 0,5s, ce qui n'est pas négligeable du tout, trois fois plus rapide, pas mal.
Avec ou sans cache, on a bien vu qu'il ne servait rigoureusement à rien de toute façon, pas la peine de ressasser hein, j'ai compris.
En modélisant avec un Enum Rock, et en utilisant un tuple au lieu d'une str pour gérer les données (on converti en liste avant de déplacer les pierres puis on remet en tuple, et on calcule un hash d'un tuple de Rock), c'est affreux.
On passe à 4s en python !
Mais on reste à 0,6s en PyPy.
Conclusion: PyPy ne déteste pas la modélisation.
Avec ou sans cache, puisque je vous dis qu'il ne sert absolument à rien, rhaaa !
Et donc après m'être bien pris la tête à virer mes histoires de hashes, tout le cache et essayer de simplifier, on réalise que de toute façon on doit passer par des enregistrements d'états immuables (on ne peut pas states.append(mon état mutable), parce que ça stocke la référence, donc l'état stocké est modifié), et donc permettre à python de calculer un hash, par exemple en stockant un tuple().
Et ça sert à rien, on n'y gagne rien, mon approche initiale était finalement la plus optimisée : rester sur des string, et les transformer en liste pour les modifier puis les recoller à la fin, est plus efficace.
Sauf avec PyPy qui s'en fout, les hashes de str, tuple, les conversions dans un sens, l'autre, retour etc, c'est pareil pour lui, ça prend rien de temps, donc toutes les approches sont équivalentes.
# Ca boucle !
Posté par vicnet . Évalué à 1.
Salut,
Pas trop de soucis ce matin avec celui la, mais j'ai triché un peu (bien que le reglement n'impose pas de n'utiliser qu'un prog et n'interdit pas d'utiliser une fichier texte contenant des valeurs :) ).
Pour le 1er, j'ai pensé à tourner la grille sur la gauche pour prendre des lignes plutot que des colonnes: ca permet d'utiliser directement des for l in lines sans trop se prendre la tête avec les colonnes…
Tiens, je vais me préparer un itérateur pour parcourir par colonne…
Ensuite, je n'ai pas effectuer les déplacements de 'O' mais directement effectué le calcul. Je savais que ca ne tiendrait pas pour le 2e mais bon, au moins j'avais un 1er résultat.
Et effectivement, au 2e, il fallait les bouger et pas seulement calculer la valeur.
Je me suis douter ensuite que ca allait boucler après un certain temps.
J'ai donc calculer 300 cycles et regarder les valeurs de chaque tableau, à la main (sans code) et j'ai ensuite donné la bonne réponse (mouais…).
Il me reste à coder la recherche d'une série dans une liste maintenant !
# Solution en Haskell
Posté par Guillaume.B . Évalué à 1. Dernière modification le 14 décembre 2023 à 13:20.
Problème intéressant, il me rappelle le jour 17 de 2022.
J'ai écrit une fonction
tilt
qui fait tomber les briques vers l'ouest.Pour cela, pour chaque ligne, je split la ligne selon # et pour chaque morceau du split, je sépare les O et les "." et je reconcatène tout en mettant les O d'abord et les "." ensuite.
Ensuite j'ai une fonction
tiltInDirection
qui se ramène àtilt
en faisant des rotate et des reverse selon le cas de figure.Pour la partie 2, je génère une suite infinie cyclique de North, West, South, East
et à partir de celle ci, je génère une liste infinie des différentes configurations après avoir effectué les tilts dans la direction donnée.
Dans cette liste, je cherche les indices x et y de la même configuration (fonction
findRepetition
) et la configuration qui nous intéresse est celle à l'indicex + (1000000000 - x) mod (y - x)
.La partie 2 tourne en 600ms. Pas très satisfait mais ça reste raisonnable.
Voici le code
[^] # Re: Solution en Haskell
Posté par Guillaume.B . Évalué à 1.
Je viens de me rendre compte que mon code n'était pas correct tout le temps même s'il marchait bien sur l'input. En effet, il se pourrait que deux configurations identiques apparaissent mais pas dans la même position dans le cycle des directions.
Par exemple, une configuration alors que la prochaine instruction est d'aller vers l'ouest puis la même configuration alors que la prochaine instruction est d'aller au nord.
Du coup, je l'ai corrigé (il y a juste à changer la fonction part2).
Ca ne change pas le temps d'exécution.
[^] # Re: Solution en Haskell
Posté par Yth (Mastodon) . Évalué à 2.
Ouais, faut enregistrer les états après un tour complet pour retourner dans une situation équivalente.
Un tour complet ne dépend que de l'état initial, alors qu'un déplacement c'est l'état initial puis la direction.
Ça marche mais faut enregistrer les deux infos, et comparer les deux infos.
[^] # Re: Solution en Haskell
Posté par Guillaume.B . Évalué à 1.
En se rendant compte qu'un cycle est un enchainement tilt, rotation, tilt, rotation, tilt, rotation, tilt, rotation, on peut écrire le code plus simplement.
Et du coup, la fonction
tiltInDirection
ne sert plus à rien.On gagne même un peu en temps d'exécution: 400ms.
[^] # Re: Solution en Haskell
Posté par Guillaume.B . Évalué à 1. Dernière modification le 15 décembre 2023 à 17:25.
J'ai réécrit ma fonction
tilt
de manière plus maligne en m'inspirant de la solution d'une autre personne.Maintenant, le temps d'exécution est passé à 200ms.
# Solution en Java 403ms
Posté par syj . Évalué à 1.
Ci-dessous ma soluce en Java pour la partie 2. Elle met 403ms une fois Java lancé (c'est comme un bon diesel Java). Et encore, j'ai une vilaine boucle qui compte jusqu'à avant le milliard.
J'optimise en utilisant des objets mutable & un bon vieux cache basé sur un Record.
# Pourquoi ça cycle
Posté par 🚲 Tanguy Ortolo (site web personnel) . Évalué à 2.
En voyant la partie 2, j'ai immédiatement pensé deux choses :
Ce dernier point est une certitude absolue. Pourquoi donc ? Parce qu'il y a 100 ligne et 100 colonnes, donc moins de 100 × 100 = 10.000 positions possibles des pierres qui roulent. En fait, bien moins que ça, parce que les positions occupées par les pierres qui ne roulent pas ne sont pas utilisables, et que seules les dispositions stables par le nord – on se comprend – sont admissibles.
Bref, en moins de 10.000 cycles je suis sûr d'avoir au moins deux fois la même disposition. Et retomber sur une disposition déjà vue, c'est aussi retomber sur la disposition suivante au cycle d'après, etc.
Chez moi ça cycle en 64 cycles.
Le code :
[^] # Re: Pourquoi ça cycle
Posté par Yth (Mastodon) . Évalué à 3.
Hmm, on a moins de 100x100 possibilités pour chaque pierre-qui-roule (chez moi c'est 8459).
Mais comme on a quelques 2000 pierres-qui-roulent (2011 dans mes données), ça fait un paquet de combinaisons possibles.
Chez moi ça a l'air d'être de l'ordre de 5e7784, soit… trop.
Mais je pense que ton raisonnement sur pourquoi on en est absolument sûr, est faux.
Perso, je n'ai qu'une double forte suspicion : ça à l'air de boucler à vue de nez, et si ça boucle pas on va flinguer notre CPU, or il y a une solution raisonnable en temps CPU, selon les principes de l'AoC.
[^] # Re: Pourquoi ça cycle
Posté par 🚲 Tanguy Ortolo (site web personnel) . Évalué à 3.
En effet. Mais c'est moins que 100×100 pour chaque galet, tout simplement parce que peu importe la position de chaque galet, ils se ressemblent tous au point d'être interchangeables. C'est plutôt la combinaison de 2.000 parmi 10.000 qui majore le nombre de possibilités. Ça fait quand même vachement beaucoup trop.
[^] # Re: Pourquoi ça cycle
Posté par Yth (Mastodon) . Évalué à 2. Dernière modification le 15 décembre 2023 à 15:44.
J'ai en effet oublié un terme dans mon calcul de combinaison, qui m'amène plutôt vers les 7e2010.
# python en 44 LoC, 110ms et 10MB RAM
Posté par steph1978 . Évalué à 2. Dernière modification le 16 décembre 2023 à 02:22.
# Python
Posté par alberic89 🐧 . Évalué à 1.
Voici ma solution, elle est rapide, mais elle m'a pris beaucoup de temps pour une et une seule raison : dans la première partie, j'ai codé une fonction qui calcule le poids si on bougeait les pierres vers le nord (en fait l'est puisque j'ai renversé le puzzle, je préfère les lignes aux colonnes).
Dans ma deuxième partie, je me dis ; « Réutilisons gaiement cette fonction après avoir effectivement fait rouler les pierres ! » 😔
L'informatique n'est pas une science exacte, on n'est jamais à l'abri d'un succès
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.