• # python, vite fait bien fait

    Posté par . Évalué à 4.

    Le parsing de l'input est presque plus long à écrire que l'application de règles : 4 boucles for imbriquées et 4 split contre 3 boucles while imbriquées.

    Voici mon code pour la partie deux. La partie est identique excepté la condition de sortie.

    import sys
    W = 1000
    H = 200
    L = [[0 for _ in range(W)] for _ in range(H)] # full or air
    def s2li(s,sep=','):
     return list(map(int,s.split(sep)))
    MAX_Y = 0
    for l in sys.stdin.read().splitlines():
     lp = l.split(" -> ")
     for f,t in zip(lp,lp[1:]):
     (a,b,c,d)= (*s2li(f),*s2li(t))
     x0 = min(a,c)
     y0 = min(b,d)
     x1 = max(a,c)
     y1 = max(b,d)
     MAX_Y = max(MAX_Y, y1)
     for x in range(x0,x1+1):
     for y in range(y0,y1+1):
     L[y][x] = 1
    for x in range(W):
     L[MAX_Y+2][x] = 1 # rock on the ground
    stop = False
    N = 0
    while not stop:
     N += 1
     y = 0
     x = 500
     more = True # can all left or right 
     while more and not stop:
     more = False
     while L[y][x]==0 : #and not stop: # if only air
     y+=1
     stop = y == MAX_Y+2
     if L[y][x-1]==0: # can fall left
     x -= 1
     more = True
     elif L[y][x+1]==0: # can fall right
     x += 1
     more = True
     else:
     stop = y == 1 # can't put more sand
     L[y-1][x] = 2 # mark sand
    print(N)

    Et une jolie nimage de la caverne remplie de sable:

    • [^] # Re: python, vite fait bien fait

      Posté par . Évalué à 2.

      Comment as-tu généré l'image?

      Excusez l'absence d'accents dans mes commentaires, j'habite en Australie et n'ai pas de clavier francais sous la main.

      • [^] # Re: python, vite fait bien fait

        Posté par . Évalué à 2.

        J'aime bien utiliser le format PNM car c'est du texte et donc facile à générer.

        Sous linux, ça se visualise nativement. Mais ça peut être converti en PNG grâce à ImageMagic : convert a.pnm -scale 400% a.png

  • # En Python, modélisé

    Posté par (site web personnel) . Évalué à 4. Dernière modification le 14 décembre 2022 à 11:40.

    from __future__ import annotations
    import enum
    import io
    import itertools
    from typing import Iterable, List, Tuple
    import numpy as np
    import numpy.typing as npt
    Coords = Tuple[int, int]
    class Material(enum.Enum):
     AIR = enum.auto()
     ROCK = enum.auto()
     SAND = enum.auto()
    class Slice:
     def __init__(self, array: npt.ArrayLike, leak: Coords) -> None:
     self.matrix: npt.NDArray = np.array(array)
     self.leak = leak
     def pour(self) -> bool:
     """Inject a unit of sand.
     Return False if it was blocked, True if it fell into the void."""
     y, x = self.leak
     while y < self.matrix.shape[0] - 1:
     y_ = y + 1
     for x_ in (x, x - 1, x + 1):
     if self.matrix[y_, x_] is Material.AIR:
     # Sand can flow down
     y = y_
     x = x_
     break
     else:
     # Blocked
     self.matrix[y, x] = Material.SAND
     return False
     # Sand fell all the way down into the void
     return True
     def __str__(self) -> str:
     result = io.StringIO()
     for line in self.matrix:
     for point in line:
     if point is Material.AIR:
     result.write(' ')
     elif point is Material.ROCK:
     result.write('█')
     elif point is Material.SAND:
     result.write('░')
     else:
     assert False # we covered all cases
     result.write('\n')
     return result.getvalue()
     def add_segment(self, start: Coords, end: Coords) -> None:
     y1, x1 = start
     y2, x2 = end
     if y1 == y2:
     x1, x2 = min(x1, x2), max(x1, x2)
     ys = (y1 for _ in range(x1, x2 + 1))
     xs = (x for x in range(x1, x2 + 1))
     elif x1 == x2:
     y1, y2 = min(y1, y2), max(y1, y2)
     ys = (y for y in range(y1, y2 + 1))
     xs = (x1 for _ in range(y1, y2 + 1))
     for y, x in zip(ys, xs):
     self.matrix[y, x] = Material.ROCK
    def import_segments(
     lines: Iterable[str]
     ) -> Tuple[List[Coords], List[Tuple[Coords, Coords]]]:
     points: List[Coords] = []
     segments: List[Tuple[Coords, Coords]] = []
     def coords(word: str) -> Coords:
     parts = word.split(',')
     if len(parts) != 2:
     raise ValueError("unexpected number of coordinates")
     return int(parts[1]), int(parts[0])
     for line in lines:
     words = line.rstrip().split(' -> ')
     segment_points = [coords(word) for word in words]
     points.extend(segment_points)
     segments.extend(itertools.pairwise(segment_points))
     return points, segments
    def import_slice1(lines: Iterable[str]) -> Slice:
     points, segments = import_segments(lines)
     y_leak, x_leak = 0, 500
     points.append((y_leak, x_leak))
     xmin = min(x for _, x in points) - 1
     xmax = max(x for _, x in points) + 1
     ymin = min(y for y, _ in points)
     ymax = max(y for y, _ in points)
     if ymin < 0:
     raise ValueError("unexpected negative coordinate")
     xshift = xmin
     matrix = np.full(((ymax + 1), xmax - xshift + 1), Material.AIR)
     result = Slice(matrix, (0, 500 - xshift))
     for (y1, x1), (y2, x2) in segments:
     result.add_segment((y1, x1 - xshift), (y2, x2 - xshift))
     return result
    def import_slice2(lines: Iterable[str]) -> Slice:
     points, segments = import_segments(lines)
     y_leak, x_leak = 0, 500
     points.append((y_leak, x_leak))
     xmin = min(x for _, x in points)
     xmax = max(x for _, x in points)
     ymin = min(y for y, _ in points)
     ymax = max(y for y, _ in points) + 2 # including the floor
     # Update xmin and xmax to accomodate a pyramid of sand
     xmin = min(xmin, x_leak - (ymax - y_leak))
     xmax = max(xmax, x_leak + (ymax - y_leak))
     if ymin < 0:
     raise ValueError("unexpected negative coordinate")
     xshift = xmin
     matrix = np.full(((ymax + 1), xmax - xshift + 1), Material.AIR)
     result = Slice(matrix, (0, 500 - xshift))
     for (y1, x1), (y2, x2) in segments:
     result.add_segment((y1, x1 - xshift), (y2, x2 - xshift))
     result.add_segment((ymax, xmin - xshift), (ymax, xmax - xshift))
     return result
    def solve1(lines: Iterable[str]) -> int:
     """Solve part 1 of today's puzzle"""
     slice_ = import_slice1(lines)
     for i in range(10000):
     if slice_.pour():
     return i
     raise ValueError("Simulantion did not end within expected limit")
    def solve2(lines: Iterable[str]) -> int:
     """Solve part 2 of today's puzzle"""
     slice_ = import_slice2(lines)
     for i in range(100000):
     _ = slice_.pour()
     if slice_.matrix[slice_.leak] is Material.SAND:
     return i + 1
     raise ValueError("Simulantion did not end within expected limit")
  • # Pistes pour la partie 2

    Posté par (site web personnel) . Évalué à 4.

    Je me demande s'il ne serait pas possible de résoudre la partie 2 bien plus vite, en calculant l'aire des zones protégées par des rochers.

    En effet, le nombre d'unités de sables tombées, c'est l'aire d'une triangle plein, moins celle des rochers qu'il contient, moins l'aire des zones protégées par ces derniers.

    Ceci dit, déterminer la zone protégée par l'ensemble des rochers, c'est loin d'être évident. La zone protégée par un segment horizontal, c'est facile, c'est un triangle dessous. La zone protégée par un segment vertical, c'est facile, c'est rien du tout. Mais quand on commence à avoir des segments horizontaux et verticaux qui se touchent, ça devient compliqué.

    Allez, une autre piste, sans doute pas beaucoup plus simple. On dirait qu'on peut déterminer la zone protégée par des rochers, non pas seulement par calcul, mais aussi par une simulation bizarre : faire couler de l'air depuis le bas vers le haut et regarder où il s'accumule. Mais ça pose le problème d'introduire cet air : il faut qu'il s'accumule et qu'il traverse en même temps les segments...

  • # Beaucoup de temps pour animer et afficher joli

    Posté par (Mastodon) . Évalué à 4.

    Parce que un truc de 351 * 175 dans le terminal c'est galère quand même...

    Bref, j'ai perdu plein de temps à faire une animation pas trop lente (j'ai fini par exponentiellement sauter des images : j'affiche l'image 2n), et réussir à faire entrer ça proprement dans le terminal. Après coup j'ai réalisé qu'il suffisait d'afficher chaque grain de sable une fois calculé, au lieu de tout retracer... Dans un terminal... Diantre...

    J'avais un bug débile aussi pour l'exo 2, comme je voulais un sol pas trop large pour l'affichage, j'ai essayé d'être intelligent, et ça n'a pas marché, j'ai fini par faire un affichage avec un sol pas très large, puis rajouter un sol très très large.

    Après j'ai triché et j'ai juste mis le sol en dur aux bonnes dimensions pour tout afficher, une fois le résultat obtenu.

    Deux belles images :
    Premier exercice
    Deuxième exercice

    Et un code probablement trop complexe.
    L'affichage déjà

    class display:
     sand = " " # "▒"
     sandcolor = 220
     void = " "
     rock = " " # "█"
     rockcolor = 94
     start = "+"
     def __init__(self, x0, x1, y0, y1, *rocks):
     self.x0, self.x1, self.y0, self.y1 = x0, x1, y0, y1
     self.bg = [[
     f"{self.color(bg=0)}{self.void}" for x in range(x0, x1+1)
     ] for y in range(y0, y1+1)]
     for x in rocks:
     self.bg[x[1]-y0][x[0]-x0] = f"{self.color(bg=self.rockcolor)}{self.rock}"
     self.bg[0][500-x0] = self.start
     self.background = "\n".join(
     "".join(self.bg[y][x] for x in range(len(self.bg[0])))
     for y in range(len(self.bg))
     )
     self.width = len(self.bg[0])
     self.height = len(self.bg)
     def color(self, bg=None, fg=None, rgb=None):
     bg = f"033円[48;5;{bg}m" if bg else ""
     fg = f"033円[38;5;{fg}m" if fg else ""
     fg = f"033円[38;2;{rgb[0]};{rgb[1]};{rgb[2]}m" if rgb else fg
     if not bg and not fg:
     return "033円[0m"
     return f"{bg}{fg}"
     def cursor(self, row=0, col=0):
     if row < 0:
     row = self.height - row
     return f"033円[{row+1};{col+1}H"
     def __call__(self, *items, wait=0):
     print(self.str(items))
     if wait:
     time.sleep(wait)
     def item(self, item):
     return (
     f"{self.cursor(col=item[0]-self.x0, row=item[1]-self.y0)}"
     f"{self.color(bg=self.sandcolor)}"
     f"{self.sand}"
     ) if (
     item[0] >= self.x0 and
     item[0] <= self.x1 and
     item[1] >= self.y0 and
     item[1] <= self.y1
     ) else ""
     def str(self, items):
     return (
     f"{self.cursor()}"
     f"{self.color()}"
     f"{self.background}"
     f"{''.join(self.item(item) for item in items)}"
     )

    Et le code en lui-même :

    def input():
     for line in sys.stdin:
     x = list(tuple(map(int, r.split(','))) for r in line.split(' -> '))
     for _ in zip(x[:-1], x[1:]):
     yield _
    class RockFormation:
     def __init__(self, input):
     self.finished = False
     self.source = (500, 0)
     self.carte = set()
     self.sand = set()
     for _ in input():
     self._add_rocks(*_)
     self.dim = self.calcdim(self.carte)
     self.rock = self.carte.copy()
     self.sand = set()
     def _add_rocks(self, r1, r2):
     x1 = min(r1[0], r2[0])
     x2 = max(r1[0], r2[0])
     y1 = min(r1[1], r2[1])
     y2 = max(r1[1], r2[1])
     self.carte.update(
     (x, y)
     for x in range(x1, x2+1)
     for y in range(y1, y2+1)
     )
     def add_rocks(self, r1, r2):
     self._add_rocks(r1, r2)
     self.dim = self.calcdim(self.carte)
     self.rock = self.carte.copy()
     def calcdim(self, carte):
     return (
     min(x[0] for x in carte),
     max(x[0] for x in carte),
     0,
     max(x[1] for x in carte),
     )
     def __call__(self):
     s = self._sand(*self.source)
     if not self.finished:
     if s in self.carte:
     print(f"Re-adding {s} !")
     raise IndexError
     self.carte.add(s)
     self.sand.add(s)
     if s == self.source:
     self.finished = True
     return s
     def _sand(self, x, y):
     if x < self.dim[0] or x > self.dim[1] or y == self.dim[3]:
     self.finished = True
     return x, y
     while (x, y+1) not in self.carte:
     y += 1
     if (x-1, y+1) not in self.carte:
     x, y = self._sand(x-1, y+1)
     elif (x+1, y+1) not in self.carte:
     x, y = self._sand(x+1, y+1)
     return x, y
    def simulation(rocks):
     d()
     while rocks.finished is False:
     print(d.item(rocks()))
    # Inputs
    rocks = RockFormation(input)
    # First simulation
    d = display(*rocks.dim, *rocks.rock)
    simulation(rocks)
    result1 = len(rocks.sand)
    print(f"{d.cursor(row=-4)}{d.color()}Resting sand : {len(rocks.sand)}")
    # Second simulation preparation
    rocks.finished = False
    # Limited floor for display
    # rocks.add_rocks(
    # (rocks.dim[0]-1, rocks.dim[3]+2),
    # (rocks.dim[1]+1, rocks.dim[3]+2)
    # )
    # d = display(*rocks.dim, *rocks.rock)
    # # Big (not too huge) floor for simulation
    # rocks.add_rocks(
    # (rocks.dim[0]-1000, rocks.dim[3]),
    # (rocks.dim[1]+1000, rocks.dim[3])
    # )
    # Enough floor knowing the solution
    rocks.add_rocks((325, 174), (675, 174))
    d = display(*rocks.dim, *rocks.rock)
    simulation(rocks)
    print(f"{d.cursor(row=-3)}{d.color()}", end='')
    print(f"Resting sand : {result1}")
    print(f"Filling sand : {len(rocks.sand)}")
    print(f"Dimensions 1 : {dim1}")
    print(f"Dimensions 2 : {rocks.dim}")
    display2 = d.str(rocks.sand)
    # Et enfin afficher le résultat pour les screenshots
    w, h = tuple(os.get_terminal_size())
    print("\n"*h)
    print(display1)
    print("\n"*h)
    print(display2)
    print(f"{d.cursor(row=-2)}{d.color()}", end='')

    Il faut vachement zoomer arrière dans le terminal, après c'est écrit trop petit pour lire, mais on a une belle animation :)

    • Yth.
    • [^] # Re: Beaucoup de temps pour animer et afficher joli

      Posté par (Mastodon) . Évalué à 2. Dernière modification le 14 décembre 2022 à 16:01.

      Et maintenant, on allume le cerveau :

      Pour l'exercice 2, la base se calcule trivialement : le sable formant une pyramide, on sait qu'il va de 500-hauteur à 500+hauteur, vu qu'on a 174 de haut (de 0 à 173), la coursive est en 174, et le sol en 175, le sable s'entasse donc de 326 à 674, et le sol doit s'étendre une case plus loin : de 325 à 675, comme « triché » dans mon code, sauf que ça se calcule de façon sûre avec d'autres données en entrée.

      Pour l'exercice 2 encore, et suivant la remarque de Tanguy sur les zones protégées par les rochers pour calculer des surfaces :
      On va créer des cases « dark » sous les lignes.
      Une ligne de (x1, y1) à (x2, y2) avec x1<=x2 va générer une ligne protégée de gauche à droite de (x1+1, max(y1, y2)) à (x2-1, max(y1, y2)), donc si x1+1>x2-1 (ça arrive quand x1==x2, ligne verticale, ou quand la ligne horizontale fait 2 de large : x2=x1+1) on ne met rien.
      Après on peut filtrer les cases bouchées (dark et rocher) sur la zone de pyramide de sable, c'est à dire quand 500-y <= x <= 500+y pour chaque élément. Ici on ne supprime rien, donc on n'a pas à gérer d'éventuels effets de bord, avec une plateforme qui dépasse de la pyramide de sable et peut protéger une zone qui serait ensevelie si la source du sable était plus haute.
      La taille de la pyramide est, en prenant le triangle de droite et en le posant retourné au dessus du triangle de gauche, un carré de la hauteur totale, soit 175^2 = 30625 grains potentiels.
      Ici ça ne suffit pas : un mur vertical sous une plate-forme va protéger une zone. Donc il faut ne pas réduire la ligne sur un côté si on a un rocher juste là.
      Les données en entrée ne sont pas collaboratives, donc en fait on essaie toujours d'élargir la plate-forme qu'on considère pour la zone protégée du dessous, sur la gauche et la droite, puis on protège la ligne en dessous en retirant un élément à gauche et un à droite.
      -> Ça fonctionne, il faut juste faire très attention à protéger une ligne en dessous des derniers rochers, le niveau de la coursive, mais pas plus bas au niveau du sol et en dessous, en traitant les données en flux on n'a pas la taille de la zone, donc la position du sol, donc on doit élaguer après coup pour le décompte : tout ce qui est en y < 175.
      Voici l'image générée avec les rochers et les zones protégées en dessous :

      Titre de l'image

      Pour mon exemple, j'ai en zones protégées (rochers et dark) 1804 cases, donc 30625-ひく1804 = 28821, c'est mon résultat !

      Pour les deux exercices :
      Quand le sable n'est pas en chute libre, il va s'empiler sur toutes les cases visiter, à terme, sauf à sortir du terrain de jeu pour l'exercice 1.
      Donc on n'est pas obligé de faire tomber le sable grain par grain, mais on peut poser des couches, avec un algo récursif.
      Il suffit de le faire prendre en remontant la récursion quand on a trouver où se poser en bas. Parce que si on ne trouve pas où se poser (exercice 1 uniquement), alors aucun grain suivant ne pourra se poser.
      C'est beaucoup plus simple à mettre en œuvre pour l'exo 2 puisqu'un fil de sable trouvera toujours un endroit où s'arrêter, donc on peut remplir en descendant, et explorer à la fois à gauche et à droite en bas de chaque chute libre.
      Pour l'exo 1, si on parcours normalement, qu'on pose les grains en remontant de la récursion et pas avant de descendre, et qu'on sort brutalement (exception python en haut de l'exploration) dès qu'on est au bout, on devrait avoir le résultat.

      Je ne l'ai pas encore fait, il est tard, je devrais commencer ma journée de taf quand même :D

      • Yth.
  • # Optimisation

    Posté par (site web personnel) . Évalué à 3.

    Visiblement, il y a une optimisation possible : une si un grain de sable, lâché du point de départ, tombe à un endroit, on peut lâcher ce grain et les suivants de cet endroit, jusqu'à ce qu'il soit ensablé...

    • [^] # Re: Optimisation

      Posté par (site web personnel) . Évalué à 3.

      Avec ladite optimisation :

      from __future__ import annotations
      import enum
      import io
      import itertools
      from typing import Iterable, Iterator, List, Tuple
      import numpy as np
      import numpy.typing as npt
      Coords = Tuple[int, int]
      class Material(enum.Enum):
       AIR = enum.auto()
       ROCK = enum.auto()
       SAND = enum.auto()
      class Slice:
       def __init__(self, array: npt.ArrayLike, leak: Coords) -> None:
       self.matrix: npt.NDArray = np.array(array)
       self.leak = leak
       def pour(self) -> Iterator[bool]:
       """Inject a unit of sand.
       Yields False if it was blocked, True if it fell into the void.
       Stops when the leak is clogged up by a mountain of sand."""
       # List of coordinates to pour sand from
       sources: List[Coords] = []
       # Start at the original leak
       y, x = self.leak
       while True:
       # Our unit of sand is injected at current coordinates (y, x)
       while y < self.matrix.shape[0] - 1:
       y_ = y + 1
       for x_ in (x, x - 1, x + 1):
       if self.matrix[y_, x_] is Material.AIR:
       # Sand can flow down:
       # * current coordinate is a good place to pour next
       # sand unit from;
       sources.append((y, x))
       # * current sand unit falls down one level.
       y = y_
       x = x_
       break
       else:
       # Ways down exhausted, sand is blocked
       self.matrix[y, x] = Material.SAND
       # Get out of the descent loop
       break
       else:
       # Descent ended, sand fell all the way down
       yield True
       continue
       # Descent did not end, sand got blocked
       yield False
       if not sources:
       # Nowhere to pour sand from, even the leak is clogged up
       break
       # Pour next sand unit from last good place
       y, x = sources.pop()
       def __str__(self) -> str:
       result = io.StringIO()
       for line in self.matrix:
       for point in line:
       if point is Material.AIR:
       result.write(' ')
       elif point is Material.ROCK:
       result.write('█')
       elif point is Material.SAND:
       result.write('░')
       else:
       assert False # we covered all cases
       result.write('\n')
       return result.getvalue()
       def add_segment(self, start: Coords, end: Coords) -> None:
       y1, x1 = start
       y2, x2 = end
       if y1 == y2:
       x1, x2 = min(x1, x2), max(x1, x2)
       ys = (y1 for _ in range(x1, x2 + 1))
       xs = (x for x in range(x1, x2 + 1))
       elif x1 == x2:
       y1, y2 = min(y1, y2), max(y1, y2)
       ys = (y for y in range(y1, y2 + 1))
       xs = (x1 for _ in range(y1, y2 + 1))
       for y, x in zip(ys, xs):
       self.matrix[y, x] = Material.ROCK
      def import_segments(
       lines: Iterable[str]
       ) -> Tuple[List[Coords], List[Tuple[Coords, Coords]]]:
       points: List[Coords] = []
       segments: List[Tuple[Coords, Coords]] = []
       def coords(word: str) -> Coords:
       parts = word.split(',')
       if len(parts) != 2:
       raise ValueError("unexpected number of coordinates")
       return int(parts[1]), int(parts[0])
       for line in lines:
       words = line.rstrip().split(' -> ')
       segment_points = [coords(word) for word in words]
       points.extend(segment_points)
       segments.extend(itertools.pairwise(segment_points))
       return points, segments
      def import_slice1(lines: Iterable[str]) -> Slice:
       points, segments = import_segments(lines)
       y_leak, x_leak = 0, 500
       points.append((y_leak, x_leak))
       xmin = min(x for _, x in points) - 1
       xmax = max(x for _, x in points) + 1
       ymin = min(y for y, _ in points)
       ymax = max(y for y, _ in points)
       if ymin < 0:
       raise ValueError("unexpected negative coordinate")
       xshift = xmin
       matrix = np.full(((ymax + 1), xmax - xshift + 1), Material.AIR)
       result = Slice(matrix, (0, 500 - xshift))
       for (y1, x1), (y2, x2) in segments:
       result.add_segment((y1, x1 - xshift), (y2, x2 - xshift))
       return result
      def import_slice2(lines: Iterable[str]) -> Slice:
       points, segments = import_segments(lines)
       y_leak, x_leak = 0, 500
       points.append((y_leak, x_leak))
       xmin = min(x for _, x in points)
       xmax = max(x for _, x in points)
       ymin = min(y for y, _ in points)
       ymax = max(y for y, _ in points) + 2 # including the floor
       # Update xmin and xmax to accomodate a pyramid of sand
       xmin = min(xmin, x_leak - (ymax - y_leak))
       xmax = max(xmax, x_leak + (ymax - y_leak))
       if ymin < 0:
       raise ValueError("unexpected negative coordinate")
       xshift = xmin
       matrix = np.full(((ymax + 1), xmax - xshift + 1), Material.AIR)
       result = Slice(matrix, (0, 500 - xshift))
       for (y1, x1), (y2, x2) in segments:
       result.add_segment((y1, x1 - xshift), (y2, x2 - xshift))
       result.add_segment((ymax, xmin - xshift), (ymax, xmax - xshift))
       return result
      def solve1(lines: Iterable[str]) -> int:
       """Solve part 1 of today's puzzle"""
       slice_ = import_slice1(lines)
       for i, fallen in enumerate(slice_.pour()):
       if fallen:
       return i
       raise ValueError("Simulation ended with unexpected sand leak clogged")
      def solve2(lines: Iterable[str]) -> int:
       """Solve part 2 of today's puzzle"""
       slice_ = import_slice2(lines)
       for i, fallen in enumerate(slice_.pour()):
       if fallen:
       raise ValueError("Simulation ended with unexpected sand under the floor")
       return i + 1
      • [^] # Re: Optimisation

        Posté par . Évalué à 2.

        J'ai essayé et ça ne m'a pas fait gagner grand chose. J'ai trouvé par contre une solution efficace.

        Au lieu de faire tomber un grain de sable, je regarde toutes les positions accessible depuis le point de départ avec comme règle les même mouvements qu'un pion aux échecs. Je passe de 27 minutes d’exécution à 0.7s.

        En groovy ça donne:

        import java.time.Duration
        import java.time.Instant
        import java.util.regex.Pattern
        def start = Instant.now()
        def sc = new Scanner(new File('input'))
        record Point(int x, int y) {}
        static Set<Point> readInput(Scanner sc) {
         def coordPattern = Pattern.compile("(\\d+),(\\d+)")
         def rocks = [] as Set<Point>
         while (sc.hasNextLine()) {
         def line = sc.nextLine()
         def matcher = coordPattern.matcher(line)
         Point prev = null
         while (matcher.find()) {
         def current = new Point(
         Integer.parseInt(matcher.group(1)),
         Integer.parseInt(matcher.group(2))
         )
         rocks << current
         if (prev != null) {
         for (def i = Math.min(prev.x(), current.x()); i < Math.max(prev.x(), current.x()); i++) {
         rocks << new Point(i, current.y())
         }
         for (def j = Math.min(prev.y(), current.y()); j < Math.max(prev.y(), current.y()); j++) {
         rocks << new Point(current.x(), j)
         }
         }
         prev = current
         }
         }
         return rocks
        }
        def rocks = readInput(sc)
        def floor = rocks.max { it.y() }.y() + 2
        def sand = 0
        def newSands = [new Point(500, 0)]
        while (!newSands.isEmpty()) {
         def nextSands = [] as Set<Point>
         for (s in newSands) {
         Set<Point> fallen = fall(s)
         .findAll { !rocks.contains(it) }
         .findAll { it.y() < floor }
         nextSands.addAll(fallen)
         }
         rocks.addAll(nextSands)
         sand += nextSands.size()
         newSands = nextSands
        }
        println(sand)
        def duration = Duration.between(start, Instant.now())
        println(duration)
        static Set<Point> fall(Point sand) {
         return [
         sand,
         new Point(sand.x(), sand.y() + 1),
         new Point(sand.x() - 1, sand.y() + 1),
         new Point(sand.x() + 1, sand.y() + 1)
         ]
        }

        https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.