2
\$\begingroup\$

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
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Dec 6, 2019 at 14:09
\$\endgroup\$
1

1 Answer 1

2
\$\begingroup\$

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.

answered Dec 6, 2019 at 18:25
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.