diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 06d5e72..9d82d69 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ # build code as object library for easier linking to tests add_library(${PROJECT_NAME}_obj OBJECT - Box.cpp Parser.cpp Algorithm.cpp) + Box.cpp Parser.cpp KuhnAlgorithm.cpp HopcroftKarpAlgorithm.cpp) add_executable(${PROJECT_NAME} main.cpp diff --git a/src/HopcroftKarpAlgorithm.cpp b/src/HopcroftKarpAlgorithm.cpp new file mode 100644 index 0000000..7b8974c --- /dev/null +++ b/src/HopcroftKarpAlgorithm.cpp @@ -0,0 +1,145 @@ +#include "HopcroftKarpAlgorithm.hpp" + +#include +#include + +/** + * @brief Contains code for the boxnesting problem + */ +namespace BoxNesting +{ +HopcroftKarpAlgorithm::HopcroftKarpAlgorithm(const std::vector& boxes) +{ + // In the boxes case left and right vertices are the same amount since + // they are the amount of boxes + this->leftVerticesCount = static_cast(boxes.size()); + this->rightVerticesCount = this->leftVerticesCount; + + this->NILL = this->leftVerticesCount; + + this->pairsLeft = std::vector(this->leftVerticesCount + 1, this->NILL); + this->pairsRight = std::vector(this->rightVerticesCount + 1, this->NILL); + this->distances = std::vector(this->leftVerticesCount + 1, 0); + + this->graph.resize(this->leftVerticesCount); + + for (int16_t i = 0; i < this->leftVerticesCount; ++i) { + this->graph[i].reserve(this->rightVerticesCount); + for (int16_t j = 0; j < this->rightVerticesCount; ++j) { + if (boxes[i] < boxes[j]) { + this->graph[i].emplace_back(j); + } + } + this->graph[i].shrink_to_fit(); + } +} + +int16_t HopcroftKarpAlgorithm::runAlgorithm() const +{ + // We have the same amount of visible boxes as there + // are vertices in the left part of the bipartite graph + // for each valid match (i.e. a box can nest inside another box) + // we remove a visible box + int16_t visibleBoxes = this->leftVerticesCount; + + while (bfs()) + { + // Find a free vertex + for (int16_t i = 0; i < leftVerticesCount; ++i) + { + if (pairsLeft[i] == this->NILL && this->dfs(i)) + { + --visibleBoxes; + } + } + } + + return visibleBoxes; +} + +bool HopcroftKarpAlgorithm::dfs(int16_t vertex) const +{ + if (vertex == this->NILL) + { + return true; + } + + auto sz = this->graph[vertex].size(); + for (int16_t j = 0; j < sz; ++j) + { + // Follow the distances set by BFS + if (this->distances[this->pairsRight[j]] == this->distances[vertex] + 1) + { + // If dfs for pair of v also returns + // true + if (dfs(this->pairsRight[j])) + { + this->pairsRight[j] = vertex; + this->pairsLeft[vertex] = j; + return true; + } + } + } + + // If there is no augmenting path beginning with u. + this->distances[vertex] = this->INFINITE; + return false; +} + +bool HopcroftKarpAlgorithm::bfs() const +{ + std::queue queue; + + // First layer of vertices (set distance as 0) + for (int16_t i = 0; i < this->leftVerticesCount; ++i) + { + // If this is a free vertex, add it to queue + if (this->pairsLeft[i] == this->NILL) + { + this->distances[i] = 0; + queue.push(i); + } + // Else set distance as infinite + // so that this vertex + // is considered next time + else + { + this->distances[i] = this->INFINITE; + } + } + + // Initialize distance to NIL as infinite + this->distances[this->NILL] = this->INFINITE; + + // the queue can only contain vertices off the left part of the bipartite graph + while (!queue.empty()) + { + // Dequeue a vertex + auto i = queue.front(); + queue.pop(); + + // If this node is not NIL and can provide a shorter path to NIL + if (this->distances[i] < this->distances[this->NILL]) + { + // Get all adjacent vertices of the dequeued vertex u + auto sz = this->graph[i].size(); + for (int16_t j = 0; j < sz; ++j) + { + // If pair of v is not considered so far + // (v, pairV[V]) is not yet explored edge. + if (this->distances[this->pairsRight[j]] == this->INFINITE) + { + // Consider the pair and add it to queue + this->distances[this->pairsRight[j]] = this->distances[i] + 1; + queue.push(this->pairsRight[j]); + } + } + } + } + + // If we could come back to NIL using alternating path of distinct + // vertices then there is an augmenting path + return this->distances[this->NILL] != this->INFINITE; +} + +} // namespace BoxNesting \ No newline at end of file diff --git a/src/HopcroftKarpAlgorithm.hpp b/src/HopcroftKarpAlgorithm.hpp new file mode 100644 index 0000000..26d9193 --- /dev/null +++ b/src/HopcroftKarpAlgorithm.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "Box.hpp" + +#include +#include +#include + +/** + * @brief Contains code for the boxnesting problem + */ +namespace BoxNesting +{ +/** + * @brief class that contains the boxnesting algorithm + */ +class HopcroftKarpAlgorithm +{ +public: + /** + * @brief Deleted default constructor + */ + HopcroftKarpAlgorithm() = delete; + + /** + * @brief Construct a new Algorithm object + * + * @param boxes The boxes to generate the internal graph for + */ + explicit HopcroftKarpAlgorithm(const std::vector& boxes); + + /** + * @brief function that runs the boxNesting algorithm and returns the number + * of visible boxes + * + * @param boxes the set of boxes to be nested + * @return the number of visible boxes after running the nesting + * algorithm + */ + [[nodiscard]] int16_t runAlgorithm() const; + +private: + bool bfs() const; + bool dfs(int16_t vertex) const; + +private: + /** + * @brief adjacencyList of left vertices generated from the boxes + * First index on vector is a vertex on the left side of the bipartite graph + * and the vector on that index contains indices of vertex on the right side of + * of the graph where an edge exists between them. + */ + std::vector> graph; + + /** + * @brief The amount of vertices in the left side of the bipartite graph + */ + int16_t leftVerticesCount; + + /** + * @brief The amount of vertices in the left side of the bipartite graph + */ + int16_t rightVerticesCount; + + /** + * @brief The index of the NILL node in the left part of the bipartite graph + */ + int16_t NILL; // NOLINT + + /** + * @brief Used as marker for infinite distance + */ + int16_t INFINITE = std::numeric_limits::max(); // NOLINT + + /** + * @brief Contains whether vertices on the left side of the bipartite graph are + * currently part of a match. Where the index is the vertex index and the + * the value is the index of the vertex in the right side of the graph that + * is matched to it. -1 indicates no match + */ + mutable std::vector pairsLeft; + + /** + * @brief Contains whether vertices on the right side of the bipartite graph are + * currently part of a match. Where the index is the vertex index and the + * the value is the index of the vertex in the left side of the graph that + * is matched to it. -1 indicates no match + */ + mutable std::vector pairsRight; + + /** + * @brief Stores distances of left vertices + */ + mutable std::vector distances; +}; + +} // namespace BoxNesting \ No newline at end of file diff --git a/src/Algorithm.cpp b/src/KuhnAlgorithm.cpp similarity index 91% rename from src/Algorithm.cpp rename to src/KuhnAlgorithm.cpp index bae5b68..e3f69dd 100644 --- a/src/Algorithm.cpp +++ b/src/KuhnAlgorithm.cpp @@ -1,4 +1,4 @@ -#include "Algorithm.hpp" +#include "KuhnAlgorithm.hpp" #include @@ -7,7 +7,7 @@ */ namespace BoxNesting { -Algorithm::Algorithm(const std::vector& boxes) +KuhnAlgorithm::KuhnAlgorithm(const std::vector& boxes) { // In the boxes case left and right vertices are the same amount since // they are the amount of boxes @@ -30,7 +30,7 @@ Algorithm::Algorithm(const std::vector& boxes) } } -int16_t Algorithm::runAlgorithm() const +int16_t KuhnAlgorithm::runAlgorithm() const { bool pathFound; do { @@ -58,7 +58,7 @@ int16_t Algorithm::runAlgorithm() const return visibleBoxes; } -bool Algorithm::kuhn(int16_t vertex) const +bool KuhnAlgorithm::kuhn(int16_t vertex) const { if (this->used[vertex]) { return false; diff --git a/src/Algorithm.hpp b/src/KuhnAlgorithm.hpp similarity index 96% rename from src/Algorithm.hpp rename to src/KuhnAlgorithm.hpp index 4322278..b219f46 100644 --- a/src/Algorithm.hpp +++ b/src/KuhnAlgorithm.hpp @@ -13,20 +13,20 @@ namespace BoxNesting /** * @brief class that contains the boxnesting algorithm */ -class Algorithm +class KuhnAlgorithm { public: /** * @brief Deleted default constructor */ - Algorithm() = delete; + KuhnAlgorithm() = delete; /** * @brief Construct a new Algorithm object * * @param boxes The boxes to generate the internal graph for */ - explicit Algorithm(const std::vector& boxes); + explicit KuhnAlgorithm(const std::vector& boxes); /** * @brief function that runs the boxNesting algorithm and returns the number diff --git a/src/main.cpp b/src/main.cpp index 31e706f..3ae9d12 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ -#include "Algorithm.hpp" +#include "HopcroftKarpAlgorithm.hpp" +#include "KuhnAlgorithm.hpp" #include "Parser.hpp" #include "version.hpp" @@ -73,7 +74,7 @@ int main(int argc, char** argv) return EXIT_SUCCESS; } - BoxNesting::Algorithm boxNestingAlgorithm(BoxNesting::Parser::getBoxes(std::cin)); + BoxNesting::HopcroftKarpAlgorithm boxNestingAlgorithm(BoxNesting::Parser::getBoxes(std::cin)); std::cout << boxNestingAlgorithm.runAlgorithm() << std::endl; diff --git a/src/version.hpp b/src/version.hpp index 0e10557..e26f5fe 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -7,7 +7,7 @@ /** Defines the project author */ #define BOXNESTING_AUTHOR "Ties Klappe & Rowan Goemans" /** Defines the human-readable project version string (e.g. a.b.c.d-123+) */ -#define BOXNESTING_VERSION "1.0.0-a56672a+" +#define BOXNESTING_VERSION "1.0.0-ebee28b+" /** Defines the project major version number */ #define BOXNESTING_VERSION_MAJOR 1 /** Defines the project major version number */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 55bee2a..ba051e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,7 +6,7 @@ add_library(test_obj OBJECT # add all tests on this level separately foreach(file - Box Parser Algorithm) + Box Parser KuhnAlgorithm HopcroftKarpAlgorithm) add_executable("test_${file}" "${file}.cpp" $ diff --git a/test/HopcroftKarpAlgorithm.cpp b/test/HopcroftKarpAlgorithm.cpp new file mode 100644 index 0000000..bb28725 --- /dev/null +++ b/test/HopcroftKarpAlgorithm.cpp @@ -0,0 +1,75 @@ +#include +#include + +#define CATCH_CONFIG_MAIN + +SCENARIO("Running the 'graph creation from a set of boxes' function") +{ + GIVEN("A set of boxes where every box except for one can nest inside another") + { + std::vector boxes{BoxNesting::Box({0.9f, 0.9f, 0.9f}), BoxNesting::Box({0.8f, 0.8f, 0.8f}), + BoxNesting::Box({0.7f, 0.7f, 0.7f}), BoxNesting::Box({0.6f, 0.6f, 0.6f})}; + + BoxNesting::HopcroftKarpAlgorithm boxNestingAlgorithm(boxes); + + THEN("Only one visible box remains after nesting") + { + auto result = boxNestingAlgorithm.runAlgorithm(); + REQUIRE(result == 1); + } + } + + GIVEN("A set of boxes where only one box can nest inside another") + { + std::vector boxes1{BoxNesting::Box({0.6f, 0.6f, 0.6f}), BoxNesting::Box({0.75f, 0.75f, 0.75f}), + BoxNesting::Box({0.9f, 0.7f, 0.7f})}; + + std::vector boxes2{BoxNesting::Box({0.75f, 0.75f, 0.75f}), + BoxNesting::Box({0.80f, 0.70f, 0.74f}), BoxNesting::Box({0.73f, 0.65f, 0.85f}), + BoxNesting::Box({0.60f, 0.90f, 0.72f}), BoxNesting::Box({0.95f, 0.55f, 0.71f}), + BoxNesting::Box({0.52f, 0.98f, 0.70f}), BoxNesting::Box({0.70f, 0.75f, 0.69f})}; + + THEN("One less box is visible after nesting") + { + BoxNesting::HopcroftKarpAlgorithm boxNestingAlgorithm1(boxes1); + BoxNesting::HopcroftKarpAlgorithm boxNestingAlgorithm2(boxes2); + + auto result1 = boxNestingAlgorithm1.runAlgorithm(); + REQUIRE(result1 == (boxes1.size() - 1)); + auto result2 = boxNestingAlgorithm2.runAlgorithm(); + REQUIRE(result2 == (boxes2.size() - 1)); + } + } + + GIVEN("A set of 5000 boxes which can all nest eachother except for one") + { + std::vector boxes; + for (uint16_t i = 0; i < 5000; ++i) { + auto length = 0.50001f + (0.00009f * i); + boxes.emplace_back(BoxNesting::Box({length, length, length})); + } + + THEN("Only one visible box remains after nesting") + { + BoxNesting::HopcroftKarpAlgorithm boxNestingAlgorithm(boxes); + auto result = boxNestingAlgorithm.runAlgorithm(); + REQUIRE(result == 1); + } + } + + GIVEN("A set of 5000 boxes where none can nest inside eachother") + { + std::vector boxes; + for (uint16_t i = 0; i < 5000; ++i) { + auto length = 0.50001f; + boxes.emplace_back(BoxNesting::Box({length, length, length})); + } + + THEN("Only one visible box remains after nesting") + { + BoxNesting::HopcroftKarpAlgorithm boxNestingAlgorithm(boxes); + auto result = boxNestingAlgorithm.runAlgorithm(); + REQUIRE(result == boxes.size()); + } + } +} \ No newline at end of file diff --git a/test/Algorithm.cpp b/test/KuhnAlgorithm.cpp similarity index 87% rename from test/Algorithm.cpp rename to test/KuhnAlgorithm.cpp index 2a0cb47..423d4cd 100644 --- a/test/Algorithm.cpp +++ b/test/KuhnAlgorithm.cpp @@ -1,4 +1,4 @@ -#include +#include #include #define CATCH_CONFIG_MAIN @@ -10,7 +10,7 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") std::vector boxes{BoxNesting::Box({0.9f, 0.9f, 0.9f}), BoxNesting::Box({0.8f, 0.8f, 0.8f}), BoxNesting::Box({0.7f, 0.7f, 0.7f}), BoxNesting::Box({0.6f, 0.6f, 0.6f})}; - BoxNesting::Algorithm boxNestingAlgorithm(boxes); + BoxNesting::KuhnAlgorithm boxNestingAlgorithm(boxes); THEN("Only one visible box remains after nesting") { @@ -31,8 +31,8 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") THEN("One less box is visible after nesting") { - BoxNesting::Algorithm boxNestingAlgorithm1(boxes1); - BoxNesting::Algorithm boxNestingAlgorithm2(boxes2); + BoxNesting::KuhnAlgorithm boxNestingAlgorithm1(boxes1); + BoxNesting::KuhnAlgorithm boxNestingAlgorithm2(boxes2); auto result1 = boxNestingAlgorithm1.runAlgorithm(); REQUIRE(result1 == (boxes1.size() - 1)); @@ -51,7 +51,7 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") THEN("Only one visible box remains after nesting") { - BoxNesting::Algorithm boxNestingAlgorithm(boxes); + BoxNesting::KuhnAlgorithm boxNestingAlgorithm(boxes); auto result = boxNestingAlgorithm.runAlgorithm(); REQUIRE(result == 1); } @@ -67,7 +67,7 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") THEN("Only one visible box remains after nesting") { - BoxNesting::Algorithm boxNestingAlgorithm(boxes); + BoxNesting::KuhnAlgorithm boxNestingAlgorithm(boxes); auto result = boxNestingAlgorithm.runAlgorithm(); REQUIRE(result == boxes.size()); }