1+ from collections import defaultdict , deque
2+ from typing import Union , List , Callable
3+ import operator
4+
5+ Operand = Union [str , int ]
6+
7+
8+ class State :
9+ def __init__ (self , program = 0 ):
10+ self .position = 0
11+ self .played_sound = 0
12+ self .recovered_sound = 0
13+ self .registers = defaultdict (lambda : 0 )
14+ self .registers ['p' ] = program
15+ self .queue = deque ()
16+ self .counterpart = None
17+ self .sent_count = 0
18+
19+
20+ def eval_operand (operand : Operand , registers : dict [str , int ]) -> int :
21+ try :
22+ return int (operand )
23+ except ValueError :
24+ return registers [operand ]
25+
26+
27+ def modify_register (modification : Callable [[int , int ], int ]):
28+ def apply_modification (state : State , register : str , value : Operand ):
29+ state .registers [register ] = modification (state .registers [register ], eval_operand (value , state .registers ))
30+ state .position += 1
31+
32+ return apply_modification
33+
34+
35+ def perform_sound (state : State , value : Operand ):
36+ state .played_sound = eval_operand (value , state .registers )
37+ state .position += 1
38+
39+
40+ def perform_recover (state : State , value : Operand ):
41+ if eval_operand (value , state .registers ) != 0 :
42+ state .recovered_sound = state .played_sound
43+ state .position += 1
44+
45+
46+ def perform_jgz (state : State , condition : Operand , offset : Operand ):
47+ if eval_operand (condition , state .registers ) > 0 :
48+ state .position += eval_operand (offset , state .registers )
49+ else :
50+ state .position += 1
51+
52+
53+ def perform_send (state : State , value : Operand ):
54+ state .counterpart .queue .append (eval_operand (value , state .registers ))
55+ state .position += 1
56+ state .sent_count += 1
57+
58+
59+ def perform_receive (state : State , value : Operand ):
60+ if state .queue :
61+ state .registers [value ] = state .queue .popleft ()
62+ state .position += 1
63+
64+
65+ instruction_handlers_1 = {
66+ 'set' : modify_register (lambda old , new : new ),
67+ 'add' : modify_register (operator .add ),
68+ 'mul' : modify_register (operator .mul ),
69+ 'mod' : modify_register (operator .mod ),
70+ 'snd' : perform_sound ,
71+ 'rcv' : perform_recover ,
72+ 'jgz' : perform_jgz
73+ }
74+
75+
76+ instruction_handlers_2 = instruction_handlers_1 .copy ()
77+ instruction_handlers_2 ['snd' ] = perform_send
78+ instruction_handlers_2 ['rcv' ] = perform_receive
79+
80+
81+ def perform_instruction (state : State , instruction : List [str ], handlers : dict [str , Callable ]):
82+ [name , * args ] = instruction
83+ handlers [name ](state , * args )
84+
85+
86+ def part1 (instructions : List [List [str ]]) -> int :
87+ state = State ()
88+ while 0 <= state .position < len (instructions ) and state .recovered_sound == 0 :
89+ perform_instruction (state , instructions [state .position ], instruction_handlers_1 )
90+ return state .recovered_sound
91+
92+
93+ def part2 (instructions : List [List [str ]]) -> int :
94+ p0 = State (0 )
95+ p1 = State (1 )
96+ p0 .counterpart = p1
97+ p1 .counterpart = p0
98+ p0_before , p1_before = None , None
99+ while p0_before != p0 .position or p1_before != p1 .position :
100+ if 0 <= p0 .position < len (instructions ):
101+ p0_before = p0 .position
102+ perform_instruction (p0 , instructions [p0 .position ], instruction_handlers_2 )
103+ if 0 <= p1 .position < len (instructions ):
104+ p1_before = p1 .position
105+ perform_instruction (p1 , instructions [p1 .position ], instruction_handlers_2 )
106+ return p1 .sent_count
107+
108+
109+ instructions = [line .rstrip ('\n ' ).split (' ' ) for line in open ('input/day18.txt' ).readlines ()]
110+
111+ print ('Part 1:' , part1 (instructions ))
112+ print ('Part 2:' , part2 (instructions ))
0 commit comments