This is my solution for Day 2 of Advent of Code 2019.
It's the eval
function I want to get reviewed. Is this acceptable Haskell code? Is it even close to being idiomatic? Also, is it OK to leverage laziness this way?
module Day2 where
import Data.Vector (Vector, fromList, head, (!), (//))
import Data.List.Split (splitOn)
type Intcode = Vector Int
data Op = Add | Mult | Noop
intToOp :: Int -> Op
intToOp 1 = Add
intToOp 2 = Mult
intToOp 99 = Noop
intToOp x = error $ "invalid opCode, should not happen" ++ show x
eval :: Intcode -> Intcode
eval intcode = go intcode 0
where
go intcode currentIndex =
let
op = intToOp $ intcode ! currentIndex
v1Pos = intcode ! (currentIndex + 1)
v2Pos = intcode ! (currentIndex + 2)
savePos = intcode ! (currentIndex + 3)
nextIndex = currentIndex + 4
v1 = intcode ! v1Pos
v2 = intcode ! v2Pos
in
case op of
Add -> go (intcode // [(savePos, (v1 + v2))]) nextIndex
Mult -> go (intcode // [(savePos, (v1 * v2))]) nextIndex
Noop -> intcode
part1 :: IO ()
part1 = do
input <- readFile "../input/day2.txt"
let intcode = fromList (read <$> splitOn "," input)
let result = eval $ intcode // [(1, 12), (2, 2)]
putStrLn $ show $ result ! 0
part2 :: IO ()
part2 = do
input <- readFile "../input/day2.txt"
let memory = fromList (read <$> splitOn "," input)
let inputs = [(i, j) | i <- [0..99], j <- [0..99]]
let results = fmap (\(noun, verb) -> (eval $ memory // [(1, noun), (2, verb)], noun, verb)) inputs
let (result, noun, verb) = Prelude.head $ filter (\(res, _, _) -> (res ! 0) == 19690720) results
putStrLn $ show $ 100 * noun + verb
-
\$\begingroup\$ Links can rot. Please include a description of the challenge here in your question. \$\endgroup\$Ethan Bierlein– Ethan Bierlein2019年12月06日 15:19:07 +00:00Commented Dec 6, 2019 at 15:19
1 Answer 1
slice
combines some lookups. The index arithmetic can be frontloaded.
eval :: Intcode -> Intcode
eval = go [0,4..] where
go (index:es) intcode =
let
[opcode, v1Pos, v2Pos, savePos] = toList $ slice index 4 intcode
v1 = intcode ! v1Pos
v2 = intcode ! v2Pos
in
case intToOp opcode of
Add -> go es $ intcode // [(savePos, v1 + v2)]
Mult -> go es $ intcode // [(savePos, v1 * v2)]
Noop -> intcode
//
takes linear time on immutable vectors. Noop
should be Halt
. This displays the unreliability of adding data types for their suggestive names. Therefore, inline intToOp
.
eval :: Intcode -> Intcode
eval = modify $ \intcode ->
void $ runMaybeT $ for_ [0,4..] $ \index -> do
[opcode, v1Pos, v2Pos, savePos] <- traverse (read intcode) [index..index+3]
v1 <- read intcode v1Pos
v2 <- read intcode v2Pos
case opcode of
1 -> lift $ write intcode savePos $ v1 + v2
2 -> lift $ write intcode savePos $ v1 * v2
99 -> empty
_ -> error $ "invalid opCode, should not happen" ++ show opcode
Of course, all of this imperativeness doesn't feel Haskell. However, interpreting an imperative language is pretty much the one time you need not feel bad about that.