• # Solution en Haskell

    Posté par . En réponse au message Advent of Code, jour 16. Évalué à 2. Dernière modification le 16 décembre 2023 à 17:31.

    C'est un problème qui peut se faire un parcours en longueur ou largeur.
    Problème: selon la direction du faisceau, les cases à visiter suivantes peuvent être différentes.
    Du coup un sommet du graphe que l'on veut parcourir ne sera pas seulement une position dans la grille mais un couple (position, direction).

    Je commence par importer mes fonctions nécessaires et définir les types utilisés dans le problème.
    Les positions et directions sont des vecteurs de dimension 2.
    J'appelerai le couple (Position, Direction) est un Beam.

    import AOC.Prelude
    import Data.List (maximum)
    import qualified Data.HashSet as Set
    import Data.Massiv.Array (Matrix, (!), (!?), B, Comp(Seq), Sz(Sz2))
    import qualified Data.Massiv.Array as A
    import Control.Parallel.Strategies (parMap, rdeepseq)
    import AOC (aoc)
    import AOC.V2 (V2(..), toIx2)
    import AOC.Parser (Parser, sepEndBy1, eol, choice, some)
    import AOC.Search (reachableFrom)
    data Tile = Empty | Horizontal | Vertical | Slash | Antislash
    type Position = V2 Int
    type Direction = V2 Int
    type Beam = (Position, Direction)
    type Grid = Matrix B Tile

    Ensuite, le parsing, rien de bien intéressant

    parser :: Parser Grid 
    parser = A.fromLists' Seq <$> some tile `sepEndBy1` eol where
     tile = choice [ Empty <$ "."
     , Horizontal <$ "-"
     , Vertical <$ "|"
     , Slash <$"/"
     , Antislash <$ "\\"
     ]

    Ensuite, vient le parcours en largeur. Je réutilise une fonction reachableFrom définie pour des problèmes précédents.
    Elle prend deux arguments
    - une fonction qui étant donné renvoit la liste des sommets voisins
    - un sommet de départ
    et renvoit l'ensemble des sommets accessibles depuis le sommet de départ.

    reachableFrom :: Hashable a => (a -> [a]) -> a -> HashSet a
    reachableFrom nborFunc start = go HSet.empty [start] where
     go visited [] = visited
     go visited (v : stack)
     | v `HSet.member` visited = go visited stack
     | otherwise = go (HSet.insert v visited) (nborFunc v ++ stack)

    Pour utiliser reachableFrom, je dois calculer, étant donné un beam, les beams suivants.
    Je le fais en deux temps en définissant d'abord une fonction nextDirections qui étant donné une direction et une tuile me renvoit les directions suivantes.

    nextDirections :: Direction -> Tile -> [Direction]
    nextDirections (V2 drow dcol) = \case
     Slash -> [V2 (-dcol) (-drow)]
     Antislash -> [V2 dcol drow]
     Horizontal | drow /= 0 -> [V2 0 (-1), V2 0 1]
     Vertical | dcol /= 0 -> [V2 (-1) 0, V2 1 0]
     _ -> [V2 drow dcol]

    A partir de ça, je peux définir ma fonction neighbors nécessaire à `reachableFrom

    neighbors :: Grid -> Beam -> [Beam]
    neighbors grid (pos, dir) = [ (nextPos, nextDir)
     | nextDir <- nextDirections dir (grid ! toIx2 pos)
     , let nextPos = pos + nextDir
     , isJust (grid !? toIx2 nextPos)
     ]

    Je peux maintenant définir ma fonction energized qui me renvoit le nombre de tuiles énergisées en utilisant les fonctions reachableFrom et neighbors définis plus haut.

    energized :: Grid -> Beam -> Int
    energized grid start = Set.size $ Set.map fst reachable where
     reachable = reachableFrom (neighbors grid) start
    part1 :: Grid -> Int
    part1 grid = energized grid (V2 0 0, V2 0 1)

    Pour la partie 2, c'est du brute-force sur toutes les positions de départ possibles, j'ai pas trouvé mieux.
    Ca se parallélise bien, j'utilise parMap qui est une version parallèle de map.

    part2 :: Grid -> Int
    part2 grid = maximum $ parMap rdeepseq (energized grid) starts where
     Sz2 h w = A.size grid
     starts = concat $
     [[(V2 r 0, V2 0 1), (V2 r (w-1), V2 0 (-1))] | r <- [0 .. h-1]]
     ++ [[(V2 0 c, V2 1 0), (V2 (h-1) c, V2 (-1) 0)] | c <- [0 .. w-1]]

    1400ms sur un seul core et 800ms en multicore pour la partie 2. Pas terrible le parallélisme, j'aurais espéré mieux. Je ne me suis peut-être pas pris comme il fallait.