Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


2015-12-07 - Some Assembly Required

(original .ipynb)

Puzzle input is a series of instructions (see test_input_str) that set some "wires" (named values, like registers) according to some very simple bitwise operations - and, or, left-shift, right-shift and not. In part one we try evaluate the instructions to find the final value written to wire "a". In part two we have to override the value written to the wire "b" to the value written to part "a" in the first part, and then re-run and find what's written to "a".

This implementation is kinda nasty - I just repeatedly loop through the instructions to see if we can evaluate anything, and if we changed anything we go through the remaining instructions. There's clearly a nicer way to do this :)

from collections import defaultdict
from ctypes import c_uint16 as ushort

puzzle_input_str = open("./puzzle_input/day7.txt").read()

test_input_str = """123 -> x
456 -> y
x AND y -> d
x OR y -> e
x LSHIFT 2 -> f
y RSHIFT 2 -> g
NOT x -> h
NOT y -> i"""


const_op  = lambda arg: arg
not_op    = lambda arg: ~arg
binary_ops = { 
    "AND": lambda arg1, arg2: arg1 & arg2,
    "OR": lambda arg1, arg2: arg1 | arg2,
    "LSHIFT": lambda arg1, arg2: arg1 << arg2,
    "RSHIFT": lambda arg1, arg2: arg1 >> arg2
}


def get_value(loc, wires):
    if type(loc) == int:
        return loc
    if type(loc) == ushort:
        return int(loc)
    elif loc.isnumeric():
        return int(loc)
    return wires[loc]


def parse_calc(calc_str):
    tokens = calc_str.split(" ")
    if len(tokens) == 1:
        return const_op, [tokens[0]]
    elif len(tokens) == 2:
        return not_op, [tokens[1]]
    else:
        return binary_ops[tokens[1]], [tokens[0], tokens[2]]


def parse_instruction(instruction_str):
    lhs_str, rhs_str = instruction_str.split(" -> ")
    calc = parse_calc(lhs_str)
    return calc, rhs_str
    
    
def all_args_set(args):
    none_args = list(filter(lambda x: x == None, args))
    return len(none_args) == 0


def part_one(input_str, b_override=None):
    instructions = [parse_instruction(line) for line in input_str.split("\n")]
    wires = defaultdict(lambda: None)
    
    if b_override:
        instructions = [(instruction,dest) for (instruction,dest) in instructions if dest != "b"]
        wires["b"] = b_override
    
    executed_instruction = True
    while executed_instruction:
        executed_instruction = False
        todo_instructions = []
        for instruction, dest in instructions:
            op, args = instruction        
            args = [ get_value(arg, wires) for arg in args ]
            if all_args_set(args):
                res = op(*[ushort(get_value(arg, wires)).value for arg in args])
                wires[dest] = res
                executed_instruction = True
            else:
                todo_instructions.append((instruction, dest))
        instructions = todo_instructions
                

    return wires["a"]
        

print("part one:", part_one(puzzle_input_str))
part one: 3176
def part_two(input_str):
    return part_one(input_str, part_one(input_str))


print("part two:", part_two(puzzle_input_str))
part two: 14710