C'est intéressant ces discussion, parce qu'on trouve toujours des bidouilles pour améliorer son code, j'en ai rajouté quelques-unes et au final, en taille 10, je suis passé de 6 secondes et 326Mo de RAM à 0,9 secondes et 12,5Mo de RAM.
C'est pas spécialement négligeable pour un code sensiblement identique.
Ne pas être récursif avec un paramètre self, mais utiliser une fonction pure, qui sera la fonction récursive, dans la méthode de classe, qui est appelée de l'extérieur.
Réduire les paramètres, j'ai diminué à deux paramètres : la position et le nombre de blocs de sources endommagées restant à placer. Si on regarde, mon plus gros problème a une complexité de 12540, en multipliant les positions de départ possible par le nombre d'indices, c'est la plus grand valeur, donc jamais je n'aurais un cache plus gros que ça.
traiter les sources endommagées par bloc, plutôt que de consomme un par un les éléments jusqu'à remplir le bon nombre. Là l'idée de Guillaume est pas mal ça accélère bien les choses.
simplifier les données en entrée pour réduire les suites de . à un seul ., en pratique c'est strictement équivalent. Par contre j'en rajoute aussi un à la fin, ça simplifie le code. En pratique on y gagne un peu, pas négligeable !
Noter la position de la dernière source endommagée, comme ça quand on a posé le dernier bloc, on peut vérifier immédiatement s'il reste une source endommagée plus loin, et valider un peu plus rapidement une fin de chemin (en pratique ça ne fait rien gagner, mais ça aurait pu).
Mon dernier point a consisté à me débarrasser de cette idée de rappeler la fonction récursive en forçant la source courante, inconnue, à fonctionnelle ou endommagée, ça réduit le nombre d'arguments à 2, la complexité de l'ensemble, la taille du cache, et même le temps d'exécution. On doit rejoindre ce que tu fais Tanguy, avec ton Condition.States qui yield OPE et BRK. M'aura fallut du temps pour en arriver là.
Par contre je suis resté avec des -1/0/1 au lieu d'un Enum, j'y perd avec un Enum. Peut-être un IntEnum, pour avoir le meilleur des deux mondes ?
Au bout du compte, toujours en taille 10 je suis descendu à moins d'une seconde et ~16Mo de RAM.
Il y a 488 387 récursions calculées, et le cache en a épargné 245 314, qui chacune en ont épargnées récursivement un nombre probablement assez incommensurable, parce que déjà en taille 2, sans le cache on monte à 5 secondes, et en taille 3 à 11 minutes.
Sans cache, point de salut dans cet exercice.
Le code final, avec les statistiques des caches :
fromsysimportstdin,argvfromfunctoolsimportcacheimportreMUL=int(argv[1]iflen(argv)>1else2)data=stdin.read().strip().splitlines()classSpring:def__init__(self,puzzle,clues,mul=1):self.clues=[int(_)foriinrange(mul)for_inclues.split(",")]self.str="?".join(re.sub("[.]+",".",puzzle)for_inrange(mul))+"."self.size=len(self.str)self.puzzle=[{"?":0,".":1,"#":-1}.get(_)for_in(self.str)]self.nextfunctional=[self.puzzle[n:].index(1)forninrange(self.size)]self.lastdamaged=([nforn,_inenumerate(self.puzzle)if_==-1]or[0])[-1]self.clue_max_pos=[self.size-sum(self.clues[-n:])-(n-1)forninrange(len(self.clues)+1)]self.clue_max_pos[0]=self.sizedefrun(self):@cachedef_run(pos,clueleft):ifpos>self.clue_max_pos[clueleft]:return0# Not enough space remainingifpos==len(self.puzzle):return1value=self.puzzle[pos]# spring could be functionalr=_run(pos+1,clueleft)ifvalue>=0else0ifvalue>0:returnr# spring could be damagedifnotclueleft:# None is available, wrong pathreturnrclue=self.clues[-clueleft]# no space for clueifclue>self.nextfunctional[pos]:returnr# clue cannot be followed by a damaged springifself.puzzle[pos+clue]==-1:returnr# No clue left, but still damaged springsifclueleft==1andpos+clue<self.lastdamaged:returnrreturnr+_run(pos+clue+1,clueleft-1)return_run(0,len(self.clues))r=_run(0,len(self.clues))self.cache_info=_run.cache_info()returnrsprings=[Spring(*line.split(),MUL)forlineindata]print(sum(s.run()forsinsprings))hits=sum(s.cache_info.hitsforsinsprings)misses=sum(s.cache_info.missesforsinsprings)print(f"Functions called = {misses}, calls cached = {hits}")
Il faut retirer les infos de cache et ne conserver que la liste des Spring, pour réduire la RAM à 12,5Mo :
[^] # Re: Rien de vraiment compliqué, il faut juste utiliser tout ce qu'on sait faire.
Posté par Yth (Mastodon) . En réponse au message Advent of Code 2023, jour 12. Évalué à 2.
C'est intéressant ces discussion, parce qu'on trouve toujours des bidouilles pour améliorer son code, j'en ai rajouté quelques-unes et au final, en taille 10, je suis passé de 6 secondes et 326Mo de RAM à 0,9 secondes et 12,5Mo de RAM.
C'est pas spécialement négligeable pour un code sensiblement identique.
.à un seul., en pratique c'est strictement équivalent. Par contre j'en rajoute aussi un à la fin, ça simplifie le code. En pratique on y gagne un peu, pas négligeable !Par contre je suis resté avec des -1/0/1 au lieu d'un Enum, j'y perd avec un Enum. Peut-être un IntEnum, pour avoir le meilleur des deux mondes ?
Au bout du compte, toujours en taille 10 je suis descendu à moins d'une seconde et ~16Mo de RAM.
Il y a 488 387 récursions calculées, et le cache en a épargné 245 314, qui chacune en ont épargnées récursivement un nombre probablement assez incommensurable, parce que déjà en taille 2, sans le cache on monte à 5 secondes, et en taille 3 à 11 minutes.
Sans cache, point de salut dans cet exercice.
Le code final, avec les statistiques des caches :
Il faut retirer les infos de cache et ne conserver que la liste des Spring, pour réduire la RAM à 12,5Mo :