Forum Programmation.autre Advent of Code 2023, day 8

PostĂ© par . Licence CC By‐SA.
Étiquettes :
0
8
déc.
2023

Une tempĂȘte de sable vous a enlevĂ© votre guide, juste aprĂšs qu'il vous ait mis en garde contre les fantĂŽmes du dĂ©sert !

Heureusement, vous avez trouvé une carte du désert dans les fontes du chameau que vous montez.

Elle se prĂ©sente sous la forme d'une suite d'instructions gauche/droite et un (扊陀) sac (ć‰Šé™€ă“ă“ăŸă§)rĂ©seau de nƓuds.

RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)

Vous remarquez qu'il faut partir du nƓud "AAA" pour arriver au nƓud "ZZZ". Pour vous dĂ©placer, il faut passer en boucle la suite de lettres "L" et "R" (gauche et droite) en regardant en commençant du dĂ©part le nom du nƓud suivant selon s'il est Ă  droite ou Ă  gauche.
On atteint l'arrivée aprÚs 2 instructions :

AAA R => CCC L => ZZZ

Pour l'exemple suivant, 6 étapes sont nécessaires :

LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)

AAA L => BBB L => AAA R => BBB L => AAA L => BBB R => ZZZ

En combien d'étapes atteint-on l'arrivée ?

Source : https://i.redd.it/2023-day-8-ai-art-haunted-wasteland-v0-vrygjlhbe35c1.png?s=41b0b35dd561772acfb20573da1f237241e50c06

Mais, attendez ! Vous revoilà à votre point de départ !
Et si c'était une carte destinée aux fantÎmes ? Les fantÎmes ne sont pas contraints par les lois de l'espace-temps.

Vous remarquez aussi quelque chose de curieux : il y a exactement le mĂȘme nombre de nƓuds finissants par "A" que par "Z" !

Il faut en fait parcourir en mĂȘme temps tous les chemins en commençant de tous les points finissants par "A" et s'arrĂȘter lorsqu'on est partout sur un point finissant par "Z".

Par exemple :

LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)

Demande 6 étapes.

Combien en faut-il pour votre carte ?

Indice : il en faut beaucoup. Brutus n'est pas la solution du jour. Il faut dans les 1014.

  • # Encore des maths, du Python et de la POO

    PostĂ© par . ÉvaluĂ© Ă  1.

    Ma solution aurait pu ĂȘtre mieux optimisĂ©e pour la lisibilitĂ© et la redondance, mais elle fonctionne. Je dĂ©finis une classe pour les nƓuds, surcharge quelques opĂ©rateurs, prĂ©traite quelques donnĂ©es, et trouve le ppcm en utilisant le module math de Python.

    #!/bin/python3
    from math import lcm
    class Nodon:
     def __init__(self,name,left,right):
     self.name = name
     self.right = right
     self.left = left
     def __repr__(self):
     return self.name
     def __eq__(self,stringnode):
     if self.name == stringnode:
     return True
     return False
     def endswith(self,letter):
     if self.name[2] == letter:
     return True
     return False
     def go(self,direction):
     if direction == "R":
     return self.right
     if direction == "L":
     return self.left
     raise ValueError
    def parse_node(puzzle):
     direction = puzzle[0]
     nodes = []
     for line in puzzle[1:]:
     if line == "":
     continue
     nodes.append(Nodon(line[:3],line[7:10],line[12:15]))
     return direction, nodes
    def run(direction, nodes):
     now = nodes[nodes.index("AAA")]
     nb = 0
     while now != "ZZZ":
     for d in direction:
     now = now.go(d)
     nb += 1
     if now == "ZZZ":
     break
     return nb
    def nodesendswith(nodes, l):
     for n in nodes:
     if n.endswith(l) == False:
     return False
     return True
    def beforerun(nodes):
     for n in nodes:
     n.right = nodes[nodes.index(n.right)]
     n.left = nodes[nodes.index(n.left)]
     return nodes
    def runboth(direction, nodes):
     now = []
     for n in nodes:
     if n.endswith("A"):
     now.append(n)
     nb = 0
     ends = []
     while len(now) != 0:
     for d in direction:
     nb += 1
     for i in range(len(now)):
     now[i] = now[i].go(d)
     if now[i].endswith("Z"):
     ends.append(nb)
     now[i] = None
     for i in range(now.count(None)):
     now.remove(None)
     if len(now) == 0:
     break
     return lcm(*ends)
    def solve1(puzzle,testing=False):
     s=0
     d, nodes = parse_node(puzzle)
     s = run(d, tuple(beforerun(nodes)))
     if testing:
     print(s)
     return s
    def solve2(puzzle,testing=False):
     s = 0
     d, nodes = parse_node(puzzle)
     s = runboth(d, tuple(beforerun(nodes)))
     if testing:
     print(s)
     return s
    test1 = """RL
    AAA = (BBB, CCC)
    BBB = (DDD, EEE)
    CCC = (ZZZ, GGG)
    DDD = (DDD, DDD)
    EEE = (EEE, EEE)
    GGG = (GGG, GGG)
    ZZZ = (ZZZ, ZZZ)
    """
    result1 = 2
    test2 = """LR
    11A = (11B, XXX)
    11B = (XXX, 11Z)
    11Z = (11B, XXX)
    22A = (22B, XXX)
    22B = (22C, 22C)
    22C = (22Z, 22Z)
    22Z = (22B, 22B)
    XXX = (XXX, XXX)
    """
    result2 = 6
    # for cli invocation
    def solve(short=False,one=True,two=True):
     s1, s2 = False , False
     if one:
     print("----Part 1----")
     if short == False:
     if solve1(test1.split("\n"),testing=True) != result1:
     print("Not working.")
     return False
     else :
     print("Maybe working?")
     with open("input.txt",'r') as file:
     lines = file.read().split("\n")
     s1 = solve1(lines)
     print(s1)
     if two:
     print("----Part 2----")
     if short == False:
     if solve2(test2.split("\n"),testing=True) != result2:
     print("Not working.")
     return False
     else :
     print("Maybe working?")
     with open("input.txt",'r') as file:
     lines = file.read().split("\n")
     s2 = solve2(lines)
     print(s2)
     return s1, s2
    if __name__ == "__main__":
     from sys import argv
     s = False
     one = True
     two = True
     if len(argv) >= 2:
     if "-s" in argv or "--summary" in argv or "--short" in argv:
     s = True
     if "1" in argv:
     two = False
     one = True
     elif "2" in argv:
     one = False
     two = True
     if "2" in argv:
     two = True
     solve(short=s,one=one,two=two)

    L'informatique n'est pas une science exacte, on n'est jamais Ă  l'abri d'un succĂšs

  • # sans PPCM, t'est cuit

    PostĂ© par . ÉvaluĂ© Ă  1.

    J'ai bien brĂ»lĂ© du CPU avant de comprendre qu'il fallait pas tout faire tourner en mĂȘme temps mais juste calculer le nombre d'Ă©tapes de chaque fantĂŽme puis de prendre leur PPCM.

    En 20 lignes de python:

    import sys
    from itertools import cycle
    from math import lcm
    I, _, *P = [l for l in sys.stdin.read().splitlines()]
    # BCN = (HFN, KFC)
    # 0123456789012345678
    P = {l[0:3]:(l[7:10],l[12:15]) for l in P}
    G = [p for p in P.keys() if p[2] == 'A']
    R = []
    for g in G:
     C = g
     N = 0
     for i in cycle(I):
     if C[2] == 'Z':
     break
     C = P[C][i == 'R']
     N += 1
     R.append(N)
    print(lcm(*R))
    • [^] # Re: sans PPCM, t'est cuit

      PostĂ© par (Mastodon) . ÉvaluĂ© Ă  2.

      Mon algo final est un peu bĂątard, parce qu'il part sur un truc plus gĂ©nĂ©rique selon les donnĂ©es de l'Ă©noncĂ© et s'arrĂȘte un peu sĂšchement grĂące Ă  ce qu'on « dĂ©couvre » dans les donnĂ©es :

      Au passage j'ai découvert itertools.cycle, trÚs pratique, au début j'avais réimplémenté la meme chose avec un générateur.

      import sys
      import re
      import math
      from dataclasses import dataclass
      from itertools import cycle
      program, _, *data = sys.stdin.read()).strip().splitlines()
      program = [int(x == "R") for x in program]
      @dataclass
      class Node:
       name: str
       left: str = None
       right: str = None
       start: bool = False
       end: bool = False
       def __bool__(self):
       return self.end
       def __getitem__(self, key):
       return self.right if key else self.left
      places = {x[0]: Node(*x) for line in data for x in [re.findall("[A-Z]{3}", line)] if x}
      for place in places.values():
       place.left = places[place.left]
       place.right = places[place.right]
       place.start = place.name[2] == "A"
       place.end = place.name[2] == "Z"
      def run(position, prog):
       _endpos = None
       _endtime = 0
       _nb = 0
       for direction in cycle(prog):
       position = position[direction]
       _nb += 1
       if position:
       if _endpos:
       break
       _endpos = position.name
       _endtime = _nb
       return _endpos, _endtime, _nb
      # Exercice 1
      endpos, ex1, loop = run(places["AAA"], program)
      # Exercice 2
      loops = []
      for place in places.values():
       if place.start:
       endpos, loopstart, loopend = run(place, program)
       loop = loopend - loopstart
       loops.append(loop)
       print(f"From {place.name} to {endpos}, loop={loopstart}+{loop}={loopend}")
      ex2 = math.lcm(*loops)

      Les affichages de la boucle de l'exercice 2 donnent chez moi :

      From VNA to KTZ, loop=15871+たす15871=わ31742
      From AAA to ZZZ, loop=21251+たす21251=わ42502
      From DPA to GGZ, loop=16409+たす16409=わ32818
      From JPA to MCZ, loop=11567+たす11567=わ23134
      From DBA to TPZ, loop=18023+たす18023=わ36046
      From QJA to FTZ, loop=14257+たす14257=わ28514
      

      On voit trĂšs clairement les deux cycles de mĂȘme longueur, dĂšs le dĂ©but.
      C'est à partir de cet affichage là qu'on saute la résolution générique pour se contenter de calculer le PPCM et finir le problÚme.

      • Yth.

Suivre le flux des commentaires

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