🚲 Tanguy Ortolo a écrit 12252 commentaires

  • [^] # Re: python, en trichant

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 13. Évalué à 6.

    On peut tricher en moins craignos. Un indice :

    $ file 13.in
    13.in: JSON data
    
  • # En Pypthon

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 13. Évalué à 5.

    Pour la définition de aoc.group_lines(lines: str), cf. hier

    from __future__ import annotations
    
    import itertools
    
    from typing import Iterable, Iterator, List, Optional, Tuple, Union
    
    import aoc
    
    
    class Element:
        def __init__(self, value: Union[List[Element], int]):
            self.value = value
    
        def __lt__(self, other: Element) -> bool:
            if isinstance(self.value, int) and isinstance(other.value, int):
                return self.value < other.value
            if isinstance(self.value, list) and isinstance(other.value, list):
                for self_elt, other_elt in zip(self.value, other.value):
                    if self_elt < other_elt:
                        return True
                    if other_elt < self_elt:
                        return False
                # Ran out of elements on one of the lists
                return len(self.value) < len(other.value)
            if isinstance(self.value, int):
                return Element([self]) < other
            if isinstance(other.value, int):
                return self < Element([other])
            assert False  # should not happen: we covered all cases
    
        def __eq__(self, other: object) -> bool:
            if not isinstance(other, Element):
                return NotImplemented
            if isinstance(self.value, int) and isinstance(other.value, int):
                return self.value == other.value
            if isinstance(self.value, list) and isinstance(other.value, list):
                return (len(self.value) == len(other.value)
                        and all(self_elt == other_elt for self_elt, other_elt
                                in zip(self.value, other.value)))
            if isinstance(self.value, int):
                return Element([self]) == other
            if isinstance(other.value, int):
                return self == Element([other])
            assert False  # should not happen: we covered all cases
    
        def __str__(self) -> str:
            if isinstance(self.value, int):
                return str(self.value)
            elif isinstance(self.value, list):
                return '[{}]'.format(
                        ','.join(str(element) for element in self.value))
            assert False  # should not happen: we covered all cases
    
    
    def import_element(chars: Iterable[str]) -> Element:
        element: Optional[Element] = None
        lists: List[List[Element]] = []  # List[List[Element]]
        current_list: Optional[List[Element]] = None
        current_int: Optional[int] = None
        for char in chars:
            if char == '[':
                if current_int is not None:
                    raise ValueError("unexpected beginning of list")
                if current_list is not None:
                    lists.append(current_list)
                current_list = []
            elif char == ']':
                if current_list is None:
                    raise ValueError("unexpected end of list")
                # If we were parsing an int, add it before closing current list
                if current_int is not None:
                    current_list.append(Element(current_int))
                    current_int = None
                if lists:
                    # We are closing a sub-list
                    prev_list = lists.pop()
                    prev_list.append(Element(current_list))
                    current_list = prev_list
                else:
                    # We are closing the top-level list
                    element = Element(current_list)
                    current_list = None
            elif char.isdecimal():
                value = int(char)
                if current_int is None:
                    current_int = value
                else:
                    current_int = 10 * current_int + value
                    continue
            elif char == ',':
                if current_list is None:
                    raise ValueError("unexpected separator")
                if current_int is not None:
                    current_list.append(Element(current_int))
                    current_int = None
            elif char == '\n':
                pass
            else:
                raise ValueError("unexpected character")
        if element is None:
            raise ValueError("nothing to parse")
        return element
    
    
    def import_pairs(lines: Iterable[str]) -> Iterator[Tuple[Element, Element]]:
        for group in aoc.group_lines(lines):
            elements = [import_element(line) for line in group]
            if len(elements) != 2:
                raise ValueError("unexpected group length")
            yield (elements[0], elements[1])
    
    
    def import_elements(lines: Iterable[str]) -> Iterator[Element]:
        for line in lines:
            if line and line != '\n':
                yield import_element(line)
    
    
    def solve1(lines: Iterable[str]) -> int:
        """Solve part 1 of today's puzzle"""
        pairs = import_pairs(lines)
        return sum(i + 1 for i, (elt1, elt2) in enumerate(pairs) if elt1 < elt2)
    
    
    def solve2(lines: Iterable[str]) -> int:
        """Solve part 2 of today's puzzle"""
        elements: Iterable[Element] = import_elements(lines)
        div1 = import_element("[[2]]")
        div2 = import_element("[[6]]")
        elements = sorted(itertools.chain(elements, (div1, div2)))
        key = 1
        for i, element in enumerate(elements):
            if element == div1 or element == div2:
                key *= i + 1
        return key
  • # Étoile

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 12. Évalué à 4.

    Au fait, vu les visualisations, vous avez évidemment remarqué que loin d'être une gorge de rivière, nous avions plutôt affaire à l'île de Numenor une montagne en forme d'étoile, n'est-ce pas ?

  • # En Python, avec un parseur d'opération

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 11. Évalué à 4.

    from __future__ import annotations
    
    import itertools
    import math
    import re
    
    from typing import Callable, Iterable, Iterator, List, Tuple
    
    
    class Test:
        def __init__(self, div: int, rcpt1: int, rcpt2: int):
            self.div = div
            self.rcpt1 = rcpt1
            self.rcpt2 = rcpt2
    
        def __call__(self, n: int):
            if n % self.div == 0:
                return self.rcpt1
            else:
                return self.rcpt2
    
    
    class Monkey:
        def __init__(self, number: int, items: List[int], op: Callable[[int], int], test: Test) -> None:
            self.number = number
            self.items = items
            self.op = op
            self.test = test
            self.activity = 0
    
        _re_monkey = re.compile(r'^Monkey (\d+):?\n?$')
        _re_items = re.compile(r'^\s*Starting items: (.*)\n?$')
        _re_op = re.compile(r'^\s*Operation: (.*)\n?$')
        _re_test = re.compile(r'^\s*Test: divisible by (\d+)\n?$')
        _re_true = re.compile(r'^\s*If true: throw to monkey (\d+)\n?$')
        _re_false = re.compile(r'^\s*If false: throw to monkey (\d+)\n?$')
    
        @classmethod
        def import_lines(class_, lines: Iterable[str]) -> Monkey:
            lines = iter(lines)
            number = int(class_._re_monkey.match(next(lines)).group(1))
            items = [int(word) for word in class_._re_items.match(next(lines)).group(1).split(', ')]
            op = import_op(class_._re_op.match(next(lines)).group(1))
            div = int(class_._re_test.match(next(lines)).group(1))
            rcpt1 = int(class_._re_true.match(next(lines)).group(1))
            rcpt2 = int(class_._re_false.match(next(lines)).group(1))
            test = Test(div, rcpt1, rcpt2)
            return class_(number, items, op, test)
    
    
    def group_key() -> Callable[[str], int]:
        key = 0
        def aux(line: str):
            nonlocal key
            if line.rstrip() == '':
                key += 1
            return key
        return aux
    
    
    def group_lines(lines: Iterable[str]) -> Iterable[Iterable[str]]:
        for _, group in itertools.groupby(lines, group_key()):
            yield (line for line in group if line.rstrip() != "")
    
    
    _re_op_unary = re.compile("^new = old ([+*]) (\d+)$")
    _re_op_binary = re.compile("^new = old ([+*]) old$")
    def import_op(line: str) -> Callable[[int], int]:
        if (m := _re_op_unary.match(line)) is not None:
            arg = int(m.group(2))
            if m.group(1) == '+':
                return lambda old: old + arg
            if m.group(1) == '*':
                return lambda old: old * arg
            raise ValueError("unrecognized operator")
        if (m := _re_op_binary.match(line)) is not None:
            if m.group(1) == '+':
                return lambda old: 2 * old
            if m.group(1) == '*':
                return lambda old: old ** 2
            raise ValueError("unrecognized operator")
        raise ValueError("unrecognized operation arity")
    
    
    class Game:
        def __init__(self, monkeys: dict[int, Monkey]) -> None:
            self.monkeys = monkeys
    
        def round(self) -> None:
            for monkey in self.monkeys.values():
                for item in monkey.items:
                    item = monkey.op(item)
                    item //= 3
                    rcpt = monkey.test(item)
                    monkey.activity += 1
                    self.monkeys[rcpt].items.append(item)
                monkey.items = []
    
        @classmethod
        def import_lines(class_, lines: Iterable[str]) -> Game:
            monkeys = {}
            for group in group_lines(lines):
                monkey = Monkey.import_lines(group)
                monkeys[monkey.number] = monkey
            return class_(monkeys)
    
        def business(self) -> int:
            activity = sorted(monkey.activity for monkey in self.monkeys.values())
            return activity[-1] * activity[-2]
    
    
    class Game2(Game):
        def __init__(self, *args, **kwargs) -> None:
            super().__init__(*args, **kwargs)
            self.div = math.lcm(*(monkey.test.div for monkey in self.monkeys.values()))
    
        def round(self) -> None:
            for monkey in self.monkeys.values():
                for item in monkey.items:
                    item = monkey.op(item) % self.div
                    rcpt = monkey.test(item)
                    monkey.activity += 1
                    self.monkeys[rcpt].items.append(item)
                monkey.items = []
    
    
    def solve1(lines: Iterable[str]) -> int:
        """Solve part 1 of today's puzzle"""
        game = Game.import_lines(lines)
        for _ in range(20):
            game.round()
        return game.business()
    
    
    def solve2(lines: Iterable[str]) -> int:
        """Solve part 2 of today's puzzle"""
        game = Game2.import_lines(lines)
        for _ in range(10000):
            game.round()
        return game.business()
  • # En Python

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 12. Évalué à 4.

    Ma première idée fonctionnait sur l'exemple mais pas sur les données réelles : parcourir récursivement une matrice de proche en proche en évitant simplement les endroits où on est déjà passé, ça atteint vite la limites de longueur de la pile d'appels.

    Du coup, j'ai fait du Dijkstra. Enfin quelque chose inspiré de son algorithme en tout cas. Ça pourrait être fait de façon entièrement itérative, mais ça ne valait pas la peine, ça reste raisonnable en récursif.

    Une fois qu'on a ça, la deuxième partie du puzzle ne pose pas spécialement de problème. Il y a tout de même deux façons de l'implémenter, une bête et méchante (on prend la première solution et on l'applique plusieurs fois), et une un rien plus futée.

    from typing import Iterable, Iterator, List, Optional, Set, Tuple
    
    import numpy as np
    
    
    Coords = Tuple[int, int]
    
    
    class Map:
        def __init__(self, matrix: np.ndarray) -> None:
            self.matrix = matrix
            self.ly, self.lx = matrix.shape
    
        def neighs(self, y: int, x: int) -> Iterator[Coords]:
            """Yield neighbours that are reachable from the given coordinates,
            considering movement rules (no climbing)"""
            for (dx, dy) in ((-1, 0), (1, 0), (0, -1), (0, 1)):
                y_ = y + dy
                x_ = x + dx
                if y_ < 0 or y_ >= self.ly or x_ < 0 or x_ >= self.lx:
                    continue
                if self.matrix[y_, x_] - self.matrix[y, x] <= 1:
                    yield y_, x_
    
        def _distances(self, starts: Iterable[Coords], end: Coords,
                       distances: np.ndarray) -> None:
            """Update distances matrix by computing walking distance from possible
            starts"""
            if starts == []:
                # Nothing left to explore
                return
            nexts = []  # List[Coords]
            for start in starts:
                start_dist = distances[start]
                if start_dist < 0:
                    raise ValueError(
                            'cannot compute distances from uncharted point')
                for neigh in self.neighs(*start):
                    neigh_dist = distances[neigh]
                    if neigh_dist < 0 or start_dist + 1 < neigh_dist:
                        # This point has either never been checked before, or has
                        # been but we have a shorter path to it
                        distances[neigh] = start_dist + 1
                        nexts.append(neigh)
            # Update distances from the points we have just updated
            self._distances(nexts, end, distances)
    
        def min_dist(self, starts: List[Coords], end: Coords) -> int:
            distances = np.full_like(self.matrix, -1)
            """Return the minimal distance to reach end, starting from starts"""
            for start in starts:
                distances[start] = 0
            self._distances(starts, end, distances)
            return distances[end]
    
    
    def import_lines(lines: Iterable[str]) -> Tuple[Map, Coords, Coords]:
        matrix = []  # type: List[List[int]]
        start = None
        end = None
        for y, line in enumerate(lines):
            matrix.append([])
            for x, char in enumerate(line.rstrip()):
                if char == 'S':
                    start = (y, x)
                    height = 0
                elif char == 'E':
                    end = (y, x)
                    height = 25
                else:
                    height = ord(char) - ord('a')
                matrix[-1].append(height)
        if start is None or end is None:
            raise ValueError("no start or end position found")
        return Map(np.array(matrix)), start, end
    
    
    def solve_both(lines: Iterable[str]) -> Tuple[int, int]:
        """Solve part 1 of today's puzzle"""
        map_, start, end = import_lines(lines)
        min1 = map_.min_dist([start], end)
        min2 = map_.min_dist([coords for coords, height  # type: ignore
                              in np.ndenumerate(map_.matrix)
                              if height == 0], end)
        return min1, min2
  • [^] # Re: À la chasse aux singes, j'envoie le Python !

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 11. Évalué à 3.

    Euh, modulo leur PPCM évidemment !

  • [^] # Re: À la chasse aux singes, j'envoie le Python !

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 11. Évalué à 4.

    Je n'ai pas encore résolu le problème de ce jour, dimanche c'est famille, mais ça va venir. 🙂

    En lisant l'énoncé, tout de même, je m'étais dit que ça devait faire des nombres qui monteraient bien vite. En y réfléchissant, je pense que j'aurais tout seul pensé à travailler modulo leur PGCD.

    Pour le code d'opération, l'eval vient tout de suite en tête bien sûr, mais ça me donne quand même quelques boutons, d'écrire du code qui a l'air d'un trou de sécurité. Réflexe professionnel je pense. À suivre, je dois toujours coder ça de toute façon.

  • [^] # Re: En Python, modélisé

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 9. Évalué à 4.

    Eh oh, je sais que j'écris du code-fleuve, mais pas la peine de se moquer non plus !

  • [^] # Re: Plus simple

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

    J'ai pas modélisé un CPU complet comme Tanguy, c'est un peu l'enclume pour écraser la mouche.

    Ça reste à voir, ça. Il y a eu un AoC où on n'arrêtait pas de ressortir un processeur bizarroïde pour l'enrichir de nouvelles instructions et variantes d'instructions existantes.

    Il y a un risque pour que ce ne soit pas la dernière fois que nous aurons à bidouiller avec du code machine de communicateur elfique…

  • [^] # Re: Où est la partie 2 ?

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 10. Évalué à 4.

    Oui, la seconde partie apparaît alors avoir rentré la bonne réponse à la première partie. Et les données d'entrée et donc les bonnes réponses sont associées à une identité en effet.

  • # En Python, modélisé

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 10. Évalué à 5. Dernière modification le 10 décembre 2022 à 13:03.

    Sans surprise, j'ai implémenté un CPU selon les spécifications fournies. Mais pour ce qui est de faire quelque chose à partir des états qu'il atteint à des cycles donnés, je me suis amusé à y introduire ce que j'ai appelé un débogueur, je vous laisse découvrir ça.

    import io
    
    from enum import Enum
    from typing import Callable, Iterable, Iterator, List, Optional, Tuple
    
    
    class Operation(Enum):
        addx = ('addx', 1, 2)
        noop = ('noop', 0, 1)
    
        def __init__(self, word: str, nargs: int, cycles: int) -> None:
            self.word = word
            self.nargs = nargs
            self.cycles = cycles
    
    
    class Instruction:
        def __init__(self, op: Operation, *args: int) -> None:
            self.op = op
            if len(args) != op.nargs:
                raise ValueError(
                        "operator {} expect {} arguments".format(op, op.nargs))
            self.args = args
    
    
    class CPU:
        def __init__(self, program: Iterable[Instruction],
                     debug: Optional[Callable[[int, int], None]] = None) -> None:
            self.X = 1
            self.cycle = 1
            self.program = program
            self.debug = debug
    
        def _apply(self, instruction: Instruction) -> None:
            if instruction.op is Operation.addx:
                self.X += instruction.args[0]
            elif instruction.op is Operation.noop:
                pass
    
        def _cycle(self) -> None:
            if self.debug is not None:
                self.debug(self.cycle, self.X)
            self.cycle += 1
    
        def run(self) -> None:
            last_instruction = None
            for instruction in self.program:
                for _ in range(instruction.op.cycles):
                    self._cycle()
                self._apply(instruction)
    
    
    def import_program(lines: Iterable[str]) -> Iterator[Instruction]:
        for line in lines:
            words = line.split()
            op = Operation[words[0]]
            args = [int(word) for word in words[1:]]
            yield Instruction(op, *args)
    
    
    class StrengthSum:
        def __init__(self) -> None:
            self.strength = 0
    
        def __call__(self, cycle: int, value: int) -> None:
            if cycle in (20, 60, 100, 140, 180, 220):
                self.strength += cycle * value
    
    
    class CRTDrawer:
        def __init__(self) -> None:
            self.crt = io.StringIO()
    
        @staticmethod
        def position(cycle: int) -> int:
            return (cycle - 1) % 40
    
        def __call__(self, cycle: int, value: int) -> None:
            position = self.position(cycle)
            if position == 0:
                self.crt.write('\n')
            if value - 1 <= position <= value + 1:
                self.crt.write('█')
            else:
                self.crt.write(' ')
    
    
    class MultiDebug:
        def __init__(self, *debugs: Callable[[int, int], None]) -> None:
            self.debugs = debugs
    
        def __call__(self, cycle: int, value: int) -> None:
            for debug in self.debugs:
                debug(cycle, value)
    
    
    def solve_both(lines: Iterable[str]) -> Tuple[int, str]:
        """Solve part 1 of today's puzzle"""
        program = import_program(lines)
        strength_report = StrengthSum()
        crt_drawer = CRTDrawer()
        cpu = CPU(program, debug=MultiDebug(strength_report, crt_drawer))
        cpu.run()
        return strength_report.strength, crt_drawer.crt.getvalue()
  • [^] # Re: En Python, modélisé

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 9. Évalué à 4. Dernière modification le 09 décembre 2022 à 18:05.

    Tu peux te passer de la position initiale dans l'ensemble des positions visitées : après le premier mouvement, la queue de la corde y sera toujours.

  • # Coin coin

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 9. Évalué à 4.

    Le problème parle de corde à nœuds, mais m'évoquerait plutôt une canne et ses canetons, pas vous ?

  • [^] # Re: En Python, modélisé

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 9. Évalué à 3. Dernière modification le 09 décembre 2022 à 16:58.

    Bon, en fait, pas besoin d'énumérer de façon aussi laborieuse bien sûr :

    from __future__ import annotations
    
    from enum import Enum
    from typing import Iterable, Iterator, Set, Tuple
    
    
    Coords = Tuple[int, int]
    
    
    class Direction(Enum):
        O  = ( 0,  0)
        U  = ( 0,  1)
        D  = ( 0, -1)
        L  = (-1,  0)
        R  = ( 1,  0)
    
        def __init__(self, dx, dy):
            self.dx = dx
            self.dy = dy
    
    
    class Knot:
        def __init__(self, x: int, y: int):
            self.x = x
            self.y = y
    
        @property
        def coords(self) -> Coords:
            return (self.x, self.y)
    
        def move(self, direction: Direction) -> None:
            self.x += direction.dx
            self.y += direction.dy
    
        def dist(self, other: Knot) -> int:
            return max(abs(self.x - other.x), abs(self.y - other.y))
    
        def follow(self, other: Knot) -> None:
            if self.dist(other) <= 1:
                return
            if self.x < other.x:
                self.x += 1
            if self.x > other.x:
                self.x -= 1
            if self.y < other.y:
                self.y += 1
            if self.y > other.y:
                self.y -= 1
    
    
    class Rope:
        def __init__(self, n_knots: int) -> None:
            self.knots = [Knot(0, 0) for _ in range(n_knots)]
    
        @property
        def head(self) -> Knot:
            return self.knots[0]
    
        @property
        def tail(self) -> Knot:
            return self.knots[-1]
    
        def move(self, direction: Direction) -> None:
            self.head.move(direction)
            for i in range(1, len(self.knots)):
                leader = self.knots[i - 1]
                follower = self.knots[i]
                follower.follow(leader)
    
    
    def import_lines(lines: Iterable[str]) -> Iterator[Direction]:
        for line in lines:
            word1, word2 = line.split()
            direction = Direction[word1]
            repeat = int(word2)
            for _ in range(repeat):
                yield direction
    
    
    def solve_both(lines: Iterable[str]) -> Tuple[int, int]:
        """Solve both parts of today's puzzle"""
        rope = Rope(10)
        visited1 = set()  # type: Set[Coords]
        visited2 = set()  # type: Set[Coords]
        for direction in import_lines(lines):
            rope.move(direction)
            visited1.add(rope.knots[1].coords)
            visited2.add(rope.tail.coords)
    
        return len(visited1), len(visited2)
  • [^] # Re: En Python, modélisé

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 9. Évalué à 3.

    Ce serait sans doute plus joli avec des coordonnées indexées (une liste de coordonnées en somme) plutôt que nommées.

    Mais ce n'est de toute façon pas très compréhensible, de cette façon-là.

  • # En Python, modélisé

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 9. Évalué à 4.

    #! /usr/bin/python3
    
    # Advent of Code 2022, day 9
    
    from __future__ import annotations
    
    from enum import Enum
    from typing import Iterable, Iterator, Set, Tuple
    
    
    Coords = Tuple[int, int]
    
    
    class Direction(Enum):
        O  = ( 0,  0)
        U  = ( 0,  1)
        D  = ( 0, -1)
        L  = (-1,  0)
        R  = ( 1,  0)
        UL = (-1,  1)
        UR = ( 1,  1)
        DL = (-1, -1)
        DR = ( 1, -1)
    
        def __init__(self, dx, dy):
            self.dx = dx
            self.dy = dy
    
    
    class Knot:
        def __init__(self, x: int, y: int):
            self.x = x
            self.y = y
    
        @property
        def coords(self) -> Coords:
            return (self.x, self.y)
    
        def move(self, direction: Direction) -> None:
            self.x += direction.dx
            self.y += direction.dy
    
        def dist(self, other: Knot) -> int:
            """"Chebyshev distance! (Not really used in my code, actually)"""
            return max(abs(self.x - other.x), abs(self.y - other.y))
    
        def direction_to(self, other: Knot) -> Direction:
            dx = other.x - self.x
            dy = other.y - self.y
            if dx < -1:
                if dy < 0:
                    return Direction.DL
                if dy == 0:
                    return Direction.L
                if dy > 0:
                    return Direction.UL
            if dx == -1:
                if dy < -1:
                    return Direction.DL
                if -1 <= dy <= 1:
                    return Direction.O
                if dy > 1:
                    return Direction.UL
            if dx == 0:
                if dy < -1:
                    return Direction.D
                if -1 <= dy <= 1:
                    return Direction.O
                if dy > 1:
                    return Direction.U
            if dx == 1:
                if dy < -1:
                    return Direction.DR
                if -1 <= dy <= 1:
                    return Direction.O
                if dy > 1:
                    return Direction.UR
            if dx > 1:
                if dy < 0:
                    return Direction.DR
                if dy == 0:
                    return Direction.R
                if dy > 0:
                    return Direction.UR
            assert False  # cannot happen, all cases were covered
    
        def follow(self, other: Knot) -> None:
            self.move(self.direction_to(other))
    
    
    class Rope:
        def __init__(self, n_knots: int) -> None:
            self.knots = [Knot(0, 0) for _ in range(n_knots)]
    
        @property
        def head(self) -> Knot:
            return self.knots[0]
    
        @property
        def tail(self) -> Knot:
            return self.knots[-1]
    
        def move(self, direction: Direction) -> None:
            self.head.move(direction)
            for i in range(1, len(self.knots)):
                leader = self.knots[i - 1]
                follower = self.knots[i]
                follower.follow(leader)
    
    
    def import_lines(lines: Iterable[str]) -> Iterator[Direction]:
        for line in lines:
            word1, word2 = line.split()
            direction = Direction[word1]
            repeat = int(word2)
            for _ in range(repeat):
                yield direction
    
    
    def solve_both(lines: Iterable[str]) -> Tuple[int, int]:
        """Solve both parts of today's puzzle"""
        rope = Rope(10)
        visited1 = set()  # type: Set[Coords]
        visited2 = set()  # type: Set[Coords]
        for direction in import_lines(lines):
            rope.move(direction)
            visited1.add(rope.knots[1].coords)
            visited2.add(rope.tail.coords)
    
        return len(visited1), len(visited2)

    J'aurais bien aimé éviter d'énumérer tous les cas de mouvement de suivi, mais je n'ai rien trouvé d'astucieux pour éviter cela.

    J'espère que dans vos solutions perso, vous aurez pensé à utiliser, en arrivant à la deuxième partie, à utiliser une seule corde pour simuler les deux…

  • [^] # Re: python procédural, moche mais efficace

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 8. Évalué à 4.

    Une petite optimisation pour la première partie : si on arrive à une hauteur de 9, pas la peine de regarder plus loin, aucun arbre ne sera plus haut que ça.

    Mais bon, la seconde partie est bien plus coûteuse en temps de toute façon.

  • # Procrastination

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 8. Évalué à 3.

    Alors qu'on a enfin quitté le camp pour aller chercher des caramboles, je ne peux m'empêcher que ça procrastine encore :

    – Voilà, le verger c'est par là…
    – Oh, regardez, la forêt que nous avons planté il y a quelques années, on pourrait y construire une cabane !
    – Oui, super, ça nous changera les idées. Lutin drone-opérateur, tu nous cartographie ça ?
    – Patron, venez nous donner un coup de main pour calculer le meilleur emplacement pour notre cabane au lieu d'essayer de régler votre communicateur. On vous l'a déjà dit, il est défectueux, et vous aurez tout le temps pour le réparer plus tard. On a plus important à faire là !
    – Et les fruits pour les rennes alors ?
    – Les quoi ? Ah, ça… On n'y est pas encore, soyez patient. Vous êtes pressé, vous avez un rendez-vous qui approche ou quoi ?

  • # Python avec Numpy

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 8. Évalué à 4.

    Bon, j'ai sorti Numpy du coup. C'est modélisé, et assez long en fait.

    # Advent of Code 2022, day 8
    
    from __future__ import annotations
    
    from math import prod
    from typing import Iterable, Iterator, Set, Tuple, Type
    
    import numpy as np
    
    
    Coords = Tuple[int, int]
    
    
    class Grid:
        def __init__(self, matrix: np.ndarray) -> None:
            self.matrix = matrix
            self.ly = matrix.shape[0]
            self.lx = matrix.shape[1]
    
        def __scans(self) -> Iterator[Iterator[Iterator[Coords]]]:
            yield (((y, x) for x in range(self.lx)) for y in range(self.ly))
            yield (((y, x) for x in range(self.lx - 1, -1, -1))
                   for y in range(self.ly))
            yield (((y, x) for y in range(self.ly)) for x in range(self.lx))
            yield (((y, x) for y in range(self.ly - 1, -1, -1))
                   for x in range(self.lx))
    
        def visible_from_outside(self) -> Set[Coords]:
            visible_trees = set()  # type: Set[Coords]
            for scan in self.__scans():
                for line in scan:
                    max_height = -1
                    for coords in line:
                        cur_height = self.matrix[coords]
                        if cur_height > max_height:
                            visible_trees.add(coords)
                            max_height = cur_height
            return visible_trees
    
        def __viewing_distance(self, orig: Coords, line: Iterator[Coords]) -> int:
            height = self.matrix[orig]
            d = 0
            for coords in line:
                d += 1
                if self.matrix[coords] >= height:
                    return d
            return d
    
        def __lines_of_sight(self, coords: Coords):
            y, x = coords
            yield ((y, x_) for x_ in range(x + 1, self.lx))
            yield ((y, x_) for x_ in range(x - 1, -1, -1))
            yield ((y_, x) for y_ in range(y + 1, self.ly))
            yield ((y_, x) for y_ in range(y - 1, -1, -1))
    
        def scenic_score(self, coords: Coords):
            return prod(self.__viewing_distance(coords, line)
                        for line in self.__lines_of_sight(coords))
    
        @classmethod
        def import_lines(class_: Type[Grid], lines: Iterable[str]) -> Grid:
            matrix = np.genfromtxt(lines, delimiter=1, autostrip=True, dtype=int)
            return class_(matrix)
    
    
    def solve_both(lines: Iterable[str]) -> Tuple[int, int]:
        """Solve both parts of today's puzzle"""
        # Import
        grid = Grid.import_lines(lines)
        # Part 1
        visible = len(grid.visible_from_outside())
        # Part 2
        max_score = max(grid.scenic_score(coords) for coords
                        in np.ndindex(grid.matrix.shape))
        return visible, max_score
  • [^] # Re: HS

    Posté par  (site web personnel) . En réponse au journal Mutuelle et mot de passe. Évalué à 3.

    En fait, en matière d'assurance, et d'assurance de quoi que ce soit en fait, je pense qu'il serait possible d'autoriser la modulation de cotisation en fonction de n'importe quoi qui relève du choix de l'assuré, à condition que le traitement des informations correspondantes soit légal évidemment. L'âge et l'état de santé par exemple, ne relèvent pas du tout du choix.

    Mais le tabagisme, le nombre d'infractions routières ou encore le nombre de soirées passées au cinéma chaque mois, relèvent du choix. Le dernier exemple n'a rien de pertinent, mais inutile d'interdire la modulation d'un tarif en fonction de cela : lorsque ce n'est pas pertinent, c'est inintéressant à appliquer, parce que cela ne correspond pas au risque assuré, que ça attirera juste une clientèle spécifique et que la remise correspondante s'avérera coûteuse pour l'assureur.

  • [^] # Re: HS

    Posté par  (site web personnel) . En réponse au journal Mutuelle et mot de passe. Évalué à 4.

    Du moment où ils ont la permission de ne moins payer la cotisation retraite (espérance de vie moindre) et de ne pas payer les taxes déjà actuelles sur les cigarettes (pas payer 2x la même chose), ok, ils vont applaudir les fumeurs de ne payer que ce qu'ils coûtent comme tu le souhaites!

    Il y a plusieurs choses là-dedans. Les taxes ont deux rôles :

    • financer le coût des soins pour les fumeurs : avec une possibilité de tarifier différemment l'assurance santé, ça devrait effectivement disparaître ;
    • financer le coût des soins aux victimes de tabagisme passif : ça doit rester ;
    • dissuader le tabagisme, notamment à cause des nuisances qu'il représente : ça doit rester aussi.

    et les accidentés vélotafs sont actuellement acceptés sans surcoût mais si tu veux des différences je militerai pour que toi tu payes un max suivant l'usage vélotaf que je jugerai pour toi comme plus dangereux que train, X ou Y tout aussi subjectif que toi

    Ça n'a rien de subjectif, ça a été étudié, et surprise, le vélotaf est dangereux, mais ne pas en faire l'est encore plus. Les assureurs sont d'accord, ce qui est l'essentiel, dans cette discussion.

    https://www.matmut.fr/assurance/nvei/conseils/velo-travail-bonnes-raisons
    https://www.ors-idf.org/nos-travaux/publications/les-benefices-et-les-risques-de-la-pratique-du-velo/

    C'est surtout démagogique pour te croire mieux que d'autres, pas factuel.

    Non, pas du tout, c'est une question de justice. C'est comme les assurances emprunteur par exemple : non fumeur, ça coûte moins cher que fumeur, c'est juste normal. Et ça ne pénalise que ceux qui le veulent bien.

    PS : perso si on va dans cette direction je serai plus sur faire sur-cotiser les non-vaccinés de tout poil (vous avez fait vos rappels adultes?) car un choix très individuel et calcul de risque qui fait que ça coûte plus (et c'est ce qu'on fait des assurances US) sans apporter assez ailleurs (risque de survivre après réa, pas d'autres taxes déjà en cours), mais la ça risque de troller :).

    Pas forcément moduler ainsi les cotisations, simplement permettre de le faire. Pour la vaccination, c'est pertinent aussi en effet. Beaucoup plus pertinent qu'une obligation plus ou moins forte en fait, puisqu'il s'agit de répercuter directement le coût statistique d'un choix personnel sur le budget de celui qui le fait. Pour responsabiliser les gens, on ne peut pas faire mieux je pense. Tant qu'il s'agit simplement de critères qui relèvent du choix des gens, aucun problème.

  • # Procrastination

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 7. Évalué à 4.

    On dirait que le Père Noël, ou les lutins, ou les deux, ne sont pas si motivés que ça pour aller effectivement courir la jungle pour récolter des caramboles. Ça traîne à faire des inventaires, à monter le camp, à nettoyer le camp, à décharger le bateau, à réorganiser le matériel déchargé, à bidouiller des communicateurs, à mettre à jour ces communicateurs…

    Ça fait six jours qu'on a débarqué, et n'a pas encore bougé du camp ! Vivement que les lutins nous fournissent une carte pour aller quelque part.

  • [^] # Re: C'est parti !

    Posté par  (site web personnel) . En réponse au journal Calendrier de l'Avent du code. Évalué à 4.

    Sujets plus simple, je suis d'accord… pour le moment. Ça pourrait se corser d'un coup !

  • [^] # Re: Vivement , le 1

    Posté par  (site web personnel) . En réponse au journal Calendrier de l'Avent du code. Évalué à 6.

    Pareil pour moi, surtout que j'aime faire du beau code, et que ça ne va pas bien avec l'idée de faire la course.

    Mais pas d'inquiétude, on devrait finir par avoir des problèmes qu'on sera déjà content de parvenir à résoudre tout court, au bout d'un moment. :-)

  • [^] # Re: un bout de AWK

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 7. Évalué à 3. Dernière modification le 07 décembre 2022 à 15:19.

    En fait vu la structure de l'input où il n'y a que de cd child et cd .. # parent, c'est nécessairement un parcours d'arbre en profondeur d'abord.

    $ cd /
    $ ls
    4212 aoc.py
    dir 2021
    dir 2022
    $ cd 2021
    $ ls
    dir 12
    $ cd ..
    $ cd 2022
    $ ls
    dir 12
    $ cd ..
    $ cd 2021
    $ cd 12
    $ ls
    42 01.py
    12 02.py
    51 03.py
    $ cd ..
    $ cd ..
    $ cd 2022
    $ cd 12
    $ ls
    12 01.py
    51 02.py
    42 03.py
    

    C'est idiot, on est d'accord. Mais ce n'est pas un parcours en profondeur d'abord.