Add solution to day 11 part 1

This commit is contained in:
Nick Krichevsky 2019-12-11 00:35:05 -05:00
parent 4a62f46f28
commit 6b5e73d89b
2 changed files with 236 additions and 0 deletions

1
day11/input.txt Normal file
View file

@ -0,0 +1 @@
3,8,1005,8,330,1106,0,11,0,0,0,104,1,104,0,3,8,102,-1,8,10,1001,10,1,10,4,10,108,0,8,10,4,10,1001,8,0,28,1,1103,17,10,1006,0,99,1006,0,91,1,102,7,10,3,8,1002,8,-1,10,101,1,10,10,4,10,108,1,8,10,4,10,1002,8,1,64,3,8,102,-1,8,10,1001,10,1,10,4,10,108,0,8,10,4,10,102,1,8,86,2,4,0,10,1006,0,62,2,1106,13,10,3,8,1002,8,-1,10,1001,10,1,10,4,10,1008,8,0,10,4,10,101,0,8,120,1,1109,1,10,1,105,5,10,3,8,102,-1,8,10,1001,10,1,10,4,10,108,1,8,10,4,10,1002,8,1,149,1,108,7,10,1006,0,40,1,6,0,10,2,8,9,10,3,8,102,-1,8,10,1001,10,1,10,4,10,1008,8,1,10,4,10,1002,8,1,187,1,1105,10,10,3,8,102,-1,8,10,1001,10,1,10,4,10,1008,8,1,10,4,10,1002,8,1,213,1006,0,65,1006,0,89,1,1003,14,10,3,8,102,-1,8,10,1001,10,1,10,4,10,108,0,8,10,4,10,102,1,8,244,2,1106,14,10,1006,0,13,3,8,102,-1,8,10,1001,10,1,10,4,10,108,0,8,10,4,10,1001,8,0,273,3,8,1002,8,-1,10,1001,10,1,10,4,10,108,1,8,10,4,10,1001,8,0,295,1,104,4,10,2,108,20,10,1006,0,94,1006,0,9,101,1,9,9,1007,9,998,10,1005,10,15,99,109,652,104,0,104,1,21102,937268450196,1,1,21102,1,347,0,1106,0,451,21101,387512636308,0,1,21102,358,1,0,1105,1,451,3,10,104,0,104,1,3,10,104,0,104,0,3,10,104,0,104,1,3,10,104,0,104,1,3,10,104,0,104,0,3,10,104,0,104,1,21101,0,97751428099,1,21102,1,405,0,1105,1,451,21102,1,179355806811,1,21101,416,0,0,1106,0,451,3,10,104,0,104,0,3,10,104,0,104,0,21102,1,868389643008,1,21102,439,1,0,1105,1,451,21102,1,709475853160,1,21102,450,1,0,1105,1,451,99,109,2,22102,1,-1,1,21101,0,40,2,21101,482,0,3,21102,1,472,0,1105,1,515,109,-2,2106,0,0,0,1,0,0,1,109,2,3,10,204,-1,1001,477,478,493,4,0,1001,477,1,477,108,4,477,10,1006,10,509,1101,0,0,477,109,-2,2105,1,0,0,109,4,2101,0,-1,514,1207,-3,0,10,1006,10,532,21101,0,0,-3,21202,-3,1,1,22101,0,-2,2,21101,1,0,3,21101,0,551,0,1105,1,556,109,-4,2106,0,0,109,5,1207,-3,1,10,1006,10,579,2207,-4,-2,10,1006,10,579,22102,1,-4,-4,1105,1,647,21201,-4,0,1,21201,-3,-1,2,21202,-2,2,3,21101,0,598,0,1106,0,556,22101,0,1,-4,21102,1,1,-1,2207,-4,-2,10,1006,10,617,21101,0,0,-1,22202,-2,-1,-2,2107,0,-3,10,1006,10,639,22102,1,-1,1,21102,1,639,0,105,1,514,21202,-2,-1,-2,22201,-4,-2,-4,109,-5,2105,1,0

235
day11/py/main.py Normal file
View file

@ -0,0 +1,235 @@
import collections
import sys
from typing import List, Tuple, Optional
# Halt indicates that the assembled program should terminate
class Halt(Exception):
pass
class Memory(collections.OrderedDict):
def __missing__(self, address):
if address < 0:
raise KeyError("Address cannot be < 0")
return 0
# Operation represents an operation that the intcode computer should do
class Operation:
OPCODE_TERMINATE = 99
OPCODE_ADD = 1
OPCODE_MULTIPLY = 2
OPCODE_INPUT = 3
OPCODE_OUTPUT = 4
OPCODE_JUMP_IF_TRUE = 5
OPCODE_JUMP_IF_FALSE = 6
OPCODE_LESS_THAN = 7
OPCODE_EQUALS = 8
OPCODE_SET_REL_BASE = 9
MODE_POSITION = 0
MODE_IMMEDIATE = 1
MODE_RELATIVE = 2
ALL_OPCODES = (OPCODE_TERMINATE, OPCODE_ADD, OPCODE_MULTIPLY, OPCODE_INPUT, OPCODE_OUTPUT,
OPCODE_JUMP_IF_TRUE, OPCODE_JUMP_IF_FALSE, OPCODE_LESS_THAN, OPCODE_EQUALS, OPCODE_SET_REL_BASE)
# Opcodes that write to memory as their last parameter
MEMORY_OPCODES = (OPCODE_ADD, OPCODE_MULTIPLY, OPCODE_INPUT, OPCODE_LESS_THAN, OPCODE_EQUALS)
def __init__(self, instruction: int):
# The opcode is the first two digits of the number, the rest are parameter modes
self.opcode: int = instruction % 100
if self.opcode not in Operation.ALL_OPCODES:
raise ValueError(f"Bad opcode: {self.opcode}")
self.modes: Tuple[int, ...] = self._extract_parameter_modes(instruction//100)
self.output = None
self.rel_base = 0
def _extract_parameter_modes(self, raw_modes) -> Tuple[int, ...]:
PARAMETER_COUNTS = {
Operation.OPCODE_TERMINATE: 0,
Operation.OPCODE_ADD: 3,
Operation.OPCODE_MULTIPLY: 3,
Operation.OPCODE_INPUT: 1,
Operation.OPCODE_OUTPUT: 1,
Operation.OPCODE_JUMP_IF_TRUE: 2,
Operation.OPCODE_JUMP_IF_FALSE: 2,
Operation.OPCODE_LESS_THAN: 3,
Operation.OPCODE_EQUALS: 3,
Operation.OPCODE_SET_REL_BASE: 1,
}
num_parameters = PARAMETER_COUNTS[self.opcode]
modes = [Operation.MODE_POSITION for i in range(num_parameters)]
mode_str = str(raw_modes)
# Iterate over the modes digits backwards, assigning them to the parameter list until we exhaust the modes
# The rest must be leading zeroes
for mode_index, digit in zip(range(num_parameters), reversed(mode_str)):
modes[mode_index] = int(digit)
return tuple(modes)
# Run the given operation, starting at the given instruction pointer
# Returns the address that the instruction pointer should become, the relative base, followed by the output of the operation, if any
def run(self, memory: Memory, instruction_pointer: int, rel_base: int = 0, program_input: Optional[int] = None) -> Tuple[int, int, Optional[int]]:
OPERATION_FUNCS = {
# nop for terminate
Operation.OPCODE_TERMINATE: Operation.terminate,
Operation.OPCODE_ADD: Operation.add,
Operation.OPCODE_MULTIPLY: Operation.multiply,
Operation.OPCODE_INPUT: Operation.input,
Operation.OPCODE_OUTPUT: Operation.output,
Operation.OPCODE_JUMP_IF_TRUE: Operation.jump_if_true,
Operation.OPCODE_JUMP_IF_FALSE: Operation.jump_if_false,
Operation.OPCODE_LESS_THAN: Operation.less_than,
Operation.OPCODE_EQUALS: Operation.equals,
Operation.OPCODE_SET_REL_BASE: Operation.set_rel_base
}
# Reset the output and rel base of a previous run
self.output = None
self.rel_base = rel_base
args = []
for i, mode in enumerate(self.modes):
# Add 1 to move past the opcode itself
pointer = instruction_pointer + i + 1
arg = memory[pointer]
# The last argument (the address parameter) must always act as an immediate
# The problem statement is misleading in this regard. You do NOT want to get an address to store the value
# at from another address.
if mode != self.MODE_IMMEDIATE and i == len(self.modes) - 1 and self.opcode in Operation.MEMORY_OPCODES:
if mode == Operation.MODE_RELATIVE:
arg = self.rel_base + arg
# Position mode is already handled since it would be arg = arg here.
elif mode == Operation.MODE_POSITION:
arg = memory[arg]
elif mode == Operation.MODE_RELATIVE:
arg = memory[self.rel_base + arg]
elif mode != Operation.MODE_IMMEDIATE:
raise ValueError(f"Invalid parameter mode {mode}")
args.append(arg)
func = OPERATION_FUNCS[self.opcode]
if program_input is None:
jump_addr = func(self, memory, *args)
else:
jump_addr = func(self, memory, program_input, *args)
out_addr = instruction_pointer + len(self.modes) + 1
if jump_addr is not None:
out_addr = jump_addr
return out_addr, self.rel_base, self.output
def terminate(self, memory: Memory) -> None:
raise Halt("catch fire")
def add(self, memory: Memory, a: int, b: int, loc: int) -> None:
memory[loc] = a + b
def multiply(self, memory: Memory, a: int, b: int, loc: int) -> None:
memory[loc] = a * b
def input(self, memory: Memory, program_input: int, loc: int) -> None:
memory[loc] = program_input
def output(self, memory: Memory, value: int) -> None:
self.output = value
print("OUTPUT:", value)
def jump_if_true(self, memory: Memory, test_value: int, new_instruction_pointer: int) -> Optional[int]:
return new_instruction_pointer if test_value != 0 else None
def jump_if_false(self, memory: Memory, test_value: int, new_instruction_pointer: int) -> Optional[int]:
return new_instruction_pointer if test_value == 0 else None
def less_than(self, memory: Memory, a: int, b: int, loc: int) -> None:
memory[loc] = int(a < b)
def equals(self, memory: Memory, a: int, b: int, loc: int) -> None:
memory[loc] = int(a == b)
def set_rel_base(self, memory: Memory, rel_base: int) -> None:
self.rel_base += rel_base
# Executes the program, returning the instruction pointer to continue at (if the program paused) and a list of all
# outputs that occurred during the program's execution
def execute_program(memory: Memory, program_inputs: List[int], initial_instruction_pointer: int = 0) -> (Optional[int], List[int]):
i = initial_instruction_pointer
input_cursor = 0
rel_base = 0
outputs = []
# Go up to the maximum address, not the number of addresses
while i < max(memory.keys()):
operation = Operation(memory[i])
program_input = None
# If we're looking for input
if operation.opcode == Operation.OPCODE_INPUT:
# If we are out of input, don't fail out, but rather just pause execution
if input_cursor >= len(program_inputs):
return i, outputs
program_input = program_inputs[input_cursor]
input_cursor += 1
try:
i, rel_base, output = operation.run(memory, i, rel_base, program_input)
except Halt:
break
if output is not None:
outputs.append(output)
# The program is finished, and we are saying there is no instruction pointer
return None, outputs
ROBOT_DIRECTIONS = ['UP', 'RIGHT', 'DOWN', 'LEFT']
ROBOT_DELTAS = {
'UP': (0, 1),
'RIGHT': (-1, 0),
'DOWN': (0, -1),
'LEFT': (1, 0)
}
def part1(inputs: Memory) -> int:
colors = collections.defaultdict(lambda: 0)
# Represents an index in ROBOT_DIRECTIONS
robot_direction = 0
robot_row, robot_col = (0, 0)
memory = inputs
last_ip = 0
while True:
d_row, d_col = ROBOT_DELTAS[ROBOT_DIRECTIONS[robot_direction]]
robot_row, robot_col = robot_row + d_row, robot_col + d_col
current_color = colors[(robot_row, robot_col)]
last_ip, outputs = execute_program(memory, [current_color], last_ip)
if last_ip is None:
break
paint_color, rotation_direction = outputs
colors[(robot_row, robot_col)] = paint_color
if rotation_direction == 0:
robot_direction -= 1
else:
robot_direction += 1
robot_direction %= len(ROBOT_DIRECTIONS)
return len(colors)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: ./main.py in_file")
sys.exit(1)
memory = Memory()
with open(sys.argv[1]) as f:
for i, item in enumerate(f.read().rstrip().split(",")):
memory[i] = int(item)
print(part1(memory))