Forum Programmation.autre Avent du Code, jour 20

Posté par  (Mastodon) . Licence CC By‑SA.
Étiquettes :
2
20
déc.
2022

On est sorti de notre Volcan, et maintenant on cherche les Elfes.
Pour ça on va hacker les données chiffrées dans le transmetteur pour trouver le fameux verger aux fruits étoilés.

Au menu : réordonner une liste cyclique en bougeant les éléments d'une certaine distance, plein de fois, avec des valeurs très grande.
Cyclique = modulo, ici il n'y a pas besoin de faire chauffer le CPU !

Cela dit, vu depuis combien de temps nos lutinelfes de Noël sont restés sans nous pour résoudre leurs problèmes, ça sent le carnage dès qu'on les auras retrouvés…

  • Yth.
  • # Un bug que j'ai résolu sans jamais le trouver.

    Posté par  (Mastodon) . Évalué à 3. Dernière modification le 20 décembre 2022 à 11:43.

    J'ai codé ma propre liste chaînée, j'ai été intelligent avec mes modulos, j'ai une réponse sans ultra-optimiser en 4 secondes pour les données de test et les données réelles.

    Et j'ai un bug.
    Tout fonctionne au poil sur les données de test, les deux exercices, et les données réelles, mais pas le second sur données réelles !
    Mauvais résultat.
    Et j'ai beau triturer, je tombe toujours sur le mauvais résultat.

    Alors j'ai recodé un peu différemment mes quelques lignes qui servent à déplacer un nombre dans la liste, je suis persuadé que fonctionnellement c'est identique, et ça fonctionne au super poil avec les données de test.
    Mais je suis malade, ça explique peut-être.
    En tout cas : résultat différent et correct cette fois-ci, avec un code un zest plus propre, mais à peine.

    Sauf que dans le cas où le mouvement est de zéro, modulo, ben j'avais un effet de bord débile, je cassais ma liste…

    Bref, ça aurait dû aller vite, mais pas.

    • Yth.
    • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

      Posté par  (Mastodon) . Évalué à 4.

      @dataclass(frozen=False)
      class Number:
          v: int
          p: int
          n: int
      
          def __call__(self):
              return self.v, self.p, self.n
      
          @property
          def next(self):
              return self.problem[self.n]
      
          @property
          def previous(self):
              return self.problem[self.p]
      
          @cached_property
          def moves(self):
              return self.value % (len(self.problem) - 1)
      
          @cached_property
          def value(self):
              return self.v * self.decryptionkey
      
      # Input handling
      def numbers(data):
          lastid = len(data) - 1
          yield Number(data[0], lastid, 1)
          for i in range(1, lastid):
              yield Number(data[i], i - 1, i + 1)
          yield Number(data[-1], lastid - 1, 0)
      
      def result(problem):
          number = [x for x in problem if x.v == 0][0]
          for _ in range(1000):
              number = number.next
          n1000 = number.value
          for _ in range(1000):
              number = number.next
          n2000 = number.value
          for _ in range(1000):
              number = number.next
          n3000 = number.value
          r = n1000 + n2000 + n3000
          return r
      
      def decrypt(problem):
          for i, number in enumerate(problem):
              if number.v == 0:
                  continue
              # removes number from list
              number.previous.n = number.n
              number.next.p = number.p
              search = number
              for _ in range(number.moves):
                  search = search.next
              # search is now the new previous
              number.n = search.n
              number.p = search.next.p
              # Inserting number
              number.next.p = i
              number.previous.n = i
      
      data = [int(x) for x in sys.stdin.read().strip().splitlines()]
      # Problème n°1
      problem = list(numbers(data))
      Number.problem = problem
      Number.decryptionkey = 1
      decrypt(problem)
      print(f"First answer = {result(problem)}")
      
      # Problème n°2
      problem = list(numbers(data))
      Number.problem = problem
      Number.decryptionkey = 811589153
      for _ in range(10):
          decrypt(problem)
      print(f"Final answer = {result(problem)}")
      • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

        Posté par  . Évalué à 3.

        Pour moi, pas de modelisation en classes aujourd'hui.
        Le truc qui m'a pris du temps c'est de realiser qu'il y avait plusieurs fois les memes valeurs dans les donnees reelles. J'ai resolu ca au plus vite en stockant une liste de tuples (valeur, rang d'apparition de cette valeur).

        is_part_2 = True
        original_sequence = []
        with open("2022/input_files/day20") as f:
            for data in f:
                n = int(data.rstrip()) * (811589153 if is_part_2 else 1)
                c = [x[0] for x in original_sequence].count(n)
                original_sequence.append((n, c))
        
        sequence = original_sequence.copy()
        for _ in range(10 if is_part_2 else 1):
            for n, occurence in original_sequence:
                old_index = sequence.index((n, occurence))
                new_index = (old_index + n) % (len(sequence) - 1)
                if new_index == 0:
                    new_index = len(sequence) - 1
                sequence.insert(new_index, sequence.pop(old_index))
        
        i0 = sequence.index((0, 0))
        a = (i0 + 1000) % len(sequence)
        b = (i0 + 2000) % len(sequence)
        c = (i0 + 3000) % len(sequence)
        print(f"{sequence[a][0]+sequence[b][0]+sequence[c][0]=}")

        Excusez l'absence d'accents dans mes commentaires, j'habite en Australie et n'ai pas de clavier francais sous la main.

        • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

          Posté par  . Évalué à 4.

          J'oubliais de dire, sans liste chainee, avec une liste python toute bete, ca prend une demi-seconde avec les donnees reelles.

          Excusez l'absence d'accents dans mes commentaires, j'habite en Australie et n'ai pas de clavier francais sous la main.

          • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

            Posté par  (Mastodon) . Évalué à 4. Dernière modification le 20 décembre 2022 à 15:02.

            C'est tellement plus intelligent que ce que j'ai fait, je suis jaloux !
            Je ne pensais pas que les listes seraient assez performantes, j'aurais dû essayer.

            Bravo !

            Tu peux pas faire ça plutôt ?

                for id, data in enumerate(f):
                    n = int(data.rstrip()) * (811589153 if is_part_2 else 1)
                    original_sequence.append((id, c))

            Tu t'en fous du nombre d'occurrence, ce que tu veux c'est que chaque élément de la liste soit unique.

            • Yth.
            • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

              Posté par  . Évalué à 2.

              Tout-a-fait! C'est plus efficace comme tu le proposes et ca marche pareil.

              Excusez l'absence d'accents dans mes commentaires, j'habite en Australie et n'ai pas de clavier francais sous la main.

              • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

                Posté par  (Mastodon) . Évalué à 2. Dernière modification le 20 décembre 2022 à 15:18.

                Faut aussi que 0 reste (0, 0), pour le retrouver à la fin.
                Mais ça gagne 35% de temps :)

                Bravo, j'admire la simplicité !

                • Yth, je note list.index() c'est pas pourri en vrai, peut-être si on a des listes de millions d'éléments, mais là c'est 5000, c'est petit.
        • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

          Posté par  (site web personnel) . Évalué à 4.

          Joli, du coup j'ai honte de montrer ma solution avec liste chaînée maison. Mais je la montre quand même, allez :

          from __future__ import annotations
          
          import io
          
          from typing import Iterable, Iterator, Optional
          
          
          class Chain:
              def __init__(self, *elts: Element) -> None:
                  self._len = len(elts)
                  self.first = elts[0]
                  last = elts[-1]
                  prev: Optional[Element] = None
                  for elt in elts:
                      if prev is not None:
                          prev.attach(elt)
                      prev = elt
                  last.attach(self.first)
          
              def __len__(self):
                  return self._len
          
              def __getitem__(self, index: int) -> Element:
                  index = index % len(self)
                  current = self.first
                  for _ in range(index):
                      current = current.next
                  return current
          
              def get_elt(self, base_elt: Element, index: int) -> Element:
                  index = index % len(self)
                  current = base_elt
                  for _ in range(index):
                      current = current.next
                  return current
          
              def __iter__(self) -> Iterator[Element]:
                  current = self.first
                  for _ in range(self._len):
                      yield current
                      current = current.next
          
              def extract(self, index: int) -> Element:
                  elt = self[index]
                  elt.detach()
                  self._len -= 1
                  return elt
          
              def extract_elt(self, elt: Element) -> None:
                  # WARNING: not checking that elt is part of self
                  elt.detach()
                  self._len -= 1
          
              def insert(self, index: int, elt: Element) -> None:
                  prev = self[index]
                  next_ = prev.next
                  # Attach (prev and elt) and (elt and next_)
                  prev.attach(elt)
                  elt.attach(next_)
                  self._len += 1
          
              def insert_elt(self, base: Element,
                             shift: int, elt: Element) -> None:
                  # WARNING: not checking that elt is part of self
                  shift %= self._len
                  prev = base
                  for _ in range(shift):
                      prev = prev.next
                  next_ = prev.next
                  # Attach (prev and elt) and (elt and next_)
                  prev.attach(elt)
                  elt.attach(next_)
                  self._len += 1
          
              def __str__(self):
                  result = io.StringIO()
                  result.write('(')
                  for elt in self:
                      result.write('{}, '.format(elt.value))
                  result.write(')')
                  return result.getvalue()
          
          
          class Element:
              def __init__(self, value: int) -> None:
                  self.value = value
                  self.prev = self
                  self.next = self
          
              def attach(self, other: Element) -> None:
                  self.next = other
                  other.prev = self
          
              def detach(self) -> None:
                  prev = self.prev
                  next_ = self.next
                  # Detach self
                  self.prev = self
                  self.next_ = self
                  # Attach prev and next_
                  prev.attach(next_)
          
          
          def solve1(lines: Iterable[str]) -> int:
              """Solve part 1 of today's puzzle"""
              elts = [Element(int(line)) for line in lines]
              seq = Chain(*elts)
              zero: Optional[elt] = None
              for elt in elts:
                  if zero is None and elt.value == 0:
                      zero = elt
                  prev = elt.prev
                  seq.extract_elt(elt)
                  seq.insert_elt(prev, elt.value, elt)
              if zero is None:
                  raise ValueError("invalid input without a zero value")
              return (seq.get_elt(zero, 1000).value
                      + seq.get_elt(zero, 2000).value
                      + seq.get_elt(zero, 3000).value)
          
          
          def solve2(lines: Iterable[str]) -> int:
              """Solve part 2 of today's puzzle"""
              key = 811589153
              elts = [Element(key * int(line)) for line in lines]
              seq = Chain(*elts)
              zero: Optional[elt] = None
              for _ in range(10):
                  for elt in elts:
                      if zero is None and elt.value == 0:
                          zero = elt
                      prev = elt.prev
                      seq.extract_elt(elt)
                      seq.insert_elt(prev, elt.value, elt)
              if zero is None:
                  raise ValueError("invalid input without a zero value")
              return (seq.get_elt(zero, 1000).value
                      + seq.get_elt(zero, 2000).value
                      + seq.get_elt(zero, 3000).value)

          À noter que c'est pas mal plus lent qu'avec des listes Python en fait… :-(

          • [^] # Re: Un bug que j'ai résolu sans jamais le trouver.

            Posté par  (Mastodon) . Évalué à 2.

            C'est clair, on fait de belles modélisations, alors que la solution d'Éric cartonne tout.

            Possible que la liste chaînée devienne plus performante si on a des millions d'éléments, mais à 5000, on se fait laminer.

            • Yth.
  • # il était vraiment null celui-ci

    Posté par  . Évalué à 2.

    J'ai pris le problème du mauvais sens, il m'a fallut plus de 3h pour comprendre qu'il ne fallait pas appliquer la transformation 3000 fois pour avoir la réponse.

    J'ai réecris 3 ou 4 fois mon code. J'ai commencé par un tableau avec des modulos au final, j'ai terminé par une liste doublement chainés comme quasiment tout le monde.

    J'ai remis une bonne 1h30 pour caler le modulo pour éviter d'appliquer les millions de déplacement.

    Bref, un très mauvaise journée. En plus, vous êtes plein à y être arrivé avant moins résultat.
    Je n'ai plus aucun espoir de rafler la seconde place.

    • [^] # Re: il était vraiment null celui-ci

      Posté par  . Évalué à 3.

      Il faut voir le verre a moitie plein, si je regarde le leaderboard plus personne ne peut te rattraper et ta place sur le podium est garantie!

      On est encore 8 actifs dans le leaderboard, donc en gros si j'ai bien compris les regles, on peut tu peux gagner au maximum 7 points par jour sur les autres, et il ne reste que 4 jours.

      Excusez l'absence d'accents dans mes commentaires, j'habite en Australie et n'ai pas de clavier francais sous la main.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.