Ma première idée fonctionnait sur l'exemple mais pas sur les données réelles : parcourir récursivement une matrice de proche en proche en évitant simplement les endroits où on est déjà passé, ça atteint vite la limites de longueur de la pile d'appels.
Du coup, j'ai fait du Dijkstra. Enfin quelque chose inspiré de son algorithme en tout cas. Ça pourrait être fait de façon entièrement itérative, mais ça ne valait pas la peine, ça reste raisonnable en récursif.
Une fois qu'on a ça, la deuxième partie du puzzle ne pose pas spécialement de problème. Il y a tout de même deux façons de l'implémenter, une bête et méchante (on prend la première solution et on l'applique plusieurs fois), et une un rien plus futée.
fromtypingimportIterable,Iterator,List,Optional,Set,TupleimportnumpyasnpCoords=Tuple[int,int]classMap:def__init__(self,matrix:np.ndarray)->None:self.matrix=matrixself.ly,self.lx=matrix.shapedefneighs(self,y:int,x:int)->Iterator[Coords]:"""Yield neighbours that are reachable from the given coordinates, considering movement rules (no climbing)"""for(dx,dy)in((-1,0),(1,0),(0,-1),(0,1)):y_=y+dyx_=x+dxify_<0ory_>=self.lyorx_<0orx_>=self.lx:continueifself.matrix[y_,x_]-self.matrix[y,x]<=1:yieldy_,x_def_distances(self,starts:Iterable[Coords],end:Coords,distances:np.ndarray)->None:"""Update distances matrix by computing walking distance from possible starts"""ifstarts==[]:# Nothing left to explorereturnnexts=[]# List[Coords]forstartinstarts:start_dist=distances[start]ifstart_dist<0:raiseValueError('cannot compute distances from uncharted point')forneighinself.neighs(*start):neigh_dist=distances[neigh]ifneigh_dist<0orstart_dist+1<neigh_dist:# This point has either never been checked before, or has# been but we have a shorter path to itdistances[neigh]=start_dist+1nexts.append(neigh)# Update distances from the points we have just updatedself._distances(nexts,end,distances)defmin_dist(self,starts:List[Coords],end:Coords)->int:distances=np.full_like(self.matrix,-1)"""Return the minimal distance to reach end, starting from starts"""forstartinstarts:distances[start]=0self._distances(starts,end,distances)returndistances[end]defimport_lines(lines:Iterable[str])->Tuple[Map,Coords,Coords]:matrix=[]# type: List[List[int]]start=Noneend=Nonefory,lineinenumerate(lines):matrix.append([])forx,charinenumerate(line.rstrip()):ifchar=='S':start=(y,x)height=0elifchar=='E':end=(y,x)height=25else:height=ord(char)-ord('a')matrix[-1].append(height)ifstartisNoneorendisNone:raiseValueError("no start or end position found")returnMap(np.array(matrix)),start,enddefsolve_both(lines:Iterable[str])->Tuple[int,int]:"""Solve part 1 of today's puzzle"""map_,start,end=import_lines(lines)min1=map_.min_dist([start],end)min2=map_.min_dist([coordsforcoords,height# type: ignoreinnp.ndenumerate(map_.matrix)ifheight==0],end)returnmin1,min2
# En Python
Posté par 🚲 Tanguy Ortolo (site web personnel) . En réponse au message Avent du Code, jour 12. Évalué à 4.
Ma première idée fonctionnait sur l'exemple mais pas sur les données réelles : parcourir récursivement une matrice de proche en proche en évitant simplement les endroits où on est déjà passé, ça atteint vite la limites de longueur de la pile d'appels.
Du coup, j'ai fait du Dijkstra. Enfin quelque chose inspiré de son algorithme en tout cas. Ça pourrait être fait de façon entièrement itérative, mais ça ne valait pas la peine, ça reste raisonnable en récursif.
Une fois qu'on a ça, la deuxième partie du puzzle ne pose pas spécialement de problème. Il y a tout de même deux façons de l'implémenter, une bête et méchante (on prend la première solution et on l'applique plusieurs fois), et une un rien plus futée.