Add solution to day 17 part 1

This commit is contained in:
Nick Krichevsky 2019-12-17 18:44:52 -05:00
parent e05d0c5578
commit 9daef0a6ea
4 changed files with 290 additions and 0 deletions

2
day17/input.txt Normal file
View file

@ -0,0 +1,2 @@
1,330,331,332,109,3890,1102,1,1182,16,1102,1471,1,24,101,0,0,570,1006,570,36,1002,571,1,0,1001,570,-1,570,1001,24,1,24,1105,1,18,1008,571,0,571,1001,16,1,16,1008,16,1471,570,1006,570,14,21102,58,1,0,1105,1,786,1006,332,62,99,21102,333,1,1,21102,73,1,0,1105,1,579,1102,0,1,572,1102,1,0,573,3,574,101,1,573,573,1007,574,65,570,1005,570,151,107,67,574,570,1005,570,151,1001,574,-64,574,1002,574,-1,574,1001,572,1,572,1007,572,11,570,1006,570,165,101,1182,572,127,101,0,574,0,3,574,101,1,573,573,1008,574,10,570,1005,570,189,1008,574,44,570,1006,570,158,1105,1,81,21102,1,340,1,1106,0,177,21102,1,477,1,1106,0,177,21101,0,514,1,21101,0,176,0,1105,1,579,99,21101,184,0,0,1106,0,579,4,574,104,10,99,1007,573,22,570,1006,570,165,102,1,572,1182,21101,375,0,1,21101,0,211,0,1105,1,579,21101,1182,11,1,21102,1,222,0,1106,0,979,21102,1,388,1,21101,0,233,0,1106,0,579,21101,1182,22,1,21102,244,1,0,1105,1,979,21102,401,1,1,21102,1,255,0,1106,0,579,21101,1182,33,1,21101,266,0,0,1105,1,979,21102,1,414,1,21102,277,1,0,1105,1,579,3,575,1008,575,89,570,1008,575,121,575,1,575,570,575,3,574,1008,574,10,570,1006,570,291,104,10,21101,0,1182,1,21101,313,0,0,1105,1,622,1005,575,327,1101,0,1,575,21102,327,1,0,1105,1,786,4,438,99,0,1,1,6,77,97,105,110,58,10,33,10,69,120,112,101,99,116,101,100,32,102,117,110,99,116,105,111,110,32,110,97,109,101,32,98,117,116,32,103,111,116,58,32,0,12,70,117,110,99,116,105,111,110,32,65,58,10,12,70,117,110,99,116,105,111,110,32,66,58,10,12,70,117,110,99,116,105,111,110,32,67,58,10,23,67,111,110,116,105,110,117,111,117,115,32,118,105,100,101,111,32,102,101,101,100,63,10,0,37,10,69,120,112,101,99,116,101,100,32,82,44,32,76,44,32,111,114,32,100,105,115,116,97,110,99,101,32,98,117,116,32,103,111,116,58,32,36,10,69,120,112,101,99,116,101,100,32,99,111,109,109,97,32,111,114,32,110,101,119,108,105,110,101,32,98,117,116,32,103,111,116,58,32,43,10,68,101,102,105,110,105,116,105,111,110,115,32,109,97,121,32,98,101,32,97,116,32,109,111,115,116,32,50,48,32,99,104,97,114,97,99,116,101,114,115,33,10,94,62,118,60,0,1,0,-1,-1,0,1,0,0,0,0,0,0,1,32,0,0,109,4,2101,0,-3,586,21002,0,1,-1,22101,1,-3,-3,21101,0,0,-2,2208,-2,-1,570,1005,570,617,2201,-3,-2,609,4,0,21201,-2,1,-2,1105,1,597,109,-4,2105,1,0,109,5,1201,-4,0,630,20102,1,0,-2,22101,1,-4,-4,21102,0,1,-3,2208,-3,-2,570,1005,570,781,2201,-4,-3,652,21001,0,0,-1,1208,-1,-4,570,1005,570,709,1208,-1,-5,570,1005,570,734,1207,-1,0,570,1005,570,759,1206,-1,774,1001,578,562,684,1,0,576,576,1001,578,566,692,1,0,577,577,21102,1,702,0,1105,1,786,21201,-1,-1,-1,1106,0,676,1001,578,1,578,1008,578,4,570,1006,570,724,1001,578,-4,578,21101,0,731,0,1106,0,786,1105,1,774,1001,578,-1,578,1008,578,-1,570,1006,570,749,1001,578,4,578,21102,756,1,0,1106,0,786,1106,0,774,21202,-1,-11,1,22101,1182,1,1,21101,774,0,0,1106,0,622,21201,-3,1,-3,1105,1,640,109,-5,2105,1,0,109,7,1005,575,802,21001,576,0,-6,20101,0,577,-5,1105,1,814,21102,1,0,-1,21101,0,0,-5,21101,0,0,-6,20208,-6,576,-2,208,-5,577,570,22002,570,-2,-2,21202,-5,41,-3,22201,-6,-3,-3,22101,1471,-3,-3,1202,-3,1,843,1005,0,863,21202,-2,42,-4,22101,46,-4,-4,1206,-2,924,21102,1,1,-1,1105,1,924,1205,-2,873,21101,35,0,-4,1106,0,924,2102,1,-3,878,1008,0,1,570,1006,570,916,1001,374,1,374,2101,0,-3,895,1102,1,2,0,1201,-3,0,902,1001,438,0,438,2202,-6,-5,570,1,570,374,570,1,570,438,438,1001,578,558,922,20101,0,0,-4,1006,575,959,204,-4,22101,1,-6,-6,1208,-6,41,570,1006,570,814,104,10,22101,1,-5,-5,1208,-5,59,570,1006,570,810,104,10,1206,-1,974,99,1206,-1,974,1101,0,1,575,21101,0,973,0,1106,0,786,99,109,-7,2106,0,0,109,6,21102,0,1,-4,21101,0,0,-3,203,-2,22101,1,-3,-3,21208,-2,82,-1,1205,-1,1030,21208,-2,76,-1,1205,-1,1037,21207,-2,48,-1,1205,-1,1124,22107,57,-2,-1,1205,-1,1124,21201,-2,-48,-2,1105,1,1041,21102,1,-4,-2,1106,0,1041,21102,-5,1,-2,21201,-4,1,-4,21207,-4,11,-1,1206,-1,1138,2201,-5,-4,1059,1202,-2,1,0,203,-2,22101,1,-3,-3,21207,-2,48,-1,1205,-1,1107,22107,57,-2,-1,1205,-1,1107,21201,-2,-48,-2,2201,-5,-4,1090,20102,10,0,-1,22201,-2,-1,-2,2201,-5,-4,1103,2101,0,-2,0,1106,0,1060,21208,-2,10,-1,1205,-1,1162,21208,-2,44,-1,1206,-1,1131,1105,1,989,21102,1,439,1,1105,1,1150,21101,477,0,1,1106,0,1150,21102,1,514,1,21101,1149,0,0,1105,1,579,99,21101,1157,0,0,1106,0,579,204,-2,104,10,99,21207,-3,22,-1,1206,-1,1138,2101,0,-5,1176,1202,-4,1,0,109,-6,2105,1,0,32,5,40,1,40,1,40,1,40,1,40,1,40,1,40,1,40,1,40,1,40,1,40,1,30,11,30,1,40,1,40,1,30,13,38,1,1,1,26,5,7,1,1,1,26,1,3,1,7,1,1,1,26,1,3,1,7,1,1,1,26,1,3,1,7,1,1,1,26,1,3,1,7,1,1,1,1,11,14,1,3,1,7,1,1,1,1,1,9,1,14,1,3,1,7,13,1,1,14,1,3,1,9,1,1,1,7,1,1,1,14,1,3,1,9,13,14,1,3,1,11,1,7,1,12,13,3,13,12,1,3,1,3,1,3,1,3,1,3,1,12,13,3,13,12,1,7,1,11,1,3,1,16,1,7,1,11,1,3,1,16,1,7,1,11,1,3,1,16,13,7,1,3,1,24,1,3,1,7,1,3,1,14,11,3,1,7,1,3,1,14,1,13,1,7,1,3,1,14,1,13,1,7,1,3,1,14,1,13,1,7,1,3,1,14,1,13,1,7,5,14,1,13,1,26,1,13,1,26,1,13,1,26,1,13,1,26,1,13,1,26,1,3,11,26,1,3,1,36,9,36,1,3,1,36,1,3,1,36,1,3,1,36,1,3,1,36,1,3,1,36,1,3,1,36,1,3,1,36,1,3,1,36,1,3,1,36,5,32

12
day17/py/Pipfile Normal file
View file

@ -0,0 +1,12 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
networkx = "*"
[requires]
python_version = "3.7"

36
day17/py/Pipfile.lock generated Normal file
View file

@ -0,0 +1,36 @@
{
"_meta": {
"hash": {
"sha256": "8498aea529913d1e405d3ce6dd6ec181feb148d271d0ef938d986b9f33fb73eb"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"decorator": {
"hashes": [
"sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce",
"sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d"
],
"version": "==4.4.1"
},
"networkx": {
"hashes": [
"sha256:cdfbf698749a5014bf2ed9db4a07a5295df1d3a53bf80bf3cbd61edf9df05fa1",
"sha256:f8f4ff0b6f96e4f9b16af6b84622597b5334bf9cae8cf9b2e42e7985d5c95c64"
],
"index": "pypi",
"version": "==2.4"
}
},
"develop": {}
}

240
day17/py/main.py Normal file
View file

@ -0,0 +1,240 @@
import collections
import enum
import sys
import networkx
from typing import List, Iterable, Tuple, Optional, Set
# 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, rel_base: int = 0):
# 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 = rel_base
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
def run(self, memory: Memory, instruction_pointer: int, program_input: Optional[int] = None) -> 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
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
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
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, base_delta: int) -> None:
self.rel_base += base_delta
# Executes the program, returning the instruction pointer to continue at (if the program paused), the relative base,
# 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, initial_rel_base: int = 0) -> Tuple[Optional[int], int, List[int]]:
i = initial_instruction_pointer
input_cursor = 0
outputs = []
rel_base = initial_rel_base
# Go up to the maximum address, not the number of addresses
while i < max(memory.keys()):
operation = Operation(memory[i], rel_base)
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, rel_base, outputs
program_input = program_inputs[input_cursor]
input_cursor += 1
try:
i = operation.run(memory, i, program_input)
output = operation.output
rel_base = operation.rel_base
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, rel_base, outputs
# Problem specific code starts here
SCAFFOLD_CHAR = ord('#')
def build_graph_from_program(program_memory: Memory) -> networkx.Graph:
# Add all edges that are directly above/below or directly left/right of our cursor
def add_adjacent_edges(scaffold_graph: networkx.Graph, row_cursor: int, col_cursor: int):
scaffold_graph.add_node((row_cursor, col_cursor))
for d_row in range(-1, 1):
for d_col in range(-1, 1):
if d_row == 0 and d_col == 0 or 0 not in (d_row, d_col):
continue
adjacent_coord = (row_cursor + d_row, col_cursor + d_col)
if adjacent_coord in scaffold_graph:
scaffold_graph.add_edge((row_cursor, col_cursor), adjacent_coord)
_, _, outputs = execute_program(program_memory, [])
scaffold_graph = networkx.Graph()
row_cursor = 0
col_cursor = 0
for item in outputs:
if item == ord('\n'):
row_cursor += 1
col_cursor = 0
continue
elif item == SCAFFOLD_CHAR:
add_adjacent_edges(scaffold_graph, row_cursor, col_cursor)
col_cursor += 1
return scaffold_graph
def part1(initial_program_memory: Memory) -> int:
scaffold_graph = build_graph_from_program(initial_program_memory)
return sum(row * col for row, col in scaffold_graph.nodes if scaffold_graph.degree((row, col)) > 2)
if __name__ == "__main__":
if len(sys.argv) != 2:
# Today's part 2 produces a lot of output, so i wanted to keep them separate
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))