{-# LANGUAGE LambdaCase #-}
import Control.Applicative
import Control.Monad.State
import Data.Char
import Data.Either
import Data.Maybe
import Data.List (intercalate)
import Data.Monoid
import qualified Data.IntMap as IM
import qualified Data.Set as S
import System.Environment
data Operation = ACC | JMP | NOP deriving (Eq,Show)
pcIncrement :: Operation -> Int -> Int
pcIncrement JMP i = i
pcIncrement _ _ = 1
accIncrement :: Operation -> Int -> Int
accIncrement ACC i = i
accIncrement _ _ = 0
runBoot :: IM.IntMap (Operation,Int) -> [(Int,Int)]
runBoot b = case IM.lookupMin b of
Nothing -> []
Just (pc0,_) -> go pc0 0
where
go pc acc = case IM.lookup pc b of
Nothing -> []
Just (op,i) -> let
pc' = pc + pcIncrement op i
acc' = acc + accIncrement op i
in (pc', acc') : go pc' acc'
repairAttempts :: Alternative f =>
IM.IntMap (Operation, Int) -> f (IM.IntMap (Operation, Int))
repairAttempts = alterEach $ \(opc,i) -> case opc of
ACC -> empty
NOP -> pure $ Just (JMP,i)
JMP -> pure $ Just (NOP,i)
alterEach :: Alternative f =>
(a -> f (Maybe a)) -> IM.IntMap a -> f (IM.IntMap a)
alterEach f m = getAlt $
IM.foldMapWithKey (\k _ -> Alt $ IM.alterF (f . fromJust) k m) m
accBeforeLoop :: [(Int,Int)] -> Maybe Int
accBeforeLoop = go (S.singleton 0) . ((0,0):) where
go v ((_,acc):r@((pc,_):_)) = either Just (flip go r) $ S.alterF (\p -> if p then Left acc else Right True) pc v
go _ _ = Nothing
accOnComplete :: Int -> [(Int,Int)] -> Maybe Int
accOnComplete t = go S.empty where
go _ [] = Nothing
go _ [(pc,acc)]
| pc == t = Just acc
| otherwise = Nothing
go v ((pc,acc):r)
| pc `S.member` v = Nothing
| otherwise = go (S.insert pc v) r
anyChar :: StateT String [] Char
anyChar = StateT (\case
[] -> []
(a:r) -> [(a,r)]
)
string :: String -> StateT String [] ()
string [] = return ()
string (a:r) = anyChar >>= \c -> if a == c
then string r
else empty
opcode :: StateT String [] Operation
opcode = (ACC <$ string "acc") <|>
(JMP <$ string "jmp") <|>
(NOP <$ string "nop")
natural :: Num a => StateT String [] a
natural = go 0 where
go acc = anyChar >>= \c -> if isDigit c
then let
acc' = acc * 10 + fromIntegral (digitToInt c)
in acc' `seq` return acc' <|> go acc'
else empty
eos :: StateT String [] ()
eos = StateT (\case
[] -> [((),[])]
_ -> []
)
neos :: StateT String [] ()
neos = StateT (\case
[] -> []
_ -> [((),[])]
)
line :: StateT String [] (Operation,Int)
line = (,) <$>
(opcode <* string " ") <*>
(
((id <$ string "+") <|> (negate <$ string "-")) <*>
natural
)
sepBy :: Alternative m => m a -> m s -> m [a]
sepBy a s = pure [] <|>
((:) <$> a <*> many (s *> a))
file :: StateT String [] (IM.IntMap (Operation,Int))
file = (IM.fromList . zip [0..]) <$> sepBy line (string "\n") <* (pure () <|> void (string "\n"))
main = do
(part:fn:_) <- getArgs
(b:_) <- evalStateT (file <* eos) <$> readFile fn
print $ case part of
"1" -> fromJust $ accBeforeLoop $ runBoot b
"2" -> let
gc = accOnComplete $ IM.size b
in head $ do
b' <- repairAttempts b
maybeToList $ gc $ runBoot b'
showOps :: IM.IntMap (Operation,Int) -> String
showOps = intercalate "\n" . map (\(op,i) ->
map toLower (show op) ++
" " ++
(if i < 0 then "-" else "+") ++
show (abs i)
) . IM.elems