Comme beaucoup (tout le monde), un petit algo de parcours de graph pour trouver le chemin le plus court.
J'ai fait une première version "naïve" qui priorisait pas les chemin et donc avançait par "inondation" (D'où le nom de fonction flood_map). J'avais un "front" (le point de départ au début) et à chaque passe je calculais un nouveau front (les cases accessibles à partir du front actuel). Dès qu'on arrive à l'arrivée, on sait combien il faut d'étapes.
Ça marche très bien, et ça a l'avantage d'être la valeur exacte.
Et je me suis amuser à faire du A*, et donc explorer une seule case du front (au lieu de toutes) et de choisir la case à privilégier en fonction d'un heuristique.
C'est beaucoup plus efficace en terme de performance (nb de case étudiées) mais ça ne donne pas forcement le résultat le plus optimal selon l'heuristique utilisé. (Heureusement que j'avais la première version)
Pour la partie deux, l'astuce c'est de partir de la fin et de s'arrêter dès qu'on trouve une case 'a'. Ça évite de faire du brute force à partant de toutes les cases a.
Voici la version A* :
(Vous pouvez le lancer avec votre input pour avoir une petite animation)
#!/usr/bin/python3fromenumimportEnumfromtimeimportsleepfrommathimportsqrtimportsysdefyield_input():importsyswithopen(sys.argv[1])asf:forlinf:l=l.strip()yieldlclassSeen(Enum):NONE=0CURRENT=1SEEN=2current_color=Nonedefprint_color(color,out):globalcurrent_colorifcurrent_color!=color:current_color=colorprint(color,end="")print(out,end="")classCell:def__init__(self,height,coord):self.height=heightself.coord=coordself.seen=Seen.NONEself.path_length=0self.previous=Noneself.is_on_the_way=False# Yield other if we can go there from selfdefyield_check_up(self,other,what=Seen.NONE):#print(f"{other.seen} {other.height} {self.height}")ifother.seen==whatandother.height<=self.height+1:other.path_length=self.path_length+1other.seen=Seen.CURRENTyieldotherdefyield_check_down(self,other,what=Seen.NONE):#print(f"{other.seen} {other.height} {self.height}")ifother.seen==whatandother.height>=self.height-1:other.path_length=self.path_length+1other.seen=Seen.CURRENTyieldotherdefprint(self):globalcurrent_colorifself.seen==Seen.NONE:color="033円[40m"elifself.seen==Seen.CURRENT:color="033円[46m"else:color="033円[44m"ifself.is_on_the_way:color="033円[41m"char=chr(ord('a')+self.height)print_color(color,char)defmove_up(coord):returncoord[0]-1,coord[1]defmove_down(coord):returncoord[0]+1,coord[1]defmove_left(coord):returncoord[0],coord[1]-1defmove_right(coord):returncoord[0],coord[1]+1classMap:def__init__(self):self.map=[]fori,lineinenumerate(yield_input()):map_line=[]forj,hinenumerate(line):ifh=="S":self.start=(i,j)height=0elifh=="E":self.end=(i,j)height=25else:height=ord(h)-ord('a')map_line.append(Cell(height,(i,j)))self.map.append(map_line)self.nb_lines=len(self.map)self.nb_columns=len(self.map[0])print(self.nb_lines,self.nb_columns)defget(self,coord):returnself.map[coord[0]][coord[1]]defclear(self):forlineinself.map:forcellinline:cell.seen=Seen.NONEcell.path_length=0cell.previous=Nonecell.is_on_the_way=False# Direction is up or downdefsee_arround(self,cell,direction,what=Seen.NONE):attr=f"yield_check_{direction}"coord=cell.coordifcoord[0]>0:yield fromgetattr(cell,attr)(self.get(move_up(coord)),what)ifcoord[0]<self.nb_lines-1:yield fromgetattr(cell,attr)(self.get(move_down(coord)),what)ifcoord[1]>0:yield fromgetattr(cell,attr)(self.get(move_left(coord)),what)ifcoord[1]<self.nb_columns-1:yield fromgetattr(cell,attr)(self.get(move_right(coord)),what)defflood_map(self,start,scoring,check,direction):to_see=[self.get(start)]round=0whileTrue:cell,to_see=to_see[0],to_see[1:]ifcheck(cell):returncell,roundcell.seen=Seen.SEENfornext_cellinlist(self.see_arround(cell,direction)):next_cell.previous=cellto_see.append(next_cell)to_see.sort(key=scoring)round+=1self.print(round)deffind_a_way_to_summit(self):# Scoring is important.# A* found quickly A solution, but it may not the best one.# This scoring is fast but not exact:# Result:422, round:1460scoring=lambdac:c.path_length+abs(c.coord[0]-self.end[0])**2+abs(c.coord[1]-self.end[1])**2# This scoring give more importance to path_length and give the good result# Result:412, round:3722# scoring = lambda c: c.path_length + sqrt(abs(c.coord[0]-self.end[0])**2 + abs(c.coord[1]-self.end[1])**2)returnself.flood_map(self.start,scoring,lambdacell:cell.coord==self.end,"up")deffind_path(self,end):current=endwhilecurrent:#0print(current.coord)current.is_on_the_way=Truecurrent=current.previousself.print(5)deffind_shorter_path_to_summit(self):# This scoring is fast but not exact:#Result:410, round: 2276scoring=lambdac:c.height# This scoring give more importance to path_length and give the good result# Result:402, round:2400# scoring = lambda c: c.path_length + c.heightreturnself.flood_map(self.end,scoring,lambdacell:cell.height==0,"down")defprint(self,round):globalcurrent_color# clear the terminalprint("033円c",end="")current_color=Noneprint(round)forlineinself.map:forcellinline:cell.print()print()sleep(0.005)defround1(map):cell,nb_round=map.find_a_way_to_summit()map.find_path(cell)map.print(cell.path_length)returncell.path_length,nb_rounddefround2(map):cell,nb_round=map.find_shorter_path_to_summit()map.find_path(cell)map.print(cell.path_length)returncell.path_length,nb_rounddefmain():map=Map()result_1=round1(map)map.clear()result_2=round2(map)print("Round 1 :",result_1)print("Round 2 :",result_2)print(map.nb_lines,map.nb_columns)if__name__=="__main__":main()
# Sans lib, avec visualisation et deuxième partie non bourrine
Posté par GaMa (site web personnel) . En réponse au message Avent du Code, jour 12. Évalué à 2.
J'ai beaucoup aimé l'exo du jour.
Comme beaucoup (tout le monde), un petit algo de parcours de graph pour trouver le chemin le plus court.
J'ai fait une première version "naïve" qui priorisait pas les chemin et donc avançait par "inondation" (D'où le nom de fonction
flood_map). J'avais un "front" (le point de départ au début) et à chaque passe je calculais un nouveau front (les cases accessibles à partir du front actuel). Dès qu'on arrive à l'arrivée, on sait combien il faut d'étapes.Ça marche très bien, et ça a l'avantage d'être la valeur exacte.
Et je me suis amuser à faire du A*, et donc explorer une seule case du front (au lieu de toutes) et de choisir la case à privilégier en fonction d'un heuristique.
C'est beaucoup plus efficace en terme de performance (nb de case étudiées) mais ça ne donne pas forcement le résultat le plus optimal selon l'heuristique utilisé. (Heureusement que j'avais la première version)
Pour la partie deux, l'astuce c'est de partir de la fin et de s'arrêter dès qu'on trouve une case 'a'. Ça évite de faire du brute force à partant de toutes les cases
a.Voici la version A* :
(Vous pouvez le lancer avec votre input pour avoir une petite animation)
Matthieu Gautier|irc:starmad