C'est pour moi l'occasion d'utiliser enum.Flag qui correspond très bien à des trucs qui peuvent avoir une superposition d'états, ici des tuyaux connectés dans différentes directions.
fromcollections.abcimportIterable,SequencefromtypingimportOptional,Selfimportenumimportioimportnumpyasnpimportnumpy.typingasnptimportaocCoords=tuple[int,int]classTile(enum.Flag):NORTH=enum.auto()SOUTH=enum.auto()EAST=enum.auto()WEST=enum.auto()def__str__(self)->str:ifselfisself.__class__(0):return' '# if self is self.EAST:# return '╶'# if self is self.WEST:# return '╴'ifself.NORTHinself:# type: ignoreifself.EASTinself:# type: ignorereturn'╚'ifself.WESTinself:# type: ignorereturn'╝'ifself.SOUTHinself:# type: ignorereturn'║'# if self is self.NORTH:# return '╵'ifself.SOUTHinself:# type: ignoreifself.EASTinself:# type: ignorereturn'╔'ifself.WESTinself:# type: ignorereturn'╗'# if self is self.SOUTH:# return '╷'ifself.EASTinself:# type: ignoreifself.WESTinself:# type: ignorereturn'═'# if self is self.WEST:# return '╴'raiseValueError('unexpected value %s'%repr(self))defvector(self)->Coords:ifselfisself.NORTH:return(-1,0)ifselfisself.SOUTH:return(1,0)ifselfisself.EAST:return(0,1)ifselfisself.WEST:return(0,-1)raiseValueError('cannot convert multiple directions to vector')@classmethoddefimport_char(cls,char:str)->Self:ifchar=='.'orchar==' ':returncls(0)ifchar=='|':returncls.NORTH|cls.SOUTH# type: ignoreifchar=='-':returncls.EAST|cls.WEST# type: ignoreifchar=='L':returncls.NORTH|cls.EAST# type: ignoreifchar=='J':returncls.NORTH|cls.WEST# type: ignoreifchar=='7':returncls.SOUTH|cls.WEST# type: ignoreifchar=='F':returncls.SOUTH|cls.EAST# type: ignoreraiseValueError('unsupported pipe description %s'%char)
Et la carte maintenant :
classMap:def__init__(self,array:Sequence[Sequence[Tile]],start:Coords):self.matrix:npt.NDArray[np.object_]=np.array(array)self.ly,self.lx=self.matrix.shapeself.start=start@classmethoddefimport_lines(cls,lines:Iterable[str])->Self:start:Optional[Coords]=Nonearray:list[list[Tile]]=[]fory,lineinenumerate(lines):array.append([])forx,charinenumerate(line.rstrip()):ifchar=='S':start=(y,x)array[-1].append(Tile(0))else:array[-1].append(Tile.import_char(char))map_=cls(array,(0,0))ifstartisnotNone:y,x=startconnections=Tile(0)ify>=1andTile.SOUTHinmap_[y-1,x]:connections|=Tile.NORTHify<map_.ly-1andTile.NORTHinmap_[y+1,x]:connections|=Tile.SOUTHifx>=1andTile.EASTinmap_[y,x-1]:connections|=Tile.WESTifx<map_.lx-1andTile.WESTinmap_[y,x+1]:connections|=Tile.EASTmap_[y,x]=connectionsmap_.start=startreturnmap_else:raiseValueError('start position not found')defneighs(self,coords:Coords)->Iterable[Coords]:y,x=coordsfordirectioninself[coords]:dy,dx=direction.vector()y_,x_=y+dy,x+dxify_>=0andy_<self.lyandx_>=0andx_<self.lx:yieldy_,x_def__getitem__(self,coords:Coords)->Tile:returnself.matrix[coords]def__setitem__(self,coords:Coords,value:Tile):self.matrix[coords]=valuedef__str__(self):s=io.StringIO()forlineinself.matrix:fortileinline:s.write(str(tile))s.write('\n')returns.getvalue()deffarthest(self)->int:distances:npt.NDArray[np.int_]=np.full(self.matrix.shape,-1,dtype=np.int_)distance=0currents:set[Coords]={self.start}whilelen(currents)>0:nexts:set[Coords]=set()forcurrentincurrents:ifdistances[current]>=0:continuedistances[current]=distancenexts.update(self.neighs(current))currents=nextsdistance+=1returndistances.max()
La solution est donnée par la méthode Map.farthest(). C'est un parcours itératif de proche en proche :
on construit une matrice de distances au point de départ, initialisée avec des -1 partout ;
on commence avec une distance courante de zéro et le point de départ comme unique point courant, mais plus tard on en aura plusieurs (en pratique, deux, mais ça pourrait être plus si on avait des tuyaux trifides) ;
pour chaque point courant si aucune distance n'a été précédemment relevée, on note la distance courante et on place ses voisins reliés dans l'ensemble des prochains points courants ;
on continue, et on ne s'arrête que quand il n'y a plus aucun point courant.
# Première partie
Posté par 🚲 Tanguy Ortolo (site web personnel) . En réponse au message Advent of Code 2023 : Jour 10. Évalué à 3.
C'est pour moi l'occasion d'utiliser
enum.Flagqui correspond très bien à des trucs qui peuvent avoir une superposition d'états, ici des tuyaux connectés dans différentes directions.Et la carte maintenant :
La solution est donnée par la méthode
Map.farthest(). C'est un parcours itératif de proche en proche :