Un jour où la solution naïve à la première partie ne passe pas à l'échelle pour la seconde partie.
J'ai d'abord fait une solution qui a explosé la RAM (4M*4M d'int, ça explose).
Puis une solution sans rien en RAM mais qui explose le CPU (j'ai timeout à 10 minutes).
Troisième solution qui est un compromis entre RAM et CPU. Je stocke des intervalles pour 4M de lignes ; comme les autres solutions présentées ici.
Ça passe, en 40 secondes avec l'aide du JIT.
code partie 2
importsysW=H=4_000_000# store position that are not reachable, row by rowL=[[(0,0)for_inrange(32)]for_inrange(H+1)]fori,linenumerate(sys.stdin.read().splitlines()):(_,_,X,Y,_,_,_,_,A,J)=l.split(" ")x,y=int(X[2:-1]),int(Y[2:-1])a,b=int(A[2:-1]),int(J[2:])d=abs(x-a)+abs(y-b)fornyinrange(y-d,y+d+1):ifny>=0andny<=H:dy=abs(ny-y)dx=d-dyL[ny][i]=(x-dx,x+dx)for(y,I)inenumerate(L):SI=sorted(I)maxx=0for(a,b)inSI:ifa>maxx+1:print(y,maxx+1,(maxx+1)*4_000_000+y)sys.exit(0)maxx=max(maxx,b)
Permet de découper (crop) une image jpeg sans toucher à la qualité. L'astuce est que la découpe ne peut se faire que par pas de 8 pixels. En effet jpeg applique une FFT sur des carrés de 8x8. Donc pour croper il suffit de supprimer les carrés qui gênent et de garder les bons.
Le parsing de l'input est presque plus long à écrire que l'application de règles : 4 boucles for imbriquées et 4 split contre 3 boucles while imbriquées.
Voici mon code pour la partie deux. La partie est identique excepté la condition de sortie.
importsysW=1000H=200L=[[0for_inrange(W)]for_inrange(H)]# full or airdefs2li(s,sep=','):returnlist(map(int,s.split(sep)))MAX_Y=0forlinsys.stdin.read().splitlines():lp=l.split(" -> ")forf,tinzip(lp,lp[1:]):(a,b,c,d)=(*s2li(f),*s2li(t))x0=min(a,c)y0=min(b,d)x1=max(a,c)y1=max(b,d)MAX_Y=max(MAX_Y,y1)forxinrange(x0,x1+1):foryinrange(y0,y1+1):L[y][x]=1forxinrange(W):L[MAX_Y+2][x]=1# rock on the groundstop=FalseN=0whilenotstop:N+=1y=0x=500more=True# can all left or right whilemoreandnotstop:more=FalsewhileL[y][x]==0:#and not stop: # if only airy+=1stop=y==MAX_Y+2ifL[y][x-1]==0:# can fall leftx-=1more=TrueelifL[y][x+1]==0:# can fall rightx+=1more=Trueelse:stop=y==1# can't put more sandL[y-1][x]=2# mark sandprint(N)
Et une jolie nimage de la caverne remplie de sable:
Posté par steph1978 .
En réponse au message Avent du Code, jour 13.
Évalué à 2.
Dernière modification le 13 décembre 2022 à 15:33.
Tu veux dire à la place du eval. Oui clairement, c'est moins craignos.
À la première lecture je pensais que tu voulais résoudre le problème en javascript 😓. Je veux dire juste en comparant les deux listes avec un <. J'ai tenté, ça marche presque 😛. Mais non en fait, les règles de comparaisons ne sont pas les mêmes.
Je galérais à organiser ma comparaison de paquets, et j'ai eu un indice d'utiliser un comparateur. Je suis reparti là dessus et ça fonctionne.
importsysP=[tuple(map(eval,p.split("\n")))forpinsys.stdin.read().split("\n\n")]defC(l,r):T=(type(l),type(r))ifT==(int,int):ifl<r:return-1returnl>r# 0 or 1ifT==(int,list):returnC([l],r)ifT==(list,int):returnC(l,[r])# list,listforqinzip(l,r):c=C(*q)ifc:returncreturnC(len(l),len(r))# part 1S=0fori,pinenumerate(P):ifC(*p)<=0:S+=i+1print(S)# part 2fromfunctoolsimportcmp_to_keyQ=[qfor(l,r)inPforqin[l,r]]Q.append([[2]])Q.append([[6]])Q.sort(key=cmp_to_key(C))print((Q.index([[2]])+1)*(Q.index([[6]])+1))
Dans mon input, tous les b ne sont que dans la deuxième colonne. Et la première colone ne contient que des a. Comme il faut nécessairement passer par un b, je n'ai testé que des départs depuis la première colonne.
J'ai passé plusieurs heures à ne pas comprendre pourquoi je ne trouvais pas la solution. Et puis j'ai compris ; j'ai loupé une phrase cruciale dans l'énoncée : on a le droit de redescendre, bordel !
J'ai utilise la très précieuse bibliothèque de manipulation de graph en Python, networkx. Mon code ne fait que parser l'input et construire la liste de transitions possibles et présente donc peu d'intérêt. Mais permet au moins de faire une joli nimage du résultat:
Si vous vous baladez sur Reddit, y en a qui ont fait des représentations de ouf.
La seconde partie était triviale dans ces conditions et m'a pris moins de deux minutes.
Posté par steph1978 .
En réponse au message Avent du Code, jour 11.
Évalué à 2.
Dernière modification le 11 décembre 2022 à 14:13.
Mince alors, toute une matinée de code à mettre à la benne 😩
Blague à part, merci pour la remarque, je confonds probablement avec map ou reversed qui retournent un itérateur.
J'imagine que pour trier, il faut charger toutes les données, donc pas d'intérêt de retourner un itérateur. Ou alors ne pas chercher d'explication, l'API python n'est pas toujours cohérente.
Posté par steph1978 .
En réponse au message Avent du Code, jour 11.
Évalué à 3.
Dernière modification le 11 décembre 2022 à 13:39.
Je suis tombé dans le piège de la solution qui ne scale pas en deuxième partie. Pas grave, on s'adapte.
Pour les deux parties, pour éviter un parsing laborieux, j'ai transformé l'input en code. Car à bien y regarder, c'est du code :)
La solution naïve ne scale pas car passé une certaine taille (32bit je crois) l'arithmétique des grands entiers sort du processeur, doit repasser par la mémoire et devient très très lente.
Chez moi c'était très net : les 500 premiers rounds sont fulgurants, moins de 1s, puis chaque round prends presque une seconde. Pour faire les 100000, en admettant avoir assez de RAM, il faudrait donc une grosse heure à 100% de CPU. Je n'ai pas cette patience.
L'astuce pour passer à l'échelle est de limiter la taille des nombres manipulés en appliquant l'arithmétique modulaire. Chaque singe à son diviseur (un nombre premier mais je crois pas que ça joue). Donc au lieu d'avoir l'item sous forme de nombre, on le transforme en une liste de restes de division, un pour chaque singe.
part 1
Je vous épargne le code complet de la première partie qui est en gros celui de la deuxième partie en un peu plus simple.
input
fromcollectionsimportdequeM=[(deque([84,72,58,51]),lambdaold:old*3,lambdax:7ifdivmod(x,13)[1]else1)# ...# same for all monkeys# ...]
part 2
input
fromcollectionsimportdequePRIMES=(13,2,7,17,5,11,3,19)defmods(x):return[x%iforiinPRIMES]M=[[deque(mods(x)forxin[84,72,58,51]),lambdaold:[((o%i)*(3%i))%ifor(o,i)inzip(old,PRIMES)],lambdax:7ifx[0]else1,0],[deque(mods(x)forxin[88,58,58]),lambdaold:[((o%i)+(8%i))%ifor(o,i)inzip(old,PRIMES)],lambdax:5ifx[1]else7,0]# ...# same for all monkeys# ...]
Je me moque gentiment. Tu as affiché clairement ton envie de faire du code propre, modélisé, typé. Et c'est louable.
Moi je pense avoir affiché mon objectif : peut importe le moyen pourvu qu'on ait la réponse le plus vite possible :)
J'ai réutilisé l'idée de l'utf8 pour le jour 10 aussi. La lecture du LCD est infiniment plus facile, plus besoin de plisser les yeux pour deviner les lettres.
Pour l'animation de la corde j'ai choisis d'afficher le numéro du noeud (0 à 9) en noir sur fond jaune ; et de déplacer la caméra de 19 d'un coup. J'aimais pas trop la voir manger le mur :). J'ai aussi géré les largeurs impaires, c'est le cas de mon terminal.
classdisplay:def__init__(self):self.w,self.h=tuple(os.get_terminal_size())border="o"self.delta=numpy.array((self.w//2,self.h//2))top=border*(self.w)wodd=self.w%2lines=[border+"░ "*(self.w//2-1)+"░"*wodd+border,border+" ░"*(self.w//2-1)+" "*wodd+border]self.background=[top+"".join(lines[h%2]forhinrange(self.h-2))+top,top+"".join(lines[1-h%2]forhinrange(self.h-2))+top]def__call__(self,knots):print("\033[0;0H",end='')x,y=self.delta+knots[0]J=19# must be odd to see scrollifx<=0:self.delta[0]+=Jifx>=self.w-1:self.delta[0]-=Jify<=0:self.delta[1]+=Jify>=self.h-1:self.delta[1]-=Jtxt=[cforcinself.background[sum(self.delta)%2]]fori,knotinenumerate(reversed(knots)):x,y=knot+self.deltatxt[int(x+y*self.w)]="\033[30;43m"+str(9-i)+"\033[0m"print("".join(txt),end='')time.sleep(.008)
Pour limiter les glitches, j'ai ajouté print("\033[0;0H", end='') au début du __call__.
Ça replace le curseur la coordonnée (0,0) du terminal ce qui évite le scrolling.
Dans la même veine, il est bénéfique de cacher le curseur avec tput civis avant de lancer l'animation. À la fin de l'animation, le remettre avec tput cnorm
J'ai fait la première partie en AWK en une petite demie heure.
J'ai eu pas mal de chance car mes calculs étaient approximatifs mais suffisant pour le cas à un noeud.
En voyant la seconde partie, j'ai tout ré-écrit en python car j'y ai vu un truc récursif.
J'ai pigé assez vite qu'il fallait mieux faire toutes les étapes, case par case pour tous les neouds.
Ça donnait toujours le bon résultat pour le premier noeud mais pas pour le dernier.
J'ai mis vraiment beaucoup de temps à débugger en pas à pas et ajuster mes calculs. Plusieurs heures :(
Pour au final un code très impératif que j'aurai presque pu laisser en AWK.
Posté par steph1978 .
En réponse au message Avent du Code, jour 8.
Évalué à 2.
Dernière modification le 08 décembre 2022 à 15:43.
Tu as eu une meilleure approche pour le première partie en effet. Il vaut mieux considérer les bords (N2) que toute les position de la grille (N3).
En pratique all va s'arrêter au premier False, ce qui fait que les chronos sont assez poche (0:33 pour les deux). Aussi parce que N est petit (N=99) et que lancer l'interpréteur est incompressible. Mais en théorie, c'est bien meilleur.
# python, on a RAMé
Posté par steph1978 . En réponse au message Avent du Code jour 15. Évalué à 2.
Un jour où la solution naïve à la première partie ne passe pas à l'échelle pour la seconde partie.
J'ai d'abord fait une solution qui a explosé la RAM (4M*4M d'int, ça explose).
Puis une solution sans rien en RAM mais qui explose le CPU (j'ai timeout à 10 minutes).
Troisième solution qui est un compromis entre RAM et CPU. Je stocke des intervalles pour 4M de lignes ; comme les autres solutions présentées ici.
Ça passe, en 40 secondes avec l'aide du JIT.
code partie 2
# et pour les images
Posté par steph1978 . En réponse au lien LosslessCut (The Swiss Army Knife of Lossless Video/Audio Editing). Évalué à 3.
https://betterjpeg.com/crop.htm
Permet de découper (crop) une image jpeg sans toucher à la qualité. L'astuce est que la découpe ne peut se faire que par pas de 8 pixels. En effet jpeg applique une FFT sur des carrés de 8x8. Donc pour croper il suffit de supprimer les carrés qui gênent et de garder les bons.
[^] # Re: python et lib de graph
Posté par steph1978 . En réponse au message Avent du Code, jour 12. Évalué à 3.
en plus joli : l'input et le resultat.
[^] # Re: python et lib de graph
Posté par steph1978 . En réponse au message Avent du Code, jour 12. Évalué à 3.
image disparue :![Source : https://git.zoocoop.com/setop/aoc2022/raw/commit/33fd08d182cbc6a7d67b156da276e1caeb65b782/d12/result.png](//img.linuxfr.org/img/68747470733a2f2f6769742e7a6f6f636f6f702e636f6d2f7365746f702f616f63323032322f7261772f636f6d6d69742f333366643038643138326362633661376436376231353664613237366531636165623635623738322f6431322f726573756c742e706e67/result.png)
[^] # Re: python, vite fait bien fait
Posté par steph1978 . En réponse au message Avent du Code, jour 14. Évalué à 2.
J'aime bien utiliser le format PNM car c'est du texte et donc facile à générer.
Sous linux, ça se visualise nativement. Mais ça peut être converti en PNG grâce à ImageMagic :
convert a.pnm -scale 400% a.png
# python, vite fait bien fait
Posté par steph1978 . En réponse au message Avent du Code, jour 14. Évalué à 4.
Le parsing de l'input est presque plus long à écrire que l'application de règles : 4 boucles
for
imbriquées et 4split
contre 3 boucleswhile
imbriquées.Voici mon code pour la partie deux. La partie est identique excepté la condition de sortie.
Et une jolie nimage de la caverne remplie de sable:
[^] # Re: Étoile
Posté par steph1978 . En réponse au message Avent du Code, jour 12. Évalué à 2.
j'ai la même mais avec une rotation.
[^] # Re: python, en trichant
Posté par steph1978 . En réponse au message Avent du Code, jour 13. Évalué à 2. Dernière modification le 13 décembre 2022 à 15:33.
Tu veux dire à la place du eval. Oui clairement, c'est moins craignos.
À la première lecture je pensais que tu voulais résoudre le problème en javascript 😓. Je veux dire juste en comparant les deux listes avec un
<
. J'ai tenté, ça marche presque 😛. Mais non en fait, les règles de comparaisons ne sont pas les mêmes.# python, en trichant
Posté par steph1978 . En réponse au message Avent du Code, jour 13. Évalué à 4.
Je galérais à organiser ma comparaison de paquets, et j'ai eu un indice d'utiliser un comparateur. Je suis reparti là dessus et ça fonctionne.
[^] # Re: En Python
Posté par steph1978 . En réponse au message Avent du Code, jour 12. Évalué à 2.
Dans mon input, tous les b ne sont que dans la deuxième colonne. Et la première colone ne contient que des a. Comme il faut nécessairement passer par un b, je n'ai testé que des départs depuis la première colonne.
# python et lib de graph
Posté par steph1978 . En réponse au message Avent du Code, jour 12. Évalué à 4.
J'ai passé plusieurs heures à ne pas comprendre pourquoi je ne trouvais pas la solution. Et puis j'ai compris ; j'ai loupé une phrase cruciale dans l'énoncée : on a le droit de redescendre, bordel !
J'ai utilise la très précieuse bibliothèque de manipulation de graph en Python, networkx. Mon code ne fait que parser l'input et construire la liste de transitions possibles et présente donc peu d'intérêt. Mais permet au moins de faire une joli nimage du résultat:
Si vous vous baladez sur Reddit, y en a qui ont fait des représentations de ouf.
La seconde partie était triviale dans ces conditions et m'a pris moins de deux minutes.
# empaquetage
Posté par steph1978 . En réponse au journal Offpunk 1.8. Évalué à 4.
J'ai fait l'empaquetage avec pyinstaller. Ça semble bien fonctionner. Ça donne un exe autoporteur (interpréteur python compris) de 26MB.
Un simple
pip install pyinstaller && pyinstaller -F -n offpunk offpunk.py
suffit.Avec la possibilité de mettre l'exe dans les releases.
Peut être une façon de simplifier l'installation pour certains utilisateurs.
[^] # Re: python, on oublie toute dignité
Posté par steph1978 . En réponse au message Avent du Code, jour 11. Évalué à 2.
En poussant un peu plus la réflexion sur l’arithmétique modulaire, on peut se passer de balader des listes et garder un nombre. Trop tard pour moi :)
# un bout de AWK
Posté par steph1978 . En réponse au message Avent du Code, jour 10. Évalué à 4.
Je me suis laissé emporté par le python du jour 9.
Le problème du jour passe très bien en AWK.
part 1, 11 lines
part 2, 20 lines
[^] # Re: python, on oublie toute dignité
Posté par steph1978 . En réponse au message Avent du Code, jour 11. Évalué à 2. Dernière modification le 11 décembre 2022 à 14:13.
Mince alors, toute une matinée de code à mettre à la benne 😩
Blague à part, merci pour la remarque, je confonds probablement avec
map
oureversed
qui retournent un itérateur.J'imagine que pour trier, il faut charger toutes les données, donc pas d'intérêt de retourner un itérateur. Ou alors ne pas chercher d'explication, l'API python n'est pas toujours cohérente.
# python, on oublie toute dignité
Posté par steph1978 . En réponse au message Avent du Code, jour 11. Évalué à 3. Dernière modification le 11 décembre 2022 à 13:39.
Je suis tombé dans le piège de la solution qui ne scale pas en deuxième partie. Pas grave, on s'adapte.
Pour les deux parties, pour éviter un parsing laborieux, j'ai transformé l'input en code. Car à bien y regarder, c'est du code :)
La solution naïve ne scale pas car passé une certaine taille (32bit je crois) l'arithmétique des grands entiers sort du processeur, doit repasser par la mémoire et devient très très lente.
Chez moi c'était très net : les 500 premiers rounds sont fulgurants, moins de 1s, puis chaque round prends presque une seconde. Pour faire les 100000, en admettant avoir assez de RAM, il faudrait donc une grosse heure à 100% de CPU. Je n'ai pas cette patience.
L'astuce pour passer à l'échelle est de limiter la taille des nombres manipulés en appliquant l'arithmétique modulaire. Chaque singe à son diviseur (un nombre premier mais je crois pas que ça joue). Donc au lieu d'avoir l'item sous forme de nombre, on le transforme en une liste de restes de division, un pour chaque singe.
part 1
Je vous épargne le code complet de la première partie qui est en gros celui de la deuxième partie en un peu plus simple.
input
part 2
input
main loop
[^] # Re: En Python, modélisé
Posté par steph1978 . En réponse au message Avent du Code, jour 9. Évalué à 2.
Je me moque gentiment. Tu as affiché clairement ton envie de faire du code propre, modélisé, typé. Et c'est louable.
Moi je pense avoir affiché mon objectif : peut importe le moyen pourvu qu'on ait la réponse le plus vite possible :)
[^] # Re: En Python, modélisé
Posté par steph1978 . En réponse au message Avent du Code, jour 9. Évalué à 2.
Ah exacte, ligne 1527 et 1529 de son programme 😇
[^] # Re: En Python, modélisé
Posté par steph1978 . En réponse au message Avent du Code, jour 9. Évalué à 2.
Splendide !
J'ai réutilisé l'idée de l'utf8 pour le jour 10 aussi. La lecture du LCD est infiniment plus facile, plus besoin de plisser les yeux pour deviner les lettres.
Pour l'animation de la corde j'ai choisis d'afficher le numéro du noeud (0 à 9) en noir sur fond jaune ; et de déplacer la caméra de 19 d'un coup. J'aimais pas trop la voir manger le mur :). J'ai aussi géré les largeurs impaires, c'est le cas de mon terminal.
[^] # Re: Plus simple
Posté par steph1978 . En réponse au message Avent du Code, jour 10. Évalué à 3.
C'est l'AoC 2019 au moins un jour sur deux il fallait ressortir son processeur "intcode".
[^] # Re: En Python, modélisé
Posté par steph1978 . En réponse au message Avent du Code, jour 9. Évalué à 3.
J'adore l'animation !
Pour limiter les glitches, j'ai ajouté
print("\033[0;0H", end='')
au début du__call__
.Ça replace le curseur la coordonnée (0,0) du terminal ce qui évite le scrolling.
Dans la même veine, il est bénéfique de cacher le curseur avec
tput civis
avant de lancer l'animation. À la fin de l'animation, le remettre avectput cnorm
[^] # Re: quick python
Posté par steph1978 . En réponse au message Avent du Code, jour 10. Évalué à 3.
Ah ça c'est vu que j'ai bidouillé pour gérer un ou deux arguments par lignes :)
Merci pour l'astuce, ça me resservira.
# quick python
Posté par steph1978 . En réponse au message Avent du Code, jour 10. Évalué à 3.
Rien à voir avec hier, les deux parties en une grosse demie heure.
part 1
part 2
# pfiu
Posté par steph1978 . En réponse au message Avent du Code, jour 9. Évalué à 3.
J'ai fait la première partie en AWK en une petite demie heure.
J'ai eu pas mal de chance car mes calculs étaient approximatifs mais suffisant pour le cas à un noeud.
En voyant la seconde partie, j'ai tout ré-écrit en python car j'y ai vu un truc récursif.
J'ai pigé assez vite qu'il fallait mieux faire toutes les étapes, case par case pour tous les neouds.
Ça donnait toujours le bon résultat pour le premier noeud mais pas pour le dernier.
J'ai mis vraiment beaucoup de temps à débugger en pas à pas et ajuster mes calculs. Plusieurs heures :(
Pour au final un code très impératif que j'aurai presque pu laisser en AWK.
Bref dur ce jour pour moi
part 1
part 2
[^] # Re: python procédural, moche mais efficace
Posté par steph1978 . En réponse au message Avent du Code, jour 8. Évalué à 2. Dernière modification le 08 décembre 2022 à 15:43.
Tu as eu une meilleure approche pour le première partie en effet. Il vaut mieux considérer les bords (N2) que toute les position de la grille (N3).
En pratique
all
va s'arrêter au premierFalse
, ce qui fait que les chronos sont assez poche (0:33 pour les deux). Aussi parce que N est petit (N=99) et que lancer l'interpréteur est incompressible. Mais en théorie, c'est bien meilleur.