1+ #include < unordered_map>
2+ #include < set>
3+ #include < array>
4+ #include < queue>
5+ #include " ../aoclib/aocio.hpp"
6+
7+ /*
8+ Problem: https://adventofcode.com/2023/day/20
9+
10+ Solutions:
11+ - Part 1: 912199500
12+ - Part 2:
13+ Notes:
14+
15+ */
16+
17+ enum class Pulse {Low, High};
18+
19+ class Network ;
20+
21+ class Module
22+ {
23+ protected:
24+ std::unordered_map<std::string, Pulse> input_pulses;
25+ std::vector<std::string> outputs;
26+ Pulse state;
27+ virtual void update_state () {};
28+
29+ public:
30+ friend Network;
31+ const std::string name;
32+
33+ Module (const std::string& name, const std::vector<std::string>& outputs) : outputs(outputs), name(name)
34+ {
35+ state = Pulse::Low;
36+ };
37+
38+ virtual ~Module () = default ;
39+
40+ // Returns true if the module will send a pulse.
41+ virtual bool receive_pulse (const std::string& sender_name, Pulse p)
42+ {
43+ assert (input_pulses.contains (sender_name));
44+ input_pulses.at (sender_name) = p;
45+ return true ;
46+ }
47+
48+ virtual void forward_pulse (Network& network, std::set<std::string>& sends_pulse, bool print = false ); // Defined after Network.
49+ };
50+
51+ class Network
52+ {
53+ public:
54+ std::unordered_map<std::string, std::unique_ptr<Module>> modules;
55+
56+ void insert_module (std::unique_ptr<Module> mod)
57+ {
58+ if (modules.contains (mod->name )) {
59+ throw std::invalid_argument (" Network::insert_module: Duplicate module" );
60+ }
61+ modules.insert ({mod->name , std::move (mod)});
62+ update_connections ();
63+ }
64+
65+ int64_t push_button (int n = 1 )
66+ {
67+ int low_pulses = 0 ;
68+ int high_pulses = 0 ;
69+
70+ for (int i = 0 ; i < n; ++i) {
71+ low_pulses += 1 ;
72+ std::queue<Module*> queue;
73+ queue.push (modules.at (" broadcaster" ).get ());
74+
75+ while (!queue.empty ()) {
76+ Module *mod = queue.front ();
77+ queue.pop ();
78+
79+ std::set<std::string> will_send_pulse;
80+ mod->forward_pulse (*this , will_send_pulse);
81+ if (mod->state == Pulse::Low) {
82+ low_pulses += mod->outputs .size ();
83+ } else {
84+ high_pulses += mod->outputs .size ();
85+ }
86+ for (const std::string& out_name : will_send_pulse) {
87+ assert (modules.contains (out_name));
88+ queue.push (modules.at (out_name).get ());
89+ }
90+ }
91+ }
92+ return low_pulses * high_pulses;
93+ }
94+
95+ private:
96+ void update_connections ()
97+ {
98+ for (auto & [input_name, input_mod] : modules) {
99+ for (const std::string& output_name : input_mod->outputs ) {
100+ if (!modules.contains (output_name)) {
101+ continue ;
102+ }
103+ Module *output_mod = modules.at (output_name).get ();
104+ output_mod->input_pulses .insert_or_assign (input_name, Pulse::Low);
105+ }
106+ }
107+ }
108+ };
109+
110+ void Module::forward_pulse (Network& network, std::set<std::string>& will_send_pulse, bool print)
111+ {
112+ update_state ();
113+ for (const std::string& output_name : outputs) {
114+ if (!network.modules .contains (output_name)) {
115+ continue ;
116+ }
117+ Module *out_mod = network.modules .at (output_name).get ();
118+ if (print) {
119+ std::string state_str = state == Pulse::Low ? " -low-> " : " -high-> " ;
120+ std::cout << name << state_str << output_name << " \n " ;
121+ }
122+ bool will_send = out_mod->receive_pulse (name, state);
123+ if (will_send) {
124+ will_send_pulse.insert (output_name);
125+ }
126+ }
127+ }
128+
129+ class FlipFlop : public Module
130+ {
131+ private:
132+ bool on = false ;
133+
134+ public:
135+ FlipFlop (const std::string& name, const std::vector<std::string>& outputs) : Module(name, outputs)
136+ {
137+ state = Pulse::Low;
138+ }
139+
140+ bool receive_pulse (const std::string& sender_name, Pulse p) override
141+ {
142+ if (p == Pulse::High) {
143+ return false ;
144+ } else {
145+ state = on ? Pulse::Low : Pulse::High;
146+ on = !on;
147+ return true ;
148+ }
149+ }
150+
151+ };
152+
153+ class Conjunction : public Module
154+ {
155+ public:
156+ Conjunction (const std::string& name, const std::vector<std::string>& outputs) : Module(name, outputs)
157+ {
158+ state = Pulse::Low;
159+ }
160+
161+ protected:
162+ void update_state () override
163+ {
164+ auto input_low = std::find_if (input_pulses.cbegin (), input_pulses.cend (), [](const auto & name_pulse) {return name_pulse.second == Pulse::Low;});
165+ bool all_inputs_high = input_low == input_pulses.cend ();
166+ if (all_inputs_high) {
167+ state = Pulse::Low;
168+ } else {
169+ state = Pulse::High;
170+ }
171+ }
172+ };
173+
174+ class Broadcast : public Module
175+ {
176+ public:
177+ Broadcast (const std::string& name, const std::vector<std::string>& outputs) : Module(name, outputs)
178+ {
179+ state = Pulse::Low;
180+ }
181+
182+ bool receive_pulse (const std::string& sender_name, Pulse p) override
183+ {
184+ state = p;
185+ return true ;
186+ }
187+ };
188+
189+ void parse_network (const std::vector<std::string>& lines, Network& network)
190+ {
191+ for (std::string line : lines) {
192+ aocio::str_remove_whitespace (line);
193+ if (!line.size ()) {
194+ continue ;
195+ }
196+ std::size_t arrow_idx = line.find (" ->" , 0 );
197+ if (arrow_idx == std::string::npos) {
198+ throw std::invalid_argument (" parse_network: Missing arrow" );
199+ }
200+
201+ std::string lhs = line.substr (0 , arrow_idx);
202+ const std::string& rhs = line.substr (arrow_idx + 2 , line.size ());
203+
204+ if (lhs.size () < 2 || rhs.size () < 1 ) {
205+ throw std::invalid_argument (" parse_network: Assignment too short" );
206+ }
207+
208+ std::vector<std::string> dest_mods;
209+ aocio::line_tokenise (rhs, " ," , " " , dest_mods);
210+
211+ if (dest_mods.size () == 0 ) {
212+ throw std::invalid_argument (" parse_network: No destination modules" );
213+ }
214+
215+ const char module_sym = lhs.at (0 );
216+
217+ if (module_sym != ' %' && module_sym != ' &' ) {
218+ if (lhs != " broadcaster" ) {
219+ throw std::invalid_argument (" parse_network: Invalid module type" );
220+ }
221+ std::unique_ptr<Module> mod = std::make_unique<Broadcast>(lhs, dest_mods);
222+ network.insert_module (std::move (mod));
223+ } else {
224+ lhs = lhs.substr (1 , lhs.size ());
225+ if (module_sym == ' %' ) {
226+ std::unique_ptr<Module> mod = std::make_unique<FlipFlop>(lhs, dest_mods);
227+ network.insert_module (std::move (mod));
228+ } else {
229+ std::unique_ptr<Module> mod = std::make_unique<Conjunction>(lhs, dest_mods);
230+ network.insert_module (std::move (mod));
231+ }
232+ }
233+ }
234+ }
235+
236+
237+ int64_t part_one (const std::vector<std::string>& lines)
238+ {
239+ Network network;
240+ parse_network (lines, network);
241+ return network.push_button (1000 );
242+ }
243+
244+ int64_t part_two (const std::vector<std::string>& lines)
245+ {
246+ return -1 ;
247+ }
248+
249+ int main ()
250+ {
251+ aocio::print_day ();
252+ std::vector<std::string> lines;
253+ std::string_view fname = AOC_INPUT_DIR" input.txt" ;
254+ bool file_loaded = aocio::file_getlines (fname, lines);
255+ if (!file_loaded) {
256+ std::cerr << " Error: " << " File '" << fname << " ' not found\n " ;
257+ return EXIT_FAILURE;
258+ }
259+
260+ aocio::remove_leading_empty_lines (lines);
261+ if (!lines.size ()) {
262+ std::cerr << " Error: " << " Input is empty" ;
263+ return EXIT_FAILURE;
264+ }
265+
266+ try {
267+ int64_t p1 = part_one (lines);
268+ std::cout << " Part 1: " << p1 << " \n " ;
269+ int64_t p2 = part_two (lines);
270+ std::cout << " Part 2: " << p2 << " \n " ;
271+ } catch (const std::exception& err) {
272+ std::cerr << " Error: " << err.what () << " \n " ;
273+ return EXIT_FAILURE;
274+ }
275+
276+ return EXIT_SUCCESS;
277+ }
0 commit comments