Skip to content

Commit cd9c800

Browse files
Day 19 (#37)
1 parent 9aaba27 commit cd9c800

17 files changed

+1038
-0
lines changed

day_19/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
aoc_day(19)

day_19/benchmark/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
aoc_add_benchmark()

day_19/lib/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
aoc_add_library()
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include "count_processing_strategy.hpp"
2+
3+
namespace aoc::day_19 {
4+
auto count_processing_strategy::handle_max_depth() const -> size_t {
5+
return 0;
6+
}
7+
auto count_processing_strategy::handle_empty() const -> size_t {
8+
return 1;
9+
}
10+
auto count_processing_strategy::check_cache(std::string_view design, pattern_matcher_state & state) const
11+
-> std::optional<size_t> {
12+
auto it = state.m_count_cache.find(design);
13+
return it != state.m_count_cache.end() ? std::optional{it->second} : std::nullopt;
14+
}
15+
auto count_processing_strategy::initial_value() const -> size_t {
16+
return 0;
17+
}
18+
auto count_processing_strategy::process_match(std::string_view next_design, size_t next_depth, size_t current_total,
19+
pattern_matcher_state & state) const -> size_t {
20+
return current_total + matcher->count_recursive(next_design, next_depth, state);
21+
}
22+
auto count_processing_strategy::should_break(size_t) const -> bool {
23+
return false;
24+
}
25+
auto count_processing_strategy::update_cache(std::string_view design, size_t result, pattern_matcher_state & state)
26+
-> void {
27+
state.m_count_cache[design] = result;
28+
}
29+
30+
} // namespace aoc::day_19
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <optional>
5+
#include <string_view>
6+
7+
#include "matcher.hpp"
8+
9+
namespace aoc::day_19 {
10+
11+
/// @brief Strategy class for counting pattern matches
12+
struct count_processing_strategy {
13+
pattern_matcher * matcher; ///< Pointer to associated pattern matcher
14+
15+
/// @brief Handles maximum recursion depth case
16+
/// @return Zero to indicate no valid combinations
17+
[[nodiscard]] auto handle_max_depth() const -> size_t;
18+
19+
/// @brief Handles empty design case
20+
/// @return One to indicate one valid combination found
21+
[[nodiscard]] auto handle_empty() const -> size_t;
22+
23+
/// @brief Checks cache for existing count
24+
/// @param design Design string to check
25+
/// @param state Current matcher state
26+
/// @return Optional containing cached count if available
27+
[[nodiscard]] auto check_cache(std::string_view design, pattern_matcher_state & state) const
28+
-> std::optional<size_t>;
29+
30+
/// @brief Provides initial value for counting
31+
/// @return Initial zero count
32+
[[nodiscard]] auto initial_value() const -> size_t;
33+
34+
/// @brief Processes a single pattern match for counting
35+
/// @param next_design Remaining design after current match
36+
/// @param next_depth Next recursion depth
37+
/// @param current_total Current count total
38+
/// @param state Current matcher state
39+
/// @return Updated total count
40+
[[nodiscard]] auto process_match(std::string_view next_design, size_t next_depth, size_t current_total,
41+
pattern_matcher_state & state) const -> size_t;
42+
43+
/// @brief Determines if counting should stop
44+
/// @param ignored Unused size_t parameter
45+
/// @return Always false as all combinations need to be counted
46+
[[nodiscard]] auto should_break(size_t) const -> bool;
47+
48+
/// @brief Updates the cache with count result
49+
/// @param design Design string being processed
50+
/// @param result Count result to cache
51+
/// @param state Current matcher state
52+
auto update_cache(std::string_view design, size_t result, pattern_matcher_state & state) -> void;
53+
};
54+
55+
} // namespace aoc::day_19
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include "match_processing_strategy.hpp"
2+
3+
namespace aoc::day_19 {
4+
5+
auto match_processing_strategy::handle_max_depth() const -> bool {
6+
return false;
7+
}
8+
auto match_processing_strategy::handle_empty() const -> bool {
9+
return true;
10+
}
11+
auto match_processing_strategy::check_cache(std::string_view design, pattern_matcher_state & state) const
12+
-> std::optional<bool> {
13+
auto it = state.m_match_cache.find(design);
14+
return it != state.m_match_cache.end() ? std::optional{it->second} : std::nullopt;
15+
}
16+
auto match_processing_strategy::initial_value() const -> bool {
17+
return false;
18+
}
19+
auto match_processing_strategy::process_match(std::string_view next_design, size_t next_depth, bool,
20+
pattern_matcher_state & state) const -> bool {
21+
return matcher->match_recursive(next_design, next_depth, state);
22+
}
23+
auto match_processing_strategy::should_break(bool result) const -> bool {
24+
return result;
25+
}
26+
auto match_processing_strategy::update_cache(std::string_view design, bool result, pattern_matcher_state & state)
27+
-> void {
28+
state.m_match_cache[design] = result;
29+
}
30+
31+
} // namespace aoc::day_19
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <optional>
5+
#include <string_view>
6+
7+
#include "matcher.hpp"
8+
9+
namespace aoc::day_19 {
10+
11+
/// @brief Strategy class for processing pattern matches
12+
struct match_processing_strategy {
13+
pattern_matcher * matcher; ///< Pointer to associated pattern matcher
14+
15+
/// @brief Handles maximum recursion depth case
16+
/// @return False to indicate matching failure
17+
[[nodiscard]] auto handle_max_depth() const -> bool;
18+
19+
/// @brief Handles empty design case
20+
/// @return True to indicate successful match
21+
[[nodiscard]] auto handle_empty() const -> bool;
22+
23+
/// @brief Checks cache for existing match result
24+
/// @param design Design string to check
25+
/// @param state Current matcher state
26+
/// @return Optional containing cached result if available
27+
[[nodiscard]] auto check_cache(std::string_view design, pattern_matcher_state & state) const -> std::optional<bool>;
28+
29+
/// @brief Provides initial value for matching
30+
/// @return Initial false value
31+
[[nodiscard]] auto initial_value() const -> bool;
32+
33+
/// @brief Processes a single pattern match
34+
/// @param next_design Remaining design after current match
35+
/// @param next_depth Next recursion depth
36+
/// @param ignored Currently unused boolean parameter
37+
/// @param state Current matcher state
38+
/// @return True if match is successful
39+
[[nodiscard]] auto process_match(std::string_view next_design, size_t next_depth, bool,
40+
pattern_matcher_state & state) const -> bool;
41+
42+
/// @brief Determines if processing should stop
43+
/// @param result Current result
44+
/// @return True if processing should stop
45+
[[nodiscard]] auto should_break(bool result) const -> bool;
46+
47+
/// @brief Updates the cache with match result
48+
/// @param design Design string being processed
49+
/// @param result Match result to cache
50+
/// @param state Current matcher state
51+
auto update_cache(std::string_view design, bool result, pattern_matcher_state & state) -> void;
52+
};
53+
} // namespace aoc::day_19

day_19/lib/matcher.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include "matcher.hpp"
2+
3+
#include <algorithm>
4+
#include <regex>
5+
#include <string>
6+
#include <string_view>
7+
#include <unordered_map>
8+
#include <vector>
9+
10+
#include "count_processing_strategy.hpp"
11+
#include "match_processing_strategy.hpp"
12+
13+
namespace aoc::day_19 {
14+
15+
pattern_matcher::pattern_matcher(std::vector<std::string> patterns) {
16+
std::ranges::sort(patterns, std::ranges::greater{}, [](auto const & p) { return p.length(); });
17+
18+
for (auto const & p : patterns) {
19+
m_patterns.emplace_back(p, std::regex_constants::optimize);
20+
}
21+
}
22+
23+
[[nodiscard]] bool pattern_matcher::can_construct(std::string_view design) {
24+
pattern_matcher_state state;
25+
return match_recursive(design, 0, state);
26+
}
27+
28+
[[nodiscard]] size_t pattern_matcher::count_unique_ways_to_construct(std::string_view design) {
29+
pattern_matcher_state state;
30+
return count_recursive(design, 0, state);
31+
}
32+
33+
[[nodiscard]] bool pattern_matcher::match_recursive(std::string_view remaining_design, size_t depth,
34+
pattern_matcher_state & state) {
35+
return process_patterns(remaining_design, depth, match_processing_strategy{this}, state);
36+
}
37+
38+
[[nodiscard]] size_t pattern_matcher::count_recursive(std::string_view remaining_design, size_t depth,
39+
pattern_matcher_state & state) {
40+
return process_patterns(remaining_design, depth, count_processing_strategy{this}, state);
41+
}
42+
43+
} // namespace aoc::day_19

day_19/lib/matcher.hpp

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#pragma once
2+
3+
#include <algorithm>
4+
#include <regex>
5+
#include <string>
6+
#include <string_view>
7+
#include <unordered_map>
8+
#include <vector>
9+
10+
namespace aoc::day_19 {
11+
12+
/// @brief State object for pattern matching operations containing caches
13+
struct pattern_matcher_state {
14+
std::unordered_map<std::string_view, bool> m_match_cache; ///< Cache for match results
15+
std::unordered_map<std::string_view, size_t> m_count_cache; ///< Cache for count results
16+
};
17+
18+
class count_processing_strategy;
19+
class match_processing_strategy;
20+
21+
/// @brief Class for matching patterns against designs and counting possible combinations
22+
class pattern_matcher {
23+
friend class count_processing_strategy;
24+
friend class match_processing_strategy;
25+
26+
public:
27+
/// @brief Constructs a pattern matcher with given patterns
28+
/// @param patterns Vector of pattern strings to match against
29+
explicit pattern_matcher(std::vector<std::string> patterns);
30+
31+
/// @brief Checks if a design can be constructed using the patterns
32+
/// @param design The design string to check
33+
/// @return True if the design can be constructed, false otherwise
34+
[[nodiscard]] bool can_construct(std::string_view design);
35+
36+
/// @brief Counts unique ways to construct a design using the patterns
37+
/// @param design The design string to analyze
38+
/// @return Number of unique ways to construct the design
39+
[[nodiscard]] size_t count_unique_ways_to_construct(std::string_view design);
40+
41+
private:
42+
static constexpr size_t MAX_RECURSION_DEPTH = 100; ///< Maximum recursion depth to prevent stack overflow
43+
std::vector<std::regex> m_patterns; ///< Compiled regex patterns
44+
45+
/// @brief Template method for processing patterns with different strategies
46+
/// @tparam Func Strategy type for processing
47+
/// @param remaining_design Remaining design to process
48+
/// @param depth Current recursion depth
49+
/// @param processing_strategy Strategy object for processing
50+
/// @param state Current matcher state
51+
/// @return Result of processing based on strategy type
52+
template <typename Func>
53+
[[nodiscard]] auto process_patterns(std::string_view remaining_design, size_t depth, Func && processing_strategy,
54+
pattern_matcher_state & state) {
55+
if (depth >= MAX_RECURSION_DEPTH) {
56+
return processing_strategy.handle_max_depth();
57+
}
58+
if (remaining_design.empty()) {
59+
return processing_strategy.handle_empty();
60+
}
61+
62+
if (auto cached = processing_strategy.check_cache(remaining_design, state)) {
63+
return *cached;
64+
}
65+
66+
auto result = processing_strategy.initial_value();
67+
for (auto const & pattern : m_patterns) {
68+
std::match_results<std::string_view::const_iterator> match;
69+
if (std::regex_search(remaining_design.begin(), remaining_design.end(), match, pattern,
70+
std::regex_constants::match_continuous)) {
71+
result = processing_strategy.process_match(remaining_design.substr(match.length()), depth + 1, result,
72+
state);
73+
if (processing_strategy.should_break(result)) {
74+
break;
75+
}
76+
}
77+
}
78+
79+
processing_strategy.update_cache(remaining_design, result, state);
80+
return result;
81+
}
82+
83+
/// @brief Recursive counting implementation
84+
[[nodiscard]] size_t count_recursive(std::string_view remaining_design, size_t depth,
85+
pattern_matcher_state & state);
86+
87+
/// @brief Recursive matching implementation
88+
[[nodiscard]] bool match_recursive(std::string_view remaining_design, size_t depth, pattern_matcher_state & state);
89+
};
90+
91+
} // namespace aoc::day_19

day_19/lib/parser.cpp

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include "parser.hpp"
2+
3+
#include <algorithm>
4+
#include <array>
5+
#include <ranges>
6+
7+
#include "../../shared/src/ranges_compatibility_layer.hpp"
8+
9+
namespace aoc::day_19 {
10+
11+
[[nodiscard]] static auto parsePatterns(std::string_view input)
12+
-> std::expected<std::vector<std::string>, std::error_code> {
13+
std::vector<std::string> patterns;
14+
auto const lines = std::ranges::views::split(input, ',') | std::ranges::views::transform([](auto const & line) {
15+
auto filtered_view = std::ranges::views::drop_while(line, [](auto c) { return c == ' '; });
16+
return std::string{filtered_view.begin(), filtered_view.end()};
17+
}) |
18+
aoc::ranges::to<std::vector<std::string>>;
19+
20+
for (auto const & line : lines) {
21+
patterns.push_back(std::string{line.begin(), line.end()});
22+
}
23+
return patterns;
24+
}
25+
26+
[[nodiscard]] static auto parseDesigns(std::string_view input)
27+
-> std::expected<std::vector<std::string>, std::error_code> {
28+
std::vector<std::string> designs;
29+
auto const lines = std::ranges::views::split(input, '\n') | std::ranges::views::transform([](auto const & line) {
30+
auto str = std::string(line.begin(), line.end());
31+
if (str.ends_with('\r')) {
32+
return str.substr(0, str.size() - 1);
33+
}
34+
return str;
35+
}) |
36+
std::ranges::views::filter([](std::string_view sv) { return !sv.empty(); }) |
37+
aoc::ranges::to<std::vector<std::string>>;
38+
for (auto const & line : lines) {
39+
designs.push_back(std::string{line.begin(), line.end()});
40+
}
41+
return designs;
42+
}
43+
44+
[[nodiscard]] auto parseInput(std::string_view input) -> std::expected<puzzle_input, std::error_code> {
45+
constexpr auto delimiters = std::array{"\r\n\r\n", "\n\n"};
46+
auto split_pos =
47+
std::ranges::find_if(delimiters, [&](auto delim) { return input.find(delim) != std::string_view::npos; });
48+
49+
if (split_pos == delimiters.end()) {
50+
return std::unexpected(std::make_error_code(std::errc::invalid_argument));
51+
}
52+
53+
auto const split_at = input.find(*split_pos);
54+
auto const patterns = input.substr(0, split_at);
55+
auto const designs = input.substr(split_at);
56+
57+
auto parsed_patterns = parsePatterns(patterns);
58+
if (!parsed_patterns) {
59+
return std::unexpected(parsed_patterns.error());
60+
}
61+
auto parsed_designs = parseDesigns(designs);
62+
if (!parsed_designs) {
63+
return std::unexpected(parsed_designs.error());
64+
}
65+
66+
return puzzle_input{std::move(*parsed_patterns), std::move(*parsed_designs)};
67+
}
68+
69+
} // namespace aoc::day_19

0 commit comments

Comments
 (0)