summaryrefslogtreecommitdiffstats
path: root/7/solution.py
blob: 77a1391295df62b3a01a1709d5659b966ff96b85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from abc import abstractmethod

class U16:
    def __init__(self, value: int):
        self.value = value & 0xffff
    def __invert__(self):
        return U16(~self.value)
    def __and__(self, other):
        return U16(self.value & other.value)
    def __or__(self, other):
        return U16(self.value | other.value)
    def __rshift__(self, other):
        return U16(self.value >> other.value)
    def __lshift__(self, other):
        return U16(self.value << other.value)
    def __str__(self):
        return str(self.value)

class Node:
    _value: U16 = None
    @property
    def value(self) -> U16:
        if not self._value:
            self._value = self.eval()
        return self._value
    def eval(self, net: dict[Node]) -> U16:
        return self.eval()
    @absrtactmethod
    def eval(self) -> U16:
        pass

class Reference(Node):
    def __init__(self, name: str):
        self.name = name
    def eval(self, net: dict[Node]) -> U16:
        return net[self.name].value

class Constant(Node):
    def __init__(self, value):
        self._value = value

class BinaryOp(Node):
    def __init__(self, left, right):
        self.left = left
        self.right = right

class And(BinaryOp):
    def eval(self) -> U16:
        return self.left.value & self.right.value

class Or(BinaryOp):
    def eval(self) -> U16:
        return self.left.value | self.right.value

class LShift(BinaryOp):
    def eval(self) -> U16:
        return self.left.value << self.right.value

class RShift(BinaryOp):
    def eval(self) -> U16:
        return self.left.value >> self.right.value

class Not(Node):
    def __init__(self, node):
        self.node = node
    def eval(self):
        return ~self.node.value

def parse_expr(net: dict[Node], expr: str) -> Node:
    parts = expr.split(' ')
    if len(parts) == 3:
        lhs = parse_expr(net, parts[0])
        rhs = parse_expr(net, parts[2])
        if parts[1] == 'AND':
            return And(lhs, rhs)
        elif parts[1] == 'OR':
            return Or(lhs, rhs)
        elif parts[1] == 'LSHIFT':
            return LShift(lhs, rhs)
        elif parts[1] == 'RSHIFT':
            return RShift(lhs, rhs)
    elif len(parts) == 2:
        if parts[0] == 'NOT':
            return Not(parse_expr(net, parts[1]))
    elif len(parts) == 1:
        if expr.isnumeric():
            return Constant(U16(int(expr)))
        if expr.isalpha():
            return Reference(net, expr)
    raise ValueError(expr)

def parse_conn(net: dict[Node], conn: str) -> tuple[str, Node]:
    expr, target = conn.split(" -> ")
    return target, parse_expr(net, expr)

def part1(inp: list[str]) -> int:
    net = dict()
    for conn in inp:
        target, node = parse_conn(net, conn)
        net[target] = node
    return net['a'].value

def part2(inp: list[str]) -> int:
    net = dict()
    for conn in inp:
        target, node = parse_conn(net, conn)
        net[target] = node
    net['b'] = Constant(part1(inp))
    return net['a'].value

if __name__ == '__main__':
    with open('input') as f:
        inp = [line.strip() for line in f]
    print(part1(inp))
    print(part2(inp))