• # Solution en Haskell

    Posté par . En réponse au message Advent of Code 2023, jour 20. Évalué à 2.

    Je peux dire que j'ai galéré sur celui là. Tout ça parce que j'ai mal lu l'énoncé.
    Je n'avais pas vu qu'il fallait traiter les gestions de pulsations sous forme de file.
    Je les traitais sous forme de pile et bizarrement ça m'a quand même donné la bonne réponse pour la première partie.

    Pour la deuxième partie, c'est comme pour le jour 8, c'est très compliqué dans le cas général mais facile parce que l'instance a de bonnes propriétés (je n'aime pas trop ce genre de journée).
    Tout d'abord, on remarque que rx a un seul prédécesseur qui est de type Conjonction et que celui a 4 prédécesseurs (que je vais appeler a, b, d) chacun de type Conjonction.
    Ensuite, si on enlève broadcaster, rx et son prédécesseur, on se retrouve avec 4 composantes connexes et donc les pulsations de chacune vont vivre leur vie indépendamment des autres.

    Si, on regarde quand a, b, c ou d envoie une pulsation forte, on remarque que ça forme un cycle sans prépériode et qu'une pulsation forte n'apparait qu'une seule fois durant un cycle.

    Il suffit donc de repérer pour a, b, c et d la première fois qu'il y a une pulsation forte et faire le PPCM entre les différentes valeurs trouvées.

    Pour le code en Haskell, j'utilise une monade State et des Lens, ce qui me permet de simplifier l'écriture.

    data Type = FlipFlop | Conjunction | Broadcaster
    data Module = Module !Type [String]
    type Network = HashMap String Module
    data NState = NState 
     { _ffState :: !(HashMap String Bool) -- the state of flip flap mdoules
     , _from :: !(HashMap String (HashMap String Bool)) -- last signal sent by predecessor
     , _nbLow :: !Int
     , _nbHigh :: !Int
     , _seen :: !(HashMap String Bool)
     }
    makeLenses ''NState
    parser :: Parser Network
    parser = insertRx . Map.fromList <$> module_ `sepEndBy1` eol where
     module_ = do
     t <- type_
     n <- name <* " -> "
     ns <- name `sepBy1` ", "
     pure (n, Module t ns)
     name = some lowerChar
     type_ = FlipFlop <$ "%" <|> Conjunction <$ "&" <|> pure Broadcaster
     insertRx = Map.insert "rx" (Module Broadcaster [])
    sendSignal :: Network -> Seq (String, String, Bool) -> State NState ()
    sendSignal network = \case
     Seq.Empty -> pure ()
     ((name, srcName, pulse) :<| queue') -> do
     if pulse then
     nbHigh += 1
     else do
     nbLow += 1
     seen . ix name .= True
     let Module type_ dests = network Map.! name
     case type_ of
     Broadcaster ->
     sendSignal network $ queue' >< Seq.fromList (map (,name, False) dests)
     FlipFlop ->
     if pulse then 
     sendSignal network queue'
     else do 
     nstate <- get
     let state = _ffState nstate Map.! name 
     ffState . ix name .= not state
     sendSignal network $ queue' >< Seq.fromList (map (,name, not state) dests)
     Conjunction -> do
     from . ix name . ix srcName .= pulse
     nstate <- get
     let signal' = any not $ Map.elems (_from nstate Map.! name)
     sendSignal network $ queue' >< Seq.fromList (map (,name, signal') dests)
    round :: Network -> State NState ()
    round network = do
     seen .= Map.map (const False) network
     sendSignal network $ Seq.singleton ("broadcaster", "$dummy", False)
    initNState :: Network -> NState
    initNState network = NState initFfState initFrom 0 0 initSeen where
     initFfState = Map.map (const False) network
     emptyFrom = Map.map (const Map.empty) network
     edgeList = concat . Map.elems $ Map.mapWithKey go network 
     go u (Module _ vs) = map (u,) vs
     initFrom = foldl' go' emptyFrom edgeList
     go' from_ (u, v) = Map.adjust (Map.insert u False) v from_
     initSeen = Map.map (const False) network
    part1 :: Network -> Int
    part1 network = _nbLow finalState * _nbHigh finalState where
     nstate = initNState network
     finalState = flip execState nstate do
     forM_ [(1::Int)..1000] \_ -> round network
    part2 :: Network -> Integer
    part2 network = foldl' lcm 1 cycles where
     nstate = initNState network
     predRx = head . Map.keys $ _from nstate Map.! "rx"
     predPredRx = Map.keys $ _from nstate Map.! predRx
     nstates = iterate' (execState (round network)) nstate
     cycles = map extractCycle predPredRx
     extractCycle name = head [ idx 
     | (idx, True) <- zip [0..] 
     . map ((Map.! name) . _seen) 
     $ nstates
     ]
    solve :: Text -> IO ()
    solve = aoc parser part1 part2