AoC code coverage
Current view: top level - puzzles/2024 - Day15.cpp (source / functions) Coverage Total Hit
Test: master Lines: 91.6 % 119 109
Test Date: 2025-12-11 19:43:23 Functions: 91.7 % 12 11

            Line data    Source code
       1              : #include "PuzzleImpl.h"
       2              : 
       3              : #include "AsciiMap.h"
       4              : #include "Grid2d.h"
       5              : #include "LinewiseInput.h"
       6              : 
       7              : #include <libassert/assert.hpp>
       8              : 
       9              : #include <algorithm>
      10              : #include <ranges>
      11              : #include <set>
      12              : #include <string>
      13              : #include <string_view>
      14              : #include <tuple>
      15              : 
      16              : using namespace std::literals;
      17              : 
      18              : using Coord = Grid2d<char>::Coord;
      19              : 
      20              : namespace {
      21              : 
      22            0 : constexpr AsciiMap<Vec2<int>> makeMovementMap() {
      23            0 :   AsciiMap<Vec2<int>> map{{0, 0}};
      24            0 :   map['^'] = {0, 1};
      25            0 :   map['v'] = {0, -1};
      26            0 :   map['<'] = {-1, 0};
      27            0 :   map['>'] = {1, 0};
      28            0 :   return map;
      29            0 : };
      30              : 
      31              : constexpr AsciiMap<Vec2<int>> movementMap = makeMovementMap();
      32              : 
      33            2 : auto parse(std::string_view const input) {
      34            2 :   auto const r = std::ranges::search(input, "\n\n"sv);
      35            2 :   std::string_view const gridStr{input.begin(), r.begin()};
      36            2 :   std::string_view const gridMovement{r.end(), input.end()};
      37              : 
      38            2 :   return std::make_tuple(LinewiseInput(gridStr).makeCharGrid2d(),
      39        80074 :                          gridMovement | std::views::filter([](char const c) { return c != '\n'; }) |
      40        40000 :                              std::views::transform([&](char const c) { return movementMap[c]; }) |
      41            2 :                              std::ranges::to<std::vector<Vec2<int>>>());
      42            2 : }
      43              : 
      44        30017 : void moveSimple(Grid2d<char> &map, Coord &robotPos, Vec2<int> const &move) {
      45        30017 :   Coord pos = robotPos;
      46        68952 :   while (map[pos] != '#' && map[pos] != '.')
      47        38935 :     pos += move;
      48              : 
      49        30017 :   if (map[pos] == '#')
      50         2926 :     return;
      51              : 
      52        30017 :   ASSUME(map[pos] == '.');
      53              : 
      54        61019 :   for (; pos != robotPos; pos -= move)
      55        33928 :     std::swap(map[pos], map[pos - move]);
      56              : 
      57        27091 :   robotPos += move;
      58        27091 : }
      59              : 
      60              : class VerticalMover {
      61              : public:
      62            1 :   VerticalMover(Grid2d<char> &map) : _map(&map), _columnStart(map.xSize()) {
      63            1 :     _curActiveColumns.reserve(map.xSize());
      64            1 :   }
      65              : 
      66         9983 :   Coord operator()(Coord robotPos, Vec2<int> const move) {
      67         9983 :     ASSUME(move.y() != 0);
      68         9983 :     Grid2d<char> &map = *_map;
      69              : 
      70         9983 :     std::ranges::fill(_columnStart, -1);
      71         9983 :     std::set<int> activeColumns;
      72         9983 :     activeColumns.insert(robotPos.x());
      73         9983 :     _columnStart[robotPos.x()] = robotPos.y();
      74         9983 :     int y = robotPos.y();
      75              : 
      76         9983 :     _pendingShifts.clear();
      77              : 
      78        21978 :     while (!activeColumns.empty()) {
      79        13424 :       int nextY = y + move.y();
      80        13424 :       _curActiveColumns.assign(activeColumns.begin(), activeColumns.end());
      81        16625 :       for (int const x : _curActiveColumns) {
      82        16625 :         switch (map[x, nextY]) {
      83         1429 :         case '#':
      84         1429 :           return robotPos;
      85        10279 :         case '.':
      86        10279 :           ASSUME(_columnStart[x] != -1);
      87        10279 :           _pendingShifts.emplace_back(Coord{x, _columnStart[x]}, Coord{x, nextY});
      88        10279 :           _columnStart[x] = -1;
      89        10279 :           activeColumns.erase(x);
      90        10279 :           break;
      91         2457 :         case '[':
      92         2457 :           if (activeColumns.insert(x + 1).second) {
      93         1314 :             ASSUME(_columnStart[x + 1] == -1);
      94         1314 :             _columnStart[x + 1] = nextY;
      95         1314 :           }
      96         2457 :           break;
      97         2460 :         case ']':
      98         2460 :           if (activeColumns.insert(x - 1).second) {
      99         1317 :             ASSUME(_columnStart[x - 1] == -1);
     100         1317 :             _columnStart[x - 1] = nextY;
     101         1317 :           }
     102         2460 :           break;
     103            0 :         default:
     104            0 :           UNREACHABLE(map[x, nextY]);
     105        16625 :         }
     106        16625 :       }
     107        11995 :       y = nextY;
     108        11995 :     }
     109              : 
     110         8554 :     for (auto const &c : _pendingShifts)
     111        22308 :       for (auto pos = c.second; pos != c.first; pos -= move)
     112        12456 :         std::swap(map[pos], map[pos - move]);
     113              : 
     114         8554 :     return robotPos + move;
     115         9983 :   }
     116              : 
     117              : private:
     118              :   Grid2d<char> *_map;
     119              :   std::vector<int> _columnStart;
     120              :   std::vector<std::pair<Coord, Coord>> _pendingShifts;
     121              :   std::vector<int> _curActiveColumns;
     122              : };
     123              : 
     124            1 : Grid2d<char> widenMap(Grid2d<char> const &map) {
     125            1 :   Grid2d<char> widenedMap(map.xSize() * 2, map.ySize());
     126              : 
     127           51 :   for (int y = 0; y < map.ySize(); ++y)
     128         2550 :     for (int x = 0; x < map.xSize(); ++x)
     129         2500 :       switch (map[x, y]) {
     130            1 :       case '@':
     131            1 :         widenedMap[x * 2, y] = '@';
     132            1 :         widenedMap[x * 2 + 1, y] = '.';
     133            1 :         break;
     134          594 :       case 'O':
     135          594 :         widenedMap[x * 2, y] = '[';
     136          594 :         widenedMap[x * 2 + 1, y] = ']';
     137          594 :         break;
     138         1905 :       default:
     139         1905 :         widenedMap[x * 2, y] = map[x, y];
     140         1905 :         widenedMap[x * 2 + 1, y] = map[x, y];
     141         2500 :       }
     142            1 :   return widenedMap;
     143            1 : }
     144              : 
     145              : } // namespace
     146              : 
     147            1 : template <> std::string solvePart1<2024, 15>(std::string_view const input) {
     148            1 :   auto [map, moves] = parse(input);
     149              : 
     150            1 :   auto robotPos = map.find('@');
     151              : 
     152            1 :   for (auto m : moves)
     153        20000 :     moveSimple(map, robotPos, m);
     154              : 
     155            1 :   return std::to_string(std::ranges::fold_left(
     156          594 :       map.flipY().findAll('O') | std::views::transform([&](auto c) { return c.y() * 100 + c.x(); }),
     157            1 :       int64_t(0), std::plus{}));
     158            1 : }
     159              : 
     160            1 : template <> std::string solvePart2<2024, 15>(std::string_view const input) {
     161            1 :   auto [map, moves] = parse(input);
     162              : 
     163            1 :   map = widenMap(map);
     164              : 
     165            1 :   auto robotPos = map.find('@');
     166              : 
     167            1 :   VerticalMover verticalMover(map);
     168              : 
     169        20000 :   for (auto move : moves) {
     170        20000 :     if (move.y() == 0)
     171        10017 :       moveSimple(map, robotPos, move);
     172         9983 :     else
     173         9983 :       robotPos = verticalMover(robotPos, move);
     174        20000 :   }
     175              : 
     176            1 :   return std::to_string(std::ranges::fold_left(
     177          594 :       map.flipY().findAll('[') | std::views::transform([&](auto c) { return c.y() * 100 + c.x(); }),
     178            1 :       int64_t(0), std::plus{}));
     179            1 : }
        

Generated by: LCOV version 2.0-1