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