In [1]:
inputLines = lines <$> readFile "input/day12.txt"
In [2]:
import qualified Data.Map as Map
import Data.Array
import Data.Char (isLetter)
import Data.Maybe (fromJust)
In [3]:
type ComputerMemory = Map.Map String Int
The state of the computer consists of the instruction pointer (the integer index of the next instruction) and the memory.
In [4]:
type ComputerState = (Int, ComputerMemory)
An instruction maps a computer state to the next computer state.
In [5]:
type Instruction = ComputerState -> ComputerState
A program is an array of instructions.
In [6]:
type Program = Array Int Instruction
Asembunny instructions are made of an opcode and one or two operands. An operand is either a constant integer or a register value.
In [7]:
data Operand = Const Int | Register String deriving(Show)
In [8]:
getValue :: Operand -> ComputerMemory -> Int
getValue (Const value) _ = value
getValue (Register registerName) memory = Map.findWithDefault 0 registerName memory
parseOperand
takes a String
and returns the corresponding Operand
.
In [9]:
parseOperand :: String -> Operand
parseOperand text
| isLetter $ head text = Register text
| otherwise = Const $ read text
-- Test if it works
map parseOperand ["-5", "d"] -- [Const (-5), Register "d"]
The following functions take one or two Operands
and return the corresponding Instruction
, i.e., the function that takes a computer state and returns the new state after the instruction is executed. Note that the instructions for all opcodes except for jnz
increase the instruction pointer by one.
cpy
writes the value of its first Operand
to the second Operand
, which must be a register
In [10]:
cpy :: Operand -> Operand -> Instruction
cpy source (Register registerName) (ip, memory) =
(succ ip, Map.insert registerName (getValue source memory) memory)
modifyRegister
takes a function that modifies an Int
and a register. The resulting instruction applies that modification to the register value.
In [11]:
modifyRegister :: (Int -> Int) -> Operand -> Instruction
modifyRegister modifier (Register registerName) (ip, memory) =
(succ ip,
Map.insert registerName (modifier $ Map.findWithDefault 0 registerName memory) memory)
inc
and dec
use modifyRegister
to increase and decrease a register value, respectively.
In [12]:
inc :: Operand -> Instruction
inc = modifyRegister succ
In [13]:
dec :: Operand -> Instruction
dec = modifyRegister pred
jnz
adds its first Operand
to the instruction pointer, unless its second operand is zero.
In [14]:
jnz :: Operand -> Operand -> Instruction
jnz value offset (ip, memory)
| getValue value memory == 0 = (succ ip, memory)
| otherwise = (ip + getValue offset memory, memory)
In [15]:
parseInstruction :: String -> Instruction
parseInstruction line
| opcode `Map.member` unaryFunctions =
fromJust (opcode `Map.lookup` unaryFunctions)
(getSingleOperand operands)
| opcode `Map.member` binaryFunctions =
fromJust (opcode `Map.lookup` binaryFunctions)
(getFirstOperand operands)
(getSecondOperand operands)
where
tokens = words line
opcode = head tokens
operands = tail tokens
unaryFunctions = Map.fromList [("inc", inc), ("dec", dec)]
binaryFunctions = Map.fromList [("cpy", cpy), ("jnz", jnz)]
getSingleOperand [p] = parseOperand p
getFirstOperand [p1, p2] = parseOperand p1
getSecondOperand [p1, p2] = parseOperand p2
In [16]:
initialState = (42, Map.fromList [("a", 1)])
instructionsAndExpectedStates = [
("inc a", (43, Map.fromList [("a", 2)])),
("inc b", (43, Map.fromList [("a", 1), ("b", 1)])),
("dec c", (43, Map.fromList [("a", 1), ("c", -1)])),
("cpy a b", (43, Map.fromList [("a", 1), ("b", 1)])),
("cpy -5 d", (43, Map.fromList [("a", 1), ("d", -5)])),
("jnz a 10", (52, Map.fromList [("a", 1)])),
("jnz b 10", (43, Map.fromList [("a", 1)]))]
finalStates = map ((\f -> f initialState) . parseInstruction . fst) instructionsAndExpectedStates
expectedStates = map snd instructionsAndExpectedStates
all (uncurry (==)) $ zip finalStates expectedStates
In [17]:
compileProgram :: [String] -> Program
compileProgram sourceLines = listArray (0, lastIndex) $ map parseInstruction sourceLines
where
lastIndex = pred numberOfInstructions
numberOfInstructions = length sourceLines
In [18]:
continueExecution :: Program -> Int -> ComputerState -> ComputerMemory
continueExecution program finalIp state
| ip == finalIp = memory
| otherwise = continueExecution program finalIp newState
where
(ip, memory) = state
newState = currentInstruction state
currentInstruction = program ! ip
In [19]:
runProgram :: ComputerMemory -> Program -> ComputerMemory
runProgram initialMemory program = continueExecution program finalIp initialState
where
(minIp, maxIp) = bounds program
finalIp = succ maxIp
initialState = (minIp, initialMemory)
In [20]:
program = compileProgram <$> inputLines
In [21]:
Map.lookup "a" . runProgram Map.empty <$> program
In [22]:
Map.lookup "a" . runProgram (Map.singleton "c" 1) <$> program