Yth a écrit 2604 commentaires

  • [^] # Re: Besoin d'énumérations ordonnées

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023, day 7. Évalué à 2. Dernière modification le 07 décembre 2023 à 15:27.

    Une idée :

    from enum import Enum
    from functools import total_ordering
    
    @total_ordering
    class Card(Enum):
        JOKER = '*'
        ONE   = '1'
        TWO   = '2'
        THREE = '3'
        FOUR  = '4'
        FIVE  = '5'
        SIX   = '6'
        SEVEN = '7'
        EIGHT = '8'
        NINE  = '9'
        TEN   = 'T'
        JACK  = 'J'
        QUEEN = 'Q'
        KING  = 'K'
        ACE   = 'A'
        def __lt__(self, other):
            return self.weight < other.weight
        def __eq__(self, other):
            return self.weight == other.weight
    
    for i, card in enumerate(Card):
        card.weight = i
    
    >>> Card('A') > Card('7')
    True
    >>> Card('J') > Card('K')
    False
    >>> a = [Card(x) for x in "A2222"]
    >>> b = [Card(x) for x in "99997"]
    >>> a > b
    True

    Un peu du hack puisque la définition même de la classe est bancale, et qu'il faut modifier après initialisation et avant utilisation, mais ça fonctionne.

    • Yth.
  • [^] # Re: T means 10

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

    Et A pour As aussi, au bout.

    • Y.
  • # Moi, aujourd'hui, je modélise tout propre tout joli !

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

    Surtout que quand j'ai voulu faire mon malin, j'avais une erreur que je n'ai jamais trouvée, mais qui s'est corrigée d'elle-même en remplaçant des chiffres par des trucs lisibles.
    Comme quoi coder propre, contrairement à la rumeur, ben c'est utile même pour soi-même aujourd'hui.

    Voilà, avec ça on corrige des bugs, et on supprime plein de commentaires devenus inutiles aussi :

    from enum import IntEnum
    class HandType(IntEnum):
        HighCard = 1
        OnePair = 2
        TwoPairs = 3
        ThreeOfAKind = 4
        FullHouse = 5
        FourOfAKind = 6
        FiveOfAKind = 7

    Comme ça on peut comparer le HandType entre deux mains, et le plus grand est plus fort.
    L'autre étape c'est de renommer les cartes pour pouvoir comparer deux mains du même type :

    data = sys.stdin.read().strip().splitlines()
    hands1 = (
        Hand(a.translate(str.maketrans("TJQKA", "abcde")), int(b))
        for line in data
        for a, b in [line.split()]
    )

    Et pour l'exercice 2, on initialise les données (hands2) tout pareil sauf qu'on fait : str.maketrans("TJQKA", "a0cde") comme ça le Joker a une valeur plus petite que les autres cartes lors de la comparaison. Et en plus on va pouvoir bosser avec la même classe Hand, et compter les Jokers, donc les "0", et comme il n'y en a pas dans les données transformées pour l'exercice 1, ça va fonctionner pour les deux !

    Maintenant notre classe Hand peut être initialisée avec les données : hand et bid, et on la décore pour pouvoir ordonner directement une liste de mains. Le cached_property permet de s'assurer qu'on ne recalculera jamais deux fois le score d'une main lors de l'exécution (an pratique ça divise le temps d'exécution par 2 environ, ça aurait pu être bien pire…). On utilise une frozen dataclass parce qu'on ne modifie pas une main, là aussi c'est de l'optimisation qui peut s'avérer déterminante en cas de problème vraiment gros.

    from functools import total_ordering, cached_property
    from dataclasses import dataclass
    @dataclass(frozen=True)
    @total_ordering
    class Hand:
        hand: str = ""
        bid: int = 0
        @cached_property
        def score(self):
            return some magic
        def __lt__(self, other):
            return (self.hand < other.hand) if self.score == other.score else (self.score < other.score)
        def __eq__(self, other):
            return False

    Ouaip, on simplifie en disant que deux mains sont toujours différentes, à toutes fins utiles, ça va plus vite, hein ?
    Parce qu'on suppose, vu l'exercice, que toutes les mains fournies sont différentes, sinon le calcul final donnera un résultat différent (sauf si elles ont le même « bid », auquel cas osef).
    De plus cut -d\ -f1 < 07.input | sort -u | wc -l; wc -l 07.input nous retourne 1000 lignes pour chaque, on a donc bien des mains uniques dans les données en entrée.
    En pratique on ne gagne rien, avec la cached_property sur le score, de toute façon on va calculer le score une seule fois par main, et ça coûte pas lourd comme comparaisons. Mais là aussi c'est lié finalement à la toute petite quantité de données.

    Bref, il ne nous reste plus qu'à résoudre, avec la fonction sorted() qui va faire tout le boulot à notre place, magique !

    ex1 = sum((i + 1) * hand.bid for i, hand in enumerate(sorted(hands1)))
    ex2 = sum((i + 1) * hand.bid for i, hand in enumerate(sorted(hands2)))

    Et enfin, voilà le code de la méthode Hand.score, là où on se rend compte que tous les magiciens ont leur « trucs ». Je ne sais pas si j'aurais pu faire plus « intelligent », mais c'est assez clair je pense.

        @cached_property
        def score(self):
            cards = set(self.hand)  # all kind of cards in hand
            nb = len(cards)  # number of different cards, including jokers
            jokers = self.hand.count("0")  # number of jokers in hand
            if nb == 5:  # abcde, abcd*
                return HandType.OnePair if jokers else HandType.HighCard
            if nb == 4:  # aabcd, aabc*, **abc
                return HandType.ThreeOfAKind if jokers else HandType.OnePair
            if nb == 3:  # aaabc, aabbc, aaab*, aabb*, aa**c, ***bc
                cards.discard("0")  # Jokers are already counted
                card = cards.pop()  # draw a non-joker card
                c = self.hand.count(card)
                if c == 2:  # aabbc, aabb*, aa**c
                    return [HandType.TwoPairs, HandType.FullHouse, HandType.FourOfAKind][jokers]
                if c == 3:  # aaabc, aaab*
                    return HandType.FourOfAKind if jokers else HandType.ThreeOfAKind
                # non-joker card U is unique: aaabU, aabbU, aaaU*, aa**U, ***bU
                card = cards.pop()
                if self.hand.count(card) == 2:  # aabbU, aa**U
                    return HandType.FourOfAKind if jokers else HandType.TwoPairs
                # aaabU, aaaU*, ***bU
                return HandType.FourOfAKind if jokers else HandType.ThreeOfAKind
            if nb == 2:  # aaa**, ***aa, aaaa*, ****a
                if jokers:  # whatever, jokers makes it Five of a kind
                    return HandType.FiveOfAKind
                card = cards.pop()
                if self.hand.count(card) in (2, 3):  # aaabb
                    return HandType.FullHouse
                # aaaab
                return HandType.FourOfAKind
            return HandType.FiveOfAKind

    Et voilà :)
    Et une fois n'est pas coutume : PyPy est largement moins efficace que Python là-dessus, on passe de 70 à 370 millisecondes, même avec les 150ms de démarrage de PyPy, c'est amplement trois fois moins bon pour l'exécution en elle-même.
    Dans tous les cas : pas de quoi faire chauffer la pièce à coups de CPU.

    • Yth.
  • [^] # Re: Découpage d'intervalles en Python

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023 : Day 5. Évalué à 2.

    Ah, je parlais juste en brute force, comme ça tu traites les graines par paquets de 1 million seulement, et ça prend moins de RAM. Que 850 millions par exemple, qui peuvent mettre une machine à genoux, et invoquer l'OOM.

    • Yth.
  • # Un zest de math, pas de modélisation

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023, day 6. Évalué à 4. Dernière modification le 06 décembre 2023 à 11:10.

    Ça fait en effet pas mal de possibilités, puisque ma réponse est de plus de 34 millions de façon de gagner.

    En fait, la meilleure course c'est simplement d'appuyer sur le bouton la moitié du temps et de laisser filer l'autre moitié.
    Mais c'est pas ça qu'on demande, on demande juste de calculer les racines d'un polynome du second degré, et de mesurer l'intervalle entre les deux.

    Ok, pour l'exercice 1, on modélise quand même, parce que ça a l'air plus simple que de réfléchir :

    races = list(zip(*[
      [
        int(y.strip())
        for y in x.split(":")[-1].strip().split()
      ] for x in sys.stdin.read().strip().splitlines()
    ][:2]))
    
    ex1 = reduce(lambda x, y: x * y, (
      sum(
        1
        for i in range(1, time)
        if i * (time - i) > distance
      )
      for time, distance in races
    ))
    print(f"Number of ways to win, score : {ex1}")

    Après, on prend peur, et on se dit que ça va être énorme, les chiffres sont gros, blabla, donc résolution de polynôme, cas limites, soustraction, résultat :

    data = sys.stdin.read().strip().splitlines()
    time, distance = (
      int(x.split(":")[-1].strip().replace(" ", ""))
      for x in data[:2]
    )
    print(f"Big race time {time}, high score : {distance}")
    Δ = math.sqrt(time**2 - 4 * distance)  # Ok, this is √Δ and not Δ
    r1 = math.floor((time - Δ) / 2 + 1)  # if r is an integer, it's not a winning race, it's a tie
    r2 = math.ceil((time + Δ) / 2 - 1)   # so we make sure to handle that edge case.
    # All winning solutions are between r1 and r2, included, hence:
    print(f"Result = {r2 - r1 + 1}")

    Bon, et puis au final on se dit que 35 millions c'est pas si lourd, alors on tente la force brute :

    ex2 = 0
    for i in range(1, time):
        if i * (time - i) > distance:
            ex2 += 1
    print(f"Brute Score : {ex2}")

    Et… des petites comparaisons :
    PyPy3, polynôme : 0,171s
    PyPy3, force brute : 0,268s
    Python3, polynôme : 0,031s
    Python3, force brute : 12,634s

    D'accord, PyPy a un surcoût au démarrage, environ 0.15 secondes, donc la solution efficace en python3 est plus rapide.
    Mais la force brute, bigre, quelle différence, dans les 50 fois plus rapide !

    Et bref, dans tous les cas, on peut y aller comme un bourrin, ou intelligemment, ya rien de difficile dans ces exercices.

    • Yth.
  • [^] # Re: Découpage d'intervalles en Python

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023 : Day 5. Évalué à 3.

    Ici on peut faire du brute-force tout découpé, donc éviter de blinder la RAM.
    Par exemple si tu redécoupes tous tes intervalles en intervalles de 1 million, et un dernier pour les éléments qui restent.
    Tu vas avoir quelque chose comme 2500/2600 intervalles, que tu traites séquentiellement, et la RAM utilisée n'est « que » pour une liste de 1 million d'éléments.

    La RAM est basse donc tu ne fais presque que du pompage de CPU, sans mettre la machine à genoux, et ça tourne a peu près vite.

    Chez moi, sur la petite machine (i3@2,3Ghz), ça fait environ 1h, en mono-thread, bien sûr (et avec PyPy, surtout pas CPython pour ça !).
    Sur l'autre qui a 4 coeurs, en découpant le travail en quatre pour profiter de tous les coeurs, ça se termine en moins d'un quart d'heure.

    Donc ça reste « facile » en force brute :)

    • Yth.
  • # Le code que j'aurais aimé faire du premier coup.

    Posté par  (Mastodon) . En réponse au message [Doublon] Advent of Code 2023 : Day 5. Évalué à 2.

    Mon premier algo a fait fondre deux CPU en même temps, sur deux machines différentes, avec partage du travail à faire, pour pondre le résultat en pas loin de 40 minutes, avec PyPy.
    Voici le second, celui qui fonctionne, qui prends moins d'un dixième de seconde pour résoudre tout le problème, et de façon assez élégante en plus, juste en exploitant le range() de Python.
    C'est pour ça que dans l'algo présenté, j'utilise la même méthode pour les deux exercices, avec des range de un élément pour l'exercice 1, je n'ai pas codé ça dès le début.
    Et non, je ne posterai pas mon premier algo, très bien pour l'exo 1, mais très crado pour le 2 ^

    Les données d'abord, je distingue les graines et le reste :

    def seedranges(seeds):
      def __iter():
        while seeds:
          src = seeds.pop(0)
          dst = seeds.pop(0)
          yield range(src, src + dst)
      return __iter()
    
    datas = [x.strip().split(" ") for x in sys.stdin]
    seeds = [int(x) for x in datas.pop(0)[1:]]
    # Exercice 1
    values = [range(seed, seed + 1) for seed in seeds]
    # Exercice 2
    values = list(seedranges(seeds))

    Les graines sont donc une liste de range().

    Ensuite les transformations pour trouver les correspondances, on va avoir une classe avec deux listes : les range() source, et les opérations à appliquer sur chaque range, qui sont simplement un entier relatif à ajouter. Et puis les noms des éléments depuis et vers lesquels on transforme :

    class Map():
      ranges = None
      operations = None
      def __init__(self, *ranges):
        self.ranges = []
        self.operations = []
        for dst, src, size in ranges:
          self.ranges.append(range(src, src + size))
          self.operations.append(dst - src)
    
    def readmap(lines):
      ranges = None
      mapname = None
      for line in lines:
        if not line[0]:
          if mapname:
            yield mapname[0], mapname[2], Map(*ranges)
          ranges = None
        elif ranges is None:
          mapname = line[0].split("-")  # ['seed', 'to', 'soil']
          ranges = []
        else:
          ranges.append(int(x) for x in line)
      yield mapname[0], mapname[2], Map(*ranges)
    
    converters = {
      src: (dst, mapping)
      for src, dst, mapping in readmap(datas)
    }

    Pour les calculs en eux-même, la méthode qui fait le travail est ajoutée à Map(), et la résolution est assez simple au bout du compte.
    On calcule tout simplement les superpositions de deux range() : on va avoir un range d'éléments non traité avant un range de transformation, un range d'éléments non traités après un range de transformation, et enfin un range transformé. On boucle sur toutes les opérations disponibles, en remettant dans le pot les range non traités (avant et après) et en retournant (yield est ton ami) les range traités.
    On n'oublie pas à la fin de retourner tous les range ayant échappés aux transformations, car ils restent identiques selon l'énoncé.
    Le code est vraiment simple grâce aux itérateurs, avec yield, on ne se fatigue pas à construire une liste, à tester des trucs : on a une donnée finale ? yield, et on continue, elle est envoyée, et on ne s'en occupe plus.
    Et on utilise les propriétés des range(), au début je voulais faire des if élément in range_operation qui est immédiat, mais en fait on n'en a pas besoin. La seule subtilité c'est qu'un range est Vrai s'il contient en pratique des éléments, et Faux sinon, donc range(20, 10) c'est faux.
    if before signifie donc simplement : si on a des éléments non traités avant.

    class Map():
      [...]
      def __call__(self, sources):
        for i, r in enumerate(self.ranges):
          op = self.operations[i]
          new_sources = []
          while sources:
            src = sources.pop()
            before = range(src.start, min(src.stop, r.start))
            after = range(max(src.start, r.stop), src.stop)
            transformed = range(max(src.start, r.start) + op, min(src.stop, r.stop) + op)
            if before:
              new_sources.append(before)
            if after:
              new_sources.append(after)
            if transformed:
              yield transformed
          sources = new_sources
        for src in sources:
          yield src

    Enfin le résolution se fait à l'identique pour les deux parties, avec la bonne valeur initiale pour values :

    while src in converters:
      dst, converter = converters[src]
      # print(f"Converting from {src} to {dst}")
      values = list(converter(values))
      src = dst
    print("Closest location is {}".format(
      min(v.start for v in values)
    ))

    Pour info, faire les 7 moulinettes sur une liste de 850 millions d'éléments, même avec PyPy, ça prend dans les dix/quinze minutes, et je parle pas de la RAM.
    Comme quoi ça peut valoir le coup de regarder les données à traiter pour savoir où on va se planter, avant de coder…
    Cela dit, un algo non optimisé, même à 45 minutes de calcul réparti sur deux machines (j'aurais pu aller plus vite en parallélélisant sur les multiples proc des machines, un range initial par exécution, en moins de 15 minutes c'était plié), c'est plus rapide que de réinventer un algorithme, le coder, le valider.

    • Yth.
  • [^] # Re: Python

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023 : Day 1. Évalué à 2.

    "zero" n'est pas dans les données, mais l'ajouter chez moi permet d'avoir la correspondance entre les textes et les index de la liste Python qui commence à 0.

    C'est une optimisation facile de lisibilité on va dire.

    Par ailleurs, j'ai codé sans regarder les données justement, donc en fait je ne savais pas que le zéro était inutilisé.
    Dans l'idée, il aurait pu l'être.

    • Yth.
  • [^] # Re: En Python

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023 : Day 4. Évalué à 2.

    J'aime bien, c'est assez propre et concis !

    Je suis sur quelque chose de nettement moins abstrait, mais l'exercice du jour est assez simple je trouve.

    import sys
    def read_element(winning, numbers):
      winning = set(int(x) for x in winning.strip().split(" ") if x)
      numbers = set(int(x) for x in numbers.strip().split(" ") if x)
      return winning, numbers
    
    def input():
      for line in sys.stdin:
        game, elements = line.strip().split(':')
        game = int(game.split(" ")[-1].strip())
        winning, numbers = read_element(*elements.strip().split("|"))
        i = numbers.intersection(winning)
        num = len(i)
        score = 2**(num - 1) if i else 0
        yield game, num, score
    
    datas = [x for x in input()]
    r = sum(s for *_, s in datas)
    print(f"Score : {r}")
    
    winnings = {card: 1 for card, *_ in datas}
    for card, num, s in datas:
      wins = winnings[card]
      for i in range(card + 1, card + num + 1):
        winnings[i] += wins
    
    print(f"Total cards: {sum(winnings.values())}")

    Et le calcul est immédiat aussi.

    • Yth.
  • [^] # Re: C'était trop simple... je suspecte un piège !

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

    Jour 2, c'est simple, on ne se fait pas déjà des noeuds dans le cerveau, c'est normal…

    • Yth.
  • [^] # Re: Commencer à représenter le problème pas trop bêtement.

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

    Alors avec plus d'abstraction, j'ai ça :

    from dataclasses import dataclass
    @dataclass(frozen=True)
    class Bag():
      red: int = 0
      green: int = 0
      blue: int = 0
      def __le__(self, other):
        if self.red > other.red or self.green > other.green or self.blue > other.blue:
          return False
        return True
      def __add__(self, other):
        return Bag(
          red=max(self.red, other.red),
          green=max(self.green, other.green),
          blue=max(self.blue, other.blue),
        )
      @property
      def power(self):
        return self.red * self.green * self.blue

    Une classe pour représenter un sac, avec des cubes rouges, verts et bleus dedans. La dataclass c'est bien pratique, ça s'initialise automatiquement avec les paramètres fournis aux noms des variables, ou alors aux valeurs par défaut.
    Par exemple Bag() c'est 0 de chaque, Bag(red=12, green=5), ça fait ce qui est écrit en laissant blue à 0.
    Il est frozen ce qui signifie qu'on ne peut pas le modifier, donc les opérations retournent une nouvelle instance de Bag, c'est utile en optimisation parce que ça va plus vite. Ici c'est sans importance.

    On définit trois opérations dessus : __le__ permet de comparer bag1 <= bag2, c'est True ssi tous les éléments de bag1 sont inférieurs ou égaux à ceux de bag2.
    __add__ c'est un peu un hack, et ça retourne un Bag avec le maximum pour chaque valeur dans les deux Bag additionnés, ça permet d'utiliser la fonction sum() pour calculer un maximum, parce que la fonction max() fait des comparaisons, et retourne le Bag en entrée le plus grand, ce qui n'a rien à voir avec ce dont on a besoin.
    Et finalement power est un attribut qui sert au calcul du score du second exercice.

    Avec cette classe on va analyser les données, en exploitant les passages d'arguments python par dictionnaire, et les valeurs par défaut de Bag, c'est pas trop abscons à lire, si on n'est pas débutant en Python (et qu'on aime les struct-comprehension) :

    def input():
      for line in sys.stdin:
        game, cubes = line.strip().split(':')
        yield int(game.split(" ")[1]), sum(
          (
            Bag(**{
              color: int(nb)
              for cubes in bag.strip().split(",")
              for nb, color in [cubes.strip().split(" ")]
            })
            for bag in cubes.split(";")
          ), Bag(),
        )

    En réalité on n'a jamais besoin des divers sacs possible, la seule chose qui nous intéresse c'est le maximum pour chaque valeur rouge, vert et bleu, d'où le sum(), et une fois cette optimisation faite, on réalise qu'on s'est furieusement compliqué la vie et qu'au final on avait juste besoin, depuis le tout début de l'exercice, de trouver ce maximum, et de ne s'intéresser qu'à lui.

    Les exercices ensuite sont triviaux, surtout le second, il n'y a vraiment plus rien à faire :

    datas = [x for x in input()]
    constraint = Bag(red=12, green=13, blue=14)
    r = sum(
      game
      for game, biggestbag in datas
      if biggestbag <= constraint
    )
    print(f"Possible games : {r}")
    
    power = sum(
      biggestbag.power
      for game, biggestbag in datas
    )
    print(f"Sum of Power of games : {power}")
    • Yth, qui vient de faire brûler un CPU sur l'exercice 5, parce que parfois c'est plus « rapide » de laisser turbiner que de trouver un meilleur algorithme, mais on en parle jour 5…
  • [^] # Re: Commencer à représenter le problème pas trop bêtement.

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023 : Day 2. Évalué à 2. Dernière modification le 04 décembre 2023 à 17:30.

    Le weekend, j'ai moins de cerveau, moins de temps, et je n'ai pas encore trop cherché les bonnes optimisations, mais je devrais commencer, pour reprendre les bonnes habitudes.
    Et me remettre en tête les structures de données moins utilisées, et pourtant tellement utiles.

    J'aurais aussi pu faire une classe cubes avec trois éléments red, green et blue, qui permet des comparaisons immédiates avec <, >, des min, max, sum, multiplications, etc.

    Après les opérations deviennent très simples. Je vais peut-être poster ça demain :)

    • Yth.
  • # On bourrine, on bourrine et on fait des bêtises...

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023 : Day 3. Évalué à 2.

    Faut dire, j'ai pu m'y mettre vers 23h30 hier soir…
    Et ma bêtise a été de transformer "927*741" en 927741 au lieu de 927 et 741 séparés.

    Déjà, la somme totale de tous les nombre du jeu se calcule ainsi :

    s = ".".join(x.strip() for x in sys.stdin)
    for i in set(x for x in s if x not in "0123456789"):
        s = s.replace(i, " ")
    all_numbers = sum(int(i) for i in s.split(" ") if i) # =598313

    Ce calcul fait à l'avance m'aurait permis de voir que 9691964 comme résultat, c'était ouvertement faux…

    Bref, j'ai commencé à chercher certaines optimisations, en utilisant des comparaisons de set() en python, qui sont plutôt efficaces. Et j'aurais probablement dû utiliser des frozenset, car c'est plus rapide, quand on n'a pas besoin de les modifier.
    Il reste aussi des optimisations faisables, mais la taille des données ne les justifient pas encore.

    Zéro structures de données, un traitement très linéaire, ça reste simple, on ne fait pas encore de réelles abstractions.

    import sys
    directions = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1))
    
    def envelope(positions):
      return set(
        tuple(sum(x) for x in zip(a, p))
        for p in positions
        for a in directions
      )
    
    numbers = list()
    symbols = set()
    gears = set()
    nl = 0
    for line in sys.stdin:
      number = []
      positions = set()
      nc = 0
      for c in line.strip():
        if c in "0123456789":
          number.append(c)
          positions.add((nl, nc))
        else:
          if c != ".":
            print(f"Symbol {c} at {nl}, {nc}")
            symbols.add((nl, nc))
          if c == "*":
            gears.add((nl, nc))
          if number:
            numbers.append((int("".join(number)), envelope(positions)))
            number = []
            positions = set()
        nc += 1
      if number:
        numbers.append((int("".join(number)), envelope(positions)))
        number = []
        positions = set()
      nl += 1

    numbers est la liste des nombres et de leur « enveloppe » c'est à dire des coordonnées de toutes les cases composant ce nombre et adjacentes à lui.
    C'est un set() de doublets.
    symbols contient la liste des coordonnées (doublets) de tous les symboles du plan.
    gears contient la liste des coordonnées des symboles * uniquement, et ne servira que pour la seconde partie.

    Je trouve le code un poil moche, et un meilleur travail sur les entrées, et les conditions boucles, pourrait alléger, mais il faut absolument ne pas se planter dans les cas limites : un nombre en fin de ligne, deux nombres séparés par un symbole (celui-là m'a mis dedans), etc.
    Être carré, clairs, précis, sinon c'est l'écran bleu… Euh, enfin, la mauvaise réponse quoi…

    La résolution des problèmes est assez simple après ça.
    Problème n°1 :

    result = list()
    nope = 0
    for number, pos in numbers:
      if symbols.intersection(pos):
        result.append(number)
      else:
        nope += number
    print(result)
    print(f"Sum of Numbers : {sum(result)} ({nope})")

    nope me sert à la validation, vu que j'ai la somme de tous ems chiffres qui vaut 598313, je dois avoir result + nope = 598313, et j'aurais pu aussi juste calculer nope et obtenir mon résultat comme ça, par soustraction.
    Ça pourra servir comme façon de faire plus tard, je me rappelle d'un exercice avec du sable qui s'écoule, qui peut se résoudre très facilement en regardant uniquement là où il ne s'écoule pas et en faisant une soustraction…

    Et le second challenge :

    gear_value = 0
    for gear in gears:
      parts = [
        number
        for number, pos in numbers
        if gear in pos
      ]
      if len(parts) == 2:
        gear_value += parts[0] * parts[1]
    
    print(f"Sum of NumbersGear Value : {gear_value})")

    Rien à recalculer, on va analyser dans l'autre sens : pour chaque « gear » on regarde dans combien de « number » il se trouve, si c'est 2, bingo, on l'ajoute.
    C'est tout.

    • Yth.
  • # Commencer à représenter le problème pas trop bêtement.

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

    Ce coup-ci, avec une analyse des données, placées dans les bonnes structures, la seconde partie de l'exercice va se faire immédiatement après la première.

    Ici on a encore un truc assez simple, et on va faire un dictionnaire avec en clé le numéro de la partie et en valeur une liste de triplets (rouge, vert, bleu), avec des zéros là où on n'a pas d'infos en entrée.
    -> Normaliser les entrées, avoir une structure assez facile à analyser ensuite.

    import sys
    from functools import reduce
    colors = {"red": 0, "green": 1, "blue": 2}
    
    def read_cubes(cubeset):
        r = [0, 0, 0]
        for cubes in cubeset.strip().split(","):
            nb, color = cubes.strip().split(" ")
            r[colors[color]] = int(nb)
        return r
    
    
    def input():
        for line in sys.stdin:
            game, cubes = line.strip().split(':')
            game = int(game.split(" ")[1])
            cubes = [read_cubes(cubeset) for cubeset in cubes.split(";")]
            yield game, cubes
    
    datas = [x for x in input()]

    Déjà, j'abuse des yields et des générateurs, c'est pas encore utile, mais ça va venir.

    Pour la partie 1 on va avoir une fonction de validation, et après on fait une somme :

    def test_elements(elements, constraint):
        for el in elements:
            for i in range(3):
                if el[i] > constraint[i]:
                    return False
        return True
    
    constraint = [12, 13, 14]
    r = sum(
        game
        for game, elements in datas
        if test_elements(elements, constraint)
    )
    print(f"Possible games : {r}")

    Et là, pour la seconde partie on n'a même plus besoin de fonction de validation, le calcul est immédiat, même si le code est moche. Y'aurait moyen avec des structures de données de numpy de se passer de certaines méthodes peu explicites avec directement des comparaisons de vecteurs, ou un produit vectoriel. Bah, pas encore, on reste en python chocolat, poire… Vanille !

    power = sum(
        reduce(lambda x, y: x * y, [max(i) for i in zip(*elements)])
        for game, elements in datas
    )
    print(f"Sum of Power of games : {power}")

    Le reduce sert à multiplier entre eux tous les éléments de la liste, et le zip va transformer une liste de triplets (r, v, b) en triplet de listes ([r, r, r…], [v, v, v…], [b, b, b…]).

    Jusqu'ici, ya pas grand chose à déclarer, on manipule des données, on n'a même pas vraiment besoin de trop se compliquer à trouver les bonnes structures de données.

    • Yth.
  • # Premier jour : on n'optimise pas, on fonce dans l'tas !

    Posté par  (Mastodon) . En réponse au message Advent of Code 2023 : Day 1. Évalué à 2.

    Le premier exercice est trivial :

    import sys
    print(sum(
        int(f"{digit[0]}{digit[-1]}")
        for line in sys.stdin
        for digit in [[x for x in line if x in "0123456789"]]
        if digit
    )

    On fait python3 01.py < 01.input ou 01.demo pour les données de test, et hop, résultat en une commande.

    Le second exercice sans optimisation consiste à rechercher les chiffres et les chiffres écrits, ajouter ça dans une liste, et faire pareil :

    import sys
    text = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
    def analyze(line):
        def _iter():
            for n in range(len(line)):
                if line[n] in "0123456789":
                    yield line[n]
                    continue
                for t in text:
                    if line[n:].startswith(t):
                        yield str(text.index(t))
                        continue
        return _iter()
    
    print(sum(
        int(f"{digit[0]}{digit[-1]}")
        for line in sys.stdin
        for digit in [list(analyze(line))]
        if digit
    )

    La fonction analyze prend une ligne, la parcours charactère par charactère, et regarde si c'est un chiffre, ou l'écriture d'un chiffre, et envoie dans l'itérateur.
    Zéro intelligence, zéro optimisation, zéro plantage.
    Bientôt on pourra plus faire comme ça !
    Mais bon, jour 1, on se dérouille les neurones, on reprend le pli des traitements de flux, on essaie déjà de mettre des itérateurs plutôt que de tout traiter en mémoire, juste pour reprendre les futurs bonnes habitudes…

    • Yth.
  • [^] # Re: y'a un forum dédié

    Posté par  (Mastodon) . En réponse au journal Advent of code 2023. Évalué à 2.

    Poste ton code ?

    • Y.
  • [^] # Re: J'y retourne !

    Posté par  (Mastodon) . En réponse au journal Advent of code 2023. Évalué à 3.

    C'était dans Programmation.Autre, j'ai pas trop le temps ce week-end, mais si quelqu'un veut démarrer.

    • Yth.
  • [^] # Re: Plusieurs leaderboards privés ?

    Posté par  (Mastodon) . En réponse au journal Advent of code 2023. Évalué à 3.

    A priori oui, on peut.

    • Yth.
  • [^] # Re: Je veux pas être méchant mais...

    Posté par  (Mastodon) . En réponse au lien COP28 à Dubaï : des scientifiques organisent leur propre sommet en protestation . Évalué à 10.

    Je m'inscris complètement en faux par rapport à l'infox comme quoi Macron serait centriste.

    • Yth.
  • [^] # Re: Normalisation

    Posté par  (Mastodon) . En réponse au lien Avec la langue — Sur l'échelle du purisme, vous vous situez où? (Une tentation très française). Évalué à 4.

    L'histoire, la géographie, les maths, la physique, la chimie, la biologie, etc.
    Tout ça non plus n'est pas « normé ».

    Par contre, comme pour la langue française, il y a un programme, assez précis, des manuels qui explicitent ce programme et sont validés par le ministère, et les élèves sont évalués sur ce programme.

    Il n'est nulle part question de droit, et aucune loi n'impose que la note d'une dictée soit faite sur une base équitable, normée ou quoi que ce soit.

    • Yth.
  • [^] # Re: wesh

    Posté par  (Mastodon) . En réponse au lien Avec la langue — Sur l'échelle du purisme, vous vous situez où? (Une tentation très française). Évalué à 7.

    Un parent ?

    • Yth :)
  • [^] # Re: Rimes de mauvaise foi

    Posté par  (Mastodon) . En réponse au lien Avec la langue — Sur l'échelle du purisme, vous vous situez où? (Une tentation très française). Évalué à 5.

    Je suis assez perturbé, toujours, par l'académie française.
    Défendre la langue oui, mais essayer de la normer, ou plutôt de normer ses évolutions, ben nettement moins.

    Défendre des constructions et des orthographes qui portent du sens et évitent des confusions, c'est important.
    Quand les gens confondent à l'écrit « serez » et « saurez », ça m'affole un poil.
    Mais lire « T ou 2m1? » me dérange beaucoup moins.

    Moi aussi j'essaie d'avoir une bonne orthographe et une bonne grammaire, de manière générale une bonne maîtrise de la langue, parce que je considère que connaître le cadre, forcément un peu rigide, est la première étape pour le transcender, et apporter du sens par les transgressions qu'on y apporte.
    Sous forme de néologismes, barbarismes, jeux de mots, fautes assumées, etc., si on connaît la « bonne façon » d'écrire, le fait d'écrire mal exprès fait aussi passer des messages.

    Et évidemment, ce que j'accepte chez moi, je l'accepte chez les autres.

    Alors oui, je tique quand je voit écrit ça, et je pourrais même faire la remarque dans certains cas. Mais les évolutions et les nouveautés, j'adore, c'est ce qu'il y a de plus excitant dans la langue, jouer avec, la faire vivre :)

    • Yth, j'ai un défaut contre les anglicismes aussi, je cherche toujours le bon mot français à la place, et je trouve qu'en général on y gagne.
  • [^] # Re: Je veux pas être méchant mais...

    Posté par  (Mastodon) . En réponse au lien COP28 à Dubaï : des scientifiques organisent leur propre sommet en protestation . Évalué à 8.

    Chais pas trop, en France, sur l'intégralité de la 5è république, on a eu 70% de présidences de droite (45 ans) et 30% de présidences de gauche (19 ans).

    Vers où cet échiquier politique penchait-il donc par le passé ?

    En tout cas, il y a eu des écologistes aux présidentielles depuis 1974, soit plus des trois quarts de l'existence de la Vè, mais l'échiquier n'a jamais penché de ce côté là.

    • Yth.
  • [^] # Re: J'y retourne !

    Posté par  (Mastodon) . En réponse au journal Advent of code 2023. Évalué à 10. Dernière modification le 01 décembre 2023 à 12:36.

    Aussi, nous sommes 4 à avoir eu 50 étoiles, et 4 de plus à en avoir eu plus de 40.
    Sachant que la fin est relativement fastidieuse, avec des algos qui peuvent prendre du temps, et donc un débuggage allongé d'autant, jusqu'à la veille de Noël où on peut avoir franchement autre chose à faire : ne pas chercher à finir est entièrement compréhensible.
    Quand ça cesse d'être fun, ça cesse d'être fun.

    Mais vivement dans quelques jours quand ça commencera à devenir fun, et qu'il faudra avoir de vraies idées d'algorithmie pour affronter les épreuves :)

    Bon courage à toutes celles et tout ceux qui vont participer !

    • Yth.
  • # J'y retourne !

    Posté par  (Mastodon) . En réponse au journal Advent of code 2023. Évalué à 10.

    Je remet ça cette année :)

    Premier conseil pour ceux qui s'y mettent : testez vos programme avec les données de démo avant d'envoyer un résultat.

    Les cas particuliers ne sont pas décrits dans les énoncés, mais sont souvent présents dans les données de test.

    Par exemple, premier jour de 2023, second challenge ligne 2 : "eightwothree".
    L'approche « facile » consiste à faire des remplacement de chaînes : "one" par "1", "two" par "2", etc.
    Si on fait ça sans réfléchir et dans l'ordre, notre chaîne devient alors : "eigh23", et on a comme valeur 23.
    Mais on devrait remplacer le début de la chaîne, "eight" par 8, et obtenir 83 comme résultat.

    Une simple validation avec les données de test montre qu'on s'est planté…
    Si ça sent le vécu, c'est parce que ça sent le vécu.

    Second conseil, en Python, mais surtout utile à partir de la moitié en général : utilisez PyPy quand les algos commencent à boucler à mort et traiter des millions de cas, ça peut aller de 20 à 50 fois plus vite, sans rien changer au code.
    Et abusez des générateurs, pour éviter les explosions de mémoire.

    • Yth.