• # Entre deux.

    Posté par (Mastodon) . En réponse au message Avent du Code, jour 10. Évalué à 3.

    Moins modélisé que Tanguy, mais plus que steph.

    import sys
    def input():
     for line in sys.stdin:
     yield tuple(line.split())
    class cpu:
     def __init__(self):
     self.X = 1
     self.history = []
     def noop(self):
     self.history.append(self.X)
     def addx(self, n):
     self.history.append(self.X)
     self.history.append(self.X)
     self.X += int(n)
     def __getitem__(self, n): # Cycle 1 is history 0
     return self.history[n-1]
     def __call__(self, row, col):
     return "#" if abs(self.history[row * 40 + col] - col) <= 1 else " "
    mycpu = cpu()
    for action, *values in input():
     getattr(mycpu, action)(*values)
    signal_strength = sum(mycpu[n]*n for n in range(20, 221, 40))
    print(f"Signal Strength : {signal_strength}")
    screen = "\n".join(
     "".join(mycpu(row, col) for col in range(40))
     for row in range(6)
    )
    print(screen)

    Là je conserve tout l'historique des valeurs de X à chaque cycle, ça n'est évidemment pas viable sur un long programme.
    Il faudrait optimiser pour la demande, c'est à dire écrire instruction après instruction l'état de l'écran pour le cycle qui passe, et faire un hook sur les valeurs de la première question, pour additionner/stocker par ailleurs.

    Au début j'avais mis un compteur de cycle dans ma classe cpu mais à la fin je ne l'utilisais pas, alors je l'ai viré...
    C'est assez facile d' « optimiser » la classe cpu pour qu'elle dessine l'écran au fur et à mesure, on fait une méthode comme ça :

     def cycle(self):
     self.screen.append("#" if abs(self.X-self.col) <= 1 else " ")
     self.col = (self.col + 1) % 40
     if not self.col:
     self.screen.append("\n")
     self.history.append(self.X)

    Et définir self.col = 0; self.screen = [] dans l'init.
    On remplace les appels à self.history.append(self.X) par self.cycle().
    On réalise que cpu.noop ne fait plus qu'exécuter cpu.cycle, donc on garde noop et addx devient :

     def addx(self, n):
     self.noop()
     self.noop()
     self.X += int(n)

    On vire le cpu.call puisqu'on dessine l'écran à la volée dans mycpu.screen et on affiche print("".join(mycpu.screen)) à la fin. On n'a plus qu'à rajouter un history à 0 pour le cycle 0 inexistant, et on n'a plus besoin de faire return self.history[n-1] dans cpu.__getitem__()

    Avec tout ça mis en place on a un code plus concis, plus clair, mais on conserve toujours l'historique pour la question 1, et s'en débarrasser oblige a faire un hook un peu crados dans cpu.noop(), conserver le numéro du cycle courant, et si cycle%40==20 ajouter à self.signal_strength self.X * self.cycle.
    On se débarrasse de l'historique.

    Plus propre ?
    Moins propre ?
    Difficile à dire...
    Mais il s'agit de débuggage, donc on peut introduire un hook cpu.debug à chaque cycle, qui servirait à ça.

    Bilan :

    import sys
    def input():
     for line in sys.stdin:
     yield tuple(line.split())
    class cpu:
     def __init__(self):
     self.X = 1
     self.history = [0]
     self.col = 0
     self.screen = []
     self.cycle = 0
     self.strength = 0
     def debug(self):
     self.cycle += 1
     if self.cycle % 40 == 20:
     self.strength += self.cycle * self.X
     def noop(self):
     self.screen.append("#" if abs(self.X-self.col) <= 1 else " ")
     self.col = (self.col + 1) % 40
     if not self.col:
     self.screen.append("\n")
     self.debug()
     def addx(self, n):
     self.noop()
     self.noop()
     self.X += int(n)
    mycpu = cpu()
    for action, *values in input():
     getattr(mycpu, action)(*values)
    print(f"Signal Strength : {mycpu.strength}")
    print("".join(mycpu.screen))

    Bilan ?
    8 octets de différence dans le fichier final, temps d'exécution rigoureusement similaire.
    En pratique si on bourrine sur les entrées, le second code est plus lent puisqu'il maintient à jour un écran.
    On peut utiliser un deque pour cpu.screen et avoir un scroll automatique, ça restreint la RAM utilisée, qui reste stable quelles que soient les données en entrées.
    Alors que le premier fait juste un history.append(), ça pompe de la RAM (à l'infini d'ailleurs) mais pas du CPU.

    • Yth