from__future__importannotationsimportenumimportioimportitertoolsfromtypingimportIterable,List,Tupleimportnumpyasnpimportnumpy.typingasnptCoords=Tuple[int,int]classMaterial(enum.Enum):AIR=enum.auto()ROCK=enum.auto()SAND=enum.auto()classSlice:def__init__(self,array:npt.ArrayLike,leak:Coords)->None:self.matrix:npt.NDArray=np.array(array)self.leak=leakdefpour(self)->bool:"""Inject a unit of sand. Return False if it was blocked, True if it fell into the void."""y,x=self.leakwhiley<self.matrix.shape[0]-1:y_=y+1forx_in(x,x-1,x+1):ifself.matrix[y_,x_]isMaterial.AIR:# Sand can flow downy=y_x=x_breakelse:# Blockedself.matrix[y,x]=Material.SANDreturnFalse# Sand fell all the way down into the voidreturnTruedef__str__(self)->str:result=io.StringIO()forlineinself.matrix:forpointinline:ifpointisMaterial.AIR:result.write(' ')elifpointisMaterial.ROCK:result.write('█')elifpointisMaterial.SAND:result.write('░')else:assertFalse# we covered all casesresult.write('\n')returnresult.getvalue()defadd_segment(self,start:Coords,end:Coords)->None:y1,x1=starty2,x2=endify1==y2:x1,x2=min(x1,x2),max(x1,x2)ys=(y1for_inrange(x1,x2+1))xs=(xforxinrange(x1,x2+1))elifx1==x2:y1,y2=min(y1,y2),max(y1,y2)ys=(yforyinrange(y1,y2+1))xs=(x1for_inrange(y1,y2+1))fory,xinzip(ys,xs):self.matrix[y,x]=Material.ROCKdefimport_segments(lines:Iterable[str])->Tuple[List[Coords],List[Tuple[Coords,Coords]]]:points:List[Coords]=[]segments:List[Tuple[Coords,Coords]]=[]defcoords(word:str)->Coords:parts=word.split(',')iflen(parts)!=2:raiseValueError("unexpected number of coordinates")returnint(parts[1]),int(parts[0])forlineinlines:words=line.rstrip().split(' -> ')segment_points=[coords(word)forwordinwords]points.extend(segment_points)segments.extend(itertools.pairwise(segment_points))returnpoints,segmentsdefimport_slice1(lines:Iterable[str])->Slice:points,segments=import_segments(lines)y_leak,x_leak=0,500points.append((y_leak,x_leak))xmin=min(xfor_,xinpoints)-1xmax=max(xfor_,xinpoints)+1ymin=min(yfory,_inpoints)ymax=max(yfory,_inpoints)ifymin<0:raiseValueError("unexpected negative coordinate")xshift=xminmatrix=np.full(((ymax+1),xmax-xshift+1),Material.AIR)result=Slice(matrix,(0,500-xshift))for(y1,x1),(y2,x2)insegments:result.add_segment((y1,x1-xshift),(y2,x2-xshift))returnresultdefimport_slice2(lines:Iterable[str])->Slice:points,segments=import_segments(lines)y_leak,x_leak=0,500points.append((y_leak,x_leak))xmin=min(xfor_,xinpoints)xmax=max(xfor_,xinpoints)ymin=min(yfory,_inpoints)ymax=max(yfory,_inpoints)+2# including the floor# Update xmin and xmax to accomodate a pyramid of sandxmin=min(xmin,x_leak-(ymax-y_leak))xmax=max(xmax,x_leak+(ymax-y_leak))ifymin<0:raiseValueError("unexpected negative coordinate")xshift=xminmatrix=np.full(((ymax+1),xmax-xshift+1),Material.AIR)result=Slice(matrix,(0,500-xshift))for(y1,x1),(y2,x2)insegments:result.add_segment((y1,x1-xshift),(y2,x2-xshift))result.add_segment((ymax,xmin-xshift),(ymax,xmax-xshift))returnresultdefsolve1(lines:Iterable[str])->int:"""Solve part 1 of today's puzzle"""slice_=import_slice1(lines)foriinrange(10000):ifslice_.pour():returniraiseValueError("Simulantion did not end within expected limit")defsolve2(lines:Iterable[str])->int:"""Solve part 2 of today's puzzle"""slice_=import_slice2(lines)foriinrange(100000):_=slice_.pour()ifslice_.matrix[slice_.leak]isMaterial.SAND:returni+1raiseValueError("Simulantion did not end within expected limit")
# En Python, modélisé
Posté par 🚲 Tanguy Ortolo (site web personnel) . En réponse au message Avent du Code, jour 14. Évalué à 4. Dernière modification le 14 décembre 2022 à 11:40.