• # Les ennuis commencent...

    Posté par (Mastodon) . En réponse au message Advent of Code 2023, jour 23. Évalué à 2.

    Pour moi, les ennuis ont commencés : pas d'ordinateur, pas de clavier, juste mon petit téléphone qui ne permet pas vraiment de voir les données avec du recul, et l'extrême pénibilité de coder avec un clavier tactile, toujours à basculer pour chercher les chiffres, les opérations, les =, :, les (), [] et {}.
    Bref, pénible, donc on code avec des variables à une ou deux lettres, on fait les algos les plus courts à écrire, et on tâtonne.

    La première partie reste assez simple, je n'ai pas mis bien longtemps et le temps de calcul sur téléphone est assez ridicule.
    Pour la seconde j'ai tenté de laisser tourner des heures avec l'algo brute-force bête et méchant, mais à part flinguer la batterie on n'arrive pas à grand chose, soyons honnêtes.

    Donc être malins.
    Et là je l'ai pas été, j'ai tout de suite tenté de réduire le problème aux nœuds d'exploration : les cases où il y a 3 ou 4 directions possibles, et je note les distances entre ces nœuds, puis j'explore le graphe réduit des nœuds.
    Sauf que j'ai cru ce problème trop complexe pour mon téléphone, et cherché à simplifier : trouver des nœuds qui sont des passages obligés.
    J'étais encouragé par le fait qu'il y en a dans les données de test.
    Ça veut dire qu'on peut découper le graphe simplifié en plusieurs graphes qui s'enchaînent.
    Donc je code, je débugge, ça marche, je lance sur les données réelles, il me laisse avec comme seul noeud intermédiaire le noeud d'arrivée, et tout ça n'a servi à rien.
    Sauf que la réponse tombe malgré tout sans trop d'attente, le problème réduit est suffisamment simple pour que la force brute s'en empare.
    Ça met ~10s sur mon PC (avec pypy bien sûr, pour le coup), pas loin de 2 minutes sur le téléphone.
    Je n'ai pas cherché plus loin, et j'ai juste remis le code au propre, en virant l'exploration inutile des nœuds intermédiaires, avant de le poster.
    La première passe, sur la carte complète, permet aussi de lister les nœuds et la distance les séparant. Techniquement, il pourrait en manquer, puisque l'exploration se fait avec les contraintes de glissement, ça n'est pas le cas, j'ai misé sur cette simplification parce que la denrée qui me manquait vraiment c'était du temps pour coder correctement les choses.

    Je me rappelle cependant avoir tenté plein de trucs et finalement avoir été déçu de la simplicité du bidule qui fournit finalement la solution, bref.
    Au passage, j'utilise une pile de chemins en cours d'exploration, un deque() bien utile pour ça, plutôt qu'une fonction récursive, on fait exploser la limite de récursivité bien trop vite sur le téléphone. Donc j'ai une boucle et une FIFO d'états à faire avancer, c'est pas trop violent sur la RAM, et on n'atteint pas les limites.

    Donc je commence, et j'ai pas fini, les codes dont je n'ai aucune réelle certitude de l'exactitude en toutes circonstances, ça sera pire dans les jours qui viennent.

    from sys import stdin
    from collections import deque
    data = stdin.read().strip().splitlines()
    W = len(data[0])
    H = len(data)
    MAX = W * H
    M = "".join(data) + "#" * W
    S = M.index(".")
    F = data[-1].index(".") + MAX - W
    D = {"<": (-1,), ">": (1,), "v": (W,), "^": (-W,), ".": (1, -1, W, -W)}
    NODES = {}
    def rexplore(start, end, nodes):
     """Exploring full problem, storing path length between nodes
     A node is a crossroad in the maze : where 3 or 4 destinations are possible.
     Start and End are also nodes.
     """
     def nextpos(pos, previous):
     for d in D.get(M[pos], 0):
     y = pos + d
     if y >= 0 and y not in previous and M[y] != "#":
     yield pos + d
     nodes[start] = {}
     nodes[end] = {}
     stack = deque()
     # Current exploring position, [previous positions leading there], last node visited
     stack.append((start + W, [start], start))
     while stack:
     x, path, last = stack.pop()
     yy = list(nextpos(x, path + [x]))
     if x == end or len(yy) > 1: # We are on a node
     if x not in nodes:
     nodes[x] = {}
     nodes[last][x] = max(nodes[last].get(x, 0), len(path) - path.index(last))
     nodes[x][last] = nodes[last][x]
     last = x
     if x == end: # End of the path, yielding it
     yield path
     continue
     for y in yy: # following all available paths
     stack.append((y, path + [x], last))
    ex1 = max(len(q) for q in rexplore(S, F, NODES))
    def explore2(start, end):
     """Dumb exploration of node graph
     Small enough that brute-force wins the day fast enough (~10s)
     """
     stack = deque([(start, [], 0)])
     while stack:
     pos, path, score = stack.pop()
     if pos == end:
     yield (score, path)
     continue
     for node, val in [_ for _ in NODES[pos].items() if _[0] not in path]:
     stack.append((node, path + [pos], score + val))
    ex2, P = max(explore2(S, F))
    • Yth.