• # On fait tomber des trucs, mais on fait pas de Tetris.

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

    C'est le dernier jour où je vais avoir le temps de la faire, la suite ça sera peut-être dans une semaine...

    J'ai inventé un algo un peu tordu, assez efficace, rude à piger, donc à débugger.
    Déjà les coordonnées sont en 1 dimension : x+y*10+z*100, on va simplifier, surtout qu'on ne fait que des mouvements vers le bas, aucun test aux bornes à faire.
    J'ajoute un bloc de 10x10 en 0 pour poser le bazar.
    Une structure de Blocs assez modélisée, une autre pour l'Univers bien plus simple et à la fin un algo tordu.

    from sys import stdin
    from functools import total_ordering
    from collections import deque
    from itertools import chain
    data = deque(stdin.read().strip().splitlines())
    @total_ordering
    class Bloc:
     def __init__(self, blocdef=None, blocname=None, cubes=None):
     self.name = blocname
     if cubes:
     self.cubes = cubes
     self.alt = min(cubes) // 100
     return
     x, y, z = zip(*((int(__) for __ in _.split(",")) for _ in blocdef.split("~")))
     self.cubes = {
     a + b * 10 + c * 100
     for a in range(min(x), max(x) + 1)
     for b in range(min(y), max(y) + 1)
     for c in range(min(z), max(z) + 1)
     }
     self.alt = min(z)
     def __iter__(self):
     for p in self.cubes:
     yield p
     def __eq__(self, o):
     return self.alt == o.alt
     def __lt__(self, o):
     return self.alt < o.alt
     def __contains__(self, _):
     return _ in self.cubes
     def __repr__(self):
     return f"Bloc#{self.name}({self.cubes})"
     def __neg__(self):
     alt = min(_ // 100 for _ in self.cubes) * 100 - 100
     return Bloc(blocname=self.name, cubes={alt + _ % 100 for _ in self.cubes})
     def __pos__(self):
     alt = max(_ // 100 for _ in self.cubes) * 100 + 100
     return Bloc(blocname=self.name, cubes={alt + _ % 100 for _ in self.cubes})
     def __sub__(self, x):
     self.cubes = {_ - 100 * x for _ in self.cubes}
    class Universe:
     def __init__(self):
     self.zone = set(range(100))
     self.blocs = {}
     def __contains__(self, _): # True if bloc can be placed in universe
     return not self.zone.intersection(_.cubes)
     def add(self, _):
     self.zone.update(_)
     for p in _:
     self.blocs[p] = _.name
    data.extendleft(["0,0,0~9,9,0"])
    blocs = sorted(Bloc(line, i) for i, line in enumerate(data))
    ground = blocs.pop(0)
    universe = Universe()
    universe.add(ground)
    for bloc in blocs:
     while -bloc in universe:
     bloc - 1
     universe.add(bloc)
    # Blocs under each bloc
    down = {
     bloc.name: {universe.blocs[p] for p in -bloc if p in universe.blocs}
     for bloc in blocs
    }
    cannot = {bloc for _ in down.values() if len(_) == 1 for bloc in _}.difference({0})
    ex1 = len(blocs) - len(cannot)

    Et l'exercice 2, prenez peur !

    topof = {}
    partial = {}
    for bloc in sorted(blocs, reverse=True):
     name = bloc.name
     if name not in topof:
     topof[name] = set()
     # Handling partially supported blocks
     mypartial = partial.pop(name, set())
     if mypartial:
     replay = True
     while replay:
     replay = False
     newpart = set()
     # All blocs partially supported elsewhere
     otherpart = set(chain.from_iterable(
     v
     for k, v in partial.items()
     if k not in topof[name]
     ))
     for _ in mypartial:
     if _ not in otherpart:
     # That bloc isn't supported by someone else elsewhere, it is mine!
     topof[name].add(_)
     topof[name].update(topof.get(_, []))
     replay = True # We'll have to restart the search here, because the world changed.
     else:
     newpart.add(_)
     mypartial = newpart
     if mypartial:
     partial[name] = mypartial
     # Sending down my informations
     up = topof.get(name, set()).union({name})
     downward = list(down.get(name, []))
     if len(downward) == 1: # Supported by only one other bloc, sending what I'm actively supporting
     topof[downward[0]] = topof.get(downward[0], set()).union(up)
     # Sending known partial information down
     for bloc in downward:
     partial[bloc] = partial.get(bloc, set()).union({name}).union(partial.get(name, {}))
     partial.pop(name, None)
    ex2 = sum(len(topof[bloc]) for bloc in cannot)

    J'ai pas vraiment le temps de remettre au propre, en gros je pars du haut, et je « pose » les blocs les uns sur les autres, en notant ceux qui sont partiellement posés, et où.
    Et puis à chaque bloc, j'essaie de simplifier par ceux qui ne sont partiellement posés plus que sur lui.
    Et... bah ça marche, en tapant bien sur le code.

    • Yth.