From b89cd42bfe7d02b86dce7aa256f1e0bcceb6e6cb Mon Sep 17 00:00:00 2001 From: Devin Wild Thomas Date: Fri, 28 Feb 2025 15:56:10 -0500 Subject: [PATCH 1/3] cpp - tiles implemented --- algorithms/astar.hpp | 2 +- main.cpp | 1 + problems/sliding_puzzle.hpp | 225 +++++++++++++++++++----------------- 3 files changed, 119 insertions(+), 109 deletions(-) diff --git a/algorithms/astar.hpp b/algorithms/astar.hpp index 06401ad..48d3623 100644 --- a/algorithms/astar.hpp +++ b/algorithms/astar.hpp @@ -40,7 +40,7 @@ class AStar : public Search { vector findPath() override { this->start(); - nodes.reserve(10'000'000); // reserve 10 million nodes + nodes.reserve(1'000'000'000); // reserve 10 million nodes // Side note, push back is amortized O(1), so we can compare the speed loss of reserve vs push_back at some point nodes.emplace_back(this->problemInstance->initial_state, diff --git a/main.cpp b/main.cpp index 3b00cf8..ac6f777 100644 --- a/main.cpp +++ b/main.cpp @@ -62,6 +62,7 @@ int main(int argc, char* argv[]) { if (problem == "tiles") { using namespace SlidingPuzzle; auto instance = SlidingTileInstance::parseInput(std::cin); + std::cerr << instance; AStar searcher(&instance, extraExpansionTime); auto path = searcher.findPath(); // print_path(path); diff --git a/problems/sliding_puzzle.hpp b/problems/sliding_puzzle.hpp index 2b7de8b..1458283 100644 --- a/problems/sliding_puzzle.hpp +++ b/problems/sliding_puzzle.hpp @@ -1,152 +1,159 @@ #pragma once +#include +#include #include +#include #include -#include #include #include - -#include "search.hpp" #include +#include "problem_instance.hpp" + using namespace std; + + namespace SlidingPuzzle { - static constexpr int SIZE = 4; // 4x4 puzzle - static constexpr int EMPTY_TILE = 0; - typedef size_t position; + constexpr std::pair pos2coord(char position){ + int y = position / 4; + int x = position % 4; + return std::make_pair(x, y); + } + + constexpr std::size_t manhattan_distance(std::pair a, std::pair b){ + return std::abs(a.first - b.first) + std::abs(a.second - b.second); + } + + + + class State { + private: + std::uint64_t packed_state; + + inline std::array expand() const{ + std::uint64_t mask = 15; + std::array retval; + for(int i = 0; i < 16; i++){ + retval[i] = mask & (packed_state >> 4*i); + } + return retval; + } + + public: + + State() = default; - struct State { - vector board; - position empty; // position of the empty tile + State(std::array state_array):packed_state(0){ + for(std::uint64_t i = 0; i < 16; i++){ + std::uint64_t val = (std::uint64_t)state_array[i] << (4*i); + packed_state |= val; + } + } bool operator == (const State& other) const { - return board == other.board; + return packed_state == other.packed_state; } // Required for use as key in unordered_map - bool operator < (const State& other) const { - return this->hash() < other.hash(); + inline std::size_t hash() const{ + std::size_t seed = 0; + boost::hash_combine(seed, packed_state); + return seed; } string toString() const { stringstream ss; - for (int val : board) { + + for (int val : expand()) { ss << val << ","; } return ss.str(); } - size_t hash() const { - size_t h = board[0]; - for (int i = 1; i < SIZE * SIZE; i++) - h += h * 3 + board[i]; + inline vector getSuccessors() const { + vector successors; + auto s = expand(); + auto gap = pos2coord(s[0]); + for(int i = 1; i < 16; i++){ + auto loc = pos2coord(s[i]); + if(manhattan_distance(loc, gap) == 1){ + // is a neighbor + auto pg = s[0]; + auto pos = s[i]; + //make the change + s[i] = pg; + s[0] = pos; + // push to successors + successors.emplace_back(s); + // revert change + s[i] = pos; + s[0] = pg; + } + } + return successors; + } + + constexpr std::size_t heuristic() const { + std::size_t h = 0; + auto s = expand(); + for(char i = 1; i<16; i++){ + auto current = pos2coord(s[i]); + auto should_be = pos2coord(i); + h += manhattan_distance(current, should_be); + } return h; } - }; - inline std::ostream& operator << (std::ostream& os, const State& state) { - for (int i = 0; i < SIZE; i++) { - for (int j = 0; j < SIZE; j++) { - fprintf(stdout, "%2d ", state.board[i * SIZE + j]); - } - fprintf(stdout, "\n"); + + friend inline std::ostream& operator << (std::ostream& os, const State& state) { + os << state.toString(); + return os; } - return os; - } + }; + - inline void applyMove(State& state, position move) { - swap(state.board[state.empty], state.board[move]); - state.empty = move; - } template class SlidingTileInstance: public ProblemInstance { public: - SlidingTileInstance(const State& initial, const State& goal) : ProblemInstance(initial), goal(goal) { - this->goal = goal; - } + SlidingTileInstance(const State& initial, const State& goal) : ProblemInstance(initial), goal(goal) {} static SlidingTileInstance parseInput(std::istream& input) { - State state, goal; - std::string line; // for inputs - - state.board.resize(SIZE * SIZE); - goal.board.resize(SIZE * SIZE); - - // Read dimensions - int rows, cols; - input >> rows >> cols; - - std::getline(input, line); // Skip the "starting positions for each tile:" line - - // Read starting positions for each tile - for (size_t i = 0; i < SIZE * SIZE; i++) { - std::getline(input, line); - input >> state.board[i]; - if (state.board[i] == EMPTY_TILE) { - state.empty = i; // Set the position of the empty tile - } + std::array initial_s; + std::array goal_s; + int width, height; + std::string s; + input >> width >> height; // not sure about order + assert(width == 4 && height == 4); + // starting positions for each tile: + input >> s >> s >> s >> s >> s; + for(int i = 0; i < 16; i++){ + int x; + input >> x; + initial_s[i] = x; } - - std::getline(input, line); // Skip the "goal positions:" line - - // Read goal positions - for (size_t i = 0; i < SIZE * SIZE; i++) { - std::getline(input, line); // Read the line "starting positions for each tile:" - input >> goal.board[i]; - if (goal.board[i] == EMPTY_TILE) { - goal.empty = i; // Set the position of the empty tile in the goal state - } + // goal positions: + input >> s >> s; + for(int i = 0; i < 16; i++){ + int x; + input >> x; + goal_s[i] = x; } - return SlidingTileInstance(state, goal); - } - - inline vector getValidMoves(const State& state) const { - vector moves; - if (state.empty >= SIZE) { - moves.push_back({state.empty - SIZE}); - } - if (state.empty % SIZE > 0) { - moves.push_back({state.empty - 1}); - } - if (state.empty % SIZE < SIZE - 1) { - moves.push_back({state.empty + 1}); - } - if (state.empty < SIZE * (SIZE - 1)) { - moves.push_back({state.empty + SIZE}); - } - return moves; + State initial(initial_s); + State goal(goal_s); + return SlidingTileInstance(initial, goal); } // The functions required by ProblemInstance - float heuristic(const State& state) const override { - int goalIndexLookup[SIZE * SIZE]; - for (int i = 0; i <= SIZE * SIZE; i++) { - goalIndexLookup[goal.board[i]] = i; - } - - int distance = 0; - for (int i = 0; i < SIZE * SIZE; i++) { - int currentTile = state.board[i]; - if (currentTile == EMPTY_TILE) { - continue; - } - int goalIndex = goalIndexLookup[currentTile]; - distance += abs(goalIndex / SIZE - i / SIZE) + abs(goalIndex % SIZE - i % SIZE); - } - return distance; + constexpr Cost heuristic(const State& state) const override{ + return state.heuristic(); } - vector getSuccessors(const State& state) const override { - vector successors; - for (const auto& move : getValidMoves(state)) { - State newState = state; - applyMove(newState, move); - successors.push_back(newState); - } - return successors; + inline vector getSuccessors(const State& state) const override { + return state.getSuccessors(); } float getCost(const State&, const State&) const override { @@ -154,16 +161,18 @@ namespace SlidingPuzzle { } size_t hash(const State& state) const override { - size_t h = state.board[0]; - for (int i = 1; i < SlidingPuzzle::SIZE * SlidingPuzzle::SIZE; i++) - h += h * 3 + state.board[i]; - return h; + return state.hash(); } inline size_t maxActionCount() const override { return 4; } + friend std::ostream& operator<<(std::ostream &os, const SlidingTileInstance &st){ + os << "Start: " << st.initial_state << " Goal: " << st.goal; + return os; + } + private: State goal; }; From f53cd189d7304b4ffeecccbc1917cc00c661c313 Mon Sep 17 00:00:00 2001 From: Devin Wild Thomas Date: Fri, 28 Feb 2025 17:31:58 -0500 Subject: [PATCH 2/3] cpp - tweaks --- algorithms/astar.hpp | 6 ++++-- algorithms/search.hpp | 7 ++++++- main.cpp | 4 ++-- problems/path_finding.hpp | 5 ++--- problems/problem_instance.hpp | 2 +- problems/sliding_puzzle.hpp | 19 +++++++++---------- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/algorithms/astar.hpp b/algorithms/astar.hpp index 48d3623..3a41d49 100644 --- a/algorithms/astar.hpp +++ b/algorithms/astar.hpp @@ -21,7 +21,7 @@ class AStar : public Search { struct Node; struct NodeCompare; - using MinHeap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; + using MinHeap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; public: AStar(const ProblemInstance* problemInstance, size_t extra_expansion_time) : Search(problemInstance){ @@ -39,9 +39,11 @@ class AStar : public Search { AStar(const ProblemInstance* problemInstance) : AStar(problemInstance, 0) {} vector findPath() override { - this->start(); nodes.reserve(1'000'000'000); // reserve 10 million nodes + open.reserve(10'000'000); + closed.reserve(10'000'000); // Side note, push back is amortized O(1), so we can compare the speed loss of reserve vs push_back at some point + this->start(); nodes.emplace_back(this->problemInstance->initial_state, 0, this->heuristic(this->problemInstance->initial_state), nullptr); diff --git a/algorithms/search.hpp b/algorithms/search.hpp index 0fba463..e84ed84 100644 --- a/algorithms/search.hpp +++ b/algorithms/search.hpp @@ -16,6 +16,8 @@ using Value = std::variant; template class Search { +private: + std::vector successors; public: Search() { @@ -46,7 +48,10 @@ class Search { const ProblemInstance *problemInstance; - inline std::vector getSuccessors(const State& state) const { return problemInstance->getSuccessors(state); } + inline std::vector& getSuccessors(const State& state) { + problemInstance->getSuccessors(state, successors); + return successors; + } inline Cost heuristic(const State& state) const { return problemInstance->heuristic(state); } inline Cost getCost(const State& state, const State& successor) const { return problemInstance->getCost(state, successor); } inline size_t hash(const State& state) const { return problemInstance->hash(state); } diff --git a/main.cpp b/main.cpp index ac6f777..8f8c94c 100644 --- a/main.cpp +++ b/main.cpp @@ -61,9 +61,9 @@ int main(int argc, char* argv[]) { if (algorithmChoice == "astar") { if (problem == "tiles") { using namespace SlidingPuzzle; - auto instance = SlidingTileInstance::parseInput(std::cin); + auto instance = SlidingTileInstance::parseInput(std::cin); std::cerr << instance; - AStar searcher(&instance, extraExpansionTime); + AStar searcher(&instance, extraExpansionTime); auto path = searcher.findPath(); // print_path(path); } else if (problem == "path") { diff --git a/problems/path_finding.hpp b/problems/path_finding.hpp index 4cef538..a0bbc04 100644 --- a/problems/path_finding.hpp +++ b/problems/path_finding.hpp @@ -127,14 +127,13 @@ namespace Pathfinding { return minDist; } - inline vector getSuccessors(const State& state) const override { - vector successors; + inline void getSuccessors(const State& state, vector& successors) const override { + successors.clear(); for (const auto& move : this->getValidMoves(state)) { State newState = state; applyMove(newState, move); successors.push_back(newState); } - return successors; } inline float getCost(const State&, const State&) const override { diff --git a/problems/problem_instance.hpp b/problems/problem_instance.hpp index 58ce510..d616c1d 100644 --- a/problems/problem_instance.hpp +++ b/problems/problem_instance.hpp @@ -17,7 +17,7 @@ class ProblemInstance { * @param state The state to get the successors for * @return A vector of successor states */ - virtual std::vector getSuccessors(const State& state) const = 0; + virtual void getSuccessors(const State& state, std::vector& successors) const = 0; /** * Get the heuristic value for a given state diff --git a/problems/sliding_puzzle.hpp b/problems/sliding_puzzle.hpp index 1458283..a0e9d76 100644 --- a/problems/sliding_puzzle.hpp +++ b/problems/sliding_puzzle.hpp @@ -16,13 +16,13 @@ using namespace std; namespace SlidingPuzzle { - constexpr std::pair pos2coord(char position){ + constexpr std::pair pos2coord(const char& position){ int y = position / 4; int x = position % 4; return std::make_pair(x, y); } - constexpr std::size_t manhattan_distance(std::pair a, std::pair b){ + constexpr std::size_t manhattan_distance(const std::pair& a, const std::pair& b){ return std::abs(a.first - b.first) + std::abs(a.second - b.second); } @@ -45,7 +45,7 @@ namespace SlidingPuzzle { State() = default; - State(std::array state_array):packed_state(0){ + State(const std::array& state_array):packed_state(0){ for(std::uint64_t i = 0; i < 16; i++){ std::uint64_t val = (std::uint64_t)state_array[i] << (4*i); packed_state |= val; @@ -72,8 +72,8 @@ namespace SlidingPuzzle { return ss.str(); } - inline vector getSuccessors() const { - vector successors; + inline void getSuccessors(vector& successors) const { + successors.clear(); auto s = expand(); auto gap = pos2coord(s[0]); for(int i = 1; i < 16; i++){ @@ -92,7 +92,6 @@ namespace SlidingPuzzle { s[0] = pg; } } - return successors; } constexpr std::size_t heuristic() const { @@ -152,12 +151,12 @@ namespace SlidingPuzzle { return state.heuristic(); } - inline vector getSuccessors(const State& state) const override { - return state.getSuccessors(); + inline void getSuccessors(const State& state, vector& successors) const override { + state.getSuccessors(successors); } - float getCost(const State&, const State&) const override { - return 1.0f; // Each move costs 1 + Cost getCost(const State&, const State&) const override { + return 1; // Each move costs 1 } size_t hash(const State& state) const override { From 857ef136b1d446c56d4d12af4e304582c3e703d2 Mon Sep 17 00:00:00 2001 From: Devin Wild Thomas Date: Fri, 28 Feb 2025 18:00:02 -0500 Subject: [PATCH 3/3] cpp - changed heap arity to 5 --- algorithms/astar.hpp | 4 ++-- algorithms/cafe.hpp | 2 +- algorithms/kbfs.hpp | 2 +- algorithms/spastar.hpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/algorithms/astar.hpp b/algorithms/astar.hpp index 3a41d49..da2f407 100644 --- a/algorithms/astar.hpp +++ b/algorithms/astar.hpp @@ -21,12 +21,12 @@ class AStar : public Search { struct Node; struct NodeCompare; - using MinHeap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; + using MinHeap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; public: AStar(const ProblemInstance* problemInstance, size_t extra_expansion_time) : Search(problemInstance){ open = MinHeap(); - closed = unordered_flat_map(0, + closed = unordered_flat_map(0, [this](const State& state) { return this->hash(state); }); diff --git a/algorithms/cafe.hpp b/algorithms/cafe.hpp index 0f0d6c0..c7b827a 100644 --- a/algorithms/cafe.hpp +++ b/algorithms/cafe.hpp @@ -31,7 +31,7 @@ class CAFE : public Search { struct Node; struct NodeCompare; - using d_ary_heap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; + using d_ary_heap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; using handle_type = typename d_ary_heap::handle_type; d_ary_heap open{}; diff --git a/algorithms/kbfs.hpp b/algorithms/kbfs.hpp index 5e40062..090c1cb 100644 --- a/algorithms/kbfs.hpp +++ b/algorithms/kbfs.hpp @@ -30,7 +30,7 @@ class KBFS : public Search { ctpl::thread_pool threadPool; struct Node; struct NodeCompare; - using d_ary_heap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; + using d_ary_heap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; using handle_type = typename d_ary_heap::handle_type; diff --git a/algorithms/spastar.hpp b/algorithms/spastar.hpp index a984eb7..4c240f4 100644 --- a/algorithms/spastar.hpp +++ b/algorithms/spastar.hpp @@ -29,7 +29,7 @@ class SPAStar : public Search { struct Node; struct NodeCompare; - using MinHeap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; + using MinHeap = boost::heap::d_ary_heap, boost::heap::mutable_, boost::heap::compare>; public: SPAStar(const ProblemInstance* problemInstance, size_t extra_expansion_time, size_t threadCount) : Search(problemInstance){