-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsolution.py
134 lines (105 loc) · 4.42 KB
/
solution.py
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
128
129
130
131
132
133
134
from typing import Dict, Tuple, Set, List
from time import perf_counter
from itertools import combinations
def parse_input(input_text: str) -> Tuple[Dict[str, int], Dict[str, Tuple[str, str, str]]]:
initial_values = {}
gates = {}
parts = input_text.strip().split('\n\n')
for line in parts[0].splitlines():
wire, value = line.split(': ')
initial_values[wire] = int(value)
for line in parts[1].splitlines():
inputs, output = line.split(' -> ')
parts = inputs.split()
if len(parts) == 3:
gates[output] = (parts[1], parts[0], parts[2])
return initial_values, gates
def evaluate_gate(op: str, a: int, b: int) -> int:
if op == 'AND':
return a & b
elif op == 'OR':
return a | b
elif op == 'XOR':
return a ^ b
raise ValueError(f"Unknown operation: {op}")
def simulate_circuit(initial_values: Dict[str, int], gates: Dict[str, Tuple[str, str, str]]) -> Dict[str, int]:
wires = initial_values.copy()
pending = set(gates.keys())
while pending:
made_progress = False
for wire in list(pending):
op, in1, in2 = gates[wire]
if in1 in wires and in2 in wires:
wires[wire] = evaluate_gate(op, wires[in1], wires[in2])
pending.remove(wire)
made_progress = True
if not made_progress and pending:
return None
return wires
def get_binary_value(wires: Dict[str, int], prefix: str) -> int:
bits = sorted([w for w in wires if w.startswith(prefix)])
value = 0
for bit in reversed(bits):
value = (value << 1) | wires[bit]
return value
def find_swapped_wires(input_text: str, show_progress: bool = False) -> str:
initial_values, gates = parse_input(input_text)
def find_gate(ta: str, tb: str, top: str) -> str:
for output, (op, a, b) in gates.items():
if op == top and ((a == ta and b == tb) or (a == tb and b == ta)):
return output
return None
swapped = []
carry = None
z_count = max(int(wire[1:]) for wire in gates if wire.startswith('z')) + 1
for i in range(z_count):
pos = f"{i:02d}"
sum_1 = find_gate(f"x{pos}", f"y{pos}", "XOR")
carry_1 = find_gate(f"x{pos}", f"y{pos}", "AND")
if carry is not None:
carry_2 = find_gate(carry, sum_1, "AND")
if carry_2 is None:
carry_1, sum_1 = sum_1, carry_1
swapped.extend([sum_1, carry_1])
carry_2 = find_gate(carry, sum_1, "AND")
sum_2 = find_gate(carry, sum_1, "XOR")
if sum_1 is not None and sum_1.startswith("z"):
sum_1, sum_2 = sum_2, sum_1
swapped.extend([sum_1, sum_2])
if carry_1 is not None and carry_1.startswith("z"):
carry_1, sum_2 = sum_2, carry_1
swapped.extend([carry_1, sum_2])
if carry_2 is not None and carry_2.startswith("z"):
carry_2, sum_2 = sum_2, carry_2
swapped.extend([carry_2, sum_2])
new_carry = find_gate(carry_1, carry_2, "OR")
if new_carry is not None and new_carry.startswith("z") and new_carry != f"z{z_count-1:02d}":
new_carry, sum_2 = sum_2, new_carry
swapped.extend([new_carry, sum_2])
carry = new_carry
else:
carry = carry_1
return ",".join(sorted(set(w for w in swapped if w)))
def solve(input_text: str, track_performance: bool = False) -> Tuple[int, str]:
if track_performance:
start = perf_counter()
part1 = simulate_circuit(*parse_input(input_text))
z_value = get_binary_value(part1, 'z')
if track_performance:
mid = perf_counter()
part2 = find_swapped_wires(input_text)
if track_performance:
end = perf_counter()
print(f"\nPerformance:")
print(f"├─ Part 1: {(mid-start):.3f} seconds")
print(f"├─ Part 2: {(end-mid):.3f} seconds")
print(f"└─ Total: {(end-start):.3f} seconds")
return z_value, part2
def main():
with open('input.txt', 'r') as file:
input_text = file.read()
part1, part2 = solve(input_text, track_performance=True)
print(f"Part 1: {part1}")
print(f"Part 2: {part2}")
if __name__ == "__main__":
main()