AoC code coverage
Current view: top level - puzzles/2023 - Day20.cpp (source / functions) Coverage Total Hit
Test: master Lines: 97.8 % 135 132
Test Date: 2025-12-11 19:43:23 Functions: 96.3 % 27 26

            Line data    Source code
       1              : #include "LinewiseInput.h"
       2              : #include "PuzzleImpl.h"
       3              : 
       4              : #include <libassert/assert.hpp>
       5              : #include <re2/re2.h>
       6              : 
       7              : #include <algorithm>
       8              : #include <cstdlib>
       9              : #include <map>
      10              : #include <memory>
      11              : #include <numeric>
      12              : #include <queue>
      13              : #include <span>
      14              : #include <string>
      15              : #include <string_view>
      16              : #include <tuple>
      17              : 
      18              : namespace {
      19              : 
      20              : constexpr bool HI = true;
      21              : constexpr bool LO = false;
      22              : 
      23              : constexpr bool ON = true;
      24              : constexpr bool OFF = false;
      25              : 
      26              : class Module;
      27              : 
      28              : using Call = std::tuple<Module *, Module *, bool>;
      29              : using CallQueue = std::queue<Call>;
      30              : 
      31              : class Module {
      32              : public:
      33              :   Module(std::string_view const name, std::shared_ptr<CallQueue> const &calls)
      34          118 :       : _name(name), _calls(calls) {}
      35              : 
      36          118 :   virtual ~Module() = default;
      37              : 
      38              :   virtual void receiveSignal(Module const *const sender, bool signal) = 0;
      39              : 
      40              :   virtual void registerInput(Module const &sender) = 0;
      41          218 :   void registerOutput(Module &receiver) { _outputs.push_back(&receiver); };
      42              : 
      43          218 :   static void connect(Module *const sender, Module *const receiver) {
      44          218 :     sender->registerOutput(*receiver);
      45          218 :     receiver->registerInput(*sender);
      46          218 :   }
      47              : 
      48        89921 :   [[nodiscard]] std::string_view name() const { return _name; }
      49              : 
      50              : protected:
      51       134947 :   void send(bool const signal) {
      52       134947 :     for (Module *m : _outputs)
      53       319623 :       _calls->emplace(this, m, signal);
      54       134947 :   }
      55              : 
      56              : private:
      57              :   std::string_view _name;
      58              :   std::shared_ptr<CallQueue> _calls;
      59              :   std::vector<Module *> _outputs;
      60              : };
      61              : 
      62              : class FlipFlopModule final : public Module {
      63              : public:
      64              :   using Module::Module;
      65              : 
      66          134 :   void registerInput(Module const &) final {}
      67              : 
      68       199843 :   void receiveSignal(Module const *const, bool signal) final {
      69       199843 :     if (signal == LO) {
      70        40099 :       _state = !_state;
      71        40099 :       send(_state == ON ? HI : LO);
      72        40099 :     }
      73       199843 :   }
      74              : 
      75              : private:
      76              :   bool _state = OFF;
      77              : };
      78              : 
      79              : class ConjunctionModule final : public Module {
      80              : public:
      81              :   using Module::Module;
      82              : 
      83           82 :   void registerInput(Module const &sender) final { _inputStates.emplace(sender.name(), LO); }
      84              : 
      85        89835 :   void receiveSignal(Module const *const sender, bool signal) final {
      86        89835 :     auto it = _inputStates.find(sender->name());
      87        89835 :     ASSUME(it != _inputStates.end());
      88        89835 :     it->second = signal;
      89              : 
      90        89835 :     if (signal == HI && _hiCallback)
      91            4 :       _hiCallback(sender->name());
      92              : 
      93       110624 :     if (std::ranges::all_of(_inputStates, [](std::pair<std::string_view, bool> const &p) {
      94       110624 :           return p.second == HI;
      95       110624 :         }))
      96        29945 :       send(LO);
      97        59890 :     else
      98        59890 :       send(HI);
      99        89835 :   }
     100              : 
     101            1 :   void registerHiCallback(std::function<void(std::string_view)> const &f) { _hiCallback = f; }
     102              : 
     103            1 :   [[nodiscard]] size_t numInputs() const { return _inputStates.size(); }
     104              : 
     105              : private:
     106              :   std::map<std::string_view, bool> _inputStates;
     107              :   std::function<void(std::string_view)> _hiCallback;
     108              : };
     109              : 
     110              : class OutputModule final : public Module {
     111              : public:
     112              :   using Module::Module;
     113            2 :   void registerInput(Module const &) final {}
     114        29945 :   void receiveSignal(Module const *const, bool) final {}
     115              : };
     116              : 
     117              : class BroadcastModule final : public Module {
     118              : public:
     119              :   using Module::Module;
     120              : 
     121            0 :   void registerInput(Module const &) final { UNREACHABLE(); }
     122              : 
     123         5013 :   void receiveSignal(Module const *const, bool signal) final { send(signal); }
     124              : };
     125              : 
     126              : class Network {
     127              : public:
     128              :   Network(std::span<std::string_view const> const flipFlopModules,
     129              :           std::span<std::string_view const> const conjunctionModules,
     130              :           std::span<std::pair<std::string_view, std::string_view> const> const connections)
     131            2 :       : _calls(std::make_shared<CallQueue>()) {
     132              : 
     133            2 :     auto [it, inserted] =
     134            2 :         _modules.emplace("broadcaster", std::make_unique<BroadcastModule>("broadcaster", _calls));
     135            2 :     ASSUME(inserted);
     136            2 :     _broadcaster = dynamic_cast<BroadcastModule *>(it->second.get());
     137            2 :     ASSUME(_broadcaster);
     138              : 
     139            2 :     for (auto const m : flipFlopModules)
     140           96 :       _modules.emplace(m, std::make_unique<FlipFlopModule>(m, _calls));
     141              : 
     142            2 :     for (auto const m : conjunctionModules)
     143           18 :       _modules.emplace(m, std::make_unique<ConjunctionModule>(m, _calls));
     144              : 
     145          218 :     for (auto const &c : connections) {
     146          218 :       auto senderIt = _modules.find(c.first);
     147          218 :       ASSUME(senderIt != _modules.end());
     148          218 :       auto receiverIt = _modules.find(c.second);
     149          218 :       if (receiverIt == _modules.end()) {
     150            2 :         receiverIt =
     151            2 :             _modules.emplace(c.second, std::make_unique<OutputModule>(c.second, _calls)).first;
     152            2 :       }
     153          218 :       Module::connect(senderIt->second.get(), receiverIt->second.get());
     154          218 :     }
     155            2 :   }
     156              : 
     157         5013 :   void pushButton() {
     158         5013 :     ++_loCtr;
     159         5013 :     _broadcaster->receiveSignal(nullptr, LO);
     160       324636 :     while (!_calls->empty()) {
     161       319623 :       auto [sender, receiver, signal] = _calls->front();
     162       319623 :       if (signal == HI)
     163       234618 :         ++_hiCtr;
     164        85005 :       else
     165        85005 :         ++_loCtr;
     166       319623 :       receiver->receiveSignal(sender, signal);
     167       319623 :       _calls->pop();
     168       319623 :     }
     169         5013 :   }
     170              : 
     171            1 :   [[nodiscard]] auto ctrs() const { return std::make_tuple(_hiCtr, _loCtr); }
     172              : 
     173            1 :   Module *findModule(std::string_view const name) {
     174            1 :     auto it = _modules.find(name);
     175            1 :     if (it == _modules.end())
     176            0 :       return nullptr;
     177            1 :     return it->second.get();
     178            1 :   }
     179              : 
     180              : private:
     181              :   std::shared_ptr<CallQueue> _calls;
     182              :   std::map<std::string_view, std::unique_ptr<Module>> _modules;
     183              :   BroadcastModule *_broadcaster;
     184              :   size_t _hiCtr = 0;
     185              :   size_t _loCtr = 0;
     186              : };
     187              : 
     188            2 : Network parse(std::string_view const input) {
     189            2 :   static const RE2 frontPattern("([%&]?)([a-z]+) -> ");
     190            2 :   static const RE2 listPattern(" ?([a-z]+),?");
     191            2 :   LinewiseInput lines(input);
     192            2 :   std::vector<std::string_view> conjunctionModules;
     193            2 :   std::vector<std::string_view> flipFlopModules;
     194            2 :   std::vector<std::pair<std::string_view, std::string_view>> connections;
     195              : 
     196          116 :   for (auto line : lines) {
     197          116 :     std::string_view c;
     198          116 :     std::string_view name;
     199          116 :     ASSUME(RE2::Consume(&line, frontPattern, &c, &name));
     200          116 :     std::string_view outputName;
     201          116 :     std::vector<std::string_view> outputs;
     202          334 :     while (RE2::Consume(&line, listPattern, &outputName))
     203          218 :       outputs.push_back(outputName);
     204              : 
     205          116 :     if (c == "%") {
     206           96 :       flipFlopModules.push_back(name);
     207           96 :     } else if (c == "&") {
     208           18 :       conjunctionModules.push_back(name);
     209           18 :     }
     210          116 :     for (auto const &output : outputs)
     211          218 :       connections.emplace_back(name, output);
     212          116 :   }
     213              : 
     214            2 :   return {flipFlopModules, conjunctionModules, connections};
     215            2 : }
     216              : 
     217              : } // namespace
     218              : 
     219            1 : template <> std::string solvePart1<2023, 20>(std::string_view const input) {
     220            1 :   Network network = parse(input);
     221              : 
     222         1001 :   for (int i = 0; i < 1000; ++i)
     223         1000 :     network.pushButton();
     224              : 
     225            1 :   auto [hiCtr, loCtr] = network.ctrs();
     226              : 
     227            1 :   return std::to_string(hiCtr * loCtr);
     228            1 : }
     229              : 
     230            1 : template <> std::string solvePart2<2023, 20>(std::string_view const input) {
     231            1 :   Network network = parse(input);
     232              : 
     233            1 :   auto *const hj = dynamic_cast<ConjunctionModule *>(network.findModule("hj"));
     234            1 :   ASSUME(hj);
     235            1 :   std::map<std::string_view, size_t> cycleLengths;
     236            1 :   size_t loopCtr = 1;
     237            4 :   hj->registerHiCallback([&cycleLengths, &loopCtr](std::string_view name) {
     238            4 :     auto it = cycleLengths.find(name);
     239            4 :     if (it == cycleLengths.end())
     240            4 :       cycleLengths.emplace(name, loopCtr);
     241            0 :     else
     242            4 :       ASSUME(loopCtr % it->second == 0);
     243            4 :   });
     244              : 
     245            1 :   size_t numInputs = hj->numInputs();
     246              : 
     247         4014 :   while (cycleLengths.size() != numInputs) {
     248         4013 :     network.pushButton();
     249         4013 :     ++loopCtr;
     250         4013 :   }
     251              : 
     252            1 :   return std::to_string(std::transform_reduce(
     253            1 :       cycleLengths.begin(), cycleLengths.end(), size_t(1),
     254            4 :       [](size_t lhs, size_t rhs) { return std::lcm(lhs, rhs); },
     255            4 :       [](auto const &p) { return p.second; }));
     256            1 : }
        

Generated by: LCOV version 2.0-1