diff --git a/2021/puzzle-23-01.cc b/2021/puzzle-23-01.cc new file mode 100644 index 0000000..237b0c4 --- /dev/null +++ b/2021/puzzle-23-01.cc @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Map: +// ############# +// #ab.c.d.e.fg# +// ###h#i#j#k### +// #l#m#n#o# +// ######### +// +// a -1- b -2- c -2- d -2- e -2- f -1- g +// \ / \ / \ / \ / +// 2 2 2 2 2 2 2 2 +// + + + + +// h i j k +// | | | | +// 1 1 1 1 +// | | | | +// l m n o + +using Position = char; +using UInt = int; +using Type = char; + +std::multimap> costs{ + {'a', {'b', 1}}, {'b', {'a', 1}}, {'b', {'c', 2}}, {'b', {'h', 2}}, {'c', {'b', 2}}, + {'c', {'d', 2}}, {'c', {'h', 2}}, {'c', {'i', 2}}, {'d', {'c', 2}}, {'d', {'e', 2}}, + {'d', {'i', 2}}, {'d', {'j', 2}}, {'e', {'d', 2}}, {'e', {'f', 2}}, {'e', {'j', 2}}, + {'e', {'k', 2}}, {'f', {'e', 2}}, {'f', {'g', 1}}, {'f', {'k', 2}}, {'g', {'f', 1}}, + {'h', {'b', 2}}, {'h', {'c', 2}}, {'h', {'l', 1}}, {'i', {'c', 2}}, {'i', {'d', 2}}, + {'i', {'m', 1}}, {'j', {'d', 2}}, {'j', {'e', 2}}, {'j', {'n', 1}}, {'k', {'e', 2}}, + {'k', {'f', 2}}, {'k', {'o', 1}}, {'l', {'h', 1}}, {'m', {'i', 1}}, {'n', {'j', 1}}, + {'o', {'k', 1}}}; + +std::map multipliers{{'A', 1}, {'B', 10}, {'C', 100}, {'D', 1000}}; + +struct State +{ + auto finished() const noexcept -> bool { return nodes_ == finished_; } + auto size() const noexcept -> UInt { return 15; } + auto node(unsigned idx) noexcept -> Type& { return nodes_[idx]; } + auto node(unsigned idx) const noexcept -> Type const& { return nodes_[idx]; } + + bool check_move(unsigned from, unsigned to) + { + if (nodes_[from] == '.' || nodes_[to] != '.') { + return false; + } + if (from > 10) { + // Only move out of the bottom row if we're not meant to be here. + return nodes_[from] != finished_[from]; + } + if (from > 6 && from < 11) { + if (to > 10) { + // Only move into bottom position if we're moving the correct piece. + return nodes_[from] == finished_[to]; + } + // Moving to top row - anything allowed. + return true; + } + if (from < 7) { + if (to < 7) { + return true; + } + // Can only move down if we're the right type. + return nodes_[from] == finished_[to]; + } + abort(); + } + + bool move(unsigned from, unsigned to) + { + if (!check_move(from, to)) { + return false; + } + std::swap(nodes_[from], nodes_[to]); + return true; + } + + constexpr bool operator<(State const& rhs) const noexcept { return nodes_ < rhs.nodes_; } + constexpr bool operator==(State const& rhs) const noexcept { return nodes_ < rhs.nodes_; } + +private: + std::array nodes_{'.'}; + static std::array finished_; +}; + +template<> +struct std::hash +{ + auto operator()(State const& key) const -> std::size_t + { + std::size_t result{0}; + for (auto i{0}; i < 15; ++i) { + result <<= 3; + if (key.node(i) != '.') { + result |= key.node(i) - 'A' + 1; + } + } + return result; + } +}; + +std::array State::finished_ = {'.', '.', '.', '.', '.', '.', '.', 'A', + 'B', 'C', 'D', 'A', 'B', 'C', 'D'}; + +auto main() -> int +{ + std::regex line2_re{R"(#(.)(.)\.(.)\.(.)\.(.)\.(.)(.)#)"}; + std::regex line3_re{"###(.)#(.)#(.)#(.)###"}; + std::regex line4_re{"#(.)#(.)#(.)#(.)#"}; + + std::string line; + std::smatch m; + std::getline(std::cin, line); + if (line != "#############") { + std::cerr << "Incorrect first line.\n"; + return 1; + } + State initial_state; + + std::getline(std::cin, line); + if (!std::regex_search(line, m, line2_re)) { + std::cerr << "Unable to match line 2 " << line << '\n'; + return 1; + } + for (unsigned i = 0; i < 7; ++i) { + initial_state.node(i) = m.str(i + 1)[0]; + } + + std::getline(std::cin, line); + if (!std::regex_search(line, m, line3_re)) { + std::cerr << "Unable to match line 3 " << line << '\n'; + return 1; + } + for (unsigned i = 0; i < 4; ++i) { + initial_state.node(7 + i) = m.str(i + 1)[0]; + } + + std::getline(std::cin, line); + if (!std::regex_search(line, m, line4_re)) { + std::cerr << "Unable to match line 4 " << line << '\n'; + return 1; + } + for (unsigned i = 0; i < 4; ++i) { + initial_state.node(11 + i) = m.str(i + 1)[0]; + } + + std::map states; + states.insert({initial_state, 0}); + std::set visited; + + while (!states.empty()) { + auto it{std::min_element(states.begin(), states.end(), [](auto const& lhs, auto const& rhs) { + return lhs.second < rhs.second; + })}; + if (visited.size() % 10'000 == 0) { + std::cout << "Visited: " << visited.size() << " number of states: " << states.size() + << " Min energy: " << it->second << '\n'; + } + + State state{it->first}; + UInt cost{it->second}; + visited.insert(state); + states.erase(state); + + if (state.finished()) { + std::cout << "Done with cost " << cost << '\n'; + return 0; + } + + for (unsigned i = 0; i < state.size(); ++i) { + if (state.node(i) == '.') { + continue; + } + auto [it_begin, it_end] = costs.equal_range(i + 'a'); + for (auto cost_it{it_begin}; cost_it != it_end; ++cost_it) { + State next_state{state}; + if (next_state.move(i, cost_it->second.first - 'a') && !visited.contains(next_state)) { + UInt next_cost = cost + cost_it->second.second * multipliers[state.node(i)]; + auto [insert_it, success] = states.insert({next_state, next_cost}); + if (!success) { + insert_it->second = std::min(insert_it->second, next_cost); + } + } + } + } + } + + std::cerr << "FAILED\n"; + return 1; +} diff --git a/2021/puzzle-23-02.cc b/2021/puzzle-23-02.cc new file mode 100644 index 0000000..9e7a0c4 --- /dev/null +++ b/2021/puzzle-23-02.cc @@ -0,0 +1,278 @@ +#include +#include +#include +#include +#include +#include +#include + +// Map: +// 0 1 2 3 4 5 6 +// 7 8 9 10 +// 11 12 13 14 +// 15 16 17 18 +// 19 20 21 22 + +using Position = unsigned; +using UInt = unsigned; +using Type = char; + +std::multimap> legal_moves{ + {0, {1, 1}}, {1, {0, 1}}, {1, {2, 2}}, {1, {7, 2}}, {2, {1, 2}}, {2, {3, 2}}, + {2, {7, 2}}, {2, {8, 2}}, {3, {2, 2}}, {3, {4, 2}}, {3, {8, 2}}, {3, {9, 2}}, + {4, {3, 2}}, {4, {5, 2}}, {4, {9, 2}}, {4, {10, 2}}, {5, {4, 2}}, {5, {6, 1}}, + {5, {10, 2}}, {6, {5, 1}}, {7, {1, 2}}, {7, {2, 2}}, {7, {11, 1}}, {8, {2, 2}}, + {8, {3, 2}}, {8, {12, 1}}, {9, {3, 2}}, {9, {4, 2}}, {9, {13, 1}}, {10, {4, 2}}, + {10, {5, 2}}, {10, {14, 1}}, {11, {7, 1}}, {12, {8, 1}}, {13, {9, 1}}, {14, {10, 1}}, + {11, {15, 1}}, {12, {16, 1}}, {13, {17, 1}}, {14, {18, 1}}, {15, {11, 1}}, {16, {12, 1}}, + {17, {13, 1}}, {18, {14, 1}}, {15, {19, 1}}, {16, {20, 1}}, {17, {21, 1}}, {18, {22, 1}}, + {19, {15, 1}}, {20, {16, 1}}, {21, {17, 1}}, {22, {18, 1}}, +}; + +std::map multipliers{{'A', 1}, {'B', 10}, {'C', 100}, {'D', 1000}}; + +struct State +{ + auto finished() const noexcept -> bool { return nodes_ == finished_; } + auto size() const noexcept -> UInt { return node_size; } + auto node(unsigned idx) noexcept -> Type& { return nodes_[idx]; } + auto node(unsigned idx) const noexcept -> Type const& { return nodes_[idx]; } + + bool check_move(unsigned from, unsigned to) + { + if (nodes_[from] == '.' || nodes_[to] != '.') { + return false; + } + if (from > 18) { + // Bottom row + // Only move out of the bottom row if we're not meant to be here. + return nodes_[from] != finished_[from]; + } + if (from > 14 && from < 19) { + // Second bottom row + if (to > 18) { + // Only move down if we're the right node + return nodes_[from] == finished_[to]; + } + // Only move up if we or a node beneath us is incorrect. + if (nodes_[from] != finished_[from]) { + return true; + } + if (nodes_[from + 4] != '.' && nodes_[from + 4] != finished_[from]) { + return true; + } + return false; + } + if (from > 10 && from < 15) { + // Second top row + if (to > 14) { + // Only move down if we're the right node and the very bottom is empty or contains the + // correct node. + if (nodes_[from + 8] != '.' && nodes_[from + 8] != finished_[to]) { + return false; + } + return nodes_[from] == finished_[to]; + } + // Only move up if we or a node beneath us is incorrect. + if (nodes_[from] != finished_[from]) { + return true; + } + if (nodes_[from + 4] != '.' && nodes_[from + 4] != finished_[from]) { + return true; + } + if (nodes_[from + 8] != '.' && nodes_[from + 8] != finished_[from]) { + return true; + } + return false; + } + if (from > 6 && from < 11) { + if (to > 10) { + // Only move down if we're the right node and the bottom two rows are empty or contain the + // correct node. + if (nodes_[from + 8] != '.' && nodes_[from + 8] != finished_[to]) { + return false; + } + if (nodes_[from + 12] != '.' && nodes_[from + 12] != finished_[to]) { + return false; + } + return nodes_[from] == finished_[to]; + } + if (nodes_[from] != finished_[from]) { + return true; + } + if (nodes_[from + 4] != '.' && nodes_[from + 4] != finished_[from]) { + return true; + } + if (nodes_[from + 8] != '.' && nodes_[from + 8] != finished_[from]) { + return true; + } + if (nodes_[from + 12] != '.' && nodes_[from + 12] != finished_[from]) { + return true; + } + return false; + } + if (from < 7) { + if (to < 7) { + // Can do any move along the row. + return true; + } + // Can only move down if we're the right type. + return nodes_[from] == finished_[to]; + } + abort(); + } + + bool move(unsigned from, unsigned to) + { + if (!check_move(from, to)) { + return false; + } + std::swap(nodes_[from], nodes_[to]); + return true; + } + + constexpr bool operator<(State const& rhs) const noexcept { return nodes_ < rhs.nodes_; } + constexpr bool operator==(State const& rhs) const noexcept { return nodes_ < rhs.nodes_; } + + constexpr static unsigned node_size{23}; + +private: + std::array nodes_{'.'}; + static std::array finished_; +}; + +std::array State::finished_ = {'.', '.', '.', '.', '.', '.', '.', 'A', + 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', + 'B', 'C', 'D', 'A', 'B', 'C', 'D'}; + +struct StateCmp +{ + constexpr bool operator()(State const* l, State const* r) const noexcept + { + if (l == nullptr && r != nullptr) { + return true; + } + if (r == nullptr) { + return false; + } + return *l < *r; + } +}; + +auto main() -> int +{ + std::regex line2_re{R"(#(.)(.)\.(.)\.(.)\.(.)\.(.)(.)#)"}; + std::regex line3_re{"###(.)#(.)#(.)#(.)###"}; + std::regex line4_re{"#(.)#(.)#(.)#(.)#"}; + + std::string line; + std::smatch m; + std::getline(std::cin, line); + if (line != "#############") { + std::cerr << "Incorrect first line.\n"; + return 1; + } + State* initial_state = new State; + + std::getline(std::cin, line); + if (!std::regex_search(line, m, line2_re)) { + std::cerr << "Unable to match line 2 " << line << '\n'; + return 1; + } + for (unsigned i = 0; i < 7; ++i) { + initial_state->node(i) = m.str(i + 1)[0]; + } + + std::getline(std::cin, line); + if (!std::regex_search(line, m, line3_re)) { + std::cerr << "Unable to match line 3 " << line << '\n'; + return 1; + } + for (unsigned i = 0; i < 4; ++i) { + initial_state->node(7 + i) = m.str(i + 1)[0]; + } + initial_state->node(11) = 'D'; + initial_state->node(12) = 'C'; + initial_state->node(13) = 'B'; + initial_state->node(14) = 'A'; + initial_state->node(15) = 'D'; + initial_state->node(16) = 'B'; + initial_state->node(17) = 'A'; + initial_state->node(18) = 'C'; + + std::getline(std::cin, line); + if (!std::regex_search(line, m, line4_re)) { + std::cerr << "Unable to match line 4 " << line << '\n'; + return 1; + } + for (unsigned i = 0; i < 4; ++i) { + initial_state->node(19 + i) = m.str(i + 1)[0]; + } + + std::map states; + std::multimap costs; + states.insert({initial_state, 0}); + costs.insert({0, initial_state}); + std::set visited; + + while (!states.empty()) { + auto it{costs.begin()}; + if (visited.size() % 10'000 == 0) { + std::cout << "Visited: " << visited.size() << " number of states: " << states.size() + << " Min energy: " << it->first << '\n'; + } + + State* state{it->second}; + UInt cost{it->first}; + visited.insert(state); + states.erase(state); + costs.erase(it); + + if (state->finished()) { + std::cout << "Done with cost " << cost << '\n'; + return 0; + } + + for (unsigned i = 0; i < state->size(); ++i) { + if (state->node(i) == '.') { + continue; + } + auto [it_begin, it_end] = legal_moves.equal_range(i); + for (auto move_it{it_begin}; move_it != it_end; ++move_it) { + State* next_state = new State{*state}; + if (next_state->move(i, move_it->second.first) && !visited.contains(next_state)) { + UInt next_cost = cost + move_it->second.second * multipliers[state->node(i)]; + auto [insert_it, success] = states.insert({next_state, next_cost}); + if (!success) { + if (next_cost < insert_it->second) { + insert_it->second = next_cost; + auto [cost_begin, cost_end] = costs.equal_range(cost); + while (cost_begin != cost_end) { + if (*cost_begin->second == *next_state) { + delete next_state; + next_state = cost_begin->second; + costs.erase(cost_begin); + break; + } + ++cost_begin; + } + costs.insert({next_cost, next_state}); + } + else { + delete next_state; + } + } + else { + costs.insert({next_cost, next_state}); + } + } + else { + delete next_state; + } + } + } + } + + std::cerr << "FAILED\n"; + return 1; +}