Skip to content

Commit

Permalink
feat(2019): day 7 - part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
rpidanny committed Dec 13, 2023
1 parent 4e622ff commit 518dc27
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 76 deletions.
9 changes: 6 additions & 3 deletions 2019/Day05/helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import sys
from typing import Tuple

sys.path.append("utils")

from intcode import parse_instruction
def parse_instruction(instr: int) -> Tuple[int, int, int, int]:
instr = str(instr).zfill(5)
op_code = int(instr[-2:])
m_3, m_2, m_1 = map(int, instr[:3])
return m_3, m_2, m_1, op_code


def run_program(
Expand Down
40 changes: 31 additions & 9 deletions 2019/Day07/helpers.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
from copy import deepcopy
import sys
from copy import deepcopy

sys.path.append("utils")

from intcode import get_program, run_program

from intcode import IntCode

def amplify(inputs: list[str], phases: list[int], input: int) -> int:
program = get_program(inputs)

def amplify(prog: list[int], phases: list[int], input: int) -> int:
output = input
for amp in range(len(phases)):
mem = deepcopy(program)
mem = deepcopy(prog)
io_ip = [phases[amp], output]
io_op = []
run_program(mem, io_ip, io_op)
output = io_op[0]
state = IntCode(mem, 0, io_ip).run()
output = state.output
return output


def amplify_with_feedback(prog: list[int], phases: list[int], input: int) -> int:
mem_arr = [deepcopy(prog) for _ in range(len(phases))]
inputs_arr = [[phase] for phase in phases]
ip_arr = [0] * len(phases)

while True:
halt = False
for idx, (inputs, mem, ip) in enumerate(zip(inputs_arr, mem_arr, ip_arr)):
inputs.append(input)

state = IntCode(mem, ip, inputs).run()
ip_arr[idx] = state.ip

if state.halted:
halt = True
continue

input = state.output

if halt:
break

return input
16 changes: 12 additions & 4 deletions 2019/Day07/solutions.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import sys
from itertools import permutations

from helpers import amplify
sys.path.append("utils")

from helpers import amplify, amplify_with_feedback
from intcode import get_program


def part1(inputs: list[str]) -> int:
prog = get_program(inputs)

max_output = 0
for phases in list(permutations(range(5), 5)):
max_output = max(max_output, amplify(inputs, list(phases), 0))
max_output = max(max_output, amplify(prog, list(phases), 0))
return max_output


def part2(inputs: list[str]) -> int:
prog = get_program(inputs)

max_output = 0
for phases in list(permutations(range(9), 5)):
max_output = max(max_output, amplify(inputs, list(phases), 0))
for phases in list(permutations(range(5, 10), 5)):
max_output = max(max_output, amplify_with_feedback(prog, list(phases), 0))
return max_output
12 changes: 9 additions & 3 deletions 2019/Day07/test_solutions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
input = get_inputs(f"{current_dir}/input.txt")


@pytest.mark.skip(reason="completed")
class TestPart1:
def test_with_test_data_1(self):
assert part1(["3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0"]) == 43210
Expand Down Expand Up @@ -45,7 +44,14 @@ def test_with_test_data(self):
)
== 139629729
)
assert (
part2(
[
"3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54,-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4,53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10"
]
)
== 18216
)

@pytest.mark.skip(reason="not implemented")
def test_with_real_data(self):
assert part2(input) == 2
assert part2(input) == 79846026
180 changes: 128 additions & 52 deletions 2019/utils/intcode.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,132 @@
from enum import Enum
from typing import Tuple


class OpCode(Enum):
ADD = 1
MUL = 2
INPUT = 3
OUTPUT = 4
JUMP_IF_TRUE = 5
JUMP_IF_FALSE = 6
LESS_THAN = 7
EQUALS = 8
HALT = 99

def num_params(self):
return {
OpCode.ADD: 3,
OpCode.MUL: 3,
OpCode.INPUT: 1,
OpCode.OUTPUT: 1,
OpCode.JUMP_IF_TRUE: 2,
OpCode.JUMP_IF_FALSE: 2,
OpCode.LESS_THAN: 3,
OpCode.EQUALS: 3,
OpCode.HALT: 0,
}[self]


def get_program(inputs: list[str]) -> list[int]:
return list(map(lambda x: int(x), inputs[0].split(",")))


def parse_instruction(instr: int) -> Tuple[int, int, int, int]:
instr = str(instr).zfill(5)
op_code = int(instr[-2:])
m_3, m_2, m_1 = map(int, instr[:3])
return m_3, m_2, m_1, op_code


def run_program(
mem: list[int], io_ip: list[int] = [], io_op: list[int] = []
) -> list[int]:
ip = 0

def get_val(addr: int, mode: int) -> int:
return mem[addr] if mode else mem[mem[addr]]

num_params = {1: 3, 2: 3, 3: 1, 4: 1, 5: 2, 6: 2, 7: 3, 8: 3, 99: 0}

while True:
_, m_2, m_1, op_code = parse_instruction(mem[ip])

jump = False

if op_code == 1:
mem[mem[ip + 3]] = get_val(ip + 1, m_1) + get_val(ip + 2, m_2)
elif op_code == 2:
mem[mem[ip + 3]] = get_val(ip + 1, m_1) * get_val(ip + 2, m_2)
elif op_code == 3:
mem[mem[ip + 1]] = io_ip.pop(0)
elif op_code == 4:
io_op.append(get_val(ip + 1, m_1))
elif op_code == 5:
if jump := get_val(ip + 1, m_1) > 0:
ip = get_val(ip + 2, m_2)
elif op_code == 6:
if jump := get_val(ip + 1, m_1) == 0:
ip = get_val(ip + 2, m_2)
elif op_code == 7:
mem[mem[ip + 3]] = 1 if get_val(ip + 1, m_1) < get_val(ip + 2, m_2) else 0
elif op_code == 8:
mem[mem[ip + 3]] = 1 if get_val(ip + 1, m_1) == get_val(ip + 2, m_2) else 0
elif op_code == 99:
break
else:
raise Exception(f"Unknown opcode: {op_code}")

if not jump:
ip += num_params[op_code] + 1

return mem
return list(map(int, inputs[0].split(",")))


class ProgramState:
def __init__(self, ip: int, mem: list[int], output: int, halted: bool) -> None:
self.ip = ip
self.mem = mem
self.output = output
self.halted = halted


class IntCode:
def __init__(self, mem: list[int], ip: int, inputs: list[int] = []) -> None:
self.mem = mem
self.ip = ip
self.inputs = inputs

def run(
self,
) -> ProgramState:
while True:
op_code, m_1, m_2, _ = self.__get_next_instruction()

if op_code == OpCode.ADD:
self.__add(m_1, m_2)
elif op_code == OpCode.MUL:
self.__mul(m_1, m_2)
elif op_code == OpCode.INPUT:
self.__input()
elif op_code == OpCode.OUTPUT:
return self.__output(op_code, m_1)
elif op_code == OpCode.JUMP_IF_TRUE:
if self.__jump_if_true(m_1, m_2):
continue
elif op_code == OpCode.JUMP_IF_FALSE:
if self.__jump_if_false(m_1, m_2):
continue
elif op_code == OpCode.LESS_THAN:
self.__less_than(m_1, m_2)
elif op_code == OpCode.EQUALS:
self.__equals(m_1, m_2)
elif op_code == OpCode.HALT:
return self.__halt()
else:
raise Exception(f"Unknown opcode: {op_code}")

self.ip += op_code.num_params() + 1

def __get_next_instruction(self) -> Tuple[OpCode, int, int, int]:
instr = str(self.mem[self.ip]).zfill(5)
op_code = OpCode(int(instr[-2:]))
m_3, m_2, m_1 = map(int, instr[:3])
return op_code, m_1, m_2, m_3

def __get_val(self, addr: int, mode: int) -> int:
return self.mem[addr] if mode else self.mem[self.mem[addr]]

def __add(self, m_1: int, m_2: int) -> None:
self.mem[self.mem[self.ip + 3]] = self.__get_val(
self.ip + 1, m_1
) + self.__get_val(self.ip + 2, m_2)

def __mul(self, m_1: int, m_2: int) -> None:
self.mem[self.mem[self.ip + 3]] = self.__get_val(
self.ip + 1, m_1
) * self.__get_val(self.ip + 2, m_2)

def __input(self) -> None:
self.mem[self.mem[self.ip + 1]] = self.inputs.pop(0)

def __output(self, op_code: OpCode, m_1: int) -> int:
io_op = self.__get_val(self.ip + 1, m_1)
return ProgramState(self.ip + op_code.num_params() + 1, self.mem, io_op, False)

def __jump_if_true(self, m_1: int, m_2: int) -> bool:
if self.__get_val(self.ip + 1, m_1) > 0:
self.ip = self.__get_val(self.ip + 2, m_2)
return True
return False

def __jump_if_false(self, m_1: int, m_2: int) -> bool:
if self.__get_val(self.ip + 1, m_1) == 0:
self.ip = self.__get_val(self.ip + 2, m_2)
return True
return False

def __less_than(self, m_1: int, m_2: int) -> None:
self.mem[self.mem[self.ip + 3]] = (
1
if self.__get_val(self.ip + 1, m_1) < self.__get_val(self.ip + 2, m_2)
else 0
)

def __equals(self, m_1: int, m_2: int) -> None:
self.mem[self.mem[self.ip + 3]] = (
1
if self.__get_val(self.ip + 1, m_1) == self.__get_val(self.ip + 2, m_2)
else 0
)

def __halt(self) -> None:
return ProgramState(self.ip, self.mem, 0, True)
10 changes: 5 additions & 5 deletions 2019/utils/test_intcode.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from intcode import get_program, parse_instruction
from intcode import get_program


class TestIntCode:
def test_get_program(self):
assert get_program(["1002, 4, 3, 4, 33"]) == [1002, 4, 3, 4, 33]

def test_parse_instruction(self):
assert parse_instruction(1002) == (0, 1, 0, 2)
assert parse_instruction(1102) == (0, 1, 1, 2)
assert parse_instruction(1182) == (0, 1, 1, 82)
# def test_parse_instruction(self):
# assert parse_instruction(1002) == (0, 1, 0, 2)
# assert parse_instruction(1102) == (0, 1, 1, 2)
# assert parse_instruction(1182) == (0, 1, 1, 82)

0 comments on commit 518dc27

Please sign in to comment.