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
\$\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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.