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

Generated by: LCOV version 2.0-1