AoC code coverage
Current view: top level - puzzles/2023 - Day03.cpp (source / functions) Coverage Total Hit
Test: master Lines: 95.6 % 137 131
Test Date: 2025-07-28 10:53:57 Functions: 100.0 % 14 14

            Line data    Source code
       1              : #include "LinewiseInput.h"
       2              : #include "PuzzleImpl.h"
       3              : 
       4              : #include <libassert/assert.hpp>
       5              : 
       6              : #include <algorithm>
       7              : #include <array>
       8              : #include <charconv>
       9              : #include <string_view>
      10              : 
      11              : namespace {
      12              : 
      13              : unsigned toUnsigned(std::string_view::const_iterator const begin,
      14         1772 :                     std::string_view::const_iterator const end) {
      15         1772 :   unsigned i = 0u;
      16         1772 :   [[maybe_unused]] std::from_chars_result result = std::from_chars(begin, end, i);
      17         1772 :   DEBUG_ASSERT(result.ec == std::errc());
      18         1772 :   return i;
      19         1772 : }
      20              : 
      21              : size_t getPartNumbers(std::string_view const line, std::string_view const topNeighborLine,
      22          140 :                       std::string_view const bottomNeighborLine) {
      23              : 
      24        22016 :   auto isDigit = [](char const c) { return std::isdigit(c); };
      25         8448 :   auto isSymbol = [](char const c) { return c != '.' && !std::isdigit(c); };
      26              : 
      27          140 :   size_t sum = 0u;
      28              : 
      29         1350 :   for (auto it = std::ranges::find_if(line, isDigit); it != line.end();
      30         1210 :        it = std::find_if(it, line.end(), isDigit)) {
      31         1210 :     auto numberStart = it;
      32         1210 :     it = std::find_if_not(it, line.end(), isDigit);
      33         1210 :     auto numberEnd = it;
      34              : 
      35         1210 :     size_t const startPos = std::distance(line.begin(), numberStart);
      36         1210 :     size_t leftNeighborPos = startPos;
      37         1210 :     if (leftNeighborPos != 0) {
      38         1197 :       --leftNeighborPos;
      39         1197 :       if (isSymbol(line[leftNeighborPos])) {
      40           88 :         sum += toUnsigned(numberStart, numberEnd);
      41           88 :         continue;
      42           88 :       }
      43         1197 :     }
      44              : 
      45         1122 :     size_t const endPos = std::distance(line.begin(), numberEnd);
      46         1122 :     size_t rightNeighborPos = endPos;
      47         1122 :     if (rightNeighborPos != line.size()) {
      48         1118 :       ++rightNeighborPos;
      49         1118 :       if (isSymbol(line[rightNeighborPos - 1u])) {
      50           93 :         sum += toUnsigned(numberStart, numberEnd);
      51           93 :         continue;
      52           93 :       }
      53         1118 :     }
      54              : 
      55         1642 :     auto checkLine = [=](std::string_view const l) {
      56         1642 :       return !l.empty() &&
      57         1642 :              std::any_of(l.begin() + leftNeighborPos, l.begin() + rightNeighborPos, isSymbol);
      58         1642 :     };
      59              : 
      60         1029 :     if (checkLine(topNeighborLine) || checkLine(bottomNeighborLine)) {
      61          890 :       sum += toUnsigned(numberStart, numberEnd);
      62          890 :     }
      63         1029 :   }
      64          140 :   return sum;
      65          140 : }
      66              : 
      67              : class Gears {
      68              : public:
      69          701 :   bool addGear(size_t const gear) {
      70          701 :     if (_numGears == 2u)
      71            0 :       return false;
      72          701 :     _gears[_numGears++] = gear;
      73          701 :     return true;
      74          701 :   }
      75              : 
      76          372 :   [[nodiscard]] size_t ratio() const { return _gears[0] * _gears[1]; }
      77              : 
      78              : private:
      79              :   std::array<size_t, 2u> _gears = {0u, 0u};
      80              :   size_t _numGears = 0u;
      81              : };
      82              : 
      83              : size_t getGearRatios(std::string_view const line, std::string_view const topNeighborLine,
      84          140 :                      std::string_view const bottomNeighborLine) {
      85              : 
      86         5964 :   auto isDigit = [](char const c) { return std::isdigit(c); };
      87              : 
      88          140 :   size_t sum = 0u;
      89              : 
      90          512 :   for (auto it = std::ranges::find(line, '*'); it != line.end();
      91          372 :        it = std::find(std::next(it), line.end(), '*')) {
      92              : 
      93          372 :     Gears gears;
      94              : 
      95          372 :     auto addGearsLeftAndRight = [&gears, isDigit](std::string_view line,
      96          388 :                                                   std::string_view::const_iterator starIt) {
      97          388 :       auto leftNeighbor = starIt;
      98          589 :       while (leftNeighbor != line.begin() && isDigit(*std::prev(leftNeighbor))) {
      99          201 :         --leftNeighbor;
     100          201 :       }
     101          388 :       if (leftNeighbor != starIt) {
     102           69 :         if (!gears.addGear(toUnsigned(leftNeighbor, starIt)))
     103            0 :           return false;
     104           69 :       }
     105              : 
     106          388 :       auto rightNeighbor = std::next(starIt);
     107          578 :       while (rightNeighbor != line.end() && isDigit(*rightNeighbor)) {
     108          190 :         ++rightNeighbor;
     109          190 :       }
     110          388 :       if (std::prev(rightNeighbor) != starIt) {
     111           67 :         if (!gears.addGear(toUnsigned(std::next(starIt), rightNeighbor)))
     112            0 :           return false;
     113           67 :       }
     114          388 :       return true;
     115          388 :     };
     116              : 
     117          372 :     ASSUME(addGearsLeftAndRight(line, it));
     118              : 
     119          372 :     size_t const starPos = std::distance(line.begin(), it);
     120          744 :     auto addGears = [&gears, starPos, isDigit, addGearsLeftAndRight](std::string_view const l) {
     121          744 :       if (starPos != 0 && starPos + 1u != l.size() && isDigit(l[starPos - 1u]) &&
     122          744 :           !isDigit(l[starPos]) && isDigit(l[starPos + 1])) {
     123              :         // special case: two numbers
     124           16 :         auto const starIt = l.begin() + starPos;
     125           16 :         return addGearsLeftAndRight(l, starIt);
     126           16 :       }
     127              : 
     128          728 :       auto findGear = [&](size_t const pos) {
     129          565 :         auto leftIt = l.begin() + pos;
     130         1053 :         while (leftIt != l.begin() && isDigit(*std::prev(leftIt))) {
     131          488 :           --leftIt;
     132          488 :         }
     133          565 :         auto rightIt = l.begin() + pos + 1u;
     134         1142 :         while (rightIt != l.end() && isDigit(*rightIt)) {
     135          577 :           ++rightIt;
     136          577 :         }
     137          565 :         return toUnsigned(leftIt, rightIt);
     138          565 :       };
     139              : 
     140          728 :       std::optional<unsigned> gear;
     141          728 :       if (isDigit(l[starPos])) {
     142          325 :         gear = findGear(starPos);
     143          403 :       } else if (starPos != 0 && isDigit(l[starPos - 1u])) {
     144          117 :         gear = findGear(starPos - 1u);
     145          286 :       } else if (starPos != l.size() - 1U && isDigit(l[starPos + 1u])) {
     146          123 :         gear = findGear(starPos + 1u);
     147          123 :       }
     148          728 :       if (gear) {
     149          565 :         if (!gears.addGear(*gear))
     150            0 :           return false;
     151          565 :       }
     152              : 
     153          728 :       return true;
     154          728 :     };
     155              : 
     156          372 :     if (!addGears(topNeighborLine))
     157            0 :       continue;
     158              : 
     159          372 :     if (!addGears(bottomNeighborLine))
     160            0 :       continue;
     161              : 
     162          372 :     sum += gears.ratio();
     163          372 :   }
     164          140 :   return sum;
     165          140 : }
     166              : 
     167              : } // namespace
     168              : 
     169            1 : template <> size_t part1<2023, 3>(std::string_view const input) {
     170            1 :   LinewiseInput const lines(input);
     171              : 
     172            1 :   size_t sum = 0u;
     173          141 :   for (auto it = lines.begin(); it != lines.end(); ++it) {
     174          140 :     std::string_view top = it == lines.begin() ? std::string_view{} : *std::prev(it);
     175          140 :     std::string_view bottom = std::next(it) == lines.end() ? std::string_view{} : *std::next(it);
     176          140 :     sum += getPartNumbers(*it, top, bottom);
     177          140 :   }
     178              : 
     179            1 :   return sum;
     180            1 : }
     181              : 
     182            1 : template <> size_t part2<2023, 3>(std::string_view const input) {
     183            1 :   LinewiseInput const lines(input);
     184              : 
     185            1 :   size_t sum = 0u;
     186          141 :   for (auto it = lines.begin(); it != lines.end(); ++it) {
     187          140 :     std::string_view top = it == lines.begin() ? std::string_view{} : *std::prev(it);
     188          140 :     std::string_view bottom = std::next(it) == lines.end() ? std::string_view{} : *std::next(it);
     189          140 :     sum += getGearRatios(*it, top, bottom);
     190          140 :   }
     191              : 
     192            1 :   return sum;
     193            1 : }
        

Generated by: LCOV version 2.0-1