AoC code coverage
Current view: top level - puzzles/2023 - Day19.cpp (source / functions) Coverage Total Hit
Test: master Lines: 93.8 % 128 120
Test Date: 2025-12-11 19:43:23 Functions: 95.2 % 21 20

            Line data    Source code
       1              : #include "LinewiseInput.h"
       2              : #include "Parsing.h"
       3              : #include "PuzzleImpl.h"
       4              : 
       5              : #include <absl/container/flat_hash_map.h>
       6              : #include <libassert/assert.hpp>
       7              : #include <memory>
       8              : #include <numeric>
       9              : #include <re2/re2.h>
      10              : 
      11              : #include <array>
      12              : #include <span>
      13              : #include <string>
      14              : #include <string_view>
      15              : #include <tuple>
      16              : 
      17              : namespace {
      18              : 
      19            0 : constexpr std::array<unsigned, 128u> makeCategoryIndexMap() {
      20            0 :   std::array<unsigned, 128u> map{};
      21            0 :   map['x'] = 0;
      22            0 :   map['m'] = 1;
      23            0 :   map['a'] = 2;
      24            0 :   map['s'] = 3;
      25            0 :   return map;
      26            0 : }
      27              : constexpr std::array<unsigned, 128u> categoryIndexMap = makeCategoryIndexMap();
      28              : 
      29              : using PartRatings = std::array<unsigned, 4u>;
      30              : 
      31              : struct Rule {
      32         2112 :   Rule(std::string_view const ruleStr) {
      33         2112 :     static const RE2 pattern = R"(([xmas])([<>])(\d+)\:([a-zAR]+))";
      34              : 
      35         2112 :     char category = 0;
      36         2112 :     ASSUME(RE2::FullMatch(ruleStr, pattern, &category, &op, &threshold, &acceptedWorkflow));
      37              : 
      38         2112 :     idx = categoryIndexMap[category];
      39         2112 :   }
      40              : 
      41              :   unsigned idx = 0;
      42              :   unsigned threshold = 0;
      43              :   char op = 0;
      44              :   std::string_view acceptedWorkflow;
      45              : };
      46              : 
      47              : struct Workflow {
      48         1052 :   Workflow(std::string_view const workflowStr) {
      49         1052 :     auto parts = split(workflowStr);
      50         1052 :     defaultWorkflow = parts.back();
      51         1052 :     parts.pop_back();
      52         1052 :     rules.assign(parts.begin(), parts.end());
      53         1052 :   }
      54              : 
      55              :   std::vector<Rule> rules;
      56              :   std::string_view defaultWorkflow;
      57              : };
      58              : 
      59              : using Workflows = absl::flat_hash_map<std::string_view, Workflow>;
      60              : 
      61              : class Node {
      62              : public:
      63         4226 :   virtual ~Node() = default;
      64              :   [[nodiscard]] virtual bool accept(PartRatings const &p) const = 0;
      65              :   [[nodiscard]] virtual size_t acceptedCombinations(PartRatings const &min,
      66              :                                                     PartRatings const &max) const = 0;
      67              : };
      68              : 
      69              : std::unique_ptr<Node> makeNode(Workflows const &wfs, std::string_view const wf);
      70              : std::unique_ptr<Node> makeNode(Workflows const &wfs, Workflow const &wf, auto it);
      71              : 
      72              : class AcceptingNode final : public Node {
      73              : public:
      74           94 :   [[nodiscard]] bool accept(PartRatings const &) const final { return true; }
      75              : 
      76              :   [[nodiscard]] size_t acceptedCombinations(PartRatings const &min,
      77          542 :                                             PartRatings const &max) const final {
      78          542 :     return size_t(max[0] - min[0] + 1) * size_t(max[1] - min[1] + 1) * size_t(max[2] - min[2] + 1) *
      79          542 :            size_t(max[3] - min[3] + 1);
      80          542 :   }
      81              : };
      82              : 
      83              : class RejectingNode final : public Node {
      84              : public:
      85          106 :   [[nodiscard]] bool accept(PartRatings const &) const final { return false; }
      86              : 
      87          515 :   [[nodiscard]] size_t acceptedCombinations(PartRatings const &, PartRatings const &) const final {
      88          515 :     return 0;
      89          515 :   };
      90              : };
      91              : 
      92              : class LessThanNode final : public Node {
      93              : public:
      94              :   LessThanNode(Workflows const &wfs, Workflow const &wf, auto it)
      95         1080 :       : _idx(it->idx), _threshold(it->threshold) {
      96              : 
      97         1080 :     ASSUME(it->op == '<');
      98              : 
      99         1080 :     _ltNode = makeNode(wfs, it->acceptedWorkflow);
     100              : 
     101         1080 :     if (std::next(it) == wf.rules.end())
     102          528 :       _geNode = makeNode(wfs, wf.defaultWorkflow);
     103          552 :     else {
     104          552 :       _geNode = makeNode(wfs, wf, std::next(it));
     105          552 :     }
     106         1080 :   }
     107              : 
     108          990 :   [[nodiscard]] bool accept(PartRatings const &p) const final {
     109          990 :     return p[_idx] < _threshold ? _ltNode->accept(p) : _geNode->accept(p);
     110          990 :   }
     111              : 
     112              :   [[nodiscard]] size_t acceptedCombinations(PartRatings const &min,
     113          540 :                                             PartRatings const &max) const final {
     114              : 
     115          540 :     PartRatings newMax = max;
     116          540 :     newMax[_idx] = _threshold - 1;
     117          540 :     PartRatings newMin = min;
     118          540 :     newMin[_idx] = _threshold;
     119              : 
     120          540 :     return _ltNode->acceptedCombinations(min, newMax) + _geNode->acceptedCombinations(newMin, max);
     121          540 :   }
     122              : 
     123              : private:
     124              :   unsigned _idx;
     125              :   unsigned _threshold;
     126              :   std::unique_ptr<Node> _ltNode;
     127              :   std::unique_ptr<Node> _geNode;
     128              : };
     129              : 
     130              : class GreaterThanNode final : public Node {
     131              : public:
     132              :   GreaterThanNode(Workflows const &wfs, Workflow const &wf, auto it)
     133         1032 :       : _idx(it->idx), _threshold(it->threshold) {
     134              : 
     135         1032 :     ASSUME(it->op == '>');
     136              : 
     137         1032 :     _gtNode = makeNode(wfs, it->acceptedWorkflow);
     138              : 
     139         1032 :     if (std::next(it) == wf.rules.end())
     140          524 :       _leNode = makeNode(wfs, wf.defaultWorkflow);
     141          508 :     else {
     142          508 :       _leNode = makeNode(wfs, wf, std::next(it));
     143          508 :     }
     144         1032 :   }
     145              : 
     146          829 :   [[nodiscard]] bool accept(PartRatings const &p) const final {
     147          829 :     return p[_idx] > _threshold ? _gtNode->accept(p) : _leNode->accept(p);
     148          829 :   }
     149              : 
     150              :   [[nodiscard]] size_t acceptedCombinations(PartRatings const &min,
     151          516 :                                             PartRatings const &max) const final {
     152              : 
     153          516 :     PartRatings newMax = max;
     154          516 :     newMax[_idx] = _threshold;
     155          516 :     PartRatings newMin = min;
     156          516 :     newMin[_idx] = _threshold + 1;
     157              : 
     158          516 :     return _gtNode->acceptedCombinations(newMin, max) + _leNode->acceptedCombinations(min, newMax);
     159          516 :   }
     160              : 
     161              : private:
     162              :   unsigned _idx;
     163              :   unsigned _threshold;
     164              :   std::unique_ptr<Node> _gtNode;
     165              :   std::unique_ptr<Node> _leNode;
     166              : };
     167              : 
     168         3166 : std::unique_ptr<Node> makeNode(Workflows const &wfs, std::string_view const wf) {
     169              : 
     170         3166 :   if (wf == "A")
     171         1084 :     return std::make_unique<AcceptingNode>();
     172         2082 :   else if (wf == "R")
     173         1030 :     return std::make_unique<RejectingNode>();
     174         1052 :   else {
     175         1052 :     auto it = wfs.find(wf);
     176         1052 :     ASSUME(it != wfs.end());
     177         1052 :     return makeNode(wfs, it->second, it->second.rules.begin());
     178         1052 :   }
     179         3166 : }
     180              : 
     181         2112 : std::unique_ptr<Node> makeNode(Workflows const &wfs, Workflow const &wf, auto it) {
     182         2112 :   if (it->op == '<')
     183         1080 :     return std::make_unique<LessThanNode>(wfs, wf, it);
     184         1032 :   else
     185         1032 :     return std::make_unique<GreaterThanNode>(wfs, wf, it);
     186         2112 : }
     187              : 
     188            2 : std::unique_ptr<Node> parseWorkflows(std::span<std::string_view const> const input) {
     189            2 :   static const RE2 pattern = R"(([a-z]+){(.*)})";
     190              : 
     191            2 :   absl::flat_hash_map<std::string_view, Workflow> workflows;
     192              : 
     193         1052 :   for (std::string_view const line : input) {
     194         1052 :     std::string_view name, ruleStr;
     195         1052 :     ASSUME(RE2::FullMatch(line, pattern, &name, &ruleStr));
     196         1052 :     workflows.emplace(name, Workflow(ruleStr));
     197         1052 :   }
     198              : 
     199            2 :   return makeNode(workflows, "in");
     200            2 : }
     201              : 
     202            1 : std::vector<PartRatings> parsePartRatings(std::span<std::string_view const> const input) {
     203            1 :   static const RE2 pattern = R"({x=(\d+),m=(\d+),a=(\d+),s=(\d+)})";
     204              : 
     205            1 :   std::vector<PartRatings> result;
     206            1 :   result.reserve(input.size());
     207              : 
     208          200 :   for (std::string_view const line : input) {
     209          200 :     PartRatings r;
     210          200 :     ASSUME(RE2::FullMatch(line, pattern, &r[0], &r[1], &r[2], &r[3]));
     211          200 :     result.push_back(r);
     212          200 :   }
     213              : 
     214            1 :   return result;
     215            1 : }
     216              : 
     217            1 : auto parse(std::string_view const input) {
     218            1 :   LinewiseInput lines(input);
     219            1 :   std::vector<std::span<std::string_view const>> splitLines = lines.splitOnEmptyLines();
     220            1 :   ASSUME(splitLines.size() == 2u);
     221            1 :   return std::make_tuple(parseWorkflows(splitLines[0]), parsePartRatings(splitLines[1]));
     222            1 : }
     223              : 
     224              : } // namespace
     225              : 
     226            1 : template <> std::string solvePart1<2023, 19>(std::string_view const input) {
     227            1 :   auto [workflows, parts] = parse(input);
     228            1 :   size_t sum = 0;
     229          200 :   for (auto const &part : parts) {
     230          200 :     if (workflows->accept(part)) {
     231           94 :       sum += std::reduce(part.begin(), part.end());
     232           94 :     }
     233          200 :   }
     234              : 
     235            1 :   return std::to_string(sum);
     236            1 : }
     237              : 
     238            1 : template <> std::string solvePart2<2023, 19>(std::string_view const input) {
     239            1 :   LinewiseInput lines(input);
     240            1 :   std::vector<std::span<std::string_view const>> splitLines = lines.splitOnEmptyLines();
     241            1 :   auto workflows = parseWorkflows(splitLines[0]);
     242            1 :   return std::to_string(workflows->acceptedCombinations({1, 1, 1, 1}, {4000, 4000, 4000, 4000}));
     243            1 : }
        

Generated by: LCOV version 2.0-1