diff --git a/day9/py/intcode_changes_since_day_7.diff b/day9/py/intcode_changes_since_day_7.diff new file mode 100644 index 0000000..ea09bc3 --- /dev/null +++ b/day9/py/intcode_changes_since_day_7.diff @@ -0,0 +1,189 @@ +--- day7intcode.py 2019-12-09 01:30:05.907769133 -0500 ++++ day9intcode.py 2019-12-09 01:29:48.539936730 -0500 +@@ -1,5 +1,5 @@ ++import collections + import sys +-import itertools + from typing import List, Tuple, Optional + + +@@ -8,6 +8,13 @@ + 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 +@@ -19,10 +26,14 @@ + 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_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 +@@ -31,6 +42,7 @@ + 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 = { +@@ -43,10 +55,8 @@ + Operation.OPCODE_JUMP_IF_FALSE: 2, + Operation.OPCODE_LESS_THAN: 3, + Operation.OPCODE_EQUALS: 3, ++ Operation.OPCODE_SET_REL_BASE: 1, + } +- # Opcodes that write to memory as their last parameter +- MEMORY_OPCODES = (Operation.OPCODE_ADD, Operation.OPCODE_MULTIPLY, Operation.OPCODE_INPUT, +- Operation.OPCODE_LESS_THAN, Operation.OPCODE_EQUALS) + + num_parameters = PARAMETER_COUNTS[self.opcode] + modes = [Operation.MODE_POSITION for i in range(num_parameters)] +@@ -56,17 +66,11 @@ + for mode_index, digit in zip(range(num_parameters), reversed(mode_str)): + modes[mode_index] = int(digit) + +- # The last argument (the address parameter) must always be in immediate mode +- # 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 self.opcode in MEMORY_OPCODES: +- modes[-1] = Operation.MODE_IMMEDIATE +- + return tuple(modes) + + # Run the given operation, starting at the given instruction pointer +- # Returns the address that the instruction pointer should become, followed by the output of the operation, if any +- def run(self, memory: List[int], instruction_pointer: int, program_input: Optional[int] = None) -> Tuple[int, Optional[int]]: ++ # 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, +@@ -77,19 +81,30 @@ + 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_EQUALS: Operation.equals, ++ Operation.OPCODE_SET_REL_BASE: Operation.set_rel_base + } + +- # Reset the output of a previous run ++ # 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] +- if mode == Operation.MODE_POSITION: ++ # 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}") + +@@ -101,47 +116,53 @@ + else: + jump_addr = func(self, memory, program_input, *args) + ++ out_addr = instruction_pointer + len(self.modes) + 1 + if jump_addr is not None: +- return jump_addr, self.output ++ out_addr = jump_addr + +- return instruction_pointer + len(self.modes) + 1, self.output ++ return out_addr, self.rel_base, self.output + +- def terminate(self, memory: List[int]) -> None: ++ def terminate(self, memory: Memory) -> None: + raise Halt("catch fire") + +- def add(self, memory: List[int], a: int, b: int, loc: int) -> None: ++ def add(self, memory: Memory, a: int, b: int, loc: int) -> None: + memory[loc] = a + b + +- def multiply(self, memory: List[int], a: int, b: int, loc: int) -> None: ++ def multiply(self, memory: Memory, a: int, b: int, loc: int) -> None: + memory[loc] = a * b + +- def input(self, memory: List[int], program_input: int, loc: int) -> None: ++ def input(self, memory: Memory, program_input: int, loc: int) -> None: + memory[loc] = program_input + +- def output(self, memory: List[int], value: int) -> None: ++ def output(self, memory: Memory, value: int) -> None: + self.output = value + print("OUTPUT:", value) + +- def jump_if_true(self, memory: List[int], test_value: int, new_instruction_pointer: int) -> Optional[int]: ++ 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: List[int], test_value: int, new_instruction_pointer: int) -> Optional[int]: ++ 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: List[int], a: int, b: int, loc: int): ++ def less_than(self, memory: Memory, a: int, b: int, loc: int) -> None: + memory[loc] = int(a < b) + +- def equals(self, memory: List[int], a: int, b: int, loc: int): ++ 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: List[int], program_inputs: List[int], initial_instruction_pointer: int = 0) -> (Optional[int], List[int]): ++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 = [] +- while i < len(memory): ++ # 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 +@@ -153,7 +174,7 @@ + input_cursor += 1 + + try: +- i, output = operation.run(memory, i, program_input) ++ i, rel_base, output = operation.run(memory, i, rel_base, program_input) + except Halt: + break +