10 votes

Day 7: Amplification Circuit

Today's problem description: https://adventofcode.com/2019/day/7


Join the Tildes private leaderboard! You can do that on this page, by entering join code 730956-de85ce0c.

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

10 comments

  1. [2]
    PapaNachos
    (edited )
    Link
    Well, I was able to do Part 1 without too much modification to my int-puter. Part 2 is going to be a bit more work, but I think I know how to do it. Part 1 - Python import math from itertools...

    Well, I was able to do Part 1 without too much modification to my int-puter. Part 2 is going to be a bit more work, but I think I know how to do it.

    Part 1 - Python
    import math
    from itertools import permutations
    verbose_mode = False
    super_verbose_mode = False
    automatic_io = True
    
    
    def read_instruction(instruction):
        #In order to deal with shorter length instructions we're going to 0-pad everything out to len==5
        #and then divide it back up
        if verbose_mode:
            print(f"Reading Instruction: {instruction}")
        instruction_components = [int(d) for d in str(instruction)]
        if len(instruction_components) == 1:
            #Pads opcode with non-functional 0, now len==2
            instruction_components.insert(0,0)
        if len(instruction_components) == 2:
            #Brings parameter_1 from null to 0, now len==3
            instruction_components.insert(0,0)
        if len(instruction_components) == 3:
            #Brings parameter_2 from null to 0, now len==4
            instruction_components.insert(0,0)
        if len(instruction_components) == 4:
            #Brings parameter_3 from null to 0, now len==5
            instruction_components.insert(0,0)
        opcode_portion = instruction_components[-2:]
        parameters_reversed = instruction_components[0:3]
        
        
        
        parameters = [int(parameters_reversed[2]), int(parameters_reversed[1]), int(parameters_reversed[0])]
        
        opcode = int((10 * opcode_portion[0]) + opcode_portion[1])
        
        if verbose_mode:
            print(f"Op Code {opcode}")
            print(f"Parameters {parameters}")
        
        return opcode, parameters
    
    def execute_instruction(op_code, parameter_codes, instruction_pointer):
        global memory
        global input_index
        global input_buffer
        global output_buffer
        address_0 = instruction_pointer + 1
        address_1 = instruction_pointer + 2
        address_2 = instruction_pointer + 3
        
        if super_verbose_mode:
            print(memory)
        
        if op_code == 1:
            val = read_value(address_0, parameter_codes[0]) + read_value(address_1, parameter_codes[1])
            write_value(address_2, val)
            instruction_pointer = instruction_pointer + 4
        elif op_code == 2:
            val = read_value(address_0, parameter_codes[0]) * read_value(address_1, parameter_codes[1])
            write_value(address_2, val)
            instruction_pointer = instruction_pointer + 4
        elif op_code == 3:
            if automatic_io == True:
                val = input_buffer[input_index]
                input_index = input_index + 1
            else:
                val = input("Input Requested: ")
            write_value(address_0, val)
            instruction_pointer = instruction_pointer + 2
        elif op_code == 4:
            if automatic_io == True:
                output_buffer.append(read_value(address_0, parameter_codes[0]))
            else:
                print(read_value(address_0, parameter_codes[0]))
            instruction_pointer = instruction_pointer + 2
        elif op_code == 5:
            val = read_value(address_0, parameter_codes[0])
            if val != 0:
                instruction_pointer = read_value(address_1, parameter_codes[1])
            else:
                instruction_pointer = instruction_pointer + 3
        elif op_code == 6:
            val = read_value(address_0, parameter_codes[0])
            if val == 0:
                instruction_pointer = read_value(address_1, parameter_codes[1])
            else:
                instruction_pointer = instruction_pointer + 3
        elif op_code == 7:
            val_0 = read_value(address_0, parameter_codes[0])
            val_1 = read_value(address_1, parameter_codes[1])
            
            if val_0 < val_1:
                write_value(address_2, 1)
            else:
                write_value(address_2, 0)
            instruction_pointer = instruction_pointer + 4
        elif op_code == 8:
            val_0 = read_value(address_0, parameter_codes[0])
            val_1 = read_value(address_1, parameter_codes[1])
            
            if val_0 == val_1:
                write_value(address_2, 1)
            else:
                write_value(address_2, 0)
            instruction_pointer = instruction_pointer + 4
        else:
            print('Something fucking broke')
            
        return instruction_pointer
    
    def read_value(address, parameter):
        global memory
        if verbose_mode:
            print("Reading memory")
            print(f"Address: {address}")
            print(f"Parameter: {parameter}")
        if super_verbose_mode:
            print(memory)
        #If parameter is 0, use the address stored in address
        if parameter == 0:
            new_address = memory[address]
            value = memory[new_address]
        #If parameter is 1, us the value stored in address
        elif parameter == 1:
            value = memory[address]
        return int(value)
    
    def write_value(address, value):
        global memory
        new_address = memory[address]
        memory[new_address] = value
        
    def run_computer(initial_memory):
        global memory
        global input_index
        memory = [int(x) for x in initial_memory.split(",")]
        
        input_index = 0
        instruction_pointer = 0
        op_code, parameters = read_instruction(memory[instruction_pointer])
    
    
        while op_code != 99:
            instruction_pointer = execute_instruction(op_code, parameters, instruction_pointer)
            op_code, parameters = read_instruction(memory[instruction_pointer])
        return memory
    
    initial_mem  = "MEMORY SEQUENCE GOES HERE"
    
    memory = []
    output_buffer = []
    input_index = []
    
    phase_list = [0,1,2,3,4]
    max_output = 0
    max_output_order = [0,1,2,3,4]
    
    for phase_sequence in list(permutations(phase_list)):
        output_buffer = []
        input_buffer = [phase_sequence[0],0]
        run_computer(initial_mem)
        input_buffer = [phase_sequence[1],output_buffer[0]]
        run_computer(initial_mem)
        input_buffer = [phase_sequence[2],output_buffer[1]]
        run_computer(initial_mem)
        input_buffer = [phase_sequence[3],output_buffer[2]]
        run_computer(initial_mem)
        input_buffer = [phase_sequence[4],output_buffer[3]]
        run_computer(initial_mem)
        if output_buffer[4] > max_output:
            max_output = output_buffer[4]
            max_output_order = phase_sequence
    print(max_output)
    print(max_output_order)
    

    Edit: Yeah, Part 2 is going to require not-insignificant rewrites of how my int-puter executes in order to account for the feedback

    Edit 2: Yeah, Part 2 was super annoying, but it was more so because I had to figure out 1)What the question was specifically asking. It was a bit unclear about how the looping was supposed to work and that sent me down wrong path 2)Fucking iterators. God damn it

    Part 2 - Still Python Okay, so I had to convert my intputer to an actual class rather than just a bunch of variables in memory. This let me keep everything consistent but required me to write "self.something" approximately 300,000,000,000 times give or take.

    The other change I made allowed me to effectively 'pause' while waiting for input. When an intputer wants an input and doesn't have one, it'll exit but save it's current conditions.

    So I looped through my intputers A-E feeding the output from one into the input of the next until I got an actual halt command. Then I finished the loop and output the result

    import math
    from itertools import permutations
    from itertools import cycle
    import itertools
    verbose_mode = False
    super_verbose_mode = False
    automatic_io = True
    
    
    class intputer:
        def __init__(self,initial_memory, name):
            #makes the int-puter
            self.memory = [int(x) for x in initial_memory.split(",")]
            self.input_buffer = []
            self.output_buffer = []
            self.input_index = 0
            self.instruction_pointer = 0
            self.name = name
            self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
        def add_input(self,val):
            self.input_buffer.append(val)
        def get_output(self):
            return self.output_buffer[-1]
        def get_name(self):
            return self.name
        def read_instruction(self,instruction):
            #In order to deal with shorter length instructions we're going to 0-pad everything out to len==5
            #and then divide it back up
            if verbose_mode:
                print(f"Reading Instruction: {instruction}")
            instruction_components = [int(d) for d in str(instruction)]
            if len(instruction_components) == 1:
                #Pads opcode with non-functional 0, now len==2
                instruction_components.insert(0,0)
            if len(instruction_components) == 2:
                #Brings parameter_1 from null to 0, now len==3
                instruction_components.insert(0,0)
            if len(instruction_components) == 3:
                #Brings parameter_2 from null to 0, now len==4
                instruction_components.insert(0,0)
            if len(instruction_components) == 4:
                #Brings parameter_3 from null to 0, now len==5
                instruction_components.insert(0,0)
            opcode_portion = instruction_components[-2:]
            parameters_reversed = instruction_components[0:3]
    
    
    
            parameters = [int(parameters_reversed[2]), int(parameters_reversed[1]), int(parameters_reversed[0])]
    
            opcode = int((10 * opcode_portion[0]) + opcode_portion[1])
    
            if verbose_mode:
                print(f"Op Code {opcode}")
                print(f"Parameters {parameters}")
    
            return opcode, parameters
    
        def execute_instruction(self, op_code, parameter_codes):
            address_0 = self.instruction_pointer + 1
            address_1 = self.instruction_pointer + 2
            address_2 = self.instruction_pointer + 3
    
            if super_verbose_mode:
                print(self.memory)
    
            if op_code == 1:
                val = self.read_value(address_0, parameter_codes[0]) + self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 2:
                val = self.read_value(address_0, parameter_codes[0]) * self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 3:
                if automatic_io == True:
                    if self.input_index >= len(self.input_buffer):
                        #We have less input than we need, wait
                        return self.instruction_pointer, 0
                    val = self.input_buffer[self.input_index]
                    self.input_index = self.input_index + 1
                else:
                    val = input("Input Requested: ")
                self.write_value(address_0, val)
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 4:
                if automatic_io == True:
                    self.output_buffer.append(self.read_value(address_0, parameter_codes[0]))
                else:
                    print(self.read_value(address_0, parameter_codes[0]))
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 5:
                val = self.read_value(address_0, parameter_codes[0])
                if val != 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 6:
                val = self.read_value(address_0, parameter_codes[0])
                if val == 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 7:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 < val_1:
                    self.write_value(address_2, 1)
                else:
                    self.write_value(address_2, 0)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 8:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 == val_1:
                    self.write_value(address_2, 1)
                else:
                    self.write_value(address_2, 0)
                self.instruction_pointer = self.instruction_pointer + 4
            else:
                print('Something fucking broke')
    
            return self.instruction_pointer, 1
    
        def read_value(self,address, parameter):
            if verbose_mode:
                print("Reading memory")
                print(f"Address: {address}")
                print(f"Parameter: {parameter}")
            if super_verbose_mode:
                print(self.memory)
            #If parameter is 0, use the address stored in address
            if parameter == 0:
                new_address = self.memory[address]
                value = self.memory[new_address]
            #If parameter is 1, us the value stored in address
            elif parameter == 1:
                value = self.memory[address]
            return int(value)
    
        def write_value(self,address, value):
            new_address = self.memory[address]
            self.memory[new_address] = value
    
        def run_puter(self):
            #SWITCH THIS TO JUST RUN 1 STEP AT A TIME
            status = 1
            while True:
                if self.op_code == 99:
                    #computer is done executing
                    return -1
                elif status == 0:
                    #pause to resume later
                    #IF WE PAUSE, LEAVE THE OPCODE AND PARAMETERS THE SAME, WE'RE WAITING FOR INPUT
                    return 0
                else:
                    self.instruction_pointer, status = self.execute_instruction(self.op_code, self.parameters)
                    self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
            return self.memory
    
    initial_mem  = "MEMORY GOES HERE"
    
    phase_list = [5,6,7,8,9]
    max_output = 0
    max_output_order = [5,6,7,8,9]
    
    for phase_sequence in list(permutations(phase_list)):
        #print(f"STARTING WITH PHASE SEQUENCE {phase_sequence}")
        intputer_A = intputer(initial_mem, 'Puter A')
        intputer_B = intputer(initial_mem, 'Puter B')
        intputer_C = intputer(initial_mem, 'Puter C')
        intputer_D = intputer(initial_mem, 'Puter D')
        intputer_E = intputer(initial_mem, 'Puter E')
        
        intputer_A.add_input(phase_sequence[0])
        intputer_B.add_input(phase_sequence[1])
        intputer_C.add_input(phase_sequence[2])
        intputer_D.add_input(phase_sequence[3])
        intputer_E.add_input(phase_sequence[4])
        
        puter_list = [intputer_A, intputer_B, intputer_C, intputer_D, intputer_E]
        
        result = None
        
        puter_loop = cycle([0,1,2,3,4])
        active_puter = puter_list[next(puter_loop)]
        
        buffered_input = 0
        
        while result != -1:
            #print(f'Running {active_puter.get_name()}')
            active_puter.add_input(buffered_input)
            result = active_puter.run_puter()
            buffered_input = active_puter.get_output()
            #print(f'Outputting {buffered_input}')
            active_puter = puter_list[next(puter_loop)]
            
        #FINAL LOOP JESUS FUCKING CHRIST
        remaining = 0
        if active_puter == intputer_B:
            remaining = 4
        if active_puter == intputer_C:
            remaining = 3
        if active_puter == intputer_D:
            remaining = 2
        if active_puter == intputer_E:
            remaining = 1
        for i in range(remaining):
            #print(f'Running {active_puter.get_name()}')
            active_puter.add_input(buffered_input)
            result = active_puter.run_puter()
            buffered_input = active_puter.get_output()
            #print(f'Outputting {buffered_input}')
            active_puter = puter_list[next(puter_loop)]    
        #print(f"FINAL OUTPUT {intputer_E.get_output()}")
        #print("----------------------------------------------")
        if intputer_E.get_output() > max_output:
            max_output = intputer_E.get_output()
            max_output_order = phase_sequence
    print(max_output)
    print(max_output_order)
    
    Not really spoilers that I struggled with It wants you to finish out the loop after one of the intputers halts.

    And make sure to remember to actually update your phase settings. I was using the old ones for a while and it caused some problems

    Edit 3: These are really ramping up in difficulty. I'm thoroughly enjoying myself

    4 votes
    1. Gyrfalcon
      Link Parent
      It looks like our solutions for part 2 have some similarities and differences. Discussion that may be spoiler for part 2 I think your approach of making each computer an object was the move. I...

      It looks like our solutions for part 2 have some similarities and differences.

      Discussion that may be spoiler for part 2

      I think your approach of making each computer an object was the move. I kind of handled it by managing their memory and instruction pointers outside in an additional method, which works but looking at your solution my way feels a bit clunky.

      3 votes
  2. Crestwave
    (edited )
    Link
    I don't like today's challenge; I found the descriptions for part 2 very undetailed and confusing, but that might just be me. Here's my horrible solution for part 1: Part 1 #!/usr/bin/awk -f {...

    I don't like today's challenge; I found the descriptions for part 2 very undetailed and confusing, but that might just be me. Here's my horrible solution for part 1:

    Part 1
    #!/usr/bin/awk -f
    { prog = prog $0 }
    
    END {
        split(prog, init, ",")
    
        highest = 0
        for (inputs[0] = 0; inputs[0] <= 4; ++inputs[0]) {
        for (inputs[1] = 0; inputs[1] <= 4; ++inputs[1]) {
            if (index(inputs[0], inputs[1]))
                continue
        for (inputs[2] = 0; inputs[2] <= 4; ++inputs[2]) {
            if (index(inputs[0] inputs[1], inputs[2]))
                continue
        for (inputs[3] = 0; inputs[3] <= 4; ++inputs[3]) {
            if (index(inputs[0] inputs[1] inputs[2], inputs[3]))
                continue
        for (inputs[4] = 0; inputs[4] <= 4; ++inputs[4]) {
            if (index(inputs[0] inputs[1] inputs[2] inputs[3], inputs[4]))
                continue
            signal = 0
            for (i = 0; i <= 4; ++i) {
                delete mem
                for (k = 1; k <= length(init); ++k)
                    mem[k-1] = init[k]
    
                input = inputs[i]
                for (ptr = 0; ptr < length(mem);) {
                    split(substr(mem[ptr], 1, length(mem[ptr])-2), modes, "")
                    c = int(substr(mem[ptr], length(mem[ptr])-1))
        
                    for (k = 1; k <= 2; ++k)
                        params[k] = modes[length(modes)-k+1] ? \
                            mem[ptr+k] : \
                            mem[mem[ptr+k]]
        
                    if (c == 1) {
                        mem[mem[ptr+3]] = params[1] + params[2]
                        ptr += 4
                    } else if (c == 2) {
                        mem[mem[ptr+3]] = params[1] * params[2]
                        ptr += 4
                    } else if (c == 3) {
                        mem[mem[ptr+1]] = int(input)
                        input = signal
                        ptr += 2
                    } else if (c == 4) {
                        signal = params[1]
                        ptr += 2
                    } else if (c == 5) {
                        ptr = params[1] ? params[2] : ptr+3
                    } else if (c == 6) {
                        ptr = params[1] ? ptr+3 : params[2]
                    } else if (c == 7) {
                        mem[mem[ptr+3]] = params[1] < params[2]
                        ptr += 4
                    } else if (c == 8) {
                        mem[mem[ptr+3]] = params[1] == params[2]
                        ptr += 4
                    } else if (c == 99) {
                        break
                    } else {
                        exit(1)
                    }
                }
                if (signal > highest)
                    highest = signal
                }
        }
        }
        }
        }
        }
        print highest
    }
    

    I'm still figuring out how to do part 2 in POSIX AWK...

    EDIT: I finally finished part 2. It seems like it maybe could have been fun, but was ruined by the fact that I missed so many things because of the vague descriptions. Maybe I'll try it again next time with the right thing in mind from the start. Here's my even worse solution for part 2, which is extremely slow and may take a couple of minutes to finish (but works!):

    Part 2
    #!/usr/bin/awk -f
    { prog = prog $0 }
    
    END {
        highest = 0
        for (inputs[0] = 5; inputs[0] <= 9; ++inputs[0]) {
        for (inputs[1] = 5; inputs[1] <= 9; ++inputs[1]) {
            if (index(inputs[0], inputs[1]))
                continue
        for (inputs[2] = 5; inputs[2] <= 9; ++inputs[2]) {
            if (index(inputs[0] inputs[1], inputs[2]))
                continue
        for (inputs[3] = 5; inputs[3] <= 9; ++inputs[3]) {
            if (index(inputs[0] inputs[1] inputs[2], inputs[3]))
                continue
        for (inputs[4] = 5; inputs[4] <= 9; ++inputs[4]) {
            if (index(inputs[0] inputs[1] inputs[2] inputs[3], inputs[4]))
                continue
    
            breakthrough = 0
    
            delete ptrs
            delete amp
            delete mem
    
            split(prog, init, ",")
            for (k = 1; k <= length(init); ++k)
                mem[k-1] = init[k]
    
            for (i in mem)
                for (j = 0; j <= 4; ++j)
                    amp[j] = amp[j] mem[i] ","
    
            signal = 0
            j = 0
            i = 0
    
            for (;;) {
                ptr = int(ptrs[i])
                split(amp[i], init, ",")
                for (k = 1; k <= length(init); ++k)
                    mem[k-1] = init[k]
    
                for (; ptr < length(mem);) {
                    split(substr(mem[ptr], 1, length(mem[ptr])-2), modes, "")
                    c = int(substr(mem[ptr], length(mem[ptr])-1))
        
                    for (k = 1; k <= 2; ++k)
                        params[k] = modes[length(modes)-k+1] ? \
                            mem[ptr+k] : \
                            mem[mem[ptr+k]]
        
                    if (c == 1) {
                        mem[mem[ptr+3]] = params[1] + params[2]
                        ptr += 4
                    } else if (c == 2) {
                        mem[mem[ptr+3]] = params[1] * params[2]
                        ptr += 4
                    } else if (c == 3) {
                        if (j <= 4) {
                            input = inputs[j++]
                            mem[mem[ptr+1]] = int(input)
                            ptr += 2
                            break
                        } else {
                            input = signal
                        }
    
                        mem[mem[ptr+1]] = int(input)
                        ptr += 2
                    } else if (c == 4) {
                        if (j > 4) {
                            signal = params[1]
                            ptr += 2
                            break
                        } else {
                            break
                        }
                        ptr += 2
                    } else if (c == 5) {
                        ptr = params[1] ? params[2] : ptr+3
                    } else if (c == 6) {
                        ptr = params[1] ? ptr+3 : params[2]
                    } else if (c == 7) {
                        mem[mem[ptr+3]] = params[1] < params[2]
                        ptr += 4
                    } else if (c == 8) {
                        mem[mem[ptr+3]] = params[1] == params[2]
                        ptr += 4
                    } else if (c == 99) {
                        breakthrough = 1
                        break
                    } else {
                        exit(1)
                    }
    
                }
    
                if (breakthrough)
                    break
    
                len = length(mem)
                amp[i] = mem[0]
                ptrs[i] = ptr
                for (ii = 1; ii <= len; ++ii)
                    amp[i] = amp[i] "," mem[ii]
    
                if (signal > highest)
                    highest = signal
    
                if (i < 4)
                    i += 1
                else
                    i = 0
        }
        }
        }
        }
        }
        }
            print highest
    }
    

    EDIT 2: Oh, apparently part 2's phase settings are just 5-9, not 0-9 like I thought. That one's on me, though. It's still slow with 5-9, but <3 seconds instead of ~3 minutes.

    3 votes
  3. Hvv
    (edited )
    Link
    Evidently, I can't stop myself from sprinting through interesting problems once I become aware of them, so I'm going to give a brutal first pass of my code essentially verbatim from my initial...

    Evidently, I can't stop myself from sprinting through interesting problems once I become aware of them, so I'm going to give a brutal first pass of my code essentially verbatim from my initial solution and a revised second pass with despaghettified code (and possibly better solution ideas) after I take a little time away.

    Hopefully this plan will make sense, though it might all just be a more glorified "messy code now, will edit later".

    Brutal first pass (C++):

    Part 1 My IntCode computer is the result of a previous brutal first pass and is thus almost hard coded.

    Luckily, the most one has to do here is store the original instructions and use appropriate data structures for input/output so that the resultant mess will spit out the right answer.

    Also, C++ has next_permutation, which is a godsend for avoiding messy permutation code.

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef vector<int> vi;
    typedef vector<ll> vl;
    vi x,w;
    const int F = 120;
    int ord[5] = {0,1,2,3,4};
    int arth(int a, int b, int c) {
    	int opc = a;
    	if(opc == 1) {
    		return b+c;
    	} else if(opc == 2) {
    		return b*c;
    	} else {
    		cout << "bad instruct\n";
    		return 0;
    	}
    }
    int main() {
    	string s;
    	cin >> s;
    	int t = 0;
    	int sgn = 1;
    	for(int i=0;i<s.size();i++) {
    		if(s[i] == ',') {
    			x.push_back(t*sgn);
    			t = 0;
    			sgn = 1;
    		} else if(s[i] == '-') {
    			sgn *= -1;
    		} else {
    			t *= 10;
    			t += (s[i]-'0');
    		}
    	}
    	x.push_back(t);
    	int maxval = -9999999;
    	for(int perm = 0;perm < F;perm++) {
    		int res = 0;
    		for(int i=0;i<5;i++) {
    			int pc = 0;
    			w = x;
    			queue<int> input;
    			input.push(ord[i]);
    			input.push(res);
    			while(pc < w.size()) {
    				int id = w[pc];
    				if(id == 99) {
    					break;
    				}
    				int params = id/100;
    				id %= 100;
    				bool pr[3];
    				for(int i=0;i<3;i++) {
    					pr[i] = (params%10 == 0);
    					params /= 10;
    				}
    				int nx[3];
    				for(int i=0;i<3;i++) {
    					if(i+pc+1 >= w.size()) {
    						break;
    					} else {
    						nx[i] = w[i+pc+1];
    						if(pr[i]) {
    							if(nx[i] < w.size()) {
    								nx[i] = w[nx[i]];
    							} else {
    							}
    						}
    					}
    				}
    				if(id == 3) {
    					int val;
    					val = input.front();
    					input.pop();
    					w[w[pc+1]] = val;
    					pc += 2;
    				} else if(id == 4) {
    					res = nx[0];
    					pc += 2;
    				} else if(id == 5) {
    					if(nx[0] != 0) {
    						pc = nx[1];
    					} else {
    						pc += 3;
    					}
    				} else if(id == 6) {
    					if(nx[0] == 0) {
    						pc = nx[1];
    					} else {
    						pc += 3;
    					}
    				} else if(id == 7) {
    					nx[2] = w[2+1+pc];
    					if(nx[0] < nx[1]) {
    						w[nx[2]] = 1;
    					} else {
    						w[nx[2]] = 0;
    					}
    					pc += 4;
    				} else if(id == 8) {
    					nx[2] = w[2+1+pc];
    					if(nx[0] == nx[1]) {
    						w[nx[2]] = 1;
    					} else {
    						w[nx[2]] = 0;
    					}
    					pc += 4;
    				} else {
    					nx[2] = w[2+1+pc];
    					int res = arth(id,nx[0],nx[1]);
    					w[nx[2]] = res;
    					pc += 4;
    				}
    			}
    		}
    		next_permutation(ord,ord+5);
    		maxval = max(maxval,res);
    	}
    	cout << maxval << '\n';
    }
    
    Part 2 We need to keep the states of 5 computers at once and achieve this with (surprise!) 5 copies of the computer.

    Furthermore, I/O is achieved with 5 queues, where each computer reads from its own queue and outputs to the next computer's input queue.

    Then, each computer's state is advanced one step at a time unless it need input when it has none, in which case that computer is temporarily halted.

    The last output is tracked with a variable and is processed once all computers halt.

    Again thanks to next_permutation, dealing with permutations requires just a few lines of number stuff and one for loop.

    Ambiguity Note: I might be reading this wrong, but the description first says that machine A takes its 5-9 number input immediately, but then goes on to say looping starts by feeding 0 into machine A.

    In fact, Machine A accepts the 5-9 input first, 0 second, and then whatever comes out of machine E.

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef vector<ll> vi;
    typedef vector<ll> vl;
    vi x,w[5];
    int ord[5] = {5,6,7,8,9};
    const int F = 120;
    ll arth(ll a, ll b, ll c) {
    	int opc = a;
    	if(opc == 1) {
    		return b+c;
    	} else if(opc == 2) {
    		return b*c;
    	} else {
    		cout << "bad instruct\n";
    		return 0;
    	}
    }
    int main() {
    	string s;
    	cin >> s;
    	int t = 0;
    	int sgn = 1;
    	for(int i=0;i<s.size();i++) {
    		if(s[i] == ',') {
    			x.push_back(t*sgn);
    			t = 0;
    			sgn = 1;
    		} else if(s[i] == '-') {
    			sgn *= -1;
    		} else {
    			t *= 10;
    			t += (s[i]-'0');
    		}
    	}
    	x.push_back(t);
    	int ma = -9999999;
    	for(int perm=0;perm<F;perm++) {
    		int pc[5] = {0,0,0,0,0};
    		queue<ll> ws[5];
    		for(int id=0;id<5;id++) {
    			w[id] = x;
    			ws[id].push(ord[id]);
    		}
    		ws[0].push(0);
    		//ambiguous question statement?
    		int lout = -1;
    		bool term[5] = {false,false,false,false,false};;
    		while(1) {
    			bool fin = true;
    			for(int idx=0;idx<5;idx++) {
    				if(term[idx]) continue;
    				fin = false;
    				if(pc[idx] < w[idx].size()) {
    					int id = w[idx][pc[idx]];
    					if(id == 99) {
    						term[idx] = true;
    						break;
    					}
    					int params = id/100;
    					id %= 100;
    					bool pr[3];
    					for(int i=0;i<3;i++) {
    						pr[i] = (params%10 == 0);
    						params /= 10;
    					}
    					int nx[3];
    					for(int i=0;i<3;i++) {
    						if(i+pc[idx]+1 >= w[idx].size()) {
    							break;
    						} else {
    							nx[i] = w[idx][i+pc[idx]+1];
    							if(pr[i]) {
    								if(nx[i] < w[idx].size()) {
    									nx[i] = w[idx][nx[i]];
    								} else {
    								}
    							}
    						}
    					}
    					if(id == 3) {
    						int val;
    						if(ws[idx].empty()) {
    							continue;
    						}
    						val = ws[idx].front();ws[idx].pop(); 
    						w[idx][w[idx][pc[idx]+1]] = val;
    						pc[idx] += 2;
    					} else if(id == 4) {
    						int nid = (idx+1)%5;
    						ws[nid].push(nx[0]);
    						lout = nx[0];
    						pc[idx] += 2;
    					} else if(id == 5) {
    						if(nx[0] != 0) {
    							pc[idx] = nx[1];
    						} else {
    							pc[idx] += 3;
    						}
    					} else if(id == 6) {
    						if(nx[0] == 0) {
    							pc[idx] = nx[1];
    						} else {
    							pc[idx] += 3;
    						}
    					} else if(id == 7) {
    						nx[2] = w[idx][2+1+pc[idx]];
    						if(nx[0] < nx[1]) {
    							w[idx][nx[2]] = 1;
    						} else {
    							w[idx][nx[2]] = 0;
    						}
    						pc[idx] += 4;
    					} else if(id == 8) {
    						nx[2] = w[idx][2+1+pc[idx]];
    						if(nx[0] == nx[1]) {
    							w[idx][nx[2]] = 1;
    						} else {
    							w[idx][nx[2]] = 0;
    						}
    						pc[idx] += 4;
    					} else {
    						nx[2] = w[idx][2+1+pc[idx]];
    						int res = arth(id,nx[0],nx[1]);
    						w[idx][nx[2]] = res;
    						pc[idx] += 4;
    					}
    				}
    			}
    			if(fin) break;
    		}
    		ma = max(ma,lout);
    		next_permutation(ord,ord+5);
    	}
    	cout << ma << "\n";
    }
    

    Second pass:

    Moved the computer code its own class and compartmentalized many of the procedures reused in solutions.

    It can definitely be further refined, but the main executed code to solve each of the problems is now much less ugly.

    Part 1 The new computer code handles I/O with its own internal queues, so all the main procedure needs to do is fiddle with numbers a little and handle permutations.
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef vector<int> vi;
    typedef vector<ll> vl;
    class Comp {
    	vl w,oc;
    	queue<ll> ins,outs;
    	ll pc;
    	bool term;
    	public:
    	Comp() {
    	}
    	Comp(vl init) {
    		oc = init;
    		w = oc;
    		term = false;
    		pc = 0;
    	}
    	void feed(ll val) {
    		ins.push(val);
    	}
    	bool empty_ins() {
    		return ins.empty();
    	}
    	bool empty_outs() {
    		return outs.empty();
    	}
    	ll poll() {
    		ll res = outs.front();
    		outs.pop();
    		return res;
    	}
    	void reset(){
    		w = oc;
    		term = false;
    		pc = 0;
    		ins = queue<ll>();
    		outs = queue<ll>();
    	}
    	ll get(ll id) {
    		return w[id];
    	}
    	bool run() {
    		while(!term) {
    			if(pc >= w.size()) {
    				term = true;
    				cout << "dropout termination\n";
    				return term;
    			}
    			int id = w[pc];
    			if(id == 99) {
    				term = true;
    				return term;
    			}
    			int params = id/100;
    			id %= 100;
    			bool pr[3];
    			for(int i=0;i<3;i++) {
    				pr[i] = (params%10 == 0);
    				params /= 10;
    			}
    			int nx[3] = {-1,-1,-1};
    			for(int i=0;i<3;i++) {
    				if(i+pc+1 >= w.size()) {
    					break;
    				} else {
    					nx[i] = w[i+pc+1];
    					if(pr[i]) {
    						if(nx[i] < w.size()) {
    							nx[i] = w[nx[i]];
    						} else {
    						}
    					}
    				}
    			}
    			if(id == 3) {
    				if(ins.empty()) {
    					return false;
    				}
    				int val = ins.front();
    				ins.pop();
    				w[w[pc+1]] = val;
    				pc += 2;
    			} else if(id == 4) {
    				outs.push(nx[0]);
    				pc += 2;
    			} else if(id == 5) {
    				if(nx[0] != 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 6) {
    				if(nx[0] == 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 7) {
    				nx[2] = w[2+1+pc];
    				if(nx[0] < nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 8) {
    				nx[2] = w[2+1+pc];
    				if(nx[0] == nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 1) {
    				nx[2] = w[2+1+pc];
    				w[nx[2]] = nx[0]+nx[1];
    				pc += 4;
    			} else if(id == 2) {
    				nx[2] = w[2+1+pc];
    				w[nx[2]] = nx[0]*nx[1];
    				pc += 4;
    			} else {
    				cout << "bad instruct\n";
    				return false;
    			}
    		}
    		return term;
    	}
    };
    
    vl x,w[5];
    void input() {
    	string s;
    	cin >> s;
    	int t = 0;
    	int sgn = 1;
    	for(int i=0;i<s.size();i++) {
    		if(s[i] == ',') {
    			x.push_back(t*sgn);
    			t = 0;
    			sgn = 1;
    		} else if(s[i] == '-') {
    			sgn *= -1;
    		} else {
    			t *= 10;
    			t += (s[i]-'0');
    		}
    	}
    	x.push_back(t);
    }
    int ord[5] = {0,1,2,3,4};
    const int F = 120;
    Comp loop[5];
    int main() {
    	input();
    	ll ma = -9999999;
    	for(int perm=0;perm<F;perm++) {
    		ll res = 0;
    		for(int i=0;i<5;i++) {
    			loop[i] = Comp(x);
    			loop[i].feed(ord[i]);
    			loop[i].feed(res);
    			loop[i].run();
    			res = loop[i].poll();
    		}
    		next_permutation(ord,ord+5);
    		ma = max(ma,res);
    	}
    	cout << ma << '\n';
    }
    
    Part 2 The new code halts on its own and returns whether it was interrupted or terminated on its own, so the main procedure just handles I/O and tracking permutations.
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef vector<int> vi;
    typedef vector<ll> vl;
    class Comp {
    	vl w,oc;
    	queue<ll> ins,outs;
    	ll pc;
    	bool term;
    	public:
    	Comp() {
    	}
    	Comp(vl init) {
    		oc = init;
    		w = oc;
    		term = false;
    		pc = 0;
    	}
    	void feed(ll val) {
    		ins.push(val);
    	}
    	bool empty_ins() {
    		return ins.empty();
    	}
    	bool empty_outs() {
    		return outs.empty();
    	}
    	ll poll() {
    		ll res = outs.front();
    		outs.pop();
    		return res;
    	}
    	void reset(){
    		w = oc;
    		term = false;
    		pc = 0;
    		ins = queue<ll>();
    		outs = queue<ll>();
    	}
    	ll get(ll id) {
    		return w[id];
    	}
    	bool run() {
    		while(!term) {
    			if(pc >= w.size()) {
    				term = true;
    				cout << "dropout termination\n";
    				return term;
    			}
    			int id = w[pc];
    			if(id == 99) {
    				term = true;
    				return term;
    			}
    			int params = id/100;
    			id %= 100;
    			bool pr[3];
    			for(int i=0;i<3;i++) {
    				pr[i] = (params%10 == 0);
    				params /= 10;
    			}
    			int nx[3] = {-1,-1,-1};
    			for(int i=0;i<3;i++) {
    				if(i+pc+1 >= w.size()) {
    					break;
    				} else {
    					nx[i] = w[i+pc+1];
    					if(pr[i]) {
    						if(nx[i] < w.size()) {
    							nx[i] = w[nx[i]];
    						} else {
    						}
    					}
    				}
    			}
    			if(id == 3) {
    				if(ins.empty()) {
    					return false;
    				}
    				int val = ins.front();
    				ins.pop();
    				w[w[pc+1]] = val;
    				pc += 2;
    			} else if(id == 4) {
    				outs.push(nx[0]);
    				pc += 2;
    			} else if(id == 5) {
    				if(nx[0] != 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 6) {
    				if(nx[0] == 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 7) {
    				nx[2] = w[2+1+pc];
    				if(nx[0] < nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 8) {
    				nx[2] = w[2+1+pc];
    				if(nx[0] == nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 1) {
    				nx[2] = w[2+1+pc];
    				w[nx[2]] = nx[0]+nx[1];
    				pc += 4;
    			} else if(id == 2) {
    				nx[2] = w[2+1+pc];
    				w[nx[2]] = nx[0]*nx[1];
    				pc += 4;
    			} else {
    				cout << "bad instruct\n";
    				return false;
    			}
    		}
    		return term;
    	}
    };
    vl x;
    void input() {
    	string s;
    	cin >> s;
    	int t = 0;
    	int sgn = 1;
    	for(int i=0;i<s.size();i++) {
    		if(s[i] == ',') {
    			x.push_back(t*sgn);
    			t = 0;
    			sgn = 1;
    		} else if(s[i] == '-') {
    			sgn *= -1;
    		} else {
    			t *= 10;
    			t += (s[i]-'0');
    		}
    	}
    	x.push_back(t);
    }
    int ord[5] = {5,6,7,8,9};
    const int F = 120;
    Comp loop[5];
    int main() {
    	input();
    	ll ma = -9999999;
    	for(int perm=0;perm<F;perm++) {
    		for(int i=0;i<5;i++) {
    			loop[i] = Comp(x);
    			loop[i].feed(ord[i]);
    		}
    		loop[0].feed(0);
    		ll lout = -1;
    		while(1) {
    			bool fin = true;
    			for(int i=0;i<5;i++) {
    				bool end = loop[i].run();
    				if(!end) {
    					fin = false;
    				}
    				int nx = (i+1)%5;
    				while(!loop[i].empty_outs()) {
    					ll val = loop[i].poll();
    					loop[nx].feed(val);
    					lout = val;
    				}
    			}
    			if(fin) break;
    		}
    		ma = max(ma,lout);
    		next_permutation(ord,ord+5);
    	}
    	cout << ma << '\n';
    }
    
    Isolated Computer Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef vector<int> vi;
    typedef vector<ll> vl;
    class Comp {
    	vl w,oc;
    	queue<ll> ins,outs;
    	ll pc;
    	bool term;
    	public:
    	Comp() {
    	}
    	Comp(vl init) {
    		oc = init;
    		w = oc;
    		term = false;
    		pc = 0;
    	}
    	void feed(ll val) {
    		ins.push(val);
    	}
    	bool empty_ins() {
    		return ins.empty();
    	}
    	bool empty_outs() {
    		return outs.empty();
    	}
    	ll poll() {
    		ll res = outs.front();
    		outs.pop();
    		return res;
    	}
    	void reset(){
    		w = oc;
    		term = false;
    		pc = 0;
    		ins = queue<ll>();
    		outs = queue<ll>();
    	}
    	ll get(ll id) {
    		return w[id];
    	}
    	bool run() {
    		while(!term) {
    			if(pc >= w.size()) {
    				term = true;
    				cout << "dropout termination\n";
    				return term;
    			}
    			int id = w[pc];
    			if(id == 99) {
    				term = true;
    				return term;
    			}
    			int params = id/100;
    			id %= 100;
    			bool pr[3];
    			for(int i=0;i<3;i++) {
    				pr[i] = (params%10 == 0);
    				params /= 10;
    			}
    			int nx[3] = {-1,-1,-1};
    			for(int i=0;i<3;i++) {
    				if(i+pc+1 >= w.size()) {
    					break;
    				} else {
    					nx[i] = w[i+pc+1];
    					if(pr[i]) {
    						if(nx[i] < w.size()) {
    							nx[i] = w[nx[i]];
    						} else {
    						}
    					}
    				}
    			}
    			if(id == 3) {
    				if(ins.empty()) {
    					return false;
    				}
    				int val = ins.front();
    				ins.pop();
    				w[w[pc+1]] = val;
    				pc += 2;
    			} else if(id == 4) {
    				outs.push(nx[0]);
    				pc += 2;
    			} else if(id == 5) {
    				if(nx[0] != 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 6) {
    				if(nx[0] == 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 7) {
    				nx[2] = w[2+1+pc];
    				if(nx[0] < nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 8) {
    				nx[2] = w[2+1+pc];
    				if(nx[0] == nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 1) {
    				nx[2] = w[2+1+pc];
    				w[nx[2]] = nx[0]+nx[1];
    				pc += 4;
    			} else if(id == 2) {
    				nx[2] = w[2+1+pc];
    				w[nx[2]] = nx[0]*nx[1];
    				pc += 4;
    			} else {
    				cout << "bad instruct\n";
    				return false;
    			}
    		}
    		return term;
    	}
    };
    
    2 votes
  4. Crespyl
    (edited )
    Link
    I rushed a bit and lost a bunch of time due to not correctly initializing my amplifiers in part 2. I had the actual feedback behavior correct for the longest time, so all my tests were passing,...

    I rushed a bit and lost a bunch of time due to not correctly initializing my amplifiers in part 2. I had the actual feedback behavior correct for the longest time, so all my tests were passing, just the search part was setting them up wrong and causing each check to output the same (plausibly correct) result.

    Day 7, Crystal
    #!/usr/bin/env crystal
    require "../lib/intcode.cr"
    
    Intcode.set_debug(ENV.has_key?("AOC_DEBUG") ? ENV["AOC_DEBUG"] == "true" : false)
    
    # Create 5 amplifier progras
    def make_amps(phase_settings, custom_prg = "")
      phase_settings.map { |phase_setting|
        if custom_prg.empty?
          vm = Intcode::VM.from_file("day7/input.txt")
        else
          vm = Intcode::VM.from_string(custom_prg)
        end
        vm.send_input(phase_setting)
        vm
      }
    end
    
    # Run each vm and feed its first output to the next machines input
    def run_serial(amps)
      amps.size.times do |i|
        amps[i].run
        output = amps[i].outputs.first
        if amps.size > i+1
          amps[i+1].send_input(output)
        end
      end
    end
    
    def run_feedback(amps)
      while amps.all? { |amp| amp.status != :halted }
        amps.each_with_index do |amp, i|
          case amp.status
          when :needs_input
            previous = amps[(i-1) % amps.size].read_output
            # if we don't get anything from the previous amp, just skip this for now
            if previous
              amp.send_input(previous)
              amp.run
            end
          when :ok
              amp.run
          end
        end
      end
    end
    
    # Test each permutation of an input set and find the best
    def find_best_serial(inputs)
      best_settings, best_output = inputs, 0
      inputs.each_permutation do |p|
        amps = make_amps(p)
        amps[0].send_input(0)
        run_serial(amps)
        output = amps.last.read_output
        if output && output > best_output
          best_output = output
          best_settings = p
        end
      end
      return best_settings, best_output
    end
    
    def find_best_feedback(inputs)
      best_settings, best_output = inputs, 0
      inputs.each_permutation do |p|
        amps = make_amps(p) # this was "make_amps(inputs)" for the longest time, rip :S
        amps[0].send_input(0)
        run_feedback(amps)
        output = amps.last.read_output
        if output && output > best_output
          best_output = output
          best_settings = p
        end
      end
      return best_settings, best_output
    end
    
    inputs = [0,1,2,3,4]
    best_settings, best_output = find_best_serial(inputs)
    
    puts "Part 1"
    puts "Best Settings: #{best_settings}"
    puts "Best Output: #{best_output}"
    
    # inputs = [9,8,7,6,5]
    # amps = make_amps(inputs, "3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26,27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5")
    
    # inputs = [9,7,8,5,6]
    # amps = make_amps(inputs, "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")
    
    # amps[0].send_input(0)
    # run_feedback(amps)
    # puts amps.last.read_output
    
    puts "\n Part 2"
    inputs = [5,6,7,8,9]
    best_settings, best_output = find_best_feedback(inputs)
    puts "Best Settings: #{best_settings}"
    puts "Best Output: #{best_output}"
    

    I went back and cleaned up a bit afterwards, to generalize running linked vms:

    Day 7 - refactored
    #!/usr/bin/env crystal
    require "../lib/intcode.cr"
    
    Intcode.set_debug(ENV.has_key?("AOC_DEBUG") ? ENV["AOC_DEBUG"] == "true" : false)
    
    # Create 5 amplifier progras
    def make_amps(phase_settings, custom_prg = "")
      phase_settings.map { |phase_setting|
        if custom_prg.empty?
          vm = Intcode::VM.from_file("day7/input.txt")
        else
          vm = Intcode::VM.from_string(custom_prg)
        end
        vm.send_input(phase_setting)
        vm
      }
    end
    
    # Run each vm and connects their outputs and inputs as indicated by the link
    # pairs
    #
    # Each pair (x,y) indicates that vm X should read its input from the output of
    # vm Y
    #
    # Returns the last output of the final vm
    def run_linked_vms(vms, links : Hash(Int32,Int32))
      # vms can be halted, input blocked, or ready; we keep working until all have
      # halted
      while vms.any? { |amp| amp.status != :halted }
        vms.each_with_index() do |amp, i|
    
          case amp.status
          when :ok           # nothing to do
            amp.run
          when :needs_input  # see if its linked vm has output and copy it over
            if links[i]
              linked_output = vms[links[i]].read_output
              if linked_output
                amp.send_input(linked_output)
                amp.run
              end
            end
          when :halted      # nothing to do
          end
    
        end
      end
    
      return vms.last.read_output
    end
    
    # Test each permutation of an input set and find the best
    def find_best(inputs, runner)
      best_settings, best_output = inputs, 0
      inputs.each_permutation do |p|
        amps = make_amps(p)
        amps[0].send_input(0)
        output = runner.call(amps)
        #output = amps.last.read_output
        if output && output > best_output
          best_output = output
          best_settings = p
        end
      end
      return best_settings, best_output
    end
    
    def find_best_serial(inputs)
      links = {1 => 0,
               2 => 1,
               3 => 2,
               4 => 3}
      find_best(inputs, ->(amps: Array(Intcode::VM)) { run_linked_vms(amps, links) })
    end
    
    def find_best_feedback(inputs)
      links = {0 => 4,
               1 => 0,
               2 => 1,
               3 => 2,
               4 => 3}
      find_best(inputs, ->(amps: Array(Intcode::VM)) { run_linked_vms(amps, links) })
    end
    
    inputs = [0,1,2,3,4]
    best_settings, best_output = find_best_serial(inputs)
    
    puts "Part 1"
    puts "Best Settings: #{best_settings}"
    puts "Best Output: #{best_output}"
    
    puts "\n Part 2"
    inputs = [5,6,7,8,9]
    best_settings, best_output = find_best_feedback(inputs)
    puts "Best Settings: #{best_settings}"
    puts "Best Output: #{best_output}"
    
    2 votes
  5. Macil
    (edited )
    Link
    Ooh, my work a few days ago to make a nice module for the Intcode VM paid off. Especially the part where I made it support callbacks for IO and allowed those callbacks to suspend the VM. I didn't...

    Ooh, my work a few days ago to make a nice module for the Intcode VM paid off. Especially the part where I made it support callbacks for IO and allowed those callbacks to suspend the VM. I didn't have to change my Intcode module for today's challenge.

    I've only just started learning and using Rust for this year's Advent of Code. I've been really liking it. It slightly reminds me of C++ but without the constant horror that I've probably done everything wrong and that the code is just working by coincidence while full of memory leaks and corruption issues. I never run into mystery memory errors at runtime here, and I feel very confident that there aren't mystery issues like that in my code. The bodies of functions can get a little boilerplatey with going through various types, but it's checked for various kinds of correctness and often doesn't have too many free parameters so it doesn't bother me much.

    I'm also a big fan of Rust's tooling. I don't have to put effort into build scripts, dependency management, or test configuration. One of my other favorite languages is Typescript, but I always feel a little uncomfortable while setting up a project for it like I'm going off the beaten path somehow. Rust's Cargo tool mostly works how I've expected.

    Day 7
    use intcode::{ExitReasonError, IOBox, InputResult, Machine, OutputResult, RunEndReason};
    use permutohedron::Heap;
    use std::collections::VecDeque;
    use std::error::Error;
    use std::io;
    
    fn main() -> Result<(), Box<dyn Error>> {
        let mut memory: Vec<i32> = Vec::new();
        let mut input = String::new();
        io::stdin().read_line(&mut input)?;
        for part in input.split(',') {
            let num: i32 = part.trim_end().parse()?;
            memory.push(num);
        }
        println!("part 1: {}", run(&memory, PART1_SETTINGS));
        println!("part 2: {}", run(&memory, PART2_SETTINGS));
        Ok(())
    }
    
    static PART1_SETTINGS: (&[i32], Attempter) = (&[0, 1, 2, 3, 4], attempt_part1);
    static PART2_SETTINGS: (&[i32], Attempter) = (&[5, 6, 7, 8, 9], attempt_part2);
    
    type Attempter = fn(&[i32], &[i32]) -> Result<i32, Box<dyn Error>>;
    
    fn run(memory: &[i32], (setting_values, attempter): (&[i32], Attempter)) -> i32 {
        let mut settings_vec = setting_values.to_vec();
        let mut largest_output: Option<i32> = None;
        let mut heap = Heap::new(&mut settings_vec);
        while let Some(settings) = heap.next_permutation() {
            match attempter(memory, settings) {
                Ok(v) => match largest_output {
                    Some(l) => {
                        if v > l {
                            largest_output = Some(v);
                        }
                    }
                    None => {
                        largest_output = Some(v);
                    }
                },
                Err(e) => {
                    eprintln!("Attempt error for settings {:?}: {}", settings, e);
                }
            }
        }
        largest_output.unwrap()
    }
    
    fn attempt_part1(memory: &[i32], settings: &[i32]) -> Result<i32, Box<dyn Error>> {
        let mut input_value: i32 = 0;
        for &setting in settings {
            let mut output_value: Option<i32> = None;
            let mut inputs: VecDeque<i32> = vec![setting, input_value].into_iter().collect();
            let mut machine = Machine::new(memory.to_vec());
            let mut io = IOBox::new(
                || match inputs.pop_front() {
                    Some(i) => Ok(InputResult::Value(i)),
                    None => Err(From::from("Machine asked for too many inputs")),
                },
                |output| {
                    output_value = Some(output);
                    Ok(OutputResult::Continue)
                },
            );
            machine.gas = 2000;
            match machine.run(&mut io)? {
                RunEndReason::Complete => {}
                e => return Err(Box::new(ExitReasonError(e))),
            }
            input_value = match output_value {
                Some(v) => v,
                None => return Err(From::from("Machine did not output anything")),
            };
        }
        Ok(input_value)
    }
    
    fn attempt_part2(memory: &[i32], settings: &[i32]) -> Result<i32, Box<dyn Error>> {
        let mut machines: Vec<(i32, Machine)> = settings
            .iter()
            .map(|&setting| (setting, Machine::new(memory.to_vec())))
            .collect();
    
        let mut first_iteration: bool = true;
        let mut last_iteration: bool = false;
        let mut input_value: i32 = 0;
        while !last_iteration {
            for (setting, machine) in &mut machines {
                let mut output_value: Option<i32> = None;
                let mut inputs: VecDeque<i32> = VecDeque::with_capacity(2);
                if first_iteration {
                    inputs.push_back(*setting);
                }
                inputs.push_back(input_value);
                let mut io = IOBox::new(
                    || match inputs.pop_front() {
                        Some(i) => Ok(InputResult::Value(i)),
                        None => Ok(InputResult::Interrupt),
                    },
                    |output| {
                        output_value = Some(output);
                        Ok(OutputResult::Continue)
                    },
                );
                machine.gas = 2000;
                match machine.run(&mut io)? {
                    RunEndReason::InputInterrupt => {}
                    RunEndReason::Complete => {
                        last_iteration = true;
                    }
                    e => return Err(Box::new(ExitReasonError(e))),
                }
                input_value = match output_value {
                    Some(v) => v,
                    None => return Err(From::from("Machine did not output anything")),
                };
            }
            first_iteration = false;
        }
        Ok(input_value)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_part1() {
            assert_eq!(
                run(
                    &[3, 15, 3, 16, 1002, 16, 10, 16, 1, 16, 15, 15, 4, 15, 99, 0, 0],
                    PART1_SETTINGS
                ),
                43210
            );
            assert_eq!(
                run(
                    &[
                        3, 23, 3, 24, 1002, 24, 10, 24, 1002, 23, -1, 23, 101, 5, 23, 23, 1, 24, 23,
                        23, 4, 23, 99, 0, 0
                    ],
                    PART1_SETTINGS
                ),
                54321
            );
            assert_eq!(
                run(
                    &[
                        3, 31, 3, 32, 1002, 32, 10, 32, 1001, 31, -2, 31, 1007, 31, 0, 33, 1002, 33, 7,
                        33, 1, 33, 31, 31, 1, 32, 31, 31, 4, 31, 99, 0, 0, 0
                    ],
                    PART1_SETTINGS
                ),
                65210
            );
        }
    
        #[test]
        fn test_part2() {
            assert_eq!(
                run(
                    &[
                        3, 26, 1001, 26, -4, 26, 3, 27, 1002, 27, 2, 27, 1, 27, 26, 27, 4, 27, 1001,
                        28, -1, 28, 1005, 28, 6, 99, 0, 0, 5
                    ],
                    PART2_SETTINGS
                ),
                139629729
            );
            assert_eq!(
                run(
                    &[
                        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
                    ],
                    PART2_SETTINGS
                ),
                18216
            );
        }
    }
    
    Intcode Module
    use std::convert::From;
    use std::error::Error;
    
    #[derive(Debug)]
    pub enum InputResult {
        Value(i32),
        Interrupt,
    }
    
    #[derive(Debug)]
    pub enum OutputResult {
        Continue,
        Interrupt,
    }
    
    pub struct IOBox<I, O>
    where
        I: FnMut() -> Result<InputResult, Box<dyn Error>>,
        O: FnMut(i32) -> Result<OutputResult, Box<dyn Error>>,
    {
        input: I,
        output: O,
    }
    
    impl<I, O> IOBox<I, O>
    where
        I: FnMut() -> Result<InputResult, Box<dyn Error>>,
        O: FnMut(i32) -> Result<OutputResult, Box<dyn Error>>,
    {
        pub fn new(input: I, output: O) -> IOBox<I, O> {
            IOBox { input, output }
        }
    }
    
    #[derive(Debug, Eq, PartialEq, Copy, Clone)]
    pub enum RunEndReason {
        InputInterrupt,
        OutputInterrupt,
        OutOfGas,
        Complete,
    }
    
    #[derive(Debug, Copy, Clone)]
    pub struct ExitReasonError(pub RunEndReason);
    
    impl std::fmt::Display for ExitReasonError {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(f, "Machine exited for unexpected reason ({:?})", self.0)
        }
    }
    
    impl Error for ExitReasonError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            None
        }
    }
    
    #[derive(Debug, Clone)]
    pub struct Machine {
        pub pc: usize,
        pub gas: u64,
        pub complete: bool,
        pub memory: Vec<i32>,
    }
    
    impl Machine {
        pub fn new(memory: Vec<i32>) -> Machine {
            Machine {
                pc: 0,
                gas: 0,
                complete: false,
                memory,
            }
        }
    
        pub fn run<
            I: FnMut() -> Result<InputResult, Box<dyn Error>>,
            O: FnMut(i32) -> Result<OutputResult, Box<dyn Error>>,
        >(
            &mut self,
            io: &mut IOBox<I, O>,
        ) -> Result<RunEndReason, Box<dyn Error>> {
            while self.gas > 0 {
                self.gas -= 1;
                let instruction: i32 = match self.memory.get(self.pc) {
                    Some(&value) => value,
                    None => {
                        return Err(From::from("Out-of-bounds access"));
                    }
                };
                let opcode = instruction % 100;
                match opcode {
                    1 => {
                        // add
                        if self.pc + 3 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        let op1 = self.read_argument(instruction, 0)?;
                        let op2 = self.read_argument(instruction, 1)?;
                        self.write_to_argument(instruction, 2, op1 + op2)?;
                        self.pc += 4;
                    }
                    2 => {
                        // multiply
                        if self.pc + 3 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        let op1 = self.read_argument(instruction, 0)?;
                        let op2 = self.read_argument(instruction, 1)?;
                        self.write_to_argument(instruction, 2, op1 * op2)?;
                        self.pc += 4;
                    }
                    3 => {
                        // input
                        if self.pc + 1 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        match (io.input)()? {
                            InputResult::Value(input_value) => {
                                self.write_to_argument(instruction, 0, input_value)?;
                                self.pc += 2;
                            }
                            InputResult::Interrupt => {
                                return Ok(RunEndReason::InputInterrupt);
                            }
                        }
                    }
                    4 => {
                        // output
                        if self.pc + 1 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        let value = self.read_argument(instruction, 0)?;
                        match (io.output)(value)? {
                            OutputResult::Continue => {
                                self.pc += 2;
                            }
                            OutputResult::Interrupt => {
                                return Ok(RunEndReason::OutputInterrupt);
                            }
                        }
                    }
                    5 => {
                        // jump-if-true
                        if self.pc + 2 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        let value = self.read_argument(instruction, 0)?;
                        if value != 0 {
                            let dst = self.read_argument(instruction, 1)?;
                            if dst < 0 {
                                return Err(From::from("Invalid jump"));
                            }
                            self.pc = dst as usize;
                        } else {
                            self.pc += 3;
                        }
                    }
                    6 => {
                        // jump-if-false
                        if self.pc + 2 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        let value = self.read_argument(instruction, 0)?;
                        if value == 0 {
                            let dst = self.read_argument(instruction, 1)?;
                            if dst < 0 {
                                return Err(From::from("Invalid jump"));
                            }
                            self.pc = dst as usize;
                        } else {
                            self.pc += 3;
                        }
                    }
                    7 => {
                        // less than
                        if self.pc + 3 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        let op1 = self.read_argument(instruction, 0)?;
                        let op2 = self.read_argument(instruction, 1)?;
                        let output = if op1 < op2 { 1 } else { 0 };
                        self.write_to_argument(instruction, 2, output)?;
                        self.pc += 4;
                    }
                    8 => {
                        // equals
                        if self.pc + 3 >= self.memory.len() {
                            return Err(From::from("Out-of-bounds access"));
                        }
                        let op1 = self.read_argument(instruction, 0)?;
                        let op2 = self.read_argument(instruction, 1)?;
                        let output = if op1 == op2 { 1 } else { 0 };
                        self.write_to_argument(instruction, 2, output)?;
                        self.pc += 4;
                    }
                    99 => {
                        // halt
                        self.complete = true;
                        return Ok(RunEndReason::Complete);
                    }
                    _ => return Err(From::from("Unknown opcode")),
                };
            }
            Ok(RunEndReason::OutOfGas)
        }
    
        fn get_mode(instruction: i32, arg: usize) -> i32 {
            match arg {
                0 => (instruction / 100) % 10,
                1 => (instruction / 1000) % 10,
                2 => (instruction / 10000) % 10,
                _ => panic!("invalid arg"),
            }
        }
    
        fn read_argument(&self, instruction: i32, arg: usize) -> Result<i32, Box<dyn Error>> {
            match Machine::get_mode(instruction, arg) {
                0 => {
                    let ptr = self.memory[self.pc + 1 + arg] as usize;
                    match self.memory.get(ptr) {
                        Some(&value) => Ok(value),
                        None => Err(From::from("Out-of-bounds access")),
                    }
                }
                1 => Ok(self.memory[self.pc + 1 + arg]),
                _ => Err(From::from("Invalid argument mode")),
            }
        }
    
        fn write_to_argument(
            &mut self,
            instruction: i32,
            arg: usize,
            value: i32,
        ) -> Result<(), Box<dyn Error>> {
            match Machine::get_mode(instruction, arg) {
                0 => {
                    let ptr = self.memory[self.pc + 1 + arg] as usize;
                    match self.memory.get_mut(ptr) {
                        Some(slot) => {
                            *slot = value;
                            Ok(())
                        }
                        None => Err(From::from("Out-of-bounds access")),
                    }
                }
                _ => Err(From::from("Invalid argument mode")),
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        use std::cmp::Ordering;
        use std::collections::VecDeque;
    
        fn run_and_return_memory(memory: Vec<i32>) -> Vec<i32> {
            let mut machine = Machine::new(memory);
            let mut io = IOBox::new(
                || Err(From::from("input should not be called")),
                |_| Err(From::from("output should not be called")),
            );
            machine.gas = 100;
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::Complete);
            assert!(machine.complete);
            machine.memory
        }
    
        fn run_and_return_output(memory: Vec<i32>, input: Vec<i32>) -> Vec<i32> {
            let mut output: Vec<i32> = Vec::new();
            let mut input: VecDeque<i32> = input.into_iter().collect();
            let mut machine = Machine::new(memory);
            let mut io = IOBox::new(
                || match input.pop_front() {
                    Some(value) => Ok(InputResult::Value(value)),
                    None => Err(From::from("input was called too many times")),
                },
                |value| {
                    output.push(value);
                    Ok(OutputResult::Continue)
                },
            );
            machine.gas = 100;
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::Complete);
            assert!(machine.complete);
            output
        }
    
        #[test]
        fn test_run_program() {
            assert_eq!(
                run_and_return_memory(vec![1, 0, 0, 0, 99]),
                [2, 0, 0, 0, 99]
            );
            assert_eq!(
                run_and_return_memory(vec![2, 3, 0, 3, 99]),
                [2, 3, 0, 6, 99]
            );
            assert_eq!(
                run_and_return_memory(vec![2, 4, 4, 5, 99, 0]),
                [2, 4, 4, 5, 99, 9801]
            );
            assert_eq!(
                run_and_return_memory(vec![1, 1, 1, 4, 99, 5, 6, 0, 99]),
                [30, 1, 1, 4, 2, 5, 6, 0, 99]
            );
            assert_eq!(
                run_and_return_memory(vec![1002, 4, 3, 4, 33]),
                [1002, 4, 3, 4, 99]
            );
        }
    
        #[test]
        fn test_comparisons() {
            // day 5 part 2 tests
    
            // check whether input is equal to 8
            assert_eq!(
                run_and_return_output(vec![3, 9, 8, 9, 10, 9, 4, 9, 99, -1, 8], vec![7]),
                [0]
            );
            assert_eq!(
                run_and_return_output(vec![3, 9, 8, 9, 10, 9, 4, 9, 99, -1, 8], vec![8]),
                [1]
            );
    
            // check whether input is less than 8
            assert_eq!(
                run_and_return_output(vec![3, 9, 7, 9, 10, 9, 4, 9, 99, -1, 8], vec![7]),
                [1]
            );
            assert_eq!(
                run_and_return_output(vec![3, 9, 7, 9, 10, 9, 4, 9, 99, -1, 8], vec![8]),
                [0]
            );
    
            // check whether input is equal to 8
            assert_eq!(
                run_and_return_output(vec![3, 3, 1108, -1, 8, 3, 4, 3, 99], vec![7]),
                [0]
            );
            assert_eq!(
                run_and_return_output(vec![3, 3, 1108, -1, 8, 3, 4, 3, 99], vec![8]),
                [1]
            );
    
            // check whether input is less than 8
            assert_eq!(
                run_and_return_output(vec![3, 3, 1107, -1, 8, 3, 4, 3, 99], vec![7]),
                [1]
            );
            assert_eq!(
                run_and_return_output(vec![3, 3, 1107, -1, 8, 3, 4, 3, 99], vec![8]),
                [0]
            );
    
            // check whether output is nonzero
            assert_eq!(
                run_and_return_output(
                    vec![3, 12, 6, 12, 15, 1, 13, 14, 13, 4, 13, 99, -1, 0, 1, 9],
                    vec![0]
                ),
                [0]
            );
            assert_eq!(
                run_and_return_output(
                    vec![3, 12, 6, 12, 15, 1, 13, 14, 13, 4, 13, 99, -1, 0, 1, 9],
                    vec![-1]
                ),
                [1]
            );
            assert_eq!(
                run_and_return_output(
                    vec![3, 3, 1105, -1, 9, 1101, 0, 0, 12, 4, 12, 99, 1],
                    vec![0]
                ),
                [0]
            );
            assert_eq!(
                run_and_return_output(
                    vec![3, 3, 1105, -1, 9, 1101, 0, 0, 12, 4, 12, 99, 1],
                    vec![-1]
                ),
                [1]
            );
    
            for i in -2..10 {
                let expected_output = match i.cmp(&8) {
                    Ordering::Less => 999,
                    Ordering::Equal => 1000,
                    Ordering::Greater => 1001,
                };
                assert_eq!(
                    run_and_return_output(
                        vec![
                            3, 21, 1008, 21, 8, 20, 1005, 20, 22, 107, 8, 21, 20, 1006, 20, 31, 1106,
                            0, 36, 98, 0, 0, 1002, 21, 125, 20, 4, 20, 1105, 1, 46, 104, 999, 1105, 1,
                            46, 1101, 1000, 1, 20, 4, 20, 1105, 1, 46, 98, 99
                        ],
                        vec![i]
                    ),
                    [expected_output]
                );
            }
        }
    
        #[test]
        fn test_input() {
            let mut inputs: VecDeque<i32> = vec![3, 99].into_iter().collect();
            let mut machine = Machine::new(vec![3, 2, -1, 4, -1]);
            let mut io = IOBox::new(
                || Ok(InputResult::Value(inputs.pop_front().unwrap())),
                |_| Err(From::from("Should not happen")),
            );
            machine.gas = std::u64::MAX;
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::Complete);
            assert!(machine.complete);
            assert_eq!(machine.memory, [3, 2, 3, 4, 99]);
        }
    
        #[test]
        fn test_input_interrupt() {
            let mut inputs: VecDeque<_> = vec![
                InputResult::Interrupt,
                InputResult::Value(3),
                InputResult::Value(99),
            ]
            .into_iter()
            .collect();
            let mut machine = Machine::new(vec![3, 2, -1, 4, -1]);
            let mut io = IOBox::new(
                || Ok(inputs.pop_front().unwrap()),
                |_| Err(From::from("Should not happen")),
            );
            machine.gas = std::u64::MAX;
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::InputInterrupt);
    
            assert!(!machine.complete);
            assert_ne!(machine.gas, 0);
            assert_eq!(machine.memory, [3, 2, -1, 4, -1]);
            assert_eq!(inputs.len(), 2);
    
            let mut io = IOBox::new(
                || Ok(inputs.pop_front().unwrap()),
                |_| Err(From::from("Should not happen")),
            );
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::Complete);
            assert!(machine.complete);
            assert_eq!(machine.memory, [3, 2, 3, 4, 99]);
        }
    
        #[test]
        fn test_output() {
            let mut outputs: Vec<i32> = Vec::new();
            let mut machine = Machine::new(vec![104, 1337, 4, 0, 99]);
            let mut io = IOBox::new(
                || Err(From::from("Should not happen")),
                |value| {
                    outputs.push(value);
                    Ok(OutputResult::Continue)
                },
            );
            machine.gas = std::u64::MAX;
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::Complete);
            assert!(machine.complete);
            assert_eq!(outputs, [1337, 104]);
        }
    
        #[test]
        fn test_output_interrupt() {
            let mut outputs: Vec<i32> = Vec::new();
            let mut machine = Machine::new(vec![104, 1337, 4, 0, 99]);
            let mut io = IOBox::new(
                || Err(From::from("Should not happen")),
                |value| {
                    outputs.push(value);
                    if outputs.len() == 1 {
                        return Ok(OutputResult::Interrupt);
                    }
                    Ok(OutputResult::Continue)
                },
            );
            machine.gas = std::u64::MAX;
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::OutputInterrupt);
            assert!(!machine.complete);
            assert_eq!(&outputs, &[1337]);
    
            let mut io = IOBox::new(
                || Err(From::from("Should not happen")),
                |value| {
                    outputs.push(value);
                    if outputs.len() == 1 {
                        return Ok(OutputResult::Interrupt);
                    }
                    Ok(OutputResult::Continue)
                },
            );
            assert_eq!(machine.run(&mut io).unwrap(), RunEndReason::Complete);
    
            assert!(machine.complete);
            assert_eq!(&outputs, &[1337, 1337, 104]);
        }
    
        #[test]
        #[should_panic]
        fn fail_run_program() {
            run_and_return_memory(vec![3, 0, 0, 0, 99]);
        }
    }
    
    2 votes
  6. aymm
    Link
    Doing it in Swift this year. Link to day 7 in my repo This took me a while. Spent the day doing some refactoring for the IntComputer. Also tried to fiddle around with parallel processing, but gave...

    Doing it in Swift this year. Link to day 7 in my repo

    This took me a while. Spent the day doing some refactoring for the IntComputer. Also tried to fiddle around with parallel processing, but gave up on that for a while. Most of the work is done in the IntComputer.swift file:

    IntComputer
    import Foundation
    
    public typealias Adress = Int
    public typealias Memory = [Int]
    public typealias Value = Int
    public typealias Output = (Value) -> Void
    public typealias Input = () -> Value?
    
    public enum ProgramState {
        case initialized
        case waitingForInput
        case running
        case finished
    }
    
    public class IOProgram {
        private var programCounter: Int
        private var memory: Memory
        private var input: Input = {() -> Value? in nil }
        private var output: Output
        private var inputQueue: Queue<Value>?
        public private(set) var state: ProgramState
        
        private init(program: Memory, output: @escaping Output) {
            self.memory = program
            self.programCounter = 0
            self.output = output
            self.state = .initialized
        }
        
        convenience init(program: Memory, input: @escaping Input, output: @escaping Output) {
            self.init(program: program, output: output)
            self.input = input
        }
        
        convenience init(program: Memory, inputQueue: Queue<Value>, output: @escaping Output) {
            self.init(program: program, output: output)
            self.input = dequeueInput
            self.inputQueue = inputQueue
        }
        
        public static func parse(program: String) -> Memory {
            return program.split(separator: ",").map {Int($0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))!}
        }
        
        public func queueInput(_ value: Value) {
            self.inputQueue?.enqueue(value)
        }
        
        private func dequeueInput() -> Value? {
            return inputQueue?.dequeue()
        }
        
        private func step() {
            let opCode = memory[programCounter]
            let operation = Operation(opCode: opCode)
            let instruction: Instruction
            
            let parameters = Parameters(opCode: opCode)
            switch operation {
            case .add:
                instruction = ArithemticInstruction(operation: +)
            case .mul:
                instruction = ArithemticInstruction(operation: *)
            case .inp:
                guard let inp = input() else {
                    state = .waitingForInput
                    return
                }
                instruction = InputInstruction(value: inp)
            case .out:
                instruction = OutputInstruction(output: output)
            case .jtr:
                instruction = JumpInstruction() { $0 != 0 }
            case .jfa:
                instruction = JumpInstruction() { $0 == 0 }
            case .lt:
                instruction = CompareInstruction() { $0 < $1 }
            case .eq:
                instruction = CompareInstruction() { $0 == $1 }
            case .hlt:
                instruction = HaltInstruction()
                state = .finished
            }
            
            programCounter = instruction.exec(
              memory: &memory,
              programCounter: programCounter,
              parameters: parameters
            )
        }
        
        public func run() {
            switch state {
            case .finished, .running:
                return
            default:
                state = .running
            }
            while state == .running {
                step()
            }
        }
    }
    
    enum Operation: Int {
        case add = 1
        case mul = 2
        case inp = 3
        case out = 4
        case jtr = 5
        case jfa = 6
        case lt = 7
        case eq = 8
        case hlt = 99
        
        init(opCode: Int) {
            self.init(rawValue: opCode % 100)!
        }
        
        var description : String {
            switch self {
            case .add: return "add"
            case .mul: return "mul"
            case .inp: return "inp"
            case .out: return "out"
            case .jtr: return "jtr"
            case .jfa: return "jfa"
            case .lt: return "let"
            case .eq: return "equ"
            case .hlt: return "hlt"
            }
        }
    }
    
    enum Mode: Int {
        case position = 0
        case immediate = 1
    
        func fetchValue(memory: Memory, pointer: Adress) -> Value {
            switch self {
            case .position:
                return memory[pointer]
            case .immediate:
                return pointer
            }
        }
    }
    
    struct Parameters {
        let first: Mode
        let second: Mode
        let third: Mode
    
        init(opCode: Int) {
            self.first = Mode(rawValue: opCode / 100 % 10)!
            self.second = Mode(rawValue: opCode / 1000 % 10)!
            self.third = Mode(rawValue: opCode / 10000 % 10)!
        }
    }
    
    protocol Instruction {
        func exec(memory: inout Memory, programCounter: Adress, parameters: Parameters) -> Adress
    }
    
    struct ArithemticInstruction: Instruction {
        let operation: (Value, Value) -> Value
        
        func exec(memory: inout Memory, programCounter: Adress, parameters: Parameters) -> Adress {
            let firstAddendAdress = memory[programCounter + 1]
            let secondAddendAdress = memory[programCounter + 2]
            let resultAdress = memory[programCounter + 3]
            
            let first = parameters.first.fetchValue(memory: memory, pointer: firstAddendAdress)
            let second = parameters.second.fetchValue(memory: memory, pointer: secondAddendAdress)
    
            memory[resultAdress] = operation(first, second)
            return programCounter + 4
        }
    }
    
    struct InputInstruction: Instruction {
        let value: Value
        
        func exec(memory: inout Memory, programCounter: Adress, parameters: Parameters) -> Adress {
            let inputAdress = memory[programCounter + 1]
            
            // Always positional
            memory[inputAdress] = value
            return programCounter + 2
        }
    }
    
    struct OutputInstruction: Instruction {
        let output: Output
        
        func exec(memory: inout Memory, programCounter: Adress, parameters: Parameters) -> Adress {
            let valueAdress = memory[programCounter + 1]
            
            output(parameters.first.fetchValue(memory: memory, pointer: valueAdress))
            return programCounter + 2
        }
    }
    
    struct JumpInstruction: Instruction {
        let condition: (Value) -> Bool
        
        func exec(memory: inout Memory, programCounter: Adress, parameters: Parameters) -> Adress {
            let valueAdress = memory[programCounter + 1]
            let destinationAdress = memory[programCounter + 2]
            
            let value = parameters.first.fetchValue(memory: memory, pointer: valueAdress)
            let destination = parameters.second.fetchValue(memory: memory, pointer: destinationAdress)
            
            return condition(value) ? destination : programCounter + 3
        }
    }
    
    struct CompareInstruction: Instruction {
        let compare: (Value, Value) -> Bool
        
        func exec(memory: inout Memory, programCounter: Adress, parameters: Parameters) -> Adress {
            let firstValueAdress = memory[programCounter + 1]
            let secondValueAdress = memory[programCounter + 2]
            let resultAdress = memory[programCounter + 3]
            
            let firstValue = parameters.first.fetchValue(memory: memory, pointer: firstValueAdress)
            let secondValue = parameters.second.fetchValue(memory: memory, pointer: secondValueAdress)
            let result = compare(firstValue, secondValue) ? 1 : 0
            
            memory[resultAdress] = result
            return programCounter + 4
        }
    }
    
    struct HaltInstruction: Instruction {
        func exec(memory: inout Memory, programCounter: Adress, parameters: Parameters) -> Adress {
            return programCounter
        }
    }
    
    2 votes
  7. mrnd
    Link
    This time the difference between parts was very large: part 1 was a breeze, but part 2 required much more. My original Intcode accepted only a static list as input, and outputted similar list. And...

    This time the difference between parts was very large: part 1 was a breeze, but part 2 required much more.

    My original Intcode accepted only a static list as input, and outputted similar list. And because everything is immutable in Elixir, linking the amplifiers together seemed very hard at first...

    One possibility was to take snapshots of memory and pointer position, and change inputs in-between. However, in the end I finally got to really benefit from Elixir: I ran each amplifier in a separate process, and added ability to pass inputs and outputs through messages. Now I can do interactive IO, but the old tests from earlier days pass too!

    The complete Intcode module
    defmodule Intcode do
      @moduledoc """
      Intcode interpreter.
    
      ## Position and immediate mode
    
      Two modes for instruction arguments are available. Position mode reads from the address specified, immediate mode uses the value itself. From right-to-left, two digits are for opcode, and the rest of the digits define mode for argument n.
    
      - Position mode opcode:  00001 / 1
      - Immediate mode opcode: 11101 (as many flags as there are parameters)
    
      ## Implemented instructions
    
      operator        | opcode | params
      --------------- | ------ | --------------
      add             | 1      | a, b, result
      multiply        | 2      | a, b, result
      read input      | 3      | write address
      write output    | 4      | read address
      jump-if-true    | 5      | bool, pointer
      jump-if-false   | 6      | bool, pointer
      less than       | 7      | a, b, result
      equals          | 8      | a, b, result
      halt            | 99     |
    
      """
    
      @doc """
      Run a program that reads process messages as input. 
    
      Requires `:intcode_pid` message with PID to write output to.
    
      If `result_pid` is given, sends the VM state to that process when finished.
    
      message      | direction  | value
      ------------ | ---------- | -----------------------------------------------
      :intcode_out | OUT        | VM state 
      :intcode_pid | IN         | PID to send output to when using "write output"
      :intcode_msg | OUT        | Message when using "write output"
    
      """
      def run_process(program, result_pid) do
        spawn(fn ->
          output_pid =
            receive do
              {:intcode_pid, pid} -> pid
            end
    
          result = execute(0, program, channel: output_pid, out: [])
    
          if is_pid(result_pid) do
            send(result_pid, {:intcode_out, result})
          end
        end)
      end
    
      @doc """
      Run a program with input list and return the output list.
      """
      def run(program, input) do
        {_, io} = execute(0, program, in: input, out: [])
        io[:out]
      end
    
      @doc """
      Run a program and return the value of address 0.
      """
      def run(program) do
        {memory, _} = execute(0, program, [])
        Enum.at(memory, 0)
      end
    
      @doc """
      Run a program with parameters i and j and return the value of address 0.
      """
      def run(program, i, j) do
        memory =
          program
          |> List.replace_at(1, i)
          |> List.replace_at(2, j)
    
        run(memory)
      end
    
      @doc """
      Execute a given program and return memory.
      """
      def execute(program) do
        {memory, _} = execute(0, program, [])
        memory
      end
    
      @doc """
      Execute a given program and return memory and output.
      """
      def execute(program, input) do
        execute(0, program, in: input, out: [])
      end
    
      @doc """
      Print out memory.
      """
      def memdump(memory) do
        memory
        |> Enum.map(&Integer.to_string/1)
        |> Enum.reduce("", &(&2 <> "," <> &1))
        |> String.slice(1..-1)
      end
    
      @doc """
      Loads a comma separated program from a given path.
      """
      def loadinput(file) do
        File.read!(file)
        |> String.trim()
        |> String.split(",")
        |> Enum.map(&String.to_integer/1)
      end
    
      # Private methods
    
      defp execute(pointer, memory, io) do
        if pointer < length(memory) do
          {new_pointer, new_memory, new_io} = parse_instruction(memory, pointer, io)
          execute(new_pointer, new_memory, new_io)
        else
          {memory, io}
        end
      end
    
      defp parse_instruction(memory, pointer, io) do
        digits =
          Enum.at(memory, pointer)
          |> Integer.digits()
          |> Enum.reverse()
    
        opcode_list = digits |> Enum.slice(0, 2)
        opcode = Enum.at(opcode_list, 0) + Enum.at(opcode_list, 1, 0) * 10
    
        modes = digits |> Enum.slice(2..-1)
    
        case opcode do
          # add
          1 ->
            {pointer + 4,
             change(
               memory,
               pointer + 3,
               reference(memory, pointer, modes, 1) +
                 reference(memory, pointer, modes, 2)
             ), io}
    
          # multiply
          2 ->
            {pointer + 4,
             change(
               memory,
               pointer + 3,
               reference(memory, pointer, modes, 1) *
                 reference(memory, pointer, modes, 2)
             ), io}
    
          # read input
          3 ->
            # Read from io[:in] Enumerable, if available,
            # otherwise expect a message
            stdin = Keyword.get(io, :in, nil)
            stdout = Keyword.get(io, :out, nil)
    
            {read_value, new_io} =
              if stdin == nil do
                receive do
                  {:intcode_msg, value} -> {value, io}
                end
              else
                {Enum.at(stdin, 0, -999),
                 [in: Enum.slice(stdin, 1..-1), out: stdout]}
              end
    
            {pointer + 2, change(memory, pointer + 1, read_value), new_io}
    
          # write output
          4 ->
            # Write to io[:out] list, if available,
            # otherwise send a message
            stdin = Keyword.get(io, :in, nil)
            stdout = Keyword.get(io, :out, nil)
            value = reference(memory, pointer, modes, 1)
    
            new_io =
              if stdin == nil do
                send(io[:channel], {:intcode_msg, value})
                [channel: io[:channel], out: [value | stdout]]
              else
                [in: stdin, out: [value | stdout]]
              end
    
            {pointer + 2, memory, new_io}
    
          # jump-if-true
          5 ->
            new_pointer =
              if reference(memory, pointer, modes, 1) != 0 do
                reference(memory, pointer, modes, 2)
              else
                pointer + 3
              end
    
            {new_pointer, memory, io}
    
          # jump-if-false
          6 ->
            new_pointer =
              if reference(memory, pointer, modes, 1) == 0 do
                reference(memory, pointer, modes, 2)
              else
                pointer + 3
              end
    
            {new_pointer, memory, io}
    
          # less than
          7 ->
            a = reference(memory, pointer, modes, 1)
            b = reference(memory, pointer, modes, 2)
    
            value =
              if a < b do
                1
              else
                0
              end
    
            {pointer + 4, change(memory, pointer + 3, value), io}
    
          # equals
          8 ->
            a = reference(memory, pointer, modes, 1)
            b = reference(memory, pointer, modes, 2)
    
            value =
              if a == b do
                1
              else
                0
              end
    
            {pointer + 4, change(memory, pointer + 3, value), io}
    
          # halt
          99 ->
            {length(memory), memory, io}
    
          unknown ->
            raise "Invalid instruction #{unknown}"
        end
      end
    
      defp reference(memory, pointer, modes, n) do
        if Enum.at(modes, n - 1, 0) == 1 do
          Enum.at(memory, pointer + n)
        else
          Enum.at(memory, Enum.at(memory, pointer + n))
        end
      end
    
      defp change(memory, pointer, value) do
        List.replace_at(memory, Enum.at(memory, pointer), value)
      end
    end
    
    Part 1 and part 2
    defmodule Day7 do
      @moduledoc "Amplification Circuit"
    
      def solution_part1(filename) do
        program = Intcode.loadinput(filename)
    
        Enum.map(get_permutations(), fn permutation ->
          run(program, permutation)
        end)
        |> Enum.max()
      end
    
      def solution_part2(filename) do
        program = Intcode.loadinput(filename)
        permutations = permute([5, 6, 7, 8, 9])
    
        Enum.each(permutations, fn permutation ->
          feedback_loop(program, permutation)
        end)
    
        # Receive all results and select maximum value
        Enum.map(0..(length(permutations) - 1), fn _ ->
          receive do
            {:intcode_out, {_, [channel: _, out: out]}} -> hd(out)
          end
        end)
        |> Enum.max()
      end
    
      @doc "Run the program once for every setting. Returns the output of the final program."
      def run(program, phase_settings) do
        Enum.reduce(phase_settings, 0, fn x, prev_output ->
          out = Intcode.run(program, [x, prev_output])
          hd(out)
        end)
      end
    
      @doc "Start the program once for every setting, setting up a loop."
      def feedback_loop(program, phase_settings) do
        # Start the amplifiers in seperate processes
        processes =
          Enum.map(0..4, fn index ->
            final = if index == 4, do: self(), else: nil
            Intcode.run_process(program, final)
          end)
    
        Enum.each(0..4, fn index ->
          process = Enum.at(processes, index)
          next = Enum.at(processes, index + 1, Enum.at(processes, 0))
    
          # Send the PID of the next amplifier in line
          send(process, {:intcode_pid, next})
    
          # Send the phase setting
          send(process, {:intcode_msg, Enum.at(phase_settings, index)})
        end)
    
        # Send initial input to the first amplifier
        send(hd(processes), {:intcode_msg, 0})
      end
    
      defp get_permutations do
        permute([0, 1, 2, 3, 4])
      end
    
      defp permute([]), do: [[]]
    
      defp permute(list) do
        for x <- list, y <- permute(list -- [x]), do: [x | y]
      end
    end
    

    Oh, and the permutation function is from Rosetta Code.

    2 votes
  8. Gyrfalcon
    Link
    This one was tough! I managed to complete it though. Not sure how many more of these I can get two stars on, since I do have real world responsibilities creeping up. Parts 1 and 2, Python 3 Part 2...

    This one was tough! I managed to complete it though. Not sure how many more of these I can get two stars on, since I do have real world responsibilities creeping up.

    Parts 1 and 2, Python 3

    Part 2 was the bulk of the work but I managed to accommodate it. I think the biggest things were fully understanding the initialization process and realizing that I could make blocking input. Once I had that I was able to manage them together and feed things around as needed. In retrospect I probably could have used some built in process and message passing tools but this was interesting so I don't regret it.

    import copy
    import itertools
    
    def get_operands(instructions, curr_instruction, pointer):
    
        for i in range(5 - len(curr_instruction)):
            curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
        out_location = instructions[pointer + 3]
    
        if curr_instruction[1] == 0:
            op_2_location = instructions[pointer+2]
        else:
            op_2_location = pointer + 2
    
        if curr_instruction[2] == 0:
            op_1_location = instructions[pointer+1]
        else:
            op_1_location = pointer + 1
    
        op_1 = instructions[op_1_location]
        op_2 = instructions[op_2_location]
    
        return [op_1, op_2, out_location]
    
    def get_io_operand(instructions, curr_instruction, pointer):
    
        for i in range(3 - len(curr_instruction)):
            curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
        if curr_instruction[-1] == 4 and curr_instruction[0] == 1:
            op = pointer + 1
        else:
            op = instructions[pointer + 1]
    
        return op
    
    def write_result(instructions, result, location):
    
        instructions[location] = result
        return instructions
    
    def add_instruction(instructions, curr_instruction, pointer):
        operands = get_operands(instructions, curr_instruction, pointer)
    
        result = operands[0] + operands[1]
        return write_result(instructions, result, operands[2])
    
    def mult_instruction(instructions, curr_instruction, pointer):
        operands = get_operands(instructions, curr_instruction, pointer)
    
        result = operands[0] * operands[1]
        return write_result(instructions, result, operands[2])
    
    def output_instruction(instructions, curr_instruction, pointer):
    
        operand = get_io_operand(instructions, curr_instruction, pointer)
    
        #print("Output is {}".format(instructions[operand]))
    
        return instructions[operand]
    
    def input_instruction(instructions, curr_instruction, pointer, user_input):
    
        operand = get_io_operand(instructions, curr_instruction, pointer)
    
        #print("User provided input is {}".format(user_input))
    
        return write_result(instructions, user_input, operand)
    
    def jump_instruction(instructions, curr_instruction, pointer, jump_type):
    
        operands = get_operands(instructions, curr_instruction, pointer)
    
        if ((operands[0] and jump_type) or (not operands[0] and not jump_type)):
            return operands[1]
        else:
            return (pointer + 3)
    
    def less_than_instruction(instructions, curr_instruction, pointer):
    
        operands = get_operands(instructions, curr_instruction, pointer)
    
        if operands[0] < operands[1]:
            return write_result(instructions, 1, operands[2])
        else:
            return write_result(instructions, 0, operands[2])
    
    def equals_instruction(instructions, curr_instruction, pointer):
    
        operands = get_operands(instructions, curr_instruction, pointer)
    
        if operands[0] == operands[1]:
            return write_result(instructions, 1, operands[2])
        else:
            return write_result(instructions, 0, operands[2])
    
    def process_instructions(instructions, inputs, pointer=0):
    
        output = None
        inputs = inputs[::-1]
    
        while True:
    
            if pointer > (len(instructions) - 1):
                break
            elif instructions[pointer] == 99:
                break
    
            curr_instruction = [int(num) for num in str(instructions[pointer])]
    
            if curr_instruction[-1] == 1:
                instructions = add_instruction(instructions, curr_instruction, pointer)
                pointer += 4
            elif curr_instruction[-1] == 2:
                instructions = mult_instruction(instructions, curr_instruction, pointer)
                pointer += 4
            elif curr_instruction[-1] == 3:
                if len(inputs) < 1: # Block on empty input
                    return output, instructions, pointer
                instructions = input_instruction(instructions, curr_instruction, pointer, inputs.pop())
                pointer += 2
            elif curr_instruction[-1] == 4:
                output = output_instruction(instructions, curr_instruction, pointer)
                pointer += 2
                return output, instructions, pointer
            elif curr_instruction[-1] == 5:
                pointer = jump_instruction(instructions, curr_instruction, pointer, True)
            elif curr_instruction[-1] == 6:
                pointer = jump_instruction(instructions, curr_instruction, pointer, False)
            elif curr_instruction[-1] == 7:
                instructions = less_than_instruction(instructions, curr_instruction, pointer)
                pointer += 4
            elif curr_instruction[-1] == 8:
                instructions = equals_instruction(instructions, curr_instruction, pointer)
                pointer += 4
    
        #print("PROGRAM TERMINATED")
    
        return output, instructions, pointer
    
    
    def amp_computers(instructions, amp_settings):
    
        amp_output = 0
    
        for setting in amp_settings:
            amp_output, __, __ = process_instructions(copy.copy(instructions), [setting, amp_output])
    
        return amp_output
    
    def calc_max_output(instructions):
    
        final_outputs = []
    
        amp_settings = list(itertools.permutations([0,1,2,3,4]))
    
        for settings in amp_settings:
            final_outputs.append(amp_computers(instructions, settings))
    
        return max(final_outputs)
    
    def feedback_computers(instructions, amp_settings):
    
        computer_states = []
    
        for i in range(5):
            computer_states.append([copy.copy(instructions), 0])
    
        exec_id = 0 # Keeps track of which computer can execute
        amp_output = 0
    
        # Initializes them with their settings and stuff
        for exec_id in range(5):
    
            _, \
            computer_states[exec_id][0], \
            computer_states[exec_id][1] = process_instructions(computer_states[exec_id][0],
                                          [amp_settings[exec_id]],
                                          computer_states[exec_id][1])
    
        exec_id = 0
    
        while True:
    
            amp_output, \
            computer_states[exec_id][0], \
            computer_states[exec_id][1] = process_instructions(computer_states[exec_id][0],
                                          [amp_output],
                                          computer_states[exec_id][1])
    
            if exec_id == 4 and amp_output is not None:
                final_amp_output = amp_output
    
            if computer_states[-1][0][computer_states[-1][1]] == 99:
                break
            else:
                exec_id += 1
                exec_id = exec_id % 5
    
        return final_amp_output
    
    def calc_max_feedback(instructions):
    
        final_outputs = []
    
        amp_settings = list(itertools.permutations([5,6,7,8,9]))
    
        for settings in amp_settings:
            final_outputs.append(feedback_computers(instructions, settings))
    
        return max(final_outputs)
    
    
    input_file = "PuzzleInput.txt"
    
    with open(input_file, 'r') as fid:
        data = fid.readline()
    
    instructions = data.strip().split(',')
    
    for idx in range(len(instructions)):
        instructions[idx] = int(instructions[idx])
    
    max_output = calc_max_output(instructions)
    
    print(max_output)
    
    
    input_file = "Test2_2.txt"
    
    with open(input_file, 'r') as fid:
        data = fid.readline()
    
    instructions = data.strip().split(',')
    
    for idx in range(len(instructions)):
        instructions[idx] = int(instructions[idx])
    
    max_output = calc_max_feedback(instructions)
    
    print(max_output)
    
    2 votes
  9. jzimbel
    Link
    I'm pretty far behind, but I really enjoyed this one as it was ideally suited to Go's channel-based concurrency model. I got each amplifier series to run concurrently, and even within a single...

    I'm pretty far behind, but I really enjoyed this one as it was ideally suited to Go's channel-based concurrency model. I got each amplifier series to run concurrently, and even within a single series had the individual amplifiers running in their own goroutines, passing inputs and outputs around via channels.

    Some notes

    I'm not including my intcode interpreter implementation because it's unchanged from day 5. All you really need to know is that interpreter.New takes a program ([]int), an input device (func() int), and an output device (func(int)).
    I'm also omitting the code that loads the puzzle input from a file into a string as I keep that logic in a separate package that's used by all of my solutions.

    If you want to view the code in GitHub, it's here. Go code is kind of rough to view in-browser—it uses tabs for indentation, and most browsers have default tab width set to 8 spaces. In GitHub you can set the tab width using the ?ts query param. I set it to 2 in that link.

    Parts 1 and 2, Go
    package d07
    
    import (
    	"strconv"
    	"strings"
    	"sync"
    
    	"github.com/jzimbel/adventofcode-go/solutions"
    	"github.com/jzimbel/adventofcode-go/solutions/y2019/interpreter"
    	"modernc.org/mathutil"
    )
    
    const (
    	ampCount          = 5
    	initialInput      = 0
    	minPhase     uint = 0
    	maxPhase     uint = 4
    )
    
    // Implements sort.Interface to take advantage of mathutil Permutation functions
    type phaseSettings [ampCount]uint
    
    func (ps *phaseSettings) Len() int {
    	return len(ps)
    }
    
    func (ps *phaseSettings) Less(i, j int) bool {
    	return ps[i] < ps[j]
    }
    
    func (ps *phaseSettings) Swap(i, j int) {
    	ps[i], ps[j] = ps[j], ps[i]
    }
    
    // phaseSettingsGenerator returns a channel that receives all permutations of phase settings and then closes.
    // ch1 := phaseSettingsGenerator(0)
    // ch1 will receive [0 1 2 3 4], [0 1 2 4 3], ...
    // ch2 := phaseSettingsGenerator(5)
    // ch2 will receive [5 6 7 8 9], [5 6 7 9 8], ...
    func phaseSettingsGenerator(offset uint) <-chan *phaseSettings {
    	ch := make(chan *phaseSettings)
    
    	go func() {
    		defer close(ch)
    		ps := &phaseSettings{}
    		for i := uint(0); i < ampCount; i++ {
    			ps[i] = i + offset
    		}
    		mathutil.PermutationFirst(ps)
    
    		var done bool
    		for !done {
    			psCopy := *ps
    			ch <- &psCopy
    			done = !mathutil.PermutationNext(ps)
    		}
    	}()
    
    	return ch
    }
    
    // makeInputDevice returns an input function to be used by the amplifier intcode program.
    // The first time the input is called, it return the phase setting for the amplifier.
    // All future calls return values received from the given channel.
    func makeInputDevice(phaseSetting uint, ch <-chan int) func() int {
    	callCount := 0
    	return func() (n int) {
    		defer func() { callCount++ }()
    		switch callCount {
    		case 0:
    			n = int(phaseSetting)
    		default:
    			n = <-ch
    		}
    		return
    	}
    }
    
    // makeOutputDevice returns an output function to be used by the amplifier intcode program.
    // This function sends its argument to the given channel.
    func makeOutputDevice(ch chan<- int) func(int) {
    	return func(n int) {
    		ch <- n
    	}
    }
    
    // makeLoopingOutputDevice is like makeOutputDevice, but the function it produces sends values to
    // two given channels instead of one. This allows for signals to be passed in a loop but also received
    // by an outside function that's interested in the final output of the amplifiers.
    func makeLoopingOutputDevice(loop chan<- int, output chan<- int) func(int) {
    	return func(n int) {
    		loop <- n
    		output <- n
    	}
    }
    
    // runAmplifiers runs a series of amplifiers with the given phase settings and returns their output.
    func runAmplifiers(codes []int, settings *phaseSettings) (signal int) {
    	// 0 -> Amp A -> Amp B -> Amp C -> Amp D -> Amp E -> (to thrusters)
    	// 5 amps, 6 channels
    	chs := [ampCount + 1]chan int{}
    	for i := range chs {
    		chs[i] = make(chan int)
    	}
    
    	for i := 0; i < ampCount; i++ {
    		go func(icpy int) {
    			interpreter.New(codes, makeInputDevice(settings[icpy], chs[icpy]), makeOutputDevice(chs[icpy+1])).Run()
    		}(i)
    	}
    
    	chs[0] <- initialInput
    	return <-chs[ampCount]
    }
    
    // runAmplifierLoop runs a series of amplifiers in a loop with the given phase settings and returns their final output when they halt.
    func runAmplifierLoop(codes []int, settings *phaseSettings) (signal int) {
    	// 0 -> Amp A -> Amp B -> Amp C -> Amp D -> Amp E -> (to thrusters upon Amp E halt)
    	//    0        1        2        3        4        0
    	// 5 amps, 5 channels
    	chs := [ampCount]chan int{}
    	for i := range chs {
    		if i == 0 {
    			// use a buffered channel for the first channel so that it can receive
    			// one more value that won't be consumed during the final iteration of the loop
    			chs[i] = make(chan int, 1)
    		} else {
    			chs[i] = make(chan int)
    		}
    	}
    	// final amplifier also sends to this channel so that we can capture outputs
    	output := make(chan int)
    
    	for i := 0; i < ampCount; i++ {
    		go func(icpy int) {
    			var outDevice func(int)
    			if icpy == ampCount-1 {
    				// when this interpreter halts, the whole amplifier loop is done
    				defer close(output)
    				outDevice = makeLoopingOutputDevice(chs[(icpy+1)%ampCount], output)
    			} else {
    				outDevice = makeOutputDevice(chs[(icpy+1)%ampCount])
    			}
    			interpreter.New(codes, makeInputDevice(settings[icpy], chs[icpy]), outDevice).Run()
    		}(i)
    	}
    
    	chs[0] <- initialInput
    	var finalSignal int
    	for n := range output {
    		finalSignal = n
    	}
    	return finalSignal
    }
    
    func run(codes []int, phaseSettingOffset uint, runner func([]int, *phaseSettings) int) (maxSignal int) {
    	ch := make(chan int)
    	wg := sync.WaitGroup{}
    
    	for settings := range phaseSettingsGenerator(phaseSettingOffset) {
    		wg.Add(1)
    		go func(settings *phaseSettings) {
    			defer wg.Done()
    			ch <- runner(codes, settings)
    		}(settings)
    	}
    
    	go func() {
    		defer close(ch)
    		wg.Wait()
    	}()
    
    	for signal := range ch {
    		if signal > maxSignal {
    			maxSignal = signal
    		}
    	}
    	return
    }
    
    func part1(codes []int) int {
    	return run(codes, 0, runAmplifiers)
    }
    
    func part2(codes []int) int {
    	return run(codes, 5, runAmplifierLoop)
    }
    
    // Solve provides the day 7 puzzle solution.
    func Solve(input string) (*solutions.Solution, error) {
    	numbers := strings.Split(input, ",")
    	codes := make([]int, len(numbers))
    	for i, n := range numbers {
    		intn, err := strconv.Atoi(n)
    		if err != nil {
    			return nil, err
    		}
    		codes[i] = intn
    	}
    
    	return &solutions.Solution{Part1: part1(codes), Part2: part2(codes)}, nil
    }