En voyant la partie 2, j'ai immédiatement pensé deux choses :
il faudrait que j'optimise un minimum ma fonction de cycle d'essorage, parce qu'on va l'appeler un certain nombre de fois, même si ce ne sera pas un milliard de fois ;
je vais finir par tomber sur un cycle de cycle en effet.
Ce dernier point est une certitude absolue. Pourquoi donc ? Parce qu'il y a 100 ligne et 100 colonnes, donc moins de 100 ×ばつ 100 = 10.000 positions possibles des pierres qui roulent. En fait, bien moins que ça, parce que les positions occupées par les pierres qui ne roulent pas ne sont pas utilisables, et que seules les dispositions stables par le nord – on se comprend – sont admissibles.
Bref, en moins de 10.000 cycles je suis sûr d'avoir au moins deux fois la même disposition. Et retomber sur une disposition déjà vue, c'est aussi retomber sur la disposition suivante au cycle d'après, etc.
Chez moi ça cycle en 64 cycles.
Le code :
fromcollections.abcimportIterable,SequencefromtypingimportOptional,Selfimportenumimportioimportitertoolsimportnumpyasnpimportnumpy.typingasnptimportaocclassTile(enum.Enum):EMPTY='.'CUBE='#'ROUND='O'def__str__(self)->str:ifselfisself.EMPTY:return' 'ifselfisself.CUBE:return'■しかく'ifselfisself.ROUND:return'○しろまる'assertFalseclassPlatform:def__init__(self,array:Sequence[Sequence[Tile]]):self.matrix:npt.NDArray[np.object_]=np.array(array)self.ly,self.lx=self.matrix.shapeself.spaces_horiz:list[list[range]]=[]self.spaces_vert:list[list[range]]=[]foryinrange(self.ly):self.spaces_horiz.append([])xs=[-1]+[xforxinrange(self.lx)ifself.matrix[y,x]isTile.CUBE]+[self.lx]forx1,x2initertools.pairwise(xs):ifx2-x1>1:self.spaces_horiz[-1].append(range(x1+1,x2))forxinrange(self.lx):self.spaces_vert.append([])ys=[-1]+[yforyinrange(self.ly)ifself.matrix[y,x]isTile.CUBE]+[self.ly]fory1,y2initertools.pairwise(ys):ify2-y1>1:self.spaces_vert[-1].append(range(y1+1,y2))@classmethoddefimport_lines(cls,lines:Iterable[str])->Self:array=[]forlineinlines:array.append([Tile(char)forcharinline.rstrip()])returncls(array)def__str__(self)->str:s=io.StringIO()forlineinself.matrix:fortileinline:s.write(str(tile))s.write('\n')returns.getvalue()defpositions(self):returntuple((y,x)for(y,x),valueinnp.ndenumerate(self.matrix)ifvalueisTile.ROUND)deftilt_north(self)->None:forxinrange(self.lx):column=self.matrix[:,x]forspaceinself.spaces_vert[x]:rounds=0foryinspace:ifcolumn[y]isTile.ROUND:rounds+=1column[y]=Tile.EMPTYforyinrange(space.start,space.start+rounds):column[y]=Tile.ROUNDdeftilt_south(self)->None:forxinrange(self.lx):column=self.matrix[:,x]forspaceinself.spaces_vert[x]:rounds=0foryinspace:ifcolumn[y]isTile.ROUND:rounds+=1column[y]=Tile.EMPTYforyinrange(space.stop-1,space.stop-1-rounds,-1):column[y]=Tile.ROUNDdeftilt_west(self)->None:foryinrange(self.ly):row=self.matrix[y]forspaceinself.spaces_horiz[y]:rounds=0forxinspace:ifrow[x]isTile.ROUND:rounds+=1row[x]=Tile.EMPTYforxinrange(space.start,space.start+rounds):row[x]=Tile.ROUNDdeftilt_east(self)->None:foryinrange(self.ly):row=self.matrix[y]forspaceinself.spaces_horiz[y]:rounds=0forxinspace:ifrow[x]isTile.ROUND:rounds+=1row[x]=Tile.EMPTYforxinrange(space.stop-1,space.stop-1-rounds,-1):row[x]=Tile.ROUNDdefcycle(self)->None:self.tilt_north()self.tilt_west()self.tilt_south()self.tilt_east()defload_north(self)->int:load=0for(y,x),tileinnp.ndenumerate(self.matrix):iftileisTile.ROUND:load+=self.ly-yreturnloaddefpart1(lines:aoc.Data)->int:"""Solve puzzle part 1: determine the sum of stuff"""platform=Platform.import_lines(lines)platform.tilt_north()returnplatform.load_north()defpart2(lines:aoc.Data)->int:"""Solve puzzle part 2: determine the sum of staff"""platform=Platform.import_lines(lines)position_cycles:dict[Tuple[Tuple[int]],int]={}target=1000000000first:Optional[int]=Noneforcycleinrange(platform.ly*platform.ly):positions=platform.positions()ifpositionsinposition_cycles:first=position_cycles[positions]breakposition_cycles[positions]=cycleplatform.cycle()else:raiseValueError("cannot find a cycle‽")assertfirstisnotNone# `first` is the number of a cycle when, /before/ cycling, the positions# were the same as now.# `cycle` is the number of current cycle, /before/ cycling.loop=cycle-firstremaining=target-firstremaining%=loopfor_inrange(remaining):platform.cycle()returnplatform.load_north()
# Pourquoi ça cycle
Posté par 🚲 Tanguy Ortolo (site web personnel) . En réponse au message Advent of Code, jour 14. Évalué à 2.
En voyant la partie 2, j'ai immédiatement pensé deux choses :
Ce dernier point est une certitude absolue. Pourquoi donc ? Parce qu'il y a 100 ligne et 100 colonnes, donc moins de 100 ×ばつ 100 = 10.000 positions possibles des pierres qui roulent. En fait, bien moins que ça, parce que les positions occupées par les pierres qui ne roulent pas ne sont pas utilisables, et que seules les dispositions stables par le nord – on se comprend – sont admissibles.
Bref, en moins de 10.000 cycles je suis sûr d'avoir au moins deux fois la même disposition. Et retomber sur une disposition déjà vue, c'est aussi retomber sur la disposition suivante au cycle d'après, etc.
Chez moi ça cycle en 64 cycles.
Le code :