summaryrefslogtreecommitdiffstats
path: root/day04/solution.cpp
blob: fe33120a4d9e7f693b70a6d4353d22c44fa7fd9a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <array>
#include <iterator>
#include <print>
#include <iostream>
#include <ranges>
#include <string_view>
#include <algorithm>
#include <functional>
#include <utility>
#include <vector>
#include <tuple>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <numeric>

using namespace std::literals;
namespace views = std::views;
namespace ranges = std::ranges;

const auto parse_input() {
  std::vector<std::string> grid;

  for (std::string line; std::getline(std::cin, line);) {
    grid.push_back(std::move(line));
  }

  return grid;
}

using IndexPair = std::pair<int, int>;

void part1(auto input) {
  uint64_t answer{0};
  const int width(input[0].size());
  const int height(input.size());

  constexpr auto zeros = views::repeat(0, 4);
  constexpr auto incr = views::iota(0, 4);
  constexpr auto decr = incr | views::transform(std::negate<>{});
  // Pack directions into a tuple because they have different types
  constexpr auto directions = std::tuple{
    views::zip(decr, zeros),    // top
    views::zip(decr, incr),     // top-right
    views::zip(zeros, incr),    // right
    views::zip(incr, incr),     // bottom-right
    views::zip(incr, zeros),    // bottom
    views::zip(incr, decr),     // bottom-left
    views::zip(zeros, decr),    // left
    views::zip(decr, decr),     // top-left
  };

  auto in_bounds = [width, height](IndexPair e) -> bool {
    auto &&[i, j] = e;
    return 0 <= i and i < height and 0 <= j and j < width;
  };

  auto get_input_element = [input](IndexPair e) -> char {
    auto &&[i, j] = e;
    return input[i][j];
  };

  for (auto &&[i, j] :
         views::cartesian_product(views::iota(0, width),
                                  views::iota(0, height))) {

    auto add_offset = [i, j](IndexPair e) {
      const auto [di, dj] = e;
      return IndexPair{i + di, j + dj};
    };

    // std::apply is needed to unfold the ‘directions’ tuple.
    std::apply([=, &answer](auto &&... dirs) {
      (((answer += ranges::equal(dirs
                                 | views::transform(add_offset)
                                 | views::filter(in_bounds)
                                 | views::transform(get_input_element), "XMAS"sv))), ...);
    }, directions);
  }

  std::println("{}", answer);
}

void part2(auto input) {
  uint64_t answer{0};
  int width(input[0].size());
  int height(input.size());

  constexpr size_t window_size = 3;
  for (auto &&v_window : input | views::slide(window_size)) {
    for (const auto &[r1, r2, r3] : views::zip(v_window[0] | views::slide(window_size),
                                               v_window[1] | views::slide(window_size),
                                               v_window[2] | views::slide(window_size))) {
      const std::array<char, 3> diagonals[] = {
        {r1[0], r2[1], r3[2]},
        {r3[0], r2[1], r1[2]},
      };

      answer += ranges::all_of(diagonals, [](const auto &diag) {
        return ranges::equal(diag, "MAS"sv) or ranges::equal(diag, "SAM"sv);
      });
    }
  }
  std::println("{}", answer);
}

int main() {
  const auto input = parse_input();

#ifndef NO_PART1
  part1(input);
#endif

#ifndef NO_PART2
  part2(input);
#endif
  return 0;
}