2015-7 - Some Assembly Required

Last edited: Feb 3, 2024 | First published: Feb 3, 2024

Source

Return to Advent of Code

Problem

See on adventofcode.com

My results

Click to see solution  [956, 40149]

Average Runtime: 0.081 seconds

Code

from adventofcode.AoCProblem import AoCProblem
from adventofcode.utils import read_text
from dotenv import load_dotenv
from os import environ
from pathlib import Path
from typing import Any


def parse_line(s: str) -> dict:
    # parses an input line into a dict for easy usage later
    _s = s.strip().split(" ")
    if len(_s) == 0:
        raise ValueError("Parsed line is empty.")
    dest = _s[-1]  # always the destination
    _s = _s[:-2]  # remove the last two items
    # f is the operation to run, so determine what it should be along with the
    # operands
    f, op1, op2 = None, None, None
    if "AND" in s:
        f = lambda a, b: int(a) & int(b)
        op1 = _s[0]
        op2 = _s[2]
    elif "OR" in s:
        f = lambda a, b: int(a) | int(b)
        op1 = _s[0]
        op2 = _s[2]
    elif "LSHIFT" in s:
        f = lambda a, b: int(a) << int(b)
        op1 = _s[0]
        op2 = _s[2]
    elif "RSHIFT" in s:
        f = lambda a, b: int(a) >> int(b)
        op1 = _s[0]
        op2 = _s[2]
    elif "NOT" in s:
        # ints in python are stored signed unless uint is used
        # this performs the not but applies 0xFFFF as bitmask
        # to do the not as if it were unsigned
        f = lambda a, b: ~int(a) & 0xFFFF
        op1 = _s[1]
    else:
        f = lambda a, b: int(a)
        op1 = _s[0]
    return {"dest": dest, "op": f, "op1": mycast(op1), "op2": mycast(op2)}


def mycast(s: Any) -> Any:
    # convert s to int if possible, otherwise dont change it
    res = None
    if s is not None:
        try:
            res = int(s)
        except (ValueError, TypeError):
            res = s
    return res


def algo1(args: str) -> int:
    # for each line, try to run the operation given using the operands
    # if the operands are not set yet, put that operation on the end of the list
    # to process, then move onto the next one
    parsed = [parse_line(line) for line in args.splitlines() if len(line) > 0]
    res = {}
    i = 0
    while len(parsed) > 0:
        d = parsed[i].get("dest")
        x0 = parsed[i].get("op1")
        x = res.get(x0, None) if type(x0) == str else x0
        y0 = parsed[i].get("op2")
        y = res.get(y0, None) if type(y0) == str else y0
        f = parsed[i].get("op")
        try:
            res[d] = f(x, y)
        except (ValueError, TypeError):
            parsed.append(parsed[i])
        parsed.pop(i)

    return res.get("a")


def algo2(args: str) -> int:
    # this is identical to algo1, but b is set to the answer for algo1
    # then b is ignored later on per the instructions
    parsed = [parse_line(line) for line in args.splitlines() if len(line) > 0]
    res = {"b": 956}
    while len(parsed) > 0:
        d = parsed[0].get("dest")
        if d == "b":
            parsed.pop(0)
        x0 = parsed[0].get("op1")
        x = res.get(x0, None) if type(x0) == str else x0
        y0 = parsed[0].get("op2")
        y = res.get(y0, None) if type(y0) == str else y0
        f = parsed[0].get("op")
        try:
            res[d] = f(x, y)
        except (ValueError, TypeError):
            parsed.append(parsed[0])
        parsed.pop(0)

    return res.get("a")


# set up problem
load_dotenv()
p = AoCProblem()
p.year = 2015
p.day = 7
p.title = "Some Assembly Required"
fn = Path(environ.get("data_folder")).absolute() / (
    Path(__file__).stem + ".txt"
)
args = read_text(fn)
p.algo = [algo1, algo2]
p.args = [args, args]
p.date_solved = "2024-02-03"
p.url = f"https://adventofcode.com/{p.year}/day/{p.day}"
p.github_url = environ.get("github_url") + "/".join(
    ["problems", Path(__file__).name]
)


if __name__ == "__main__":
    # test the given input
    p.run(["123 -> a"], n=1, a=0)
    assert p.solution[0] == 123, "Result incorrect"
    p.run(["456 -> a"], n=1, a=0)
    assert p.solution[0] == 456, "Result incorrect"
    txt = """
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 -> a
"""
    p.run([txt], n=1, a=0)
    assert p.solution[0] == 65079, "Result incorrect"

    # test
    p.run(n=1)

    # get average time
    p.run()

    # save to website markdown
    p.save_for_website(__file__)