• # On aime les range, youpi, youpi !

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

    Pour la première partie, je fais des jolies eval avec des fonction lambda, pour avoir une résolution simple et élégante.

    from sys import stdin
    from dataclasses import dataclass
    data = stdin.read().strip().splitlines()
    class Workflow:
     def __init__(self, x):
     def dest(r):
     if r == 'A':
     return None # No more rules, accepted
     if r == 'R':
     return False # Rejected
     return r # Next rule
     self.name, rules = line.strip("}").split("{")
     rules = [_.split(":") for _ in rules.split(",")]
     self.default = dest(rules.pop()[-1])
     self.rules = {eval(f"lambda x: x.{t}"): dest(d) for t, d in rules}
     self.infos = [(t[0], t[1], int(t[2:]), dest(d)) for t, d in rules] # for ex2
     def __call__(self, part): # Applying this whole workflow to a part
     for rule, dst in self.rules.items():
     if rule(part):
     return dst
     return self.default
    @dataclass(frozen=True)
    class Parts:
     x: int = 0
     m: int = 0
     a: int = 0
     s: int = 0
     @property
     def val(self):
     return self.x + self.m + self.a + self.s
     def __bool__(self): # Following workflows until accepted or rejected
     next = 'in'
     while next: # Neither None nor False
     next = workflows[next](self)
     return next is None # Acepted
    # Data analysis
    workflows = {}
    parts = False
    for line in data:
     if not line:
     parts = []
     continue
     if parts is False:
     w = Workflow(line)
     workflows[w.name] = w
     else:
     parts.append(eval(f"Parts({line[1:-1]})"))
    # 1st problem
    ex1 = sum(part.val for part in parts if part)

    Pour le second, je suis tombé dans le piège : < et > ne sont pas l'inverse l'un de l'autre, j'ai souffert pour voir ça :(
    Là on considère des range, des intervalles. Comme on code en python, [1, 4000] ça va être range(1, 4001), d'où les 4001 dans le code.
    Pour l'algo, je considère un état : 4 ranges pour x, m, s et a, un workflow et une étape dans le workflow, ce qui donne donc une règle (rule).
    Je prends mes états courants, je leur applique une règle ce qui va diviser chacun de ces états en au mieux deux états : celui qui respecte la règle en cours, et celui qui ne la respecte pas. Chacun progressant, soit vers une nouvelle règle, soit vers l'étape suivante du workflow.
    Je trie les états ayant une règle à appliquer pour mon itération suivante, et je garde de côté les états étant au stade accepté. Les rejetés sont donc abandonnés là.

    Après il reste à bien lire l'énoncé : non, on ne cherche pas la valeur de toutes les pièces qui sont valables, mais juste à les dénombrer, donc il suffit de multiplier entre eux les tailles des 4 intervalles d'un état accepté.

    Comme on découpe toujours en deux morceaux disjoints, nos états ne se chevauchent jamais, donc on peut tous les dénombrer et additionner.

    @dataclass
    class PartRange:
     x: tuple = (1, 4001)
     m: tuple = (1, 4001)
     a: tuple = (1, 4001)
     s: tuple = (1, 4001)
     rule: str = 'in'
     step: int = 0
     def split(self):
     rule = workflows[self.rule]
     if self.step == len(rule.infos):
     self.rule, self.step = rule.default, 0
     yield self
     return
     key, test, num, dest = rule.infos[self.step]
     if test == ">": # ...
     num += 1
     a, b = getattr(self, key)
     if num > a: # range before num
     new = self.__dict__.copy()
     new[key] = (a, num)
     new["rule"], new["step"] = (dest, 0) if test == "<" else (self.rule, self.step + 1)
     yield PartRange(**new)
     if num <= b: # range after num
     new = self.__dict__.copy()
     new[key] = (num, b)
     new["rule"], new["step"] = (dest, 0) if test == ">" else (self.rule, self.step + 1)
     yield PartRange(**new)
     @property
     def value(self):
     x = len(range(*self.x))
     m = len(range(*self.m))
     a = len(range(*self.a))
     s = len(range(*self.s))
     return x * m * a * s
    parts = [PartRange()]
    accepted = []
    while parts:
     parts = [part for _ in parts for part in _.split()]
     accepted.extend(_ for _ in parts if _.rule is None) # Saving accepted
     parts = [_ for _ in parts if _.rule] # Pruning accepted and rejected
    ex2 = sum(_.value for _ in accepted)

    Le temps d'exécution est négligeable, ~90ms.
    J'ai quand même pas été super rapide pour débugger mes âneries.

    Yth.