Add Puzzles for 2022 day 12
This commit is contained in:
159
2022/graph-utils.h
Normal file
159
2022/graph-utils.h
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// Created by mgretton on 10/01/2022.
|
||||
//
|
||||
|
||||
#ifndef ADVENT_OF_CODE_GRAPH_UTILS_H
|
||||
#define ADVENT_OF_CODE_GRAPH_UTILS_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
/** \brief Implement Dijkstra's algorithm.
|
||||
*
|
||||
* @tparam Cost Cost type
|
||||
* @tparam Node Node class
|
||||
* @tparam TransitionManager Class type that manages state transitions
|
||||
* @tparam FinishedFn Function type to call to test whether a node matches finished state
|
||||
* @param initial Initial node
|
||||
* @param initial_cost Cost of initial node
|
||||
* @param transition_manager Object to manage state transitions
|
||||
* @return Node, Cost pair indicating success. On failure default node with
|
||||
* maximum cost is returned.
|
||||
*
|
||||
* \a Cost Must be a numeric type. \a Node must be a class which is default constructable, be less
|
||||
* than comparable.
|
||||
*
|
||||
* \a transition_manager must be an object with the following public interfaces:
|
||||
* \code
|
||||
* auto is_finished(Node const& node) -> bool;
|
||||
*
|
||||
* template <typename Inserter>
|
||||
* void generate_children(Node const& node, Inserter inserter);
|
||||
* \endcode
|
||||
*
|
||||
* The \c generate_children() method is called whenever we examine the node \c node, and should
|
||||
* call \c inserter(next_node, cost_delta) for every node directly reachable from \c node. Where
|
||||
* \c next_node is the next node to visit and \c cost_delta is the incremental cost to visit the new
|
||||
* node. \c inserter should be called for all possible nodes that are visitable, it will ensure
|
||||
* that duplicates are managed correctly/
|
||||
*
|
||||
* The \c is_finished() method should return true if the given node is in a finished state.
|
||||
*/
|
||||
template<typename Cost, typename Node, typename TransitionManager>
|
||||
auto dijkstra(Node const& initial, Cost initial_cost, TransitionManager transition_manager)
|
||||
-> std::pair<Node, Cost>
|
||||
{
|
||||
/** Helper struct to order node pointers. */
|
||||
struct NodePCmp
|
||||
{
|
||||
auto operator()(Node const* lhs, Node const* rhs) const noexcept -> bool
|
||||
{
|
||||
if (lhs == nullptr && rhs != nullptr) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return *lhs < *rhs;
|
||||
}
|
||||
|
||||
auto operator()(Node const* lhs, Node const& rhs) const noexcept -> bool
|
||||
{
|
||||
if (lhs == nullptr) {
|
||||
return true;
|
||||
}
|
||||
return *lhs < rhs;
|
||||
}
|
||||
|
||||
auto operator()(Node const& lhs, Node const* rhs) const noexcept -> bool
|
||||
{
|
||||
if (rhs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return lhs < *rhs;
|
||||
}
|
||||
};
|
||||
|
||||
/* We maintain two maps - one from node to cost and the other from cost to nodes. */
|
||||
/** \c nodes maintains a map of all the nodes we've visited or want to visit. Nodes that cost
|
||||
* less than the current cost at the front of costs have been visited. The rest haven't.
|
||||
*/
|
||||
std::map<Node const*, Cost, NodePCmp> nodes;
|
||||
/** \c costs maintains a map of costs to the nodes that cost that much to visit. */
|
||||
std::map<Cost, std::unordered_set<Node const*>> costs;
|
||||
Cost current_cost{initial_cost};
|
||||
Node const* current_node{new Node(initial)};
|
||||
nodes.insert({current_node, current_cost});
|
||||
costs.insert({current_cost, {current_node}});
|
||||
|
||||
/* Helper lambda to clean up after ourselves. */
|
||||
auto cleanup = [](auto& nodes) {
|
||||
for (auto const& it : nodes) {
|
||||
delete it.first;
|
||||
}
|
||||
};
|
||||
|
||||
/* Helper lambda to insert into the maps. */
|
||||
auto inserter = [&costs, &nodes, ¤t_cost](Node const& node, Cost cost) {
|
||||
cost += current_cost;
|
||||
auto node_it{nodes.find(&node)};
|
||||
/* Skip inserting nodes we've already visited, or that would cost more to visit. */
|
||||
if (node_it != nodes.end() && node_it->second <= cost) {
|
||||
return;
|
||||
}
|
||||
|
||||
Node const* nodep{nullptr};
|
||||
if (node_it == nodes.end()) {
|
||||
nodep = new Node(node);
|
||||
nodes.insert({nodep, cost});
|
||||
}
|
||||
else {
|
||||
/* Node has a cheaper cost than we thought: Remove the node from its old cost list */
|
||||
nodep = node_it->first;
|
||||
auto cost_it{costs.find(node_it->second)};
|
||||
assert(cost_it != costs.end());
|
||||
cost_it->second.erase(nodep);
|
||||
|
||||
/* Now update the cost in the nodes map and use the nodep as the node pointer. */
|
||||
node_it->second = cost;
|
||||
}
|
||||
|
||||
auto [cost_it, success] = costs.insert({cost, {}});
|
||||
cost_it->second.insert(nodep);
|
||||
};
|
||||
|
||||
std::uint64_t iter{0};
|
||||
while (!costs.empty()) {
|
||||
auto cost_it{costs.begin()};
|
||||
current_cost = cost_it->first;
|
||||
assert(iter < nodes.size());
|
||||
assert(std::accumulate(costs.begin(), costs.end(), std::size_t{0}, [](auto a, auto c) {
|
||||
return a + c.second.size();
|
||||
}) == nodes.size() - iter);
|
||||
for (auto& nodep : cost_it->second) {
|
||||
if (iter++ % 100'000 == 0) {
|
||||
std::cout << "Iteration: " << iter << " cost " << current_cost
|
||||
<< " total number of nodes: " << nodes.size()
|
||||
<< ", number of costs left to visit: " << costs.size()
|
||||
<< ", number of nodes left: " << (nodes.size() - iter) << '\n';
|
||||
}
|
||||
if (transition_manager.is_finished(*nodep)) {
|
||||
auto result{std::make_pair(*nodep, current_cost)};
|
||||
cleanup(nodes);
|
||||
return result;
|
||||
}
|
||||
transition_manager.generate_children(*nodep, inserter);
|
||||
}
|
||||
costs.erase(costs.begin());
|
||||
}
|
||||
|
||||
cleanup(nodes);
|
||||
return {Node{}, std::numeric_limits<Cost>::max()};
|
||||
}
|
||||
|
||||
#endif // ADVENT_OF_CODE_GRAPH_UTILS_H
|
124
2022/puzzle-12-01.cc
Normal file
124
2022/puzzle-12-01.cc
Normal file
@@ -0,0 +1,124 @@
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "graph-utils.h"
|
||||
|
||||
using Int = std::int64_t;
|
||||
|
||||
struct Pos
|
||||
{
|
||||
Pos() noexcept = default;
|
||||
Pos(Int x, Int y) noexcept : x_(x), y_(y) {}
|
||||
Pos(Pos const&) noexcept = default;
|
||||
auto operator=(Pos const&) noexcept -> Pos& = default;
|
||||
Pos(Pos&&) noexcept = default;
|
||||
auto operator=(Pos&&) noexcept -> Pos& = default;
|
||||
~Pos() noexcept = default;
|
||||
|
||||
auto x() const { return x_; }
|
||||
auto y() const { return y_; }
|
||||
|
||||
private:
|
||||
Int x_{0};
|
||||
Int y_{0};
|
||||
};
|
||||
|
||||
auto operator+(Pos const& x, Pos const& y) noexcept -> Pos
|
||||
{
|
||||
return Pos{x.x() + y.x(), x.y() + y.y()};
|
||||
}
|
||||
|
||||
auto operator<(Pos const& l, Pos const& r) noexcept -> bool
|
||||
{
|
||||
return l.y() < r.y() || (l.y() == r.y() && l.x() < r.x());
|
||||
}
|
||||
|
||||
auto operator==(Pos const& l, Pos const& r) noexcept -> bool
|
||||
{
|
||||
return l.x() == r.x() && l.y() == r.y();
|
||||
}
|
||||
|
||||
using Height = char;
|
||||
|
||||
struct Map
|
||||
{
|
||||
Map(std::istream& is)
|
||||
{
|
||||
std::string line;
|
||||
while (std::getline(std::cin, line)) {
|
||||
auto s = line.find('S');
|
||||
if (s != std::string::npos) {
|
||||
start_ = Pos{static_cast<Int>(s), static_cast<Int>(rows_.size())};
|
||||
}
|
||||
auto e = line.find('E');
|
||||
if (e != std::string::npos) {
|
||||
end_ = Pos{static_cast<Int>(e), static_cast<Int>(rows_.size())};
|
||||
}
|
||||
rows_.push_back(line);
|
||||
}
|
||||
}
|
||||
auto start() const -> Pos { return start_; }
|
||||
auto end() const -> Pos { return end_; }
|
||||
auto width() const -> Int { return rows_[0].length(); }
|
||||
auto depth() const -> Int { return rows_.size(); }
|
||||
auto height(Pos const& p) const -> Height
|
||||
{
|
||||
if (p == start_) {
|
||||
return 'a';
|
||||
}
|
||||
if (p == end_) {
|
||||
return 'z';
|
||||
}
|
||||
return rows_[p.y()][p.x()];
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> rows_;
|
||||
Pos start_{0, 0};
|
||||
Pos end_{0, 0};
|
||||
};
|
||||
|
||||
struct StateTranstitionManager
|
||||
{
|
||||
explicit StateTranstitionManager(Map const& map) : map_(map) {}
|
||||
|
||||
Map const& map_;
|
||||
|
||||
bool is_finished(Pos const& pos) { return pos == map_.end(); }
|
||||
|
||||
template<typename Inserter>
|
||||
void generate_children(Pos const& current, Inserter inserter)
|
||||
{
|
||||
std::array<Pos, 4> moves = {Pos{0, 1}, Pos{0, -1}, Pos{1, 0}, Pos{-1, 0}};
|
||||
for (auto const& move : moves) {
|
||||
auto pos{current + move};
|
||||
// Check in bounds
|
||||
if (pos.x() < 0 || pos.x() > map_.width() || pos.y() < 0 || pos.y() > map_.depth()) {
|
||||
continue;
|
||||
}
|
||||
// check height hasn't changed too far
|
||||
if (map_.height(pos) > map_.height(current) + 1) {
|
||||
continue;
|
||||
}
|
||||
inserter(pos, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
Map map(std::cin);
|
||||
Pos initial_state{map.start()};
|
||||
|
||||
auto [finished_state, finished_cost] =
|
||||
dijkstra(initial_state, Int{0}, StateTranstitionManager{map});
|
||||
std::cout << "Done with cost " << finished_cost << '\n';
|
||||
return 0;
|
||||
}
|
116
2022/puzzle-12-02.cc
Normal file
116
2022/puzzle-12-02.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "graph-utils.h"
|
||||
|
||||
using Int = std::int64_t;
|
||||
|
||||
struct Pos
|
||||
{
|
||||
Pos() noexcept = default;
|
||||
Pos(Int x, Int y) noexcept : x_(x), y_(y) {}
|
||||
Pos(Pos const&) noexcept = default;
|
||||
auto operator=(Pos const&) noexcept -> Pos& = default;
|
||||
Pos(Pos&&) noexcept = default;
|
||||
auto operator=(Pos&&) noexcept -> Pos& = default;
|
||||
~Pos() noexcept = default;
|
||||
|
||||
[[nodiscard]] auto x() const { return x_; }
|
||||
[[nodiscard]] auto y() const { return y_; }
|
||||
|
||||
private:
|
||||
Int x_{0};
|
||||
Int y_{0};
|
||||
};
|
||||
|
||||
auto operator+(Pos const& x, Pos const& y) noexcept -> Pos
|
||||
{
|
||||
return Pos{x.x() + y.x(), x.y() + y.y()};
|
||||
}
|
||||
|
||||
auto operator<(Pos const& l, Pos const& r) noexcept -> bool
|
||||
{
|
||||
return l.y() < r.y() || (l.y() == r.y() && l.x() < r.x());
|
||||
}
|
||||
|
||||
auto operator==(Pos const& l, Pos const& r) noexcept -> bool
|
||||
{
|
||||
return l.x() == r.x() && l.y() == r.y();
|
||||
}
|
||||
|
||||
using Height = char;
|
||||
|
||||
struct Map
|
||||
{
|
||||
explicit Map(std::istream& is)
|
||||
{
|
||||
std::string line;
|
||||
while (std::getline(is, line)) {
|
||||
auto s = line.find('S');
|
||||
if (s != std::string::npos) {
|
||||
start_ = Pos{static_cast<Int>(s), static_cast<Int>(rows_.size())};
|
||||
line[s] = 'a';
|
||||
}
|
||||
auto e = line.find('E');
|
||||
if (e != std::string::npos) {
|
||||
end_ = Pos{static_cast<Int>(e), static_cast<Int>(rows_.size())};
|
||||
line[e] = 'z';
|
||||
}
|
||||
rows_.push_back(line);
|
||||
}
|
||||
}
|
||||
[[nodiscard]] auto start() const -> Pos { return start_; }
|
||||
[[nodiscard]] auto end() const -> Pos { return end_; }
|
||||
[[nodiscard]] auto width() const -> Int { return static_cast<Int>(rows_[0].length()); }
|
||||
[[nodiscard]] auto depth() const -> Int { return static_cast<Int>(rows_.size()); }
|
||||
[[nodiscard]] auto height(Pos const& p) const -> Height
|
||||
{
|
||||
return rows_[p.y()][p.x()];
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> rows_;
|
||||
Pos start_{0, 0};
|
||||
Pos end_{0, 0};
|
||||
};
|
||||
|
||||
struct StateTranstitionManager
|
||||
{
|
||||
explicit StateTranstitionManager(Map map) : map_(std::move(map)) {}
|
||||
|
||||
Map map_;
|
||||
|
||||
[[nodiscard]] auto is_finished(Pos const& pos) const -> bool { return map_.height(pos) == 'a'; }
|
||||
|
||||
template<typename Inserter>
|
||||
void generate_children(Pos const& current, Inserter inserter) const
|
||||
{
|
||||
std::array<Pos, 4> const moves{Pos{0, 1}, Pos{0, -1}, Pos{1, 0}, Pos{-1, 0}};
|
||||
for (auto const& move : moves) {
|
||||
auto pos{current + move};
|
||||
// Check in bounds
|
||||
if (pos.x() < 0 || pos.x() > map_.width() || pos.y() < 0 || pos.y() > map_.depth()) {
|
||||
continue;
|
||||
}
|
||||
// check height hasn't changed too far
|
||||
if (map_.height(pos) < map_.height(current) - 1) {
|
||||
continue;
|
||||
}
|
||||
inserter(pos, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
Map const map(std::cin);
|
||||
Pos const initial_state{map.end()};
|
||||
|
||||
auto [finished_state, finished_cost] =
|
||||
dijkstra(initial_state, Int{0}, StateTranstitionManager{map});
|
||||
std::cout << "Done with cost " << finished_cost << '\n';
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user