diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a2465eacd3d11e4fa56c7a234db2e2e4b8b8fb35..e5587b3faf105f7f9c75a59009e2a828d44827e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -98,8 +98,15 @@ day_4_question_1: stage: run script: - pipenv run python3 day_4/resolve_4_1.py + when: manual day_4_question_2: stage: run script: - - pipenv run python3 day_4/resolve_4_2.py \ No newline at end of file + - pipenv run python3 day_4/resolve_4_2.py + when: manual + +day_5_question_1: + stage: run + script: + - pipenv run python3 day_5/resolve_5_1.py diff --git a/day_5/resolve.material b/day_5/resolve.material new file mode 100644 index 0000000000000000000000000000000000000000..0ac4ffdf4cf5dcdb72814d28314ebaa22ff52282 --- /dev/null +++ b/day_5/resolve.material @@ -0,0 +1 @@ +3,225,1,225,6,6,1100,1,238,225,104,0,1101,78,5,225,1,166,139,224,101,-74,224,224,4,224,1002,223,8,223,1001,224,6,224,1,223,224,223,1002,136,18,224,101,-918,224,224,4,224,1002,223,8,223,101,2,224,224,1,224,223,223,1001,83,84,224,1001,224,-139,224,4,224,102,8,223,223,101,3,224,224,1,224,223,223,1102,55,20,225,1101,53,94,225,2,217,87,224,1001,224,-2120,224,4,224,1002,223,8,223,1001,224,1,224,1,224,223,223,102,37,14,224,101,-185,224,224,4,224,1002,223,8,223,1001,224,1,224,1,224,223,223,1101,8,51,225,1102,46,15,225,1102,88,87,224,1001,224,-7656,224,4,224,102,8,223,223,101,7,224,224,1,223,224,223,1101,29,28,225,1101,58,43,224,1001,224,-101,224,4,224,1002,223,8,223,1001,224,6,224,1,224,223,223,1101,93,54,225,101,40,191,224,1001,224,-133,224,4,224,102,8,223,223,101,3,224,224,1,223,224,223,1101,40,79,225,4,223,99,0,0,0,677,0,0,0,0,0,0,0,0,0,0,0,1105,0,99999,1105,227,247,1105,1,99999,1005,227,99999,1005,0,256,1105,1,99999,1106,227,99999,1106,0,265,1105,1,99999,1006,0,99999,1006,227,274,1105,1,99999,1105,1,280,1105,1,99999,1,225,225,225,1101,294,0,0,105,1,0,1105,1,99999,1106,0,300,1105,1,99999,1,225,225,225,1101,314,0,0,106,0,0,1105,1,99999,1008,226,677,224,1002,223,2,223,1005,224,329,1001,223,1,223,1107,226,677,224,1002,223,2,223,1005,224,344,1001,223,1,223,8,677,226,224,1002,223,2,223,1006,224,359,1001,223,1,223,1108,226,677,224,1002,223,2,223,1006,224,374,101,1,223,223,1007,677,677,224,102,2,223,223,1006,224,389,1001,223,1,223,8,226,677,224,102,2,223,223,1006,224,404,101,1,223,223,1007,226,226,224,1002,223,2,223,1006,224,419,101,1,223,223,107,677,226,224,1002,223,2,223,1006,224,434,1001,223,1,223,1007,226,677,224,102,2,223,223,1005,224,449,101,1,223,223,1107,226,226,224,1002,223,2,223,1005,224,464,1001,223,1,223,107,226,226,224,102,2,223,223,1006,224,479,101,1,223,223,108,226,226,224,1002,223,2,223,1006,224,494,101,1,223,223,107,677,677,224,102,2,223,223,1005,224,509,1001,223,1,223,1008,677,677,224,1002,223,2,223,1006,224,524,101,1,223,223,1107,677,226,224,102,2,223,223,1006,224,539,1001,223,1,223,108,677,226,224,102,2,223,223,1006,224,554,1001,223,1,223,1108,677,226,224,102,2,223,223,1005,224,569,1001,223,1,223,8,677,677,224,1002,223,2,223,1005,224,584,1001,223,1,223,7,677,677,224,1002,223,2,223,1005,224,599,101,1,223,223,1108,226,226,224,102,2,223,223,1006,224,614,101,1,223,223,1008,226,226,224,1002,223,2,223,1005,224,629,101,1,223,223,7,677,226,224,102,2,223,223,1006,224,644,1001,223,1,223,7,226,677,224,102,2,223,223,1005,224,659,101,1,223,223,108,677,677,224,1002,223,2,223,1006,224,674,101,1,223,223,4,223,99,226 diff --git a/day_5/resolve_5_1.py b/day_5/resolve_5_1.py new file mode 100644 index 0000000000000000000000000000000000000000..13f3a654a5fa0b9798e91da13a3b7f4b3a5f3f44 --- /dev/null +++ b/day_5/resolve_5_1.py @@ -0,0 +1,186 @@ +"""Resolution for the day 5 question 1.""" +from abc import ABC, abstractmethod + + +def main(): + """Main function for day 5 question 1.""" + data_set = get_data() + compute_program(data_set, 1) + + +def patch_program(program, noun, verb): + """Apply the replaces requested.""" + new_program = program.copy() + new_program[1] = noun + new_program[2] = verb + return new_program + + +def get_data(): + """Get data from file.""" + data_set = None + with open("day_5/resolve.material") as material: + raw_data_set = material.read().replace("\n", "") + + data_set = [int(data) for data in raw_data_set.split(",")] + return data_set + + +def compute_program(program, parameter=None): + """Computes the given program.""" + machine = Intcode(program, parameter) + machine.run_next() + return machine.program + + +class Intcode: + """Defines the intcode machine.""" + + def __init__(self, program, program_input=None): + """Initializes the machine.""" + self.program = program + self.parameter_pointer = 0 + self.input = program_input + self.output = None + + def run_next(self): + """Runs the next iteration for our program.""" + if self.operation == 1: + action = Opcode1(self) + action.run() + elif self.operation == 2: + action = Opcode2(self) + action.run() + elif self.operation == 3: + action = Opcode3(self) + action.run() + elif self.operation == 4: + action = Opcode4(self) + action.run() + elif self.operation != 99: + raise ValueError + + @property + def operation(self): + """Returns the operation.""" + first = self.program[self.parameter_pointer] + operation = int(str(first)[-2:]) + return operation + + +class Opcode(ABC): + """Metaclass to implement opcodes.""" + + def __init__(self, intcode_prog): + """Initialize our opcode.""" + self.intcode_prog = intcode_prog + self.length = 0 + + @property + def parameters(self): + """Returns all parameters based on length.""" + return self.intcode_prog.program[ + self.intcode_prog.parameter_pointer : self.intcode_prog.parameter_pointer + + self.length + ] + + @property + def parameter_modes(self): + """Returns a list to know the sequence for immediate and positionnal.""" + first = self.intcode_prog.program[self.intcode_prog.parameter_pointer] + parameter_modes = str(first)[:-2].zfill(self.length) + return parameter_modes + + @property + def values(self): + """Returns for this opcode block for parameter either the real value, value at position, or value if immediate.""" + values = [] + for i in range(self.length): + value = None + try: + if self.parameter_modes[-i] == "0": + value = self.intcode_prog.program[self.parameters[i]] + else: + value = self.parameters[i] + values.append(value) + except IndexError: + values.append(None) + return values + + @abstractmethod + def run(self): + """Do actions for this opcode.""" + pass + + def next(self): + """Sends pointer to next opcode.""" + self.intcode_prog.parameter_pointer += self.length + + +class Opcode1(Opcode): + """Opcode 1 is an addition.""" + + def __init__(self, intcode_prog): + """Initialize for opcode 1.""" + super().__init__(intcode_prog) + self.length = 4 + + def run(self): + """Do the addititon.""" + value_1 = self.values[1] + value_2 = self.values[2] + self.intcode_prog.program[self.parameters[3]] = value_1 + value_2 + self.next() + self.intcode_prog.run_next() + + +class Opcode2(Opcode): + """Opcode 2 is an multiplication.""" + + def __init__(self, intcode_prog): + """Initialize for opcode 2.""" + super().__init__(intcode_prog) + self.length = 4 + + def run(self): + """Do the multiplication.""" + value_1 = self.values[1] + value_2 = self.values[2] + self.intcode_prog.program[self.parameters[3]] = value_1 * value_2 + self.next() + self.intcode_prog.run_next() + + +class Opcode3(Opcode): + """Opcode 3 is parameter reading.""" + + def __init__(self, intcode_prog): + """Initialize for opcode 3.""" + super().__init__(intcode_prog) + self.length = 2 + + def run(self): + """Do the insert input.""" + self.intcode_prog.program[self.parameters[1]] = self.intcode_prog.input + self.next() + self.intcode_prog.run_next() + + +class Opcode4(Opcode): + """Opcode 4 is parameter writing.""" + + def __init__(self, intcode_prog): + """Initialize for opcode 4.""" + super().__init__(intcode_prog) + self.length = 2 + + def run(self): + """Do the save output.""" + self.intcode_prog.output = self.values[1] + print(self.intcode_prog.output) + self.next() + self.intcode_prog.run_next() + + +if __name__ == "__main__": + main() diff --git a/day_5/test_resolve_5_1.py b/day_5/test_resolve_5_1.py new file mode 100644 index 0000000000000000000000000000000000000000..39aa9af4f62e960a2a9e480d7fddeac5d6ce4334 --- /dev/null +++ b/day_5/test_resolve_5_1.py @@ -0,0 +1,250 @@ +"""Tests for question 2 day 2.""" +import pytest +from contextlib import nullcontext +import copy +from unittest.mock import patch, mock_open, MagicMock +from day_5 import resolve_5_1 + + +def concreter(A): + """Makes an abstract class concrete.""" + if "__abstractmethods__" not in A.__dict__: + return A + new_dict = A.__dict__.copy() + for abstractmethod in A.__abstractmethods__: + new_dict[abstractmethod] = lambda x, *args, **kwargs: (x, args, kwargs) + return type("dummy_concrete_{}".format(A.__name__), (A,), new_dict) + + +def test_main(capsys): + """Test for the main function.""" + real = None + resolve_5_1.main() + + captured = capsys.readouterr() + real_raw = captured.out + real = real_raw.split("\n")[0:-2] + expected = ["0"] * len(real) + assert real == expected + + +def test_patch_program(): + """Test for the patch_program function.""" + material = [0, 0, 0, 0] + expected = [0, 12, 2, 0] + + real = resolve_5_1.patch_program(material, 12, 2) + + assert real == expected + + +def test_get_data(): + """Test for the get_data function.""" + material = "3, 4\n\n" + expected = [3, 4] + + real = None + with patch("builtins.open", mock_open(read_data=material)): + real = resolve_5_1.get_data() + + assert real == expected + + +@pytest.mark.parametrize( + "material, expected", + [ + ( + [1, 9, 10, 3, 2, 3, 11, 0, 99, 30, 40, 50], + [3500, 9, 10, 70, 2, 3, 11, 0, 99, 30, 40, 50], + ), + ([1, 0, 0, 0, 99], [2, 0, 0, 0, 99]), + ([2, 3, 0, 3, 99], [2, 3, 0, 6, 99]), + ([2, 4, 4, 5, 99, 0], [2, 4, 4, 5, 99, 9801]), + ([1, 1, 1, 4, 99, 5, 6, 0, 99], [30, 1, 1, 4, 2, 5, 6, 0, 99]), + ], +) +def test_compute_program(material, expected): + """Test for the compute_program function.""" + real = resolve_5_1.compute_program(material) + + assert real == expected + + +class TestIntcode: + """Test the Intcode class.""" + + def test_init(self): + """Test the init function.""" + material = [1, 2, 3, 2, 99] + test_object = resolve_5_1.Intcode(material) + assert test_object.program == material + assert test_object.parameter_pointer == 0 + + @pytest.mark.parametrize( + "material, expected, context", + [ + ([1, 2, 3, 2, 99], [1, 2, 5, 2, 99], nullcontext()), + ([2, 2, 3, 2, 99], [2, 2, 6, 2, 99], nullcontext()), + ([5, 2, 3, 2, 99], [5, 2, 3, 2, 99], pytest.raises(ValueError)), + ], + ) + def test_run_next(self, material, expected, context): + """Test the run_next function.""" + test_object = resolve_5_1.Intcode(material) + with context: + test_object.run_next() + assert test_object.program == expected + + @pytest.mark.parametrize( + "material, expected", + [([1, 2, 3, 2, 99], 1), ([2, 2, 3, 2, 99], 2), ([10101, 2, 3, 2, 99], 1)], + ) + def test_operation(self, material, expected): + """Test the operation method.""" + test_object = resolve_5_1.Intcode(material) + result = test_object.operation + assert result == expected + + +class TestOpcode: + """Test the Opcode virtual class.""" + + OpcodeTester = concreter(resolve_5_1.Opcode) + test_material = [1, 2, 3] + test_intcode = resolve_5_1.Intcode(test_material) + test_object = OpcodeTester(test_intcode) + + def test_init(self): + """Test the init function.""" + assert self.test_object.length == 0 + assert self.test_object.intcode_prog == self.test_intcode + + @pytest.mark.parametrize( + "position, length, expected", [(0, 0, [],), (0, 1, [1]), (1, 2, [2, 3])] + ) + def test_parameters(self, position, length, expected): + """Test the parameters virtual property.""" + cur_test_object = copy.deepcopy(self.test_object) + cur_test_object.intcode_prog.parameter_pointer = position + cur_test_object.length = length + assert cur_test_object.parameters == expected + + @pytest.mark.parametrize( + "pointer, length,program, expected", + [(0, 4, [10101, 2, 2, 1, 99], "0101"), (0, 4, [1, 2, 2, 1, 99], "0000")], + ) + def test_parameter_modes(self, pointer, length, program, expected): + """Test the parameters virtual property.""" + cur_test_object = copy.deepcopy(self.test_object) + cur_test_object.intcode_prog.parameter_pointer = pointer + cur_test_object.length = length + cur_test_object.intcode_prog.program = program + assert cur_test_object.parameter_modes == expected + + @pytest.mark.parametrize( + "pointer, length, program, expected", + [ + (0, 4, [10101, 2, 2, 1, 99], [None, 2, 2, 1]), + (0, 4, [1, 2, 2, 1, 99], [2, 2, 2, 2]), + ], + ) + def test_values(self, pointer, length, program, expected): + """Test the parameters virtual property.""" + cur_test_object = copy.deepcopy(self.test_object) + cur_test_object.intcode_prog.parameter_pointer = pointer + cur_test_object.length = length + cur_test_object.intcode_prog.program = program + assert cur_test_object.values == expected + + @pytest.mark.parametrize( + "position, length, expected", [(0, 0, 0,), (0, 1, 1), (1, 2, 3)] + ) + def test_next(self, position, length, expected): + """Test the next function.""" + cur_test_object = copy.deepcopy(self.test_object) + cur_test_object.intcode_prog.parameter_pointer = position + cur_test_object.length = length + cur_test_object.next() + assert cur_test_object.intcode_prog.parameter_pointer == expected + + +class TestOpcode1: + """Test the Opcode1 class.""" + + test_material = [1, 2, 3, 2] + test_intcode = resolve_5_1.Intcode(test_material) + test_object = resolve_5_1.Opcode1(test_intcode) + + def test_init(self): + """Test the init function.""" + assert self.test_object.intcode_prog == self.test_intcode + assert self.test_object.length == 4 + + def test_run(self): + """Test the run function.""" + self.test_object.intcode_prog.run_next = MagicMock() + self.test_object.run() + self.test_object.intcode_prog.run_next.assert_called_once() + assert self.test_object.intcode_prog.program == [1, 2, 5, 2] + + +class TestOpcode2: + """Test the Opcode2 class.""" + + test_material = [1, 2, 3, 2] + test_intcode = resolve_5_1.Intcode(test_material) + test_object = resolve_5_1.Opcode2(test_intcode) + + def test_init(self): + """Test the init function.""" + assert self.test_object.intcode_prog == self.test_intcode + assert self.test_object.length == 4 + + def test_run(self): + """Test the run function.""" + self.test_object.intcode_prog.run_next = MagicMock() + self.test_object.run() + self.test_object.intcode_prog.run_next.assert_called_once() + assert self.test_object.intcode_prog.program == [1, 2, 6, 2] + + +class TestOpcode3: + """Test the Opcode3 class.""" + + test_material = [3, 2, 3, 2] + test_input = 43 + test_intcode = resolve_5_1.Intcode(test_material, test_input) + test_object = resolve_5_1.Opcode3(test_intcode) + + def test_init(self): + """Test the init function.""" + assert self.test_object.intcode_prog == self.test_intcode + assert self.test_object.length == 2 + + def test_run(self): + """Test the run function.""" + self.test_object.intcode_prog.run_next = MagicMock() + self.test_object.run() + self.test_object.intcode_prog.run_next.assert_called_once() + assert self.test_object.intcode_prog.program == [3, 2, 43, 2] + + +class TestOpcode4: + """Test the Opcode3 class.""" + + test_material = [4, 2, 3, 2] + test_intcode = resolve_5_1.Intcode(test_material) + test_object = resolve_5_1.Opcode4(test_intcode) + + def test_init(self): + """Test the init function.""" + assert self.test_object.intcode_prog == self.test_intcode + assert self.test_object.length == 2 + + def test_run(self): + """Test the run function.""" + self.test_object.intcode_prog.run_next = MagicMock() + self.test_object.run() + self.test_object.intcode_prog.run_next.assert_called_once() + assert self.test_object.intcode_prog.program == [4, 2, 3, 2] + assert self.test_object.intcode_prog.output == 3