summaryrefslogtreecommitdiffstats
path: root/7/solution.py
blob: 6eee284d3947089bb3ce9ab4b1d18c5891b4de97 (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
116
117
118
119
120
121
122
123
124
125
126
127
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)
    def __repr__(self):
        return f'{self.value}'

class Node:
    _value: U16 | None = None
    @property
    def value(self) -> U16:
        if self._value is None:
            self._value = self.eval()
        return self._value
    @abstractmethod
    def eval(self) -> U16:
        pass
    @abstractmethod
    def __repr__(self) -> str:
        pass

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

class Constant(Node):
    def __init__(self, value):
        self._value = value
    def __repr__(self) -> str:
        return f'Constant({self.value})'

class BinaryOp(Node):
    def __init__(self, left, right):
        self.left = left
        self.right = right
    def __repr__(self) -> str:
        return f'{self.__class__.__name__}({self.left}, {self.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 __repr__(self) -> str:
        return 'Not({self.node})'

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))