Voilà le code à rajouter, on refait la classe en ZoneSensor pour retourner les bornes de l'intervalle sur une ligne, et à l'intérieur de la zone [0, 4000000] :
classZoneSensor:def__init__(self,*args):self.sx,self.sy,self.bx,self.by,self.zone,*self.other=argsself.distance=manhattan(*args)def__contains__(self,line):returnabs(self.sy-line)<=self.distancedef__getitem__(self,line):deltax=self.distance-abs(self.sy-line)returnmax(0,self.sx-deltax),min(self.zone,self.sx+deltax)zone=line_to_check*2sensors=[ZoneSensor(*(int(x)forxinline),zone)forlineindata]try:forlineinrange(zone,-1,-1):r=sorted([sensor[line]forsensorinsensorsiflineinsensor])ifr[0][0]!=0:raiseBaseException(0,line)xmax=0fors,einr:ifs>xmax:raiseBaseException(s-1,line)xmax=max(xmax,e)ifxmax!=zone:raiseBaseException(zone,line)print("Nothing found, there is a bug")exceptBaseExceptionase:x,y=e.argsprint(f"Beacon on position {x}, {y}, value={x*4000000+y}")
Le sorted au début de la boucle va ordonner par le premier élément du tuple, donc le début des intervalles, puis en cas d'égalité par le second. A partir de là on sait que les intervalles vont de gauche à droite.
On note la fin de notre intervalle consolidé, et si l'intervalle suivant commence après, bingo, on a trouvé !
On teste vite fait au début ou à la fin, si notre case à trouver ne serait pas au début ou à la fin, et voilà.
Et bien sûr, on va pas se fouler, exception quand on a trouvé quelque chose : on arrête tout et on jubile !
À noter que je pars de la ligne du bas, mon nez m'a dit que l'auteur allait fourber et mettre la ligne à trouver vers la fin, j'avais raison, la mienne est la 3 391 794.
C'est peut-être là où je gagne le plus de temps en fait.
Sinon, pour essayer d'avoir de la chance, on peut parcourir la zone avec un index qui se balade en parcourant tout.
step = un nombre premier avec 4000000
i = 4000000 # parce que line % 4000000 ne vaudra jamais 4000000
i = (i + step) % 4000000
En moyenne on va mettre pareil que si on ne sait pas où est la ligne, mais on tape au milieu dès le début et comme la probabilité que l'auteur n'ait pas mis sa ligne proche du bord, pour empêcher la force brute, est assez élevée, on a plus de chance d'avoir de la chance.
De l'autre côté (lignes croissantes de 0 à 4000000) ça prend 1min11s, et bien sûr on a le même résultat.
Avec un step de 747319 (nombre premier, donc premier avec 4000000, choisi au pif), je trouve le résultat en 49 secondes, assez proche de la moitié de la recherche totale ((1m11s+19s)/2=45s).
Posté par Yth (Mastodon) .
En réponse au message Avent du Code jour 15.
Évalué à 2.
Dernière modification le 15 décembre 2022 à 10:37.
Soyons honnêtes, le premier exercice, je l'ai bourriné en force brute.
Objectif : soit une ligne donnée, trouver le nombre de cases qui ne contiennent pas de balises.
Donc les cases visibles par une robot-senseur quelconque, moins les balises sur la ligne (piège, haha !).
Je suis tombé dans un autre piège : il faut scanner la ligne 2 000 000, mais pour l'exemple (pour valider le code) il faut scanner la ligne 10. Mais bon, la réponse pour la ligne 10 du problème complet, c'pas la réponse attendue, bref…
La méthode bourrine :
* pour chaque robot-senseur qui voit la ligne donnée :
* faire un set() des cases vues set(range(début, fin)) ;
* union des sets() ;
* compter sa taille ;
* lister pour chaque robot-senseur sa balise, si elle est sur la bonne ligne ;
* supprimer les balises trouvées.
Ça marche en 1,25 secondes sur les données réelles, le résultat étant de 5838453 on a quand même fait un set-comprehension en double boucle d'une taille non négligeable.
La rumeur comme quoi Python c'est lent, c'pas toujours vrai hein, ya aucune optimisation dans mon code.
importsysimportredefmanhattan(x1,y1,x2,y2,*args):returnabs(x1-x2)+abs(y1-y2)classSensor:def__init__(self,*args):self.sx,self.sy,self.bx,self.by,*self.other=argsself.distance=manhattan(*args)def__contains__(self,line):returnabs(self.sy-line)<=self.distancedef__getitem__(self,line):deltax=self.distance-abs(self.sy-line)returnrange(self.sx-deltax,self.sx+deltax+1)# inputsline_to_check=2000000iflen(sys.argv)<=1elseint(sys.argv[1])zone=line_to_check*2regex=re.compile(r"Sensor at x=(-?\d+), y=(-?\d+): "r"closest beacon is at x=(-?\d+), y=(-?\d+)")data=[regex.match(line).groups()forlineinsys.stdin]sensors=[Sensor(*(int(x)forxinline),zone)forlineindata]result1=len(set(posforsensorinsensorsifline_to_checkinsensorforposinsensor[line_to_check]))-len(set(s.bxforsinsensorsifs.by==line_to_check))print(f"No beacon on {result1} positions\n")
Pour l'exercice 2, la méthode bourrine est hors de question.
Parce que sinon, mon PC serait toujours en train de calculer là.
Et l'énergie coûte plus cher que mon temps de cerveau disponible.
Optimisé, ça prend quand même 19 secondes, le ventilo a le temps de décoller.
Posté par Yth (Mastodon) .
En réponse au message Avent du Code, jour 14.
Évalué à 2.
Dernière modification le 14 décembre 2022 à 16:01.
Et maintenant, on allume le cerveau :
Pour l'exercice 2, la base se calcule trivialement : le sable formant une pyramide, on sait qu'il va de 500-hauteur à 500+hauteur, vu qu'on a 174 de haut (de 0 à 173), la coursive est en 174, et le sol en 175, le sable s'entasse donc de 326 à 674, et le sol doit s'étendre une case plus loin : de 325 à 675, comme « triché » dans mon code, sauf que ça se calcule de façon sûre avec d'autres données en entrée.
Pour l'exercice 2 encore, et suivant la remarque de Tanguy sur les zones protégées par les rochers pour calculer des surfaces :
On va créer des cases « dark » sous les lignes.
Une ligne de (x1, y1) à (x2, y2) avec x1<=x2 va générer une ligne protégée de gauche à droite de (x1+1, max(y1, y2)) à (x2-1, max(y1, y2)), donc si x1+1>x2-1 (ça arrive quand x1==x2, ligne verticale, ou quand la ligne horizontale fait 2 de large : x2=x1+1) on ne met rien.
Après on peut filtrer les cases bouchées (dark et rocher) sur la zone de pyramide de sable, c'est à dire quand 500-y <= x <= 500+y pour chaque élément. Ici on ne supprime rien, donc on n'a pas à gérer d'éventuels effets de bord, avec une plateforme qui dépasse de la pyramide de sable et peut protéger une zone qui serait ensevelie si la source du sable était plus haute.
La taille de la pyramide est, en prenant le triangle de droite et en le posant retourné au dessus du triangle de gauche, un carré de la hauteur totale, soit 175^2 = 30625 grains potentiels.
Ici ça ne suffit pas : un mur vertical sous une plate-forme va protéger une zone. Donc il faut ne pas réduire la ligne sur un côté si on a un rocher juste là.
Les données en entrée ne sont pas collaboratives, donc en fait on essaie toujours d'élargir la plate-forme qu'on considère pour la zone protégée du dessous, sur la gauche et la droite, puis on protège la ligne en dessous en retirant un élément à gauche et un à droite.
-> Ça fonctionne, il faut juste faire très attention à protéger une ligne en dessous des derniers rochers, le niveau de la coursive, mais pas plus bas au niveau du sol et en dessous, en traitant les données en flux on n'a pas la taille de la zone, donc la position du sol, donc on doit élaguer après coup pour le décompte : tout ce qui est en y < 175.
Voici l'image générée avec les rochers et les zones protégées en dessous :
Pour mon exemple, j'ai en zones protégées (rochers et dark) 1804 cases, donc 30625-1804 = 28821, c'est mon résultat !
Pour les deux exercices :
Quand le sable n'est pas en chute libre, il va s'empiler sur toutes les cases visiter, à terme, sauf à sortir du terrain de jeu pour l'exercice 1.
Donc on n'est pas obligé de faire tomber le sable grain par grain, mais on peut poser des couches, avec un algo récursif.
Il suffit de le faire prendre en remontant la récursion quand on a trouver où se poser en bas. Parce que si on ne trouve pas où se poser (exercice 1 uniquement), alors aucun grain suivant ne pourra se poser.
C'est beaucoup plus simple à mettre en œuvre pour l'exo 2 puisqu'un fil de sable trouvera toujours un endroit où s'arrêter, donc on peut remplir en descendant, et explorer à la fois à gauche et à droite en bas de chaque chute libre.
Pour l'exo 1, si on parcours normalement, qu'on pose les grains en remontant de la récursion et pas avant de descendre, et qu'on sort brutalement (exception python en haut de l'exploration) dès qu'on est au bout, on devrait avoir le résultat.
Je ne l'ai pas encore fait, il est tard, je devrais commencer ma journée de taf quand même :D
Parce que un truc de 351 * 175 dans le terminal c'est galère quand même…
Bref, j'ai perdu plein de temps à faire une animation pas trop lente (j'ai fini par exponentiellement sauter des images : j'affiche l'image 2n), et réussir à faire entrer ça proprement dans le terminal. Après coup j'ai réalisé qu'il suffisait d'afficher chaque grain de sable une fois calculé, au lieu de tout retracer… Dans un terminal… Diantre…
J'avais un bug débile aussi pour l'exo 2, comme je voulais un sol pas trop large pour l'affichage, j'ai essayé d'être intelligent, et ça n'a pas marché, j'ai fini par faire un affichage avec un sol pas très large, puis rajouter un sol très très large.
Après j'ai triché et j'ai juste mis le sol en dur aux bonnes dimensions pour tout afficher, une fois le résultat obtenu.
Deux belles images :
Et un code probablement trop complexe.
L'affichage déjà
classdisplay:sand=" "# "▒"sandcolor=220void=" "rock=" "# "█"rockcolor=94start="+"def__init__(self,x0,x1,y0,y1,*rocks):self.x0,self.x1,self.y0,self.y1=x0,x1,y0,y1self.bg=[[f"{self.color(bg=0)}{self.void}"forxinrange(x0,x1+1)]foryinrange(y0,y1+1)]forxinrocks:self.bg[x[1]-y0][x[0]-x0]=f"{self.color(bg=self.rockcolor)}{self.rock}"self.bg[0][500-x0]=self.startself.background="\n".join("".join(self.bg[y][x]forxinrange(len(self.bg[0])))foryinrange(len(self.bg)))self.width=len(self.bg[0])self.height=len(self.bg)defcolor(self,bg=None,fg=None,rgb=None):bg=f"\033[48;5;{bg}m"ifbgelse""fg=f"\033[38;5;{fg}m"iffgelse""fg=f"\033[38;2;{rgb[0]};{rgb[1]};{rgb[2]}m"ifrgbelsefgifnotbgandnotfg:return"\033[0m"returnf"{bg}{fg}"defcursor(self,row=0,col=0):ifrow<0:row=self.height-rowreturnf"\033[{row+1};{col+1}H"def__call__(self,*items,wait=0):print(self.str(items))ifwait:time.sleep(wait)defitem(self,item):return(f"{self.cursor(col=item[0]-self.x0, row=item[1]-self.y0)}"f"{self.color(bg=self.sandcolor)}"f"{self.sand}")if(item[0]>=self.x0anditem[0]<=self.x1anditem[1]>=self.y0anditem[1]<=self.y1)else""defstr(self,items):return(f"{self.cursor()}"f"{self.color()}"f"{self.background}"f"{''.join(self.item(item) for item in items)}")
Et le code en lui-même :
definput():forlineinsys.stdin:x=list(tuple(map(int,r.split(',')))forrinline.split(' -> '))for_inzip(x[:-1],x[1:]):yield_classRockFormation:def__init__(self,input):self.finished=Falseself.source=(500,0)self.carte=set()self.sand=set()for_ininput():self._add_rocks(*_)self.dim=self.calcdim(self.carte)self.rock=self.carte.copy()self.sand=set()def_add_rocks(self,r1,r2):x1=min(r1[0],r2[0])x2=max(r1[0],r2[0])y1=min(r1[1],r2[1])y2=max(r1[1],r2[1])self.carte.update((x,y)forxinrange(x1,x2+1)foryinrange(y1,y2+1))defadd_rocks(self,r1,r2):self._add_rocks(r1,r2)self.dim=self.calcdim(self.carte)self.rock=self.carte.copy()defcalcdim(self,carte):return(min(x[0]forxincarte),max(x[0]forxincarte),0,max(x[1]forxincarte),)def__call__(self):s=self._sand(*self.source)ifnotself.finished:ifsinself.carte:print(f"Re-adding {s} !")raiseIndexErrorself.carte.add(s)self.sand.add(s)ifs==self.source:self.finished=Truereturnsdef_sand(self,x,y):ifx<self.dim[0]orx>self.dim[1]ory==self.dim[3]:self.finished=Truereturnx,ywhile(x,y+1)notinself.carte:y+=1if(x-1,y+1)notinself.carte:x,y=self._sand(x-1,y+1)elif(x+1,y+1)notinself.carte:x,y=self._sand(x+1,y+1)returnx,ydefsimulation(rocks):d()whilerocks.finishedisFalse:print(d.item(rocks()))# Inputsrocks=RockFormation(input)# First simulationd=display(*rocks.dim,*rocks.rock)simulation(rocks)result1=len(rocks.sand)print(f"{d.cursor(row=-4)}{d.color()}Resting sand : {len(rocks.sand)}")# Second simulation preparationrocks.finished=False# Limited floor for display# rocks.add_rocks(# (rocks.dim[0]-1, rocks.dim[3]+2),# (rocks.dim[1]+1, rocks.dim[3]+2)# )# d = display(*rocks.dim, *rocks.rock)# # Big (not too huge) floor for simulation# rocks.add_rocks(# (rocks.dim[0]-1000, rocks.dim[3]),# (rocks.dim[1]+1000, rocks.dim[3])# )# Enough floor knowing the solutionrocks.add_rocks((325,174),(675,174))d=display(*rocks.dim,*rocks.rock)simulation(rocks)print(f"{d.cursor(row=-3)}{d.color()}",end='')print(f"Resting sand : {result1}")print(f"Filling sand : {len(rocks.sand)}")print(f"Dimensions 1 : {dim1}")print(f"Dimensions 2 : {rocks.dim}")display2=d.str(rocks.sand)# Et enfin afficher le résultat pour les screenshotsw,h=tuple(os.get_terminal_size())print("\n"*h)print(display1)print("\n"*h)print(display2)print(f"{d.cursor(row=-2)}{d.color()}",end='')
Il faut vachement zoomer arrière dans le terminal, après c'est écrit trop petit pour lire, mais on a une belle animation :)
Ouais, j'ai essayé de faire un hack pour remplacer les chiffres seuls par des listes de chiffres, et après faire un json.loads() et juste comparer, en bricolant quelques trucs du genre on arrive à faire tourner les données de démo.
Pour faire court, c'est le premier exercice du calendrier où je n'ai pas fourni la bonne réponse du premier coup…
On peut faire un truc rigolo sur la partie travail, on garde packet et input() identiques, et on reste sur du générateur/itérateur jusqu'à la fin avec le sort :
defanalyse(decoder):score=0forid,(a,b)inenumerate(input()):yieldayieldbifa<=b:score+=id+1print(f"Pairs in the right order : {score}")fordindecoder:yieldddecoder=[packet([[2]]),packet([[6]])]s=sorted(analyse(decoder))print(f"Decoder Key = {(s.index(decoder[0])+1) * (s.index(decoder[1])+1)}")
J'ai un code équivalent, avec un peu plus de modélisation, mais sinon, c'est algorithmiquement identique :
importsysimportjsonfromfunctoolsimporttotal_ordering@total_orderingclasspacket:def__init__(self,data):self.data=datadefcompare(self,a,b):iftype(a)==type(b)==int:ifa==b:returnNonereturna<biftype(a)==int:a=[a]iftype(b)==int:b=[b]fori,jinzip(a,b):c=self.compare(i,j)ifcisnotNone:returncelse:returnself.compare(len(a),len(b))returnNonedef__lt__(self,other):returnself.compare(self.data,other.data)isTruedef__eq__(self,other):returnself.compare(self.data,other.data)isNonedefinput():x=Noneforlineinsys.stdin:ifline=="\n":x=NoneelifxisnotNone:yieldx,packet(json.loads(line))else:x=packet(json.loads(line))score=0all_packets=[]forid,(a,b)inenumerate(input()):all_packets+=[a,b]ifa<=b:score+=id+1print(f"Pairs in the right order : {score}")decoder=[packet([[2]]),packet([[6]])]s=sorted(all_packets+decoder)print(f"Decoder Key = {(s.index(decoder[0])+1) * (s.index(decoder[1])+1)}")
Je n'ai pas songé à split("\n\n"), ça fait que j'ai une première partie qui bosse en flux, mais comme pour la seconde il faut trier, on est obligé d'avoir toutes les données chargées, change rien.
Et ma fonction de comparaison est très centrée sur l'exercice : a < b c'est True, a > b c'est False et a == b c'est None, ergo on poursuit. C'est plus propre de faire -1/0/1.
classdisplay:def__init__(self,input,width,heights):self.heights=heightsself.bgcolors=[[min(255,max(232,heights[c]+231))forcinline]forlineininput]self.background="\n".join("".join(f"{self.color(c)} "forcinline)forlineinself.bgcolors)self.bgcolors=sum(self.bgcolors,[])self.width=widthdefcolor(self,bg=None,fg=None,rgb=None):bg=f"\033[48;5;{bg}m"ifbgelse""fg=f"\033[38;5;{fg}m"iffgelse""fg=f"\033[38;2;{rgb[0]};{rgb[1]};{rgb[2]}m"ifrgbelsefgreturnf"{bg}{fg}"defcursor(self,row=0,col=0):returnf"\033[{row};{col}H"def__call__(self,*visited):print(self.cursor(),end='')print(self.background)forvinvisited:print(f"{self.cursor(*divmod(v, self.width))}{self.color(bg=self.bgcolors[v], fg=160)}o")time.sleep(.1)# On ajoute ces deux lignes juste avant la troisième :d=display(input,w,heights)d()input="".join(input)# Et enfin en dernière ligne de main():d(*v1,*v2)# Et pour éviter d'avoir les résultats en tout moche au milieu :s1=main([start],[end])s2=main([posforpos,heightinenumerate(map)ifheight==1],[end])print(d.cursor(row=h+1),end='')print("\033[0m",end='')print(f"Path found in {s1} moves !")print(f"Path found in {s2} moves !")
Et il faut mettre le terminal en grand, parce que sinon ça va faire très très moche et buggé !
On a une animation des cases en cours de visite, sur fond de montagne enneigée (dégradé noir en bas, et blanc en haut).
Ah, classe :)
J'ai rien de joli à montrer ce coup-ci.
Mais bon, journée présentiel au taf hier, trois heures dans la voiture, 4h en réunions, j'ai à peine eu le temps de bricoler une solution, en ayant réfléchi sur le trajet…
J'ai fait un parcours de graphe inventé à la volée : je n'ai jamais été fichu de retenir le moindre des algos de parcours que j'ai pu apprendre, alors je suis condamné à réinventer la roue !
J'ai des cases en entrée, je regarde pour chacune d'elles où elles peuvent aller (haut, bas, gauche, droite, si la différence de hauteur est bonne), et si je n'ai pas déjà été là (donc matrice des cases visitées avec distance au départ dedans).
Je note l'ensemble des cases nouvellement visitées, et j'itère sur cet ensemble.
Donc a priori inutile de stocker le score pour le retrouver et calculer la suite comme dans mon code plus bas : il suffit de compter les étapes.
Instinctivement, je me suis dis que ça pouvait grossir, et donc j'ai décidé de manger le problème par les deux bouts, donc j'explore en montant depuis "S" et en descendant depuis "E".
Je stocke des scores positifs en montant, et négatifs en descendant.
Si je rencontre une case visitée depuis l'autre bout (case négative si je monte ou positive si je descends), j'ai fait la liaison.
En pratique je peux m'arrêter là : si je suis en train de monter c'est étape*2+1, si je suis en train de descendre c'est étape*2+2.
Dans mon code j'additionne les valeurs absolues : la valeur que j'aurais dû mettre dans la case, et celle déjà présente, et j'ai mon résultat.
Pour l'exercice 2, comme je fournis déjà une liste de case par itération, il suffit de mettre comme liste de démarrage tout les "a" et le "S", vraiment rien à coder, la dernière ligne du programme et basta.
Ah, et j'ai aligné, je bosse sur un tableau à une dimension, au lieu d'une matrice.
Et les mouvements possibles ne sont pas (haut, bas, gauche droite) mais (-1, +1, -largeur, +largeur) et un mouvement doit être dans [0, taille[
Allez, trêve de blabla, code !
heights={v:(26ifk==27elsek)or1fork,vinenumerate("SabcdefghijklmnopqrstuvwxyzE")}input=[lineforlineinsys.stdin.read().strip().splitlines()]w,h=len(input[0]),len(input)size=w*hmoves=[1,-1,w,-w]input="".join(input)map=[heights[c]forcininput]start=input.index("S")end=input.index("E")deftest(score,newpos,height,sign):""" False : mouvement impossible True : mouvement possible vers case non visitée int : mouvement possible vers case visitée, retourne le score de cette case """ifnewpos<0ornewpos>=size:returnFalseif(sign>0andmap[newpos]>height+1)\
or(sign<0andmap[newpos]<height-1):returnFalseifscore[newpos]isNone:returnTruereturnscore[newpos]defmove(score,pos,sign=1):height=map[pos]visited=[]solution=Falseforminmoves:newpos=pos+mt=test(score,newpos,height,sign)ifnott:# Movement not allowedcontinueiftisTrue:# New tile visitedscore[newpos]=score[pos]+signvisited.append(newpos)continueift*sign>0:# Already visitedcontinue# Visited from the other side : solution found !visited.append(newpos)solution=min(solutionorsize,1+sign*score[pos]-sign*score[newpos])returnvisited,solutiondefmain(s1,s2):score=[0if(posins1orposins2)elseNoneforposinrange(size)]unfinished=Truewhileunfinished:v1,v2=[],[]forposins1:v,solution=move(score,pos,1)v1+=vifsolution:print(score)print(f"Path found in {solution} moves !")unfinished=Falseforposins2:v,solution=move(score,pos,-1)v2+=vifsolution:print(score)print(f"Path found in {solution} moves !")unfinished=Falses1,s2=v1,v2main([start],[end])main([posforpos,heightinenumerate(map)ifheight==1],[end])
Pour le « truc qui scale pas » de la seconde partie, un bug dans la première partie m'a fait tomber dessus très tôt, et la solution est assez simple.
Un mélange de propre et de hack tout moche pour celui-là, avec un affichage qui montre l'avancement des calculs, ya 9s pour la seconde partie tout de même…
L'idée pour la seconde passe c'est que tout peut se passer au modulo du produit de tous les modulos. Même au PGCM de tous les modulos des différents singes, mais le produit suffit, on a des nombres suffisamment petits pour que ça roule vite et bien.
Donc la première passe retourne ce produit des modulos, qui est nourri à la seconde passe, en modifiant la classe de base avant instanciation des nouveaux singes.
Joli hack bien crade quoi :)
Après on a quelques bidouilles d'affichage pour lister les singes et faire défiler le n° de round, et ainsi voir l'avancement des calculs.
Et bien sûr, le cœur du problème avec un bon gros eval sur l'opération à effectuer, sinon c'pas drôle !
J'aime bien l'idée, c'est bourrin, c'est fun, ça fait du code plus léger, et ça ressemble à ce qu'on ferait à la main avec une maquette ou une feuille de papier : on fait tourner (ou on tourne autour) !
Par contre c'est faux je pense.
Il y a des arbres visibles depuis plusieurs côtés, et tu vas les compter plusieurs fois…
Posté par Yth (Mastodon) .
En réponse au message Avent du Code, jour 10.
Évalué à 2.
Dernière modification le 10 décembre 2022 à 20:17.
C'est un peu l'idée derrière mon propre code.
Et si on l'enrichissait ?
Par exemple si on veut ajouter une commande affxy a b - qui fait la fonction affine a*x+b - à deux arguments et trois cycles par exemple, dans mon code il suffit d'ajouter cette méthode :
defaffxy(self,a,b):self.noop()# ou :self.noop()# self.history.append(self.X)self.noop()# pour la première versionself.X=int(a)*self.X+int(b)
On a un fond en échiquier, qui bascule quand on « déplace la caméra », ça permet de visuellement voir que ça défile.
Ça marche parce qu'on décale toujours de 1, et ça alterne entre les deux fonds quelle que soit la direction du déplacement.
Et avec des caractères utf-8 de bloc plein, ou gris, c'est plus joli.
Il faut te connecter, d'une façon ou d'une autre, j'utilise mon compte github perso.
Et là tu as un jeu de donnée personnalisées en entrée, et tu peux fournir le résultat dans un champs spécifique en bas de page.
Ça te donne accès au second exercice.
Le premier point, d'utiliser for l in sys.stdin permet de ne pas lire tout l'input, mais simplement d'itérer sur les lignes, on reste plus bas en RAM, on traite un flux.
Le second point, c'est d'utiliser *q pour prendre « ce qui reste ».
Dans le cas d'une ligne "noop" on a m="noop" et q=[].
Dans le cas de "addx XX" on a m="addx" et q=[XX], une list avec la valeur en premier (et unique) élément.
Mais en refilant *q à int, il « unpack » et int(*q) devient équivalent à int(XX).
Avec ça tu gères des instructions différentes, avec autant de paramètres que tu veux, tu t'en fous au niveau du parsing, tu veux juste connaître l'instruction et passer le reste à la gestion de l'instruction.
Ça évite de rajouter un argument factice, et de splitter en ne prenant que les deux premiers.
Yth.
PS: et aussi, chapeau pour la maîtrise d'awk, j'en suis pas là !
importsysdefinput():forlineinsys.stdin:yieldtuple(line.split())classcpu:def__init__(self):self.X=1self.history=[]defnoop(self):self.history.append(self.X)defaddx(self,n):self.history.append(self.X)self.history.append(self.X)self.X+=int(n)def__getitem__(self,n):# Cycle 1 is history 0returnself.history[n-1]def__call__(self,row,col):return"#"ifabs(self.history[row*40+col]-col)<=1else" "mycpu=cpu()foraction,*valuesininput():getattr(mycpu,action)(*values)signal_strength=sum(mycpu[n]*nforninrange(20,221,40))print(f"Signal Strength : {signal_strength}")screen="\n".join("".join(mycpu(row,col)forcolinrange(40))forrowinrange(6))print(screen)
Là je conserve tout l'historique des valeurs de X à chaque cycle, ça n'est évidemment pas viable sur un long programme.
Il faudrait optimiser pour la demande, c'est à dire écrire instruction après instruction l'état de l'écran pour le cycle qui passe, et faire un hook sur les valeurs de la première question, pour additionner/stocker par ailleurs.
Au début j'avais mis un compteur de cycle dans ma classe cpu mais à la fin je ne l'utilisais pas, alors je l'ai viré…
C'est assez facile d' « optimiser » la classe cpu pour qu'elle dessine l'écran au fur et à mesure, on fait une méthode comme ça :
Et définir self.col = 0; self.screen = [] dans l'init.
On remplace les appels à self.history.append(self.X) par self.cycle().
On réalise que cpu.noop ne fait plus qu'exécuter cpu.cycle, donc on garde noop et addx devient :
On vire le cpu.call puisqu'on dessine l'écran à la volée dans mycpu.screen et on affiche print("".join(mycpu.screen)) à la fin. On n'a plus qu'à rajouter un history à 0 pour le cycle 0 inexistant, et on n'a plus besoin de faire return self.history[n-1] dans cpu.__getitem__()
Avec tout ça mis en place on a un code plus concis, plus clair, mais on conserve toujours l'historique pour la question 1, et s'en débarrasser oblige a faire un hook un peu crados dans cpu.noop(), conserver le numéro du cycle courant, et si cycle%40==20 ajouter à self.signal_strengthself.X * self.cycle.
On se débarrasse de l'historique.
Plus propre ?
Moins propre ?
Difficile à dire…
Mais il s'agit de débuggage, donc on peut introduire un hook cpu.debug à chaque cycle, qui servirait à ça.
Bilan ?
8 octets de différence dans le fichier final, temps d'exécution rigoureusement similaire.
En pratique si on bourrine sur les entrées, le second code est plus lent puisqu'il maintient à jour un écran.
On peut utiliser un deque pour cpu.screen et avoir un scroll automatique, ça restreint la RAM utilisée, qui reste stable quelles que soient les données en entrées.
Alors que le premier fait juste un history.append(), ça pompe de la RAM (à l'infini d'ailleurs) mais pas du CPU.
[^] # Re: Une solution brillante
Posté par Yth (Mastodon) . En réponse au message Avent du Code jour 15. Évalué à 2.
Ah, c'est joli ça, oui !
Comme quoi on ferait mieux de réfléchir avant d'agir hein…
[^] # Re: Force Brute.
Posté par Yth (Mastodon) . En réponse au message Avent du Code jour 15. Évalué à 2. Dernière modification le 15 décembre 2022 à 13:05.
En très pythonesque, l'itération par nombre premier se fait comme ça :
[^] # Re: Force Brute.
Posté par Yth (Mastodon) . En réponse au message Avent du Code jour 15. Évalué à 2.
Voilà le code à rajouter, on refait la classe en ZoneSensor pour retourner les bornes de l'intervalle sur une ligne, et à l'intérieur de la zone [0, 4000000] :
Le sorted au début de la boucle va ordonner par le premier élément du tuple, donc le début des intervalles, puis en cas d'égalité par le second. A partir de là on sait que les intervalles vont de gauche à droite.
On note la fin de notre intervalle consolidé, et si l'intervalle suivant commence après, bingo, on a trouvé !
On teste vite fait au début ou à la fin, si notre case à trouver ne serait pas au début ou à la fin, et voilà.
Et bien sûr, on va pas se fouler, exception quand on a trouvé quelque chose : on arrête tout et on jubile !
À noter que je pars de la ligne du bas, mon nez m'a dit que l'auteur allait fourber et mettre la ligne à trouver vers la fin, j'avais raison, la mienne est la
3 391 794
.C'est peut-être là où je gagne le plus de temps en fait.
Sinon, pour essayer d'avoir de la chance, on peut parcourir la zone avec un index qui se balade en parcourant tout.
i = (i + step) % 4000000
En moyenne on va mettre pareil que si on ne sait pas où est la ligne, mais on tape au milieu dès le début et comme la probabilité que l'auteur n'ait pas mis sa ligne proche du bord, pour empêcher la force brute, est assez élevée, on a plus de chance d'avoir de la chance.
De l'autre côté (lignes croissantes de 0 à 4000000) ça prend 1min11s, et bien sûr on a le même résultat.
Avec un step de 747319 (nombre premier, donc premier avec 4000000, choisi au pif), je trouve le résultat en 49 secondes, assez proche de la moitié de la recherche totale (
(1m11s+19s)/2=45s
).# Force Brute.
Posté par Yth (Mastodon) . En réponse au message Avent du Code jour 15. Évalué à 2. Dernière modification le 15 décembre 2022 à 10:37.
Soyons honnêtes, le premier exercice, je l'ai bourriné en force brute.
Objectif : soit une ligne donnée, trouver le nombre de cases qui ne contiennent pas de balises.
Donc les cases visibles par une robot-senseur quelconque, moins les balises sur la ligne (piège, haha !).
Je suis tombé dans un autre piège : il faut scanner la ligne 2 000 000, mais pour l'exemple (pour valider le code) il faut scanner la ligne 10. Mais bon, la réponse pour la ligne 10 du problème complet, c'pas la réponse attendue, bref…
La méthode bourrine :
* pour chaque robot-senseur qui voit la ligne donnée :
* faire un set() des cases vues
set(range(début, fin))
;* union des sets() ;
* compter sa taille ;
* lister pour chaque robot-senseur sa balise, si elle est sur la bonne ligne ;
* supprimer les balises trouvées.
Ça marche en 1,25 secondes sur les données réelles, le résultat étant de 5838453 on a quand même fait un set-comprehension en double boucle d'une taille non négligeable.
La rumeur comme quoi Python c'est lent, c'pas toujours vrai hein, ya aucune optimisation dans mon code.
Pour l'exercice 2, la méthode bourrine est hors de question.
Parce que sinon, mon PC serait toujours en train de calculer là.
Et l'énergie coûte plus cher que mon temps de cerveau disponible.
Optimisé, ça prend quand même 19 secondes, le ventilo a le temps de décoller.
[^] # Re: Beaucoup de temps pour animer et afficher joli
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 14. Évalué à 2.
[^] # Re: Beaucoup de temps pour animer et afficher joli
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 14. Évalué à 2.
Au passage, les images sont générées comme un bourrin :
Yth.
[^] # Re: Beaucoup de temps pour animer et afficher joli
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 14. Évalué à 2. Dernière modification le 14 décembre 2022 à 16:01.
Et maintenant, on allume le cerveau :
Pour l'exercice 2, la base se calcule trivialement : le sable formant une pyramide, on sait qu'il va de 500-hauteur à 500+hauteur, vu qu'on a 174 de haut (de 0 à 173), la coursive est en 174, et le sol en 175, le sable s'entasse donc de 326 à 674, et le sol doit s'étendre une case plus loin : de 325 à 675, comme « triché » dans mon code, sauf que ça se calcule de façon sûre avec d'autres données en entrée.
Pour l'exercice 2 encore, et suivant la remarque de Tanguy sur les zones protégées par les rochers pour calculer des surfaces :
On va créer des cases « dark » sous les lignes.
Une ligne de
(x1, y1)
à(x2, y2)
avecx1<=x2
va générer une ligne protégée de gauche à droite de(x1+1, max(y1, y2))
à(x2-1, max(y1, y2))
, donc six1+1>x2-1
(ça arrive quandx1==x2
, ligne verticale, ou quand la ligne horizontale fait 2 de large :x2=x1+1
) on ne met rien.Après on peut filtrer les cases bouchées (dark et rocher) sur la zone de pyramide de sable, c'est à dire quand
500-y <= x <= 500+y
pour chaque élément. Ici on ne supprime rien, donc on n'a pas à gérer d'éventuels effets de bord, avec une plateforme qui dépasse de la pyramide de sable et peut protéger une zone qui serait ensevelie si la source du sable était plus haute.La taille de la pyramide est, en prenant le triangle de droite et en le posant retourné au dessus du triangle de gauche, un carré de la hauteur totale, soit
175^2 = 30625
grains potentiels.Ici ça ne suffit pas : un mur vertical sous une plate-forme va protéger une zone. Donc il faut ne pas réduire la ligne sur un côté si on a un rocher juste là.
Les données en entrée ne sont pas collaboratives, donc en fait on essaie toujours d'élargir la plate-forme qu'on considère pour la zone protégée du dessous, sur la gauche et la droite, puis on protège la ligne en dessous en retirant un élément à gauche et un à droite.
-> Ça fonctionne, il faut juste faire très attention à protéger une ligne en dessous des derniers rochers, le niveau de la coursive, mais pas plus bas au niveau du sol et en dessous, en traitant les données en flux on n'a pas la taille de la zone, donc la position du sol, donc on doit élaguer après coup pour le décompte : tout ce qui est en
y < 175
.Voici l'image générée avec les rochers et les zones protégées en dessous :
Pour mon exemple, j'ai en zones protégées (rochers et dark) 1804 cases, donc 30625-1804 = 28821, c'est mon résultat !
Pour les deux exercices :
Quand le sable n'est pas en chute libre, il va s'empiler sur toutes les cases visiter, à terme, sauf à sortir du terrain de jeu pour l'exercice 1.
Donc on n'est pas obligé de faire tomber le sable grain par grain, mais on peut poser des couches, avec un algo récursif.
Il suffit de le faire prendre en remontant la récursion quand on a trouver où se poser en bas. Parce que si on ne trouve pas où se poser (exercice 1 uniquement), alors aucun grain suivant ne pourra se poser.
C'est beaucoup plus simple à mettre en œuvre pour l'exo 2 puisqu'un fil de sable trouvera toujours un endroit où s'arrêter, donc on peut remplir en descendant, et explorer à la fois à gauche et à droite en bas de chaque chute libre.
Pour l'exo 1, si on parcours normalement, qu'on pose les grains en remontant de la récursion et pas avant de descendre, et qu'on sort brutalement (exception python en haut de l'exploration) dès qu'on est au bout, on devrait avoir le résultat.
Je ne l'ai pas encore fait, il est tard, je devrais commencer ma journée de taf quand même :D
# Beaucoup de temps pour animer et afficher joli
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 14. Évalué à 4.
Parce que un truc de 351 * 175 dans le terminal c'est galère quand même…
Bref, j'ai perdu plein de temps à faire une animation pas trop lente (j'ai fini par exponentiellement sauter des images : j'affiche l'image 2n), et réussir à faire entrer ça proprement dans le terminal. Après coup j'ai réalisé qu'il suffisait d'afficher chaque grain de sable une fois calculé, au lieu de tout retracer… Dans un terminal… Diantre…
J'avais un bug débile aussi pour l'exo 2, comme je voulais un sol pas trop large pour l'affichage, j'ai essayé d'être intelligent, et ça n'a pas marché, j'ai fini par faire un affichage avec un sol pas très large, puis rajouter un sol très très large.
Après j'ai triché et j'ai juste mis le sol en dur aux bonnes dimensions pour tout afficher, une fois le résultat obtenu.
Deux belles images :


Et un code probablement trop complexe.
L'affichage déjà
Et le code en lui-même :
Il faut vachement zoomer arrière dans le terminal, après c'est écrit trop petit pour lire, mais on a une belle animation :)
[^] # Re: python, en trichant
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 13. Évalué à 3.
Ouais, j'ai essayé de faire un hack pour remplacer les chiffres seuls par des listes de chiffres, et après faire un json.loads() et juste comparer, en bricolant quelques trucs du genre on arrive à faire tourner les données de démo.
Pour faire court, c'est le premier exercice du calendrier où je n'ai pas fourni la bonne réponse du premier coup…
Après j'ai été moins « malin », et j'ai réussi…
[^] # Re: python, en trichant
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 13. Évalué à 2.
On peut faire un truc rigolo sur la partie travail, on garde packet et input() identiques, et on reste sur du générateur/itérateur jusqu'à la fin avec le sort :
[^] # Re: En Pypthon
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 13. Évalué à 2.
Ah, j'avoue, j'ai même pas imaginé deux secondes faire un vrai parsing des données :)
[^] # Re: python, en trichant
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 13. Évalué à 2.
J'ai un code équivalent, avec un peu plus de modélisation, mais sinon, c'est algorithmiquement identique :
Je n'ai pas songé à split("\n\n"), ça fait que j'ai une première partie qui bosse en flux, mais comme pour la seconde il faut trier, on est obligé d'avoir toutes les données chargées, change rien.
Et ma fonction de comparaison est très centrée sur l'exercice : a < b c'est True, a > b c'est False et a == b c'est None, ergo on poursuit. C'est plus propre de faire -1/0/1.
[^] # Re: Étoile
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 12. Évalué à 3.
Le rendu de mon paysage, en doublant la largeur pour faire moins écrasé.
Complètement Numénor oui.
[^] # Re: python et lib de graph
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 12. Évalué à 3.
Pour faire du joli on fait ça :
Et il faut mettre le terminal en grand, parce que sinon ça va faire très très moche et buggé !
On a une animation des cases en cours de visite, sur fond de montagne enneigée (dégradé noir en bas, et blanc en haut).
[^] # Re: python et lib de graph
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 12. Évalué à 2.
Ah, classe :)
J'ai rien de joli à montrer ce coup-ci.
Mais bon, journée présentiel au taf hier, trois heures dans la voiture, 4h en réunions, j'ai à peine eu le temps de bricoler une solution, en ayant réfléchi sur le trajet…
J'ai fait un parcours de graphe inventé à la volée : je n'ai jamais été fichu de retenir le moindre des algos de parcours que j'ai pu apprendre, alors je suis condamné à réinventer la roue !
J'ai des cases en entrée, je regarde pour chacune d'elles où elles peuvent aller (haut, bas, gauche, droite, si la différence de hauteur est bonne), et si je n'ai pas déjà été là (donc matrice des cases visitées avec distance au départ dedans).
Je note l'ensemble des cases nouvellement visitées, et j'itère sur cet ensemble.
Donc a priori inutile de stocker le score pour le retrouver et calculer la suite comme dans mon code plus bas : il suffit de compter les étapes.
Instinctivement, je me suis dis que ça pouvait grossir, et donc j'ai décidé de manger le problème par les deux bouts, donc j'explore en montant depuis "S" et en descendant depuis "E".
Je stocke des scores positifs en montant, et négatifs en descendant.
Si je rencontre une case visitée depuis l'autre bout (case négative si je monte ou positive si je descends), j'ai fait la liaison.
En pratique je peux m'arrêter là : si je suis en train de monter c'est étape*2+1, si je suis en train de descendre c'est étape*2+2.
Dans mon code j'additionne les valeurs absolues : la valeur que j'aurais dû mettre dans la case, et celle déjà présente, et j'ai mon résultat.
Pour l'exercice 2, comme je fournis déjà une liste de case par itération, il suffit de mettre comme liste de démarrage tout les "a" et le "S", vraiment rien à coder, la dernière ligne du programme et basta.
Ah, et j'ai aligné, je bosse sur un tableau à une dimension, au lieu d'une matrice.
Et les mouvements possibles ne sont pas (haut, bas, gauche droite) mais (-1, +1, -largeur, +largeur) et un mouvement doit être dans [0, taille[
Allez, trêve de blabla, code !
[^] # Re: À la chasse aux singes, j'envoie le Python !
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 11. Évalué à 2.
Ah oui, j'ai sorti l'eval avec réticence, mais c'est tellement adapté à ce cas précis…
Professionnellement il faudrait salement valider la chaîne, et probablement qu'un parseur aurait été la solution, pour ne pas faire d'eval.
# À la chasse aux singes, j'envoie le Python !
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 11. Évalué à 3.
Pour le « truc qui scale pas » de la seconde partie, un bug dans la première partie m'a fait tomber dessus très tôt, et la solution est assez simple.
Un mélange de propre et de hack tout moche pour celui-là, avec un affichage qui montre l'avancement des calculs, ya 9s pour la seconde partie tout de même…
L'idée pour la seconde passe c'est que tout peut se passer au modulo du produit de tous les modulos. Même au PGCM de tous les modulos des différents singes, mais le produit suffit, on a des nombres suffisamment petits pour que ça roule vite et bien.
Donc la première passe retourne ce produit des modulos, qui est nourri à la seconde passe, en modifiant la classe de base avant instanciation des nouveaux singes.
Joli hack bien crade quoi :)
Après on a quelques bidouilles d'affichage pour lister les singes et faire défiler le n° de round, et ainsi voir l'avancement des calculs.
Et bien sûr, le cœur du problème avec un bon gros
eval
sur l'opération à effectuer, sinon c'pas drôle ![^] # Re: En Python, modélisé
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 9. Évalué à 2.
C'est Tanguy qui a pensé à l'utf8 en premier pour le jour 10, je reprends les bonnes idées :)
[^] # Re: En faisant pivoter la forêt
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 8. Évalué à 2. Dernière modification le 10 décembre 2022 à 20:25.
Ya ça en numpy :
https://numpy.org/doc/stable/reference/generated/numpy.rot90.html
J'aime bien l'idée, c'est bourrin, c'est fun, ça fait du code plus léger, et ça ressemble à ce qu'on ferait à la main avec une maquette ou une feuille de papier : on fait tourner (ou on tourne autour) !
Par contre c'est faux je pense.
Il y a des arbres visibles depuis plusieurs côtés, et tu vas les compter plusieurs fois…
[^] # Re: Plus simple
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 10. Évalué à 2. Dernière modification le 10 décembre 2022 à 20:17.
C'est un peu l'idée derrière mon propre code.
Et si on l'enrichissait ?
Par exemple si on veut ajouter une commande
affxy a b
- qui fait la fonction affinea*x+b
- à deux arguments et trois cycles par exemple, dans mon code il suffit d'ajouter cette méthode :Voilà, c'est géré.
[^] # Re: En Python, modélisé
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 9. Évalué à 3.
Bonne idée de remettre le curseur en haut !
Et encore une petite amélioration inutile donc indispensable :
On a un fond en échiquier, qui bascule quand on « déplace la caméra », ça permet de visuellement voir que ça défile.
Ça marche parce qu'on décale toujours de 1, et ça alterne entre les deux fonds quelle que soit la direction du déplacement.
Et avec des caractères utf-8 de bloc plein, ou gris, c'est plus joli.
[^] # Re: Où est la partie 2 ?
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 10. Évalué à 3.
Il faut te connecter, d'une façon ou d'une autre, j'utilise mon compte github perso.
Et là tu as un jeu de donnée personnalisées en entrée, et tu peux fournir le résultat dans un champs spécifique en bas de page.
Ça te donne accès au second exercice.
[^] # Re: quick python
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 10. Évalué à 4. Dernière modification le 10 décembre 2022 à 15:14.
Tu peux simplifier un peu ta gestion des input en faisant ça :
Le premier point, d'utiliser
for l in sys.stdin
permet de ne pas lire tout l'input, mais simplement d'itérer sur les lignes, on reste plus bas en RAM, on traite un flux.Le second point, c'est d'utiliser *q pour prendre « ce qui reste ».
Dans le cas d'une ligne "noop" on a m="noop" et q=[].
Dans le cas de "addx XX" on a m="addx" et q=[XX], une list avec la valeur en premier (et unique) élément.
Mais en refilant *q à int, il « unpack » et int(*q) devient équivalent à int(XX).
Avec ça tu gères des instructions différentes, avec autant de paramètres que tu veux, tu t'en fous au niveau du parsing, tu veux juste connaître l'instruction et passer le reste à la gestion de l'instruction.
Ça évite de rajouter un argument factice, et de splitter en ne prenant que les deux premiers.
PS: et aussi, chapeau pour la maîtrise d'awk, j'en suis pas là !
# Entre deux.
Posté par Yth (Mastodon) . En réponse au message Avent du Code, jour 10. Évalué à 3.
Moins modélisé que Tanguy, mais plus que steph.
Là je conserve tout l'historique des valeurs de X à chaque cycle, ça n'est évidemment pas viable sur un long programme.
Il faudrait optimiser pour la demande, c'est à dire écrire instruction après instruction l'état de l'écran pour le cycle qui passe, et faire un hook sur les valeurs de la première question, pour additionner/stocker par ailleurs.
Au début j'avais mis un compteur de cycle dans ma classe
cpu
mais à la fin je ne l'utilisais pas, alors je l'ai viré…C'est assez facile d' « optimiser » la classe cpu pour qu'elle dessine l'écran au fur et à mesure, on fait une méthode comme ça :
Et définir
self.col = 0; self.screen = []
dans l'init.On remplace les appels à
self.history.append(self.X)
parself.cycle()
.On réalise que
cpu.noop
ne fait plus qu'exécutercpu.cycle
, donc on gardenoop
etaddx
devient :On vire le
cpu.call
puisqu'on dessine l'écran à la volée dansmycpu.screen
et on afficheprint("".join(mycpu.screen))
à la fin. On n'a plus qu'à rajouter un history à 0 pour le cycle 0 inexistant, et on n'a plus besoin de fairereturn self.history[n-1]
danscpu.__getitem__()
Avec tout ça mis en place on a un code plus concis, plus clair, mais on conserve toujours l'historique pour la question 1, et s'en débarrasser oblige a faire un hook un peu crados dans
cpu.noop()
, conserver le numéro du cycle courant, et sicycle%40==20
ajouter àself.signal_strength
self.X * self.cycle
.On se débarrasse de l'historique.
Plus propre ?
Moins propre ?
Difficile à dire…
Mais il s'agit de débuggage, donc on peut introduire un hook cpu.debug à chaque cycle, qui servirait à ça.
Bilan :
Bilan ?
8 octets de différence dans le fichier final, temps d'exécution rigoureusement similaire.
En pratique si on bourrine sur les entrées, le second code est plus lent puisqu'il maintient à jour un écran.
On peut utiliser un deque pour cpu.screen et avoir un scroll automatique, ça restreint la RAM utilisée, qui reste stable quelles que soient les données en entrées.
Alors que le premier fait juste un history.append(), ça pompe de la RAM (à l'infini d'ailleurs) mais pas du CPU.
[^] # Re: Vivement , le 1
Posté par Yth (Mastodon) . En réponse au journal Calendrier de l'Avent du code. Évalué à 3.
Et je suis repassé tout juste devant avec encore un nouvel inscrit !
C'est rigolo remarque de voir ça évoluer :)
Tu remarqueras que si on trie par global score on est tous à égalité à 0 hein…