importdataclassesimportenumimportioimportrefromcollections.abcimportIterable,IteratorfromtypingimportClassVar,Selfimportnumpyasnpimportnumpy.typingasnptimportaocCoords=tuple[int,int]Vector=tuple[int,int]classDirection(enum.Enum):UP='U'DOWN='D'LEFT='L'RIGHT='R'@propertydefvector(self)->Vector:ifselfisself.UP:return(-1,0)ifselfisself.DOWN:return(1,0)ifselfisself.LEFT:return(0,-1)ifselfisself.RIGHT:return(0,1)assertFalse,"we covered all cases"deftranslate(self,position:Coords)->Coords:y,x=positiondy,dx=self.vectorreturny+dy,x+dx@dataclasses.dataclass(frozen=True)classInstruction:direction:Directionlength:intimport_pattern1:ClassVar[re.Pattern]=re.compile(r'^([UDLR]) (\d+) \(#[0-9A-Fa-f]{6}\)\n?$')import_pattern2:ClassVar[re.Pattern]=re.compile(r'^[UDLR] \d+ \(#([0-9A-Fa-f]{5})([0-9A-Fa-f])\)\n?$')@classmethoddefimport_line1(cls,line:str)->Self:if(m:=cls.import_pattern1.match(line))isnotNone:returncls(Direction(m[1]),int(m[2]))raiseValueError("invalid instruction line")@classmethoddefimport_line2(cls,line:str)->Self:if(m:=cls.import_pattern2.match(line))isnotNone:length=int(m[1],16)ifm[2]=='0':direction=Direction.RIGHTelifm[2]=='1':direction=Direction.DOWNelifm[2]=='2':direction=Direction.LEFTelifm[2]=='3':direction=Direction.UPelse:raiseValueError("invalid instruction line")returncls(direction,length)raiseValueError("invalid instruction line")defapply(self,position:Coords)->Coords:y,x=positiondy,dx=self.direction.vectorreturny+self.length*dy,x+self.length*dxclassTerrain1:def__init__(self,instructions:Iterable[Instruction])->None:position:Coords=(0,0)excavated:set[Coords]={(0,0)}forinstructionininstructions:for_inrange(instruction.length):position=instruction.direction.translate(position)excavated.add(position)ymin,ymax=0,0xmin,xmax=0,0for(y,x)inexcavated:ymin=min(ymin,y)ymax=max(ymax,y)xmin=min(xmin,x)xmax=max(xmax,x)# Time to write a terrain matrix# It needs to span the entire excavated area, plus a margin:# * vertically: from ymin - 1 to ymax + 1 included;# * horizontally: fron xmin - 1 to xmax + 1 included.self.matrix:npt.NDArray[np.bool_]=np.zeros((ymax-ymin+3,xmax-xmin+3),dtype=np.bool_)self.ly,self.lx=self.matrix.shapefor(y,x)inexcavated:y=y-ymin+1x=x-xmin+1self.matrix[y,x]=Truedefdig_pool(self)->None:defneighs(coords:Coords)->Iterator[Coords]:def_neighs(coords:Coords):y,x=coordsyieldy,x-1yieldy,x+1yieldy-1,xyieldy+1,xfory,xin_neighs(coords):if0<=y<self.lyand0<=x<self.lx:yieldy,xoutside=np.zeros_like(self.matrix)visited=np.zeros_like(self.matrix)start=(0,0)outside[start]=Truevisited[start]=Truecurrents:set[Coords]={(0,0)}whilelen(currents)>0:nexts:set[Coords]=set()forpositionincurrents:forneighinneighs(position):ifvisited[neigh]:continueifnotself.matrix[neigh]:outside[neigh]=Truenexts.add(neigh)visited[neigh]=Truecurrents=nextsforposition,_innp.ndenumerate(self.matrix):# type: ignoreifnotoutside[position]:self.matrix[position]=Truedefarea(self)->int:returnnp.sum(self.matrix)# type: ignoredef__str__(self)->str:s=io.StringIO()forlineinself.matrix:forexcavatedinline:ifexcavated:s.write('█')else:s.write(' ')s.write('\n')returns.getvalue()@dataclasses.dataclass(frozen=True)classHSegment:x1:intx2:inty:intdefcuts(self,x:int)->bool:returnself.x1<=x<=self.x2def__len__(self)->int:returnself.x2-self.x1+1@dataclasses.dataclass(frozen=True)classVSegment:x:inty1:inty2:intdefcuts(self,y:int)->bool:returnself.y1<=y<=self.y2def__len__(self)->int:returnself.y2-self.y1+1classTerrain2:def__init__(self,instructions:Iterable[Instruction]):hsegments:set[HSegment]=set()vsegments:set[VSegment]=set()ys:set[int]={0,1}xs:set[int]={0,1}ymin,ymax=0,1xmin,xmax=0,1y,x=0,0forinstructionininstructions:y_,x_=instruction.apply((y,x))ymin,ymax=min(ymin,y_),max(ymax,y_+1)xmin,xmax=min(xmin,x_),max(xmax,x_+1)ys.add(y_)ys.add(y_+1)xs.add(x_)xs.add(x_+1)ifinstruction.directionisDirection.UP:vsegments.add(VSegment(x,y_,y))elifinstruction.directionisDirection.DOWN:vsegments.add(VSegment(x,y,y_))elifinstruction.directionisDirection.LEFT:hsegments.add(HSegment(x_,x,y))elifinstruction.directionisDirection.RIGHT:hsegments.add(HSegment(x,x_,y))else:assertFalse,"we covered all cases for instruction direction"y,x=y_,x_ys.add(ymin-1)ys.add(ymax+1)xs.add(xmin-1)xs.add(xmax+1)self.ys=sorted(ys)self.xs=sorted(xs)self.matrix:npt.NDArray[np.bool_]=np.zeros((len(ys)-1,len(xs)-1),dtype=np.bool_)self.lj,self.li=self.matrix.shapeforhsegmentinhsegments:j=self.ys.index(hsegment.y)foriinrange(self.xs.index(hsegment.x1),self.xs.index(hsegment.x2)+1):self.matrix[j,i]=Trueforvsegmentinvsegments:i=self.xs.index(vsegment.x)forjinrange(self.ys.index(vsegment.y1),self.ys.index(vsegment.y2)+1):self.matrix[j,i]=Truedefdig_pool(self)->None:defneighs(coords:Coords)->Iterator[Coords]:def_neighs(coords:Coords):j,i=coordsyieldj,i-1yieldj,i+1yieldj-1,iyieldj+1,iforj,iin_neighs(coords):if0<=j<self.ljand0<=i<self.li:yieldj,ioutside=np.zeros_like(self.matrix)visited=np.zeros_like(self.matrix)start=(0,0)outside[start]=Truevisited[start]=Truecurrents:set[Coords]={(0,0)}whilelen(currents)>0:nexts:set[Coords]=set()forpositionincurrents:forneighinneighs(position):ifvisited[neigh]:continueifnotself.matrix[neigh]:outside[neigh]=Truenexts.add(neigh)visited[neigh]=Truecurrents=nextsforposition,_innp.ndenumerate(self.matrix):# type: ignoreifnotoutside[position]:self.matrix[position]=Truedefarea(self)->int:count=0forjinrange(self.lj):foriinrange(self.li):ifself.matrix[j,i]:count+=((self.ys[j+1]-self.ys[j])*(self.xs[i+1]-self.xs[i]))returncountdef__str__(self)->str:s=io.StringIO()forlineinself.matrix:forexcavatedinline:ifexcavated:s.write('█')else:s.write(' ')s.write('\n')returns.getvalue()defpart1(lines:aoc.Data)->int:"""Solve puzzle part 1: determine the sum of stuff"""terrain=Terrain1(Instruction.import_line1(line)forlineinlines)terrain.dig_pool()returnterrain.area()defpart2(lines:aoc.Data)->int:"""Solve puzzle part 2: determine the sum of staff"""terrain=Terrain2(Instruction.import_line2(line)forlineinlines)terrain.dig_pool()returnterrain.area()
[^] # Re: Sans géométrie
Posté par 🚲 Tanguy Ortolo (site web personnel) . En réponse au message Advent of Code, jour 18. Évalué à 3. Dernière modification le 18 décembre 2023 à 20:01.
Le code :