Duet

Part 1

I am going to adopt a weird approach, executing the register instances as python scipts in the global namespace.

snd X plays a sound with a frequency equal to the value of X. set X Y sets register X to the value of Y. add X Y increases register X by the value of Y. mul X Y sets register X to the result of multiplying the value contained in register X by the value of Y. mod X Y sets register X to the remainder of dividing the value contained in register X by the value of Y (that is, it sets X to the result of X modulo Y). rcv X recovers the frequency of the last sound played, but only when the value of X is not zero. (If it is zero, the command does nothing.) jgz X Y jumps with an offset of the value of Y, but only if the value of X is greater than zero. (An offset of 2 skips the next instruction, an offset of -1 jumps to the previous instruction, and so on.)

In [23]:
import csv

def parse_registers(input_path):
    registers = []
    with open(input_path, 'rt') as f_input:
        csv_reader = csv.reader(f_input, delimiter=' ')
        for line in csv_reader:
            registers.append((line[0], tuple(line[1:])))
    return registers

In [24]:
def r_set(x, y):
    exec('{0} = {1}'.format(x, y), globals())

def r_add(x, y):
    exec('{0} += {1}'.format(x, y), globals())
    
def r_mul(x, y):
    if x not in globals(): globals()[x] = 0
    exec('{0} *= {1}'.format(x, y), globals())

def r_mod(x, y):
    exec('{0} = {0} % {1}'.format(x, y), globals())

def r_snd(x):
    exec('last_played = {0}'.format(x), globals())

def r_rcv(x):
    exec('if {0} != 0: recovered = last_played'.format(x), globals())

In [25]:
import re

def exec_register(reg):
    comm = reg[0]
    argx = reg[1][0]
    if len(reg[1]) == 1:
        globals()['r_' + comm](argx)
    if len(reg[1]) > 1:
        argy = reg[1][1]
        globals()['r_' + comm](argx, argy)
    
def retrieve(string):
    if re.search('\d+', string):
        return int(string)
    else:
        return globals()[string]

def read_registers(registers):
    head = 0
    while recovered is None:
        reg = registers[head]
        if reg[0] != 'jgz':
            exec_register(reg)
            head += 1
        else:
            req = retrieve(reg[1][0])
            offset = retrieve(reg[1][1])
            if req > 0:
                head += offset
            else:
                head += 1
    return last_played

Test


In [26]:
registers = parse_registers('input.test1.txt')
recovered = None
last_played = None
read_registers(registers)


Out[26]:
4

Solution


In [27]:
registers = parse_registers('input.txt')
recovered = None
last_played = None
read_registers(registers)


Out[27]:
9423

Part 2

We take a different approach now: let's define a dictionary for each variable in each scope of "duet".


In [162]:
from collections import defaultdict

def init():
    globals()['prog'] = (defaultdict(int), defaultdict(int))
    globals()['prog'][1]['p'] = 1
    globals()['queue'] = ([], [])
    globals()['halt'] = [False, False]
    globals()['sent_count'] = 0

In [225]:
def retrieve(string, prog):
    if re.search('\d+', string):
        return int(string)
    else:
        return prog[string]

def r_set(x, y, i):
    y = retrieve(y, prog[i])
    prog[i][x] = y

def r_add(x, y, i):
    y = retrieve(y, prog[i])
    prog[i][x] += y
    
def r_mul(x, y, i):
    y = retrieve(y, prog[i])
    prog[i][x] *= y

def r_mod(x, y, i):
    y = retrieve(y, prog[i])
    prog[i][x] = prog[i][x] % y

def r_snd(x, i):
    x = retrieve(x, prog[i])
    queue[(i + 1) % 2].append(x)
    if halt[(i + 1) % 2] == True:
        halt[(i + 1) % 2] = False
    if i == 1: 
        globals()['sent_count'] += 1    

def r_rcv(x, i):
    try:
        prog[i][x] = queue[i].pop(0)
    except IndexError:
        globals()['halt'][i] = True

In [226]:
import re

def exec_register(reg, i):
    comm = reg[0]
    argx = reg[1][0]
    if len(reg[1]) == 1:
        globals()['r_' + comm](argx, i)
    if len(reg[1]) > 1:
        argy = reg[1][1]
        globals()['r_' + comm](argx, argy, i)

In [234]:
def run_duet(registers):
    head = [0, 0]
    while (not halt[0]) or (not halt[1]):
        i = halt.index(False)
        reg = registers[head[i]]
        if reg[0] != 'jgz':
            exec_register(reg, i)
            if not halt[i]:
                head[i] += 1
        else:
            req = retrieve(reg[1][0], prog[i])
            offset = retrieve(reg[1][1], prog[i])
            if req > 0:
                head[i] += offset
            else:
                head[i] += 1

Test


In [235]:
init()
registers = parse_registers('input.test1.txt')
run_duet(registers)
assert(sent_count == 1)

In [236]:
init()
registers = parse_registers('input.test2.txt')
run_duet(registers)
assert(sent_count == 3)

Solution


In [237]:
init()
registers = parse_registers('input.txt')
run_duet(registers)
print(sent_count)


7620