diff --git a/.clang-tidy b/.clang-tidy index 1a46ce7..062e153 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,4 @@ -Checks: '*,-google-runtime-references,-fuchsia-default-arguments,-google-readability-namespace-comments,-hicpp-deprecated-headers,-llvm-header-guard,-llvm-namespace-comment,-modernize-deprecated-headers,-fuchsia-overloaded-operator' +Checks: '*,-google-runtime-references,-fuchsia-default-arguments,-clang-diagnostic-sign-compare,-google-readability-namespace-comments,-cppcoreguidelines-pro-bounds-constant-array-index,-hicpp-deprecated-headers,-llvm-header-guard,-llvm-namespace-comment,-modernize-deprecated-headers,-fuchsia-overloaded-operator' WarningsAsErrors: '' HeaderFilterRegex: '.*' AnalyzeTemporaryDtors: false diff --git a/cmake/code.cmake b/cmake/code.cmake index 2c18fae..bf5e747 100644 --- a/cmake/code.cmake +++ b/cmake/code.cmake @@ -7,11 +7,14 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# disable sign compare since we use only signed integers types +add_compile_options(-Wno-sign-compare) + # set warning flags for debug if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "GNU") - add_compile_options(-Wall -Wextra -Wconversion) + add_compile_options(-Wall -Wextra) if(${WERROR}) add_compile_options(-Werror) endif() diff --git a/include/BoxNesting/Algorithm.hpp b/include/BoxNesting/Algorithm.hpp deleted file mode 100644 index 397b09b..0000000 --- a/include/BoxNesting/Algorithm.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include -#include - -/** - * @brief Contains code for the boxnesting problem - */ -namespace BoxNesting -{ -/** - * @brief class that contains the boxnesting algorithm - */ -class Algorithm -{ -public: - /** - * @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]] uint16_t runAlgorithm(const std::vector& boxes) const; - - /** - * @brief function creates a bipartite graph from a vector of boxes - * It will create two vertices for each box one in the left - * and one in the right part of the graph - * Then it will create edges between the left and the right part - * of the graph if the box in the left vertex can nest inside - * the box on the right vertex - * - * @param boxes the set of boxes that will be used to create the graph - * - * @return The constructed BipartiteGraph - */ - [[nodiscard]] Graph::BipartiteGraph createGraphFromBoxes(const std::vector& boxes) const; - -private: - /** - * @brief Run the kuhn algorithm. It's based on the DFS and will try to find - * chains of maximum length. - * - * @param vertex The index of the vertex in the left part of the - * Bipartite graph to start running the algorithm from - * - * @return Whether a chain could be made - */ - bool kuhn(uint16_t vertex) const; - -private: - /** - * @brief Graph generated from the boxes - */ - mutable Graph::BipartiteGraph graph; - - /** - * @brief Contains whether a vertex was allready explored during the current kuhn run - */ - mutable std::vector used; - - /** - * @brief Contains whether vertices on the right side of the bipartite graph are part - * of a chain - */ - mutable std::vector pairs; -}; - -} // namespace BoxNesting \ No newline at end of file diff --git a/include/BoxNesting/Box.hpp b/include/BoxNesting/Box.hpp deleted file mode 100644 index 1d24a45..0000000 --- a/include/BoxNesting/Box.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include -#include - -/** - * @brief Contains code for the boxnesting problem - */ -namespace BoxNesting -{ -/** - * @brief Class representing a box - */ -class Box -{ -public: - /** - * @brief Deleted default constructor - */ - Box() = delete; - - /** - * Default virtual destructor - */ - virtual ~Box() = default; - - /** - * Default copy constructor - * \param b - */ - Box(const Box& b) = default; - - /** - * Default move constructor - * \param b - */ - Box(Box&& b) = default; - - /** - * Default copy assignment operator - * \param b - * \return - */ - Box& operator=(const Box& b) = default; - - /** - * Default move assignment operator - * \param b - * \return - */ - Box& operator=(Box&& b) = default; - - /** - * @brief Construct a new Box object - * - * @param sideLengths The length of the sides of the box in each dimensions - * @return A new Box object - */ - explicit Box(const std::array& sideLengths); - - /** - * @brief Check if this box can nest inside other box - * - * @param b The box to check if this box can nest inside - * @return True if this box can nest inside the passed box, False otherwise - */ - [[nodiscard]] bool isNestable(const Box& b) const; - - /** - * @brief Get the side lengths of the box - * - * @return array containing the side lengths ordered from smallest to largest - */ - [[nodiscard]] const std::array& getSideLengths() const { return this->sideLengths; } - - /** - * @brief The minimum length that a side of a box may have - * Is non-inclusive so the length x > minLength - */ - static constexpr float minLength = 0.5; - - /** - * @brief The maximum length that a side of a box may have - * Is non-inclusive so the length x < maxLength - */ - static constexpr float maxLength = 1.0; - - /** - * @brief Static assert on the assumption any box can only contain a single box nested inside - */ - static_assert(minLength * 2 >= maxLength, - "More then 1 box can be fit inside a larger box. " - "The algorithm may only be used in the case that any box can contain a single box."); - -private: - /** - * @brief The lengths of each side ordered from smallest to longest - */ - std::array sideLengths; -}; -} diff --git a/include/BoxNesting/Parser.hpp b/include/BoxNesting/Parser.hpp deleted file mode 100644 index c6d26ab..0000000 --- a/include/BoxNesting/Parser.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -/** - * @brief Contains code for the boxnesting problem - */ -namespace BoxNesting -{ -/** - * @brief Parser class that handles reading from input to obtain boxes - */ -class Parser -{ -public: - /** - * @brief Delete constructor - */ - Parser() = delete; - - /** - * @brief Delete virtual destructor - */ - virtual ~Parser() = delete; - - /** - * @brief Delete copy constructor - * \param b - */ - Parser(const Parser& b) = delete; - - /** - * @brief Delete move constructor - * \param b - */ - Parser(Parser&& b) = delete; - - /** - * @brief Delete copy assignment operator - * \param b - * \return - */ - Parser& operator=(const Parser& b) = delete; - - /** - * @brief Delete move assignment operator - * \param b - * \return - */ - Parser& operator=(Parser&& b) = delete; - - /** - * @brief Get boxes from stdin. - * - * @throw ParserError Parsing failed due to malformed input - * @throw std::invalid_argument Parsing succeeded but the box specification is not valid - * - * @param inputStream The inputStream to read from - * @return A set of boxes - */ - [[nodiscard]] static std::vector getBoxes(std::istream& inputStream); - -private: - /** - * @brief Wait for box specification from inputStream and return a box object from it - - * @throw ParserError Parsing failed due to malformed input - * @throw std::invalid_argument Parsing succeeded but the box specification is not valid - * - * @param inputStream The inputStream to read from - * @return Instantiated box object - */ - [[nodiscard]] static Box parseBoxSpecification(std::istream& inputStream); -}; -} // BoxNesting diff --git a/include/BoxNesting/ParserError.hpp b/include/BoxNesting/ParserError.hpp deleted file mode 100644 index ce781de..0000000 --- a/include/BoxNesting/ParserError.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include - -/** - * @brief Contains code for the boxnesting problem - */ -namespace BoxNesting -{ -/** - * @brief Exception that is thrown when error occurs during parsing - */ -struct ParserError : public std::runtime_error -{ -public: - /** - * @brief Construct a new Parser Exception object - * - * @param msg The message to set - * @return A new ParserError object - */ - explicit ParserError(const std::string& msg) : std::runtime_error(msg), innerException(nullptr) - { - } - - /** - * @brief Construct a new Parser Exception object - * - * @param msg The message to set - * @param innerException innerException to set - * @return A new ParserError object - */ - ParserError(const std::string& msg, const std::exception& innerException) : - std::runtime_error(msg), innerException(std::make_exception_ptr(innerException)) - { - } - - /** - * @brief Contains the original exception if there was a caught exception - */ - const std::exception_ptr innerException; -}; -} // BoxNesting \ No newline at end of file diff --git a/include/Graph/AdjacencyMatrix.hpp b/include/Graph/AdjacencyMatrix.hpp deleted file mode 100644 index 29236b4..0000000 --- a/include/Graph/AdjacencyMatrix.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include - -/** - * @brief Contains generic code that represents a graph and its properties - */ -namespace Graph -{ -/** - * @brief Class that can be used to store an AdjacencyMatrix continously in memory - * - * @tparam T The type to store in the matrix - */ -template class AdjacencyMatrix -{ -public: - /** - * @brief Construct a new empty AdjacencyMatrix object - */ - AdjacencyMatrix() : data(0) - { - } - - /** - * @brief Construct a new AdjacencyMatrix object - * - * @param l The amount of left vertices - * @param r The amount of right vertices - */ - AdjacencyMatrix(size_t l, size_t r) : data(l * r), leftCount(l), rightCount(r) - { - - } - - /** - * @brief Get element at specified indices - * - * @param l The left vertex index - * @param r The right vertex index - * - * @throws std::out_of_range if index is out of range - * - * @return Value at that index - */ - [[nodiscard]] T& at(size_t l, size_t r) - { - return this->data.at(l * this->rightCount + r); - } - - /** - * @brief Get const element at specified indices - * - * @param l The left vertex index - * @param r The right vertex index - * - * @throws std::out_of_range if index is out of range - * - * @return Const value at that index - */ - [[nodiscard]] const T& at(size_t l, size_t r) const { return this->data.at(l * this->rightCount + r); } - -private: - /** - * @brief Contains the data of the adjacencyMatrix - */ - std::vector data; - - /** - * @brief Contains the amount of left vertices - */ - size_t leftCount = 0; - - /** - * @brief Contains the amount of right vertices - */ - size_t rightCount = 0; -}; - -} // namespace Graph diff --git a/include/Graph/BipartiteGraph.hpp b/include/Graph/BipartiteGraph.hpp deleted file mode 100644 index b02174e..0000000 --- a/include/Graph/BipartiteGraph.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include - -#include - -/** - * @brief Contains generic code that represents a graph and its properties - */ -namespace Graph -{ -/** - * @brief template class representing a Bipartite graph - * - * @tparam T The type of the value to contain in the vertices. - */ -template class BipartiteGraph -{ -public: - /** - * @brief Construct a new empty bipartite graph - */ - BipartiteGraph() = default; - - /** - * @brief Construct a new Bipartite Graph object from two sets of vertices - * - * @param leftVertices The left part of the bipartite graph - * @param rightVertices The right aprt of the bipartite graph - */ - BipartiteGraph(std::vector> leftVertices, std::vector> rightVertices); - - /** - * @brief function that adds an edge to the graph - * - * @throw std::logic_error If v1 or v2 are vertices that are not in the graph. - * - * @param v1 source vertex index of the edge must be in the left vertices - * @param v2 destination vertex index of the edge must be in the right vertices - */ - void addEdge(uint16_t v1, uint16_t v2); - - /** - * @brief function that checks if there is an edge between two vertices. - * - * @throw std::logic_error If v1 or v2 are vertices that are not in the graph. - * - * @param v1 the source vertex index of the edge must be in the left vertices - * @param v2 the destination vertex index of the edge must be in the left vertices - * @return true if there is an edge - * @return false if there is no edge - */ - [[nodiscard]] bool isEdgeBetween(uint16_t v1, uint16_t v2) const; - - /** - * @brief Getter for the adjacency list object - * - * @return The adjacency list by reference - */ - [[nodiscard]] const AdjacencyMatrix& getAdjacencyMatrix() const noexcept; - - /** - * @brief Get the left Vertices of the bipartite graph - * - * @return The left vertices of the bipartite graph - */ - [[nodiscard]] const std::vector>& getLeftVertices() const noexcept; - - /** - * @brief Get the rigt Vertices of the bipartite graph - * - * @return The right vertices of the bipartite graph - */ - [[nodiscard]] const std::vector>& getRightVertices() const noexcept; - -private: - /** - * @brief Contains the left vertices of the graph - */ - std::vector> leftVertices; - - /** - * @brief Contains the rights vertices of the graph - */ - std::vector> rightVertices; - - /** - * @brief Contains the edges between the left and right vertices - * The vector specialization of bool is broken so we use uint8_t instead of bool - */ - AdjacencyMatrix adjacencyMatrix; -}; - -} // namespace Graph - -#include diff --git a/include/Graph/BipartiteGraph.ipp b/include/Graph/BipartiteGraph.ipp deleted file mode 100644 index 271c3fd..0000000 --- a/include/Graph/BipartiteGraph.ipp +++ /dev/null @@ -1,40 +0,0 @@ -#include - -#include -#include - -namespace Graph -{ -template -BipartiteGraph::BipartiteGraph(std::vector> leftVertices, std::vector> rightVertices) : - leftVertices(std::move(leftVertices)), rightVertices(std::move(rightVertices)), - adjacencyMatrix(AdjacencyMatrix(this->leftVertices.size(), this->rightVertices.size())) -{ -} - -template void BipartiteGraph::addEdge(uint16_t v1, uint16_t v2) -{ - this->adjacencyMatrix.at(v1, v2) = true; -} - -template bool BipartiteGraph::isEdgeBetween(uint16_t v1, uint16_t v2) const -{ - return this->adjacencyMatrix.at(v1, v2); -} - -template const AdjacencyMatrix& BipartiteGraph::getAdjacencyMatrix() const noexcept -{ - return this->adjacencyMatrix; -} - -template const std::vector>& BipartiteGraph::getLeftVertices() const noexcept -{ - return this->leftVertices; -} - -template const std::vector>& BipartiteGraph::getRightVertices() const noexcept -{ - return this->rightVertices; -} - -} // namespace Graph \ No newline at end of file diff --git a/include/Graph/Vertex.hpp b/include/Graph/Vertex.hpp deleted file mode 100644 index 12bb071..0000000 --- a/include/Graph/Vertex.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include - -/** - * @brief Contains generic code that represents a graph and its properties - */ -namespace Graph -{ -/** - * @brief template class representation of a vertex of type T - */ -template class Vertex -{ -public: - /** - * @brief Explicit deletion of default constructor. - */ - Vertex() = delete; - - /** - * @brief Construct a new Vertex object. - * - * @param con the object of customizable vertex-type T that contains the - * vertex content - */ - explicit Vertex(T con); - - /** - * @brief comparison operator - * - * @param rhs the other vertex object it should be compared to - * @return true if the id's are the same - * @return false if the id's aren't the same - */ - bool operator==(const Vertex& rhs) const; - - /** - * @brief getter for the Vertex-Type T content. - * - * @return T the information object - */ - [[nodiscard]] const T& getContent() const; - - /** - * @brief getter for the id of the vertex - * - * @return uint16_t the id - */ - [[nodiscard]] uint16_t getId() const; - -private: - /** - * @brief Static counter that is used to create new id - * everytime a new vertex is constructed - */ - static uint16_t idCounter; - - /** - * @brief The id of the vertex - */ - const uint16_t id; - - /** - * @brief The content of the vertex - */ - const T content; -}; - -} // namespace Graph - -#include diff --git a/include/Graph/Vertex.ipp b/include/Graph/Vertex.ipp deleted file mode 100644 index e4d21da..0000000 --- a/include/Graph/Vertex.ipp +++ /dev/null @@ -1,24 +0,0 @@ -namespace Graph -{ -template uint16_t Vertex::idCounter = 0; - -template Vertex::Vertex(T con) : id(++idCounter), content(std::move(con)) -{ -} - -template bool Vertex::operator==(const Vertex& rhs) const -{ - return this->id == rhs.getId(); -} - -template const T& Vertex::getContent() const -{ - return this->content; -} - -template uint16_t Vertex::getId() const -{ - return this->id; -} - -} // namespace Graph \ No newline at end of file diff --git a/src/AdjacencyMatrix.hpp b/src/AdjacencyMatrix.hpp deleted file mode 100644 index 267b277..0000000 --- a/src/AdjacencyMatrix.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include - -/** - * @brief Contains generic code that represents a graph and its properties - */ -namespace Graph -{ -/** - * @brief Class that can be used to store an AdjacencyMatrix continously in memory - * - * @tparam T The type to store in the matrix - */ -template class AdjacencyMatrix -{ -public: - /** - * @brief Construct a new empty AdjacencyMatrix object - */ - AdjacencyMatrix() : data(0) - { - } - - /** - * @brief Construct a new AdjacencyMatrix object - * - * @param l The amount of left vertices - * @param r The amount of right vertices - */ - AdjacencyMatrix(size_t l, size_t r) : data(l * r), leftCount(l), rightCount(r) - { - - } - - /** - * @brief Get element at specified indices - * - * @param l The left vertex index - * @param r The right vertex index - * - * @throws std::out_of_range if index is out of range - * - * @return Value at that index - */ - [[nodiscard]] T& at(size_t l, size_t r) - { - return this->data.at(l * this->rightCount + r); - } - - /** - * @brief Get const element at specified indices - * - * @param l The left vertex index - * @param r The right vertex index - * - * @throws std::out_of_range if index is out of range - * - * @return Const value at that index - */ - [[nodiscard]] const T& at(size_t l, size_t r) const { return this->data.at(l * this->rightCount + r); } - -private: - /** - * @brief Contains the data of the adjacencyMatrix - */ - std::vector data; - - /** - * @brief Contains the amount of left vertices - */ - size_t leftCount = 0; - - /** - * @brief Contains the amount of right vertices - */ - size_t rightCount = 0; -}; - -} // namespace Graph diff --git a/src/Algorithm.cpp b/src/Algorithm.cpp index 4a736e3..bae5b68 100644 --- a/src/Algorithm.cpp +++ b/src/Algorithm.cpp @@ -1,104 +1,80 @@ #include "Algorithm.hpp" #include -#include /** * @brief Contains code for the boxnesting problem */ namespace BoxNesting { -uint16_t Algorithm::runAlgorithm(const std::vector& boxes) const +Algorithm::Algorithm(const std::vector& boxes) { - this->graph = createGraphFromBoxes(boxes); - - // n is the amount of vertices in the left part of the bipartite graph - // k the amount of vertices in the right part of the bipartite graph - // They are the same in our boxes case but could be different - // in other cases - - // since we know graph size doesn't exceed uint16_t we can cast here safely - auto n = static_cast(graph.getLeftVertices().size()); - auto k = static_cast(graph.getRightVertices().size()); - - // We use 0 for no pair and the index of the vertex + 1 for found pair - this->pairs = std::vector(n, 0); - this->used = std::vector(k, false); - - // iterate through vertices of the left side - // of the bipartate graph - for (uint16_t i = 0; i < n; ++i) { - fill(used.begin(), used.end(), false); - kuhn(i); + // 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->pairsLeft = std::vector(this->leftVerticesCount, -1); + this->pairsRight = std::vector(this->rightVerticesCount, -1); + this->used = std::vector(this->leftVerticesCount, false); + + 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(); } +} - uint16_t matches = 0; - for (uint16_t i = 0; i < k; ++i) { - if (pairs[i] != 0) { - ++matches; +int16_t Algorithm::runAlgorithm() const +{ + bool pathFound; + do { + fill(this->used.begin(), this->used.end(), false); + pathFound = false; + // remember to start only from free vertices which are not visited yet + for (int16_t i = 0; i < this->leftVerticesCount; ++i) { + if (this->pairsLeft[i] == -1 && !this->used[i]) { + pathFound |= kuhn(i); + } + } + } while (pathFound); + + // 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; + for (int16_t i = 0; i < this->rightVerticesCount; ++i) { + if (this->pairsRight[i] != -1) { + --visibleBoxes; } } - // amount of visible boxes is the total amount of boxes - // minus the matches. Since a match means a box can nest inside another box - return static_cast(boxes.size() - matches); + return visibleBoxes; } -bool Algorithm::kuhn(uint16_t vertex) const +bool Algorithm::kuhn(int16_t vertex) const { if (this->used[vertex]) { return false; } this->used[vertex] = true; - auto sz = this->graph.getRightVertices().size(); - for (uint16_t i = 0; i < sz; ++i) { - if (!this->graph.isEdgeBetween(vertex, i)) { - continue; - } - // Correct for the NILL value by removing/adding one to convert to vertex index - if (pairs[i] == 0 || kuhn(static_cast(pairs[i] - 1))) { - pairs[i] = static_cast(vertex + 1); + auto sz = this->graph[vertex].size(); + for (int16_t i = 0; i < sz; ++i) { + auto to = this->graph[vertex][i]; + if (this->pairsRight[to] == -1 || kuhn(this->pairsRight[to])) { + this->pairsRight[to] = vertex; + this->pairsLeft[vertex] = to; return true; } } return false; } -Graph::BipartiteGraph Algorithm::createGraphFromBoxes(const std::vector& boxes) const -{ - std::vector> leftVertices; - std::transform(boxes.begin(), boxes.end(), std::back_inserter(leftVertices), - [](const auto& b) { return Graph::Vertex(b); }); - - std::vector> rightVertices; - std::transform(boxes.begin(), boxes.end(), std::back_inserter(rightVertices), - [](const auto& b) { return Graph::Vertex(b); }); - - // Add two distinct vertices to the graph for each box - // by constructing the graph this way we know that - // the first half of the adjacency list belongs to - // to the left side of the bipartite graph - // and the second half of the adjacency list belongs - // to the right side of the bipartite graph - Graph::BipartiteGraph graph(leftVertices, rightVertices); - - // Create edges between left and right vertices if the left box can nest in - // the right box - const auto& lv = graph.getLeftVertices(); - const auto& rv = graph.getRightVertices(); - for (uint16_t i = 0; i < lv.size(); ++i) { - for (uint16_t j = 0; j < rv.size(); ++j) { - auto& v1 = lv.at(i); - auto& v2 = rv.at(j); - - if (v1.getContent().isNestable(v2.getContent())) { - graph.addEdge(i, j); - } - } - } - - return graph; -} - } // namespace BoxNesting \ No newline at end of file diff --git a/src/Algorithm.hpp b/src/Algorithm.hpp index 8ce19f2..4322278 100644 --- a/src/Algorithm.hpp +++ b/src/Algorithm.hpp @@ -1,9 +1,9 @@ #pragma once -#include "BipartiteGraph.hpp" #include "Box.hpp" #include +#include /** * @brief Contains code for the boxnesting problem @@ -16,6 +16,18 @@ namespace BoxNesting class Algorithm { public: + /** + * @brief Deleted default constructor + */ + Algorithm() = delete; + + /** + * @brief Construct a new Algorithm object + * + * @param boxes The boxes to generate the internal graph for + */ + explicit Algorithm(const std::vector& boxes); + /** * @brief function that runs the boxNesting algorithm and returns the number * of visible boxes @@ -24,21 +36,7 @@ class Algorithm * @return the number of visible boxes after running the nesting * algorithm */ - [[nodiscard]] uint16_t runAlgorithm(const std::vector& boxes) const; - - /** - * @brief function creates a bipartite graph from a vector of boxes - * It will create two vertices for each box one in the left - * and one in the right part of the graph - * Then it will create edges between the left and the right part - * of the graph if the box in the left vertex can nest inside - * the box on the right vertex - * - * @param boxes the set of boxes that will be used to create the graph - * - * @return The constructed BipartiteGraph - */ - [[nodiscard]] Graph::BipartiteGraph createGraphFromBoxes(const std::vector& boxes) const; + [[nodiscard]] int16_t runAlgorithm() const; private: /** @@ -50,24 +48,48 @@ class Algorithm * * @return Whether a chain could be made */ - bool kuhn(uint16_t vertex) const; + bool kuhn(int16_t vertex) const; private: /** - * @brief Graph generated from the boxes + * @brief Graph generated from the boxes in adjacency list format + * 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 */ - mutable Graph::BipartiteGraph graph; + int16_t rightVerticesCount; /** - * @brief Contains whether a vertex was allready explored during the current kuhn run + * @brief Contains whether a vertex was allready explored during a single + * run of finding augmenting paths */ mutable std::vector used; /** - * @brief Contains whether vertices on the right side of the bipartite graph are part - * of a chain + * @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 pairs; + mutable std::vector pairsRight; }; } // namespace BoxNesting \ No newline at end of file diff --git a/src/BipartiteGraph.hpp b/src/BipartiteGraph.hpp deleted file mode 100644 index 9fb841d..0000000 --- a/src/BipartiteGraph.hpp +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -#include -#include - -#include "AdjacencyMatrix.hpp" -#include "Vertex.hpp" - -/** - * @brief Contains generic code that represents a graph and its properties - */ -namespace Graph -{ -/** - * @brief template class representing a Bipartite graph - * - * @tparam T The type of the value to contain in the vertices. - */ -template class BipartiteGraph -{ -public: - /** - * @brief Construct a new empty bipartite graph - */ - BipartiteGraph() = default; - - /** - * @brief Construct a new Bipartite Graph object from two sets of vertices - * - * @param leftVertices The left part of the bipartite graph - * @param rightVertices The right aprt of the bipartite graph - */ - BipartiteGraph(std::vector> leftVertices, std::vector> rightVertices) : - leftVertices(std::move(leftVertices)), rightVertices(std::move(rightVertices)), - adjacencyMatrix(AdjacencyMatrix(this->leftVertices.size(), this->rightVertices.size())) - { - } - - /** - * @brief function that adds an edge to the graph - * - * @throw std::logic_error If v1 or v2 are vertices that are not in the graph. - * - * @param v1 source vertex index of the edge must be in the left vertices - * @param v2 destination vertex index of the edge must be in the right vertices - */ - void addEdge(uint16_t v1, uint16_t v2) - { - this->adjacencyMatrix.at(v1, v2) = true; - } - - /** - * @brief function that checks if there is an edge between two vertices. - * - * @throw std::logic_error If v1 or v2 are vertices that are not in the graph. - * - * @param v1 the source vertex index of the edge must be in the left vertices - * @param v2 the destination vertex index of the edge must be in the left vertices - * @return true if there is an edge - * @return false if there is no edge - */ - [[nodiscard]] bool isEdgeBetween(uint16_t v1, uint16_t v2) const { - return this->adjacencyMatrix.at(v1, v2); - } - - /** - * @brief Getter for the adjacency list object - * - * @return The adjacency list by reference - */ - [[nodiscard]] const AdjacencyMatrix& getAdjacencyMatrix() const noexcept - { - return this->adjacencyMatrix; - } - - /** - * @brief Get the left Vertices of the bipartite graph - * - * @return The left vertices of the bipartite graph - */ - [[nodiscard]] const std::vector>& getLeftVertices() const noexcept - { - return this->leftVertices; - } - - /** - * @brief Get the rigt Vertices of the bipartite graph - * - * @return The right vertices of the bipartite graph - */ - [[nodiscard]] const std::vector>& getRightVertices() const noexcept - { - return this->rightVertices; - } - -private: - /** - * @brief Contains the left vertices of the graph - */ - std::vector> leftVertices; - - /** - * @brief Contains the rights vertices of the graph - */ - std::vector> rightVertices; - - /** - * @brief Contains the edges between the left and right vertices - * The vector specialization of bool is broken so we use uint8_t instead of bool - */ - AdjacencyMatrix adjacencyMatrix; -}; - -} // namespace Graph diff --git a/src/Box.cpp b/src/Box.cpp index 9cb6ff2..bc6ce52 100644 --- a/src/Box.cpp +++ b/src/Box.cpp @@ -22,14 +22,21 @@ Box::Box(const std::array& sideLengths) : sideLengths(sideLengths) } } -bool Box::isNestable(const Box& b) const +bool Box::operator<(const Box& b) const { - for (std::size_t i = 0; i < this->sideLengths.size(); ++i) { - if (this->sideLengths.at(i) >= b.sideLengths.at(i)) { + const auto sz = this->sideLengths.size(); + for (uint8_t i = 0; i < sz; ++i) { + if (this->sideLengths[i] >= b.sideLengths[i]) { return false; } } return true; } + +const std::array& Box::getSideLengths() const +{ + return this->sideLengths; +} + } // BoxNesting \ No newline at end of file diff --git a/src/Box.hpp b/src/Box.hpp index c2999d1..c20c029 100644 --- a/src/Box.hpp +++ b/src/Box.hpp @@ -59,21 +59,19 @@ class Box explicit Box(const std::array& sideLengths); /** - * @brief Check if this box can nest inside other box + * @brief Compare two boxes by checking if they can nest inside eachother * - * @param b The box to check if this box can nest inside - * @return True if this box can nest inside the passed box, False otherwise + * @param b The box compare this instance to + * @return True if this box can nest inside box b */ - [[nodiscard]] bool isNestable(const Box& b) const; + [[nodiscard]] bool operator<(const Box& b) const; /** * @brief Get the side lengths of the box * * @return array containing the side lengths ordered from smallest to largest */ - [[nodiscard]] const std::array& getSideLengths() const { - return this->sideLengths; - } + [[nodiscard]] const std::array& getSideLengths() const; /** * @brief The minimum length that a side of a box may have @@ -88,7 +86,7 @@ class Box static constexpr float maxLength = 1.0; /** - * @brief Static assert on the assumption any box can only contain a single box nested inside + * @brief Static assert on the assumption any box can only contain a maximum single box nested inside */ static_assert(minLength * 2 >= maxLength, "More then 1 box can be fit inside a larger box. " diff --git a/src/Parser.cpp b/src/Parser.cpp index 83390c4..a260c05 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -13,8 +13,8 @@ std::vector Parser::getBoxes(std::istream& inputStream) std::string inputLine; std::getline(inputStream, inputLine); - uint16_t boxCount = 0; - static const auto max = std::numeric_limits::max(); + int16_t boxCount = 0; + static const auto max = std::numeric_limits::max() - 1; try { auto tmp = std::stoi(inputLine); if (tmp <= 0 || tmp > max) { diff --git a/src/Vertex.hpp b/src/Vertex.hpp deleted file mode 100644 index 79d1287..0000000 --- a/src/Vertex.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include -#include - -/** - * @brief Contains generic code that represents a graph and its properties - */ -namespace Graph -{ -/** - * @brief template class representation of a vertex of type T - */ -template class Vertex -{ -public: - /** - * @brief Explicit deletion of default constructor. - */ - Vertex() = delete; - - /** - * @brief Construct a new Vertex object. - * - * @param con the object of customizable vertex-type T that contains the - * vertex content - */ - explicit Vertex(T con) : id(++idCounter), content(std::move(con)) - { - } - - /** - * @brief comparison operator - * - * @param rhs the other vertex object it should be compared to - * @return true if the id's are the same - * @return false if the id's aren't the same - */ - bool operator==(const Vertex& rhs) const - { - return this->id == rhs.getId(); - } - - /** - * @brief getter for the Vertex-Type T content. - * - * @return T the information object - */ - [[nodiscard]] const T& getContent() const - { - return this->content; - } - - /** - * @brief getter for the id of the vertex - * - * @return uint16_t the id - */ - [[nodiscard]] uint16_t getId() const - { - return this->id; - } - -private: - /** - * @brief Static counter that is used to create new id - * everytime a new vertex is constructed - */ - static uint16_t idCounter; - - /** - * @brief The id of the vertex - */ - const uint16_t id; - - /** - * @brief The content of the vertex - */ - const T content; -}; - -template uint16_t Vertex::idCounter = 0; - -} // namespace Graph diff --git a/src/main.cpp b/src/main.cpp index c87a411..31e706f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include +#include void printVersion() { @@ -72,11 +73,9 @@ int main(int argc, char** argv) return EXIT_SUCCESS; } - const auto boxes = BoxNesting::Parser::getBoxes(std::cin); + BoxNesting::Algorithm boxNestingAlgorithm(BoxNesting::Parser::getBoxes(std::cin)); - BoxNesting::Algorithm boxNestingAlgorithm; - - std::cout << boxNestingAlgorithm.runAlgorithm(boxes) << std::endl; + std::cout << boxNestingAlgorithm.runAlgorithm() << std::endl; return EXIT_SUCCESS; } diff --git a/src/version.hpp b/src/version.hpp index fe0a723..0e10557 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-871b6ad+" +#define BOXNESTING_VERSION "1.0.0-a56672a+" /** Defines the project major version number */ #define BOXNESTING_VERSION_MAJOR 1 /** Defines the project major version number */ diff --git a/test/Algorithm.cpp b/test/Algorithm.cpp index 826bc4c..2a0cb47 100644 --- a/test/Algorithm.cpp +++ b/test/Algorithm.cpp @@ -5,46 +5,16 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") { - BoxNesting::Algorithm boxNestingAlgorithm; - - GIVEN("A set of boxes") - { - std::vector boxes{BoxNesting::Box({0.6f, 0.6f, 0.6f}), BoxNesting::Box({0.7f, 0.7f, 0.7f}), - BoxNesting::Box({0.8f, 0.8f, 0.8f})}; - - WHEN("Creating a graph from the set of boxes") - { - const auto graph = boxNestingAlgorithm.createGraphFromBoxes(boxes); - - THEN("There can only be edges from vertex a to b if a nests inside b") - { - const auto& lv = graph.getLeftVertices(); - const auto& rv = graph.getRightVertices(); - for (uint16_t i = 0; i < lv.size(); ++i) { - for (uint16_t j = 0; j < rv.size(); ++j) { - if (graph.isEdgeBetween(i, j)) { - auto& v1 = lv.at(i); - auto& v2 = rv.at(j); - - auto isNestable = v1.getContent().isNestable(v2.getContent()) - || v2.getContent().isNestable(v1.getContent()); - - REQUIRE(isNestable); - } - } - } - } - } - } - 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::Algorithm boxNestingAlgorithm(boxes); + THEN("Only one visible box remains after nesting") { - auto result = boxNestingAlgorithm.runAlgorithm(boxes); + auto result = boxNestingAlgorithm.runAlgorithm(); REQUIRE(result == 1); } } @@ -61,9 +31,12 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") THEN("One less box is visible after nesting") { - auto result1 = boxNestingAlgorithm.runAlgorithm(boxes1); + BoxNesting::Algorithm boxNestingAlgorithm1(boxes1); + BoxNesting::Algorithm boxNestingAlgorithm2(boxes2); + + auto result1 = boxNestingAlgorithm1.runAlgorithm(); REQUIRE(result1 == (boxes1.size() - 1)); - auto result2 = boxNestingAlgorithm.runAlgorithm(boxes2); + auto result2 = boxNestingAlgorithm2.runAlgorithm(); REQUIRE(result2 == (boxes2.size() - 1)); } } @@ -78,7 +51,8 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") THEN("Only one visible box remains after nesting") { - auto result = boxNestingAlgorithm.runAlgorithm(boxes); + BoxNesting::Algorithm boxNestingAlgorithm(boxes); + auto result = boxNestingAlgorithm.runAlgorithm(); REQUIRE(result == 1); } } @@ -93,7 +67,8 @@ SCENARIO("Running the 'graph creation from a set of boxes' function") THEN("Only one visible box remains after nesting") { - auto result = boxNestingAlgorithm.runAlgorithm(boxes); + BoxNesting::Algorithm boxNestingAlgorithm(boxes); + auto result = boxNestingAlgorithm.runAlgorithm(); REQUIRE(result == boxes.size()); } } diff --git a/test/BipartiteGraph.cpp b/test/BipartiteGraph.cpp deleted file mode 100644 index cd39c1c..0000000 --- a/test/BipartiteGraph.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include -#include - -SCENARIO("A graph of boxes add edged and check if no vertex has an edge to itsself", "[Graph]") -{ - GIVEN("A basic set of boxes") - { - std::vector> leftVertices{ - Graph::Vertex(BoxNesting::Box({0.6f, 0.6f, 0.61f})), - Graph::Vertex(BoxNesting::Box({0.6f, 0.6f, 0.51f})), - Graph::Vertex(BoxNesting::Box({0.6f, 0.51f, 0.6f}))}; - - std::vector> rightVertices{ - Graph::Vertex(BoxNesting::Box({0.6f, 0.6f, 0.61f})), - Graph::Vertex(BoxNesting::Box({0.6f, 0.6f, 0.51f})), - Graph::Vertex(BoxNesting::Box({0.6f, 0.51f, 0.6f}))}; - - Graph::BipartiteGraph graph(leftVertices, rightVertices); - - WHEN("Adding some edges") - { - graph.addEdge(0, 1); - graph.addEdge(1, 2); - - THEN("Existing edges indeed exist") - { - REQUIRE(graph.isEdgeBetween(0, 1) == true); - REQUIRE(graph.isEdgeBetween(1, 2) == true); - REQUIRE(graph.isEdgeBetween(0, 2) == false); - REQUIRE(graph.isEdgeBetween(2, 0) == false); - } - - THEN("A vertex does not have an edge to itself") - { - REQUIRE(graph.isEdgeBetween(0, 0) == false); - REQUIRE(graph.isEdgeBetween(1, 1) == false); - REQUIRE(graph.isEdgeBetween(1, 1) == false); - } - } - } -} \ No newline at end of file diff --git a/test/Box.cpp b/test/Box.cpp index 70cf71e..b2e091a 100644 --- a/test/Box.cpp +++ b/test/Box.cpp @@ -86,33 +86,33 @@ SCENARIO("Boxes can nest inside another if their dimensions allow it", "[Box]") THEN("they can nest inside eachother if their dimensions allow it") { - REQUIRE(e.isNestable(a) == true); + REQUIRE((e < a) == true); } THEN("a box cannot nest inside itself") { - REQUIRE(a.isNestable(a) == false); - REQUIRE(b.isNestable(b) == false); - REQUIRE(c.isNestable(c) == false); - REQUIRE(d.isNestable(d) == false); - REQUIRE(e.isNestable(e) == false); + REQUIRE((a < a) == false); + REQUIRE((b < b) == false); + REQUIRE((c < c) == false); + REQUIRE((d < d) == false); + REQUIRE((e < e) == false); } THEN("a larger box cannot nest inside a smaller box") { - REQUIRE(e.isNestable(d) == false); - REQUIRE(e.isNestable(c) == false); - REQUIRE(e.isNestable(b) == false); + REQUIRE((e < d) == false); + REQUIRE((e < c) == false); + REQUIRE((e < b) == false); } WHEN("a box a can nest inside a box b and box b can nest inside box c") { - REQUIRE(f.isNestable(e) == true); - REQUIRE(e.isNestable(a) == true); + REQUIRE((f < e) == true); + REQUIRE((e < a) == true); THEN("box a can nest iniside box c") { - REQUIRE(f.isNestable(a) == true); + REQUIRE((f < a) == true); } } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c488b69..55bee2a 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 BipartiteGraph Algorithm) + Box Parser Algorithm) add_executable("test_${file}" "${file}.cpp" $ diff --git a/test/Parser.cpp b/test/Parser.cpp index ea80093..a13b8d9 100644 --- a/test/Parser.cpp +++ b/test/Parser.cpp @@ -53,7 +53,7 @@ SCENARIO("Parsing box specifcations from inputStream") std::stringstream stream; THEN("a ParserError is thrown") { - stream << (std::numeric_limits::max() + 1) << std::endl; + stream << (std::numeric_limits::max()) << std::endl; REQUIRE_THROWS_AS(BoxNesting::Parser::getBoxes(stream), BoxNesting::ParserError); stream.str(std::string()); stream << std::numeric_limits::max() << std::endl;