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