diff --git a/README.md b/README.md index 812001591..ef7f78058 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,7 @@ System Dependencies (note that we will automatically download the dependency if - [`gtest`](https://github.com/google/googletest/): Google test library (only when test is enabled, i.e. `MANIFOLD_TEST=ON`) Other dependencies: -- [`graphlite`](https://github.com/haasdo95/graphlite): connected components algorithm. -- [`Clipper2`](https://github.com/AngusJohnson/Clipper2j): provides our 2D subsystem +- [`Clipper2`](https://github.com/AngusJohnson/Clipper2): provides our 2D subsystem - [`quickhull`](https://github.com/akuukka/quickhull): 3D convex hull algorithm. ## What's here diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 9fbd9de66..f16f648ee 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -14,7 +14,7 @@ endif() target_link_libraries( ${PROJECT_NAME} - PRIVATE manifold sdf graphlite cross_section + PRIVATE manifold sdf cross_section ) target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include) diff --git a/src/manifold/CMakeLists.txt b/src/manifold/CMakeLists.txt index 31fb99fee..72cff48d7 100644 --- a/src/manifold/CMakeLists.txt +++ b/src/manifold/CMakeLists.txt @@ -20,7 +20,7 @@ add_library(${PROJECT_NAME} ${SOURCE_FILES}) target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include) target_link_libraries(${PROJECT_NAME} PUBLIC utilities cross_section - PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite Clipper2 quickhull + PRIVATE collider polygon ${MANIFOLD_INCLUDE} Clipper2 quickhull ) target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS}) diff --git a/src/manifold/src/constructors.cpp b/src/manifold/src/constructors.cpp index 186478a1b..0873eee29 100644 --- a/src/manifold/src/constructors.cpp +++ b/src/manifold/src/constructors.cpp @@ -16,7 +16,6 @@ #include "cross_section.h" #include "csg_tree.h" -#include "graph.h" #include "impl.h" #include "par.h" #include "polygon.h" @@ -465,17 +464,14 @@ Manifold Manifold::Compose(const std::vector& manifolds) { * containing a copy of the original. It is the inverse operation of Compose(). */ std::vector Manifold::Decompose() const { - Graph graph; + UnionFind<> uf(NumVert()); + // Graph graph; auto pImpl_ = GetCsgLeafNode().GetImpl(); - for (int i = 0; i < NumVert(); ++i) { - graph.add_nodes(i); - } for (const Halfedge& halfedge : pImpl_->halfedge_) { - if (halfedge.IsForward()) - graph.add_edge(halfedge.startVert, halfedge.endVert); + if (halfedge.IsForward()) uf.unionXY(halfedge.startVert, halfedge.endVert); } std::vector componentIndices; - const int numComponents = ConnectedComponents(componentIndices, graph); + const int numComponents = uf.connectedComponents(componentIndices); if (numComponents == 1) { std::vector meshes(1); diff --git a/src/manifold/src/impl.cpp b/src/manifold/src/impl.cpp index fc723be48..40fcbd636 100644 --- a/src/manifold/src/impl.cpp +++ b/src/manifold/src/impl.cpp @@ -19,7 +19,6 @@ #include #include -#include "graph.h" #include "hashtable.h" #include "mesh_fixes.h" #include "par.h" @@ -272,11 +271,11 @@ struct CheckCoplanarity { VecView comp2tri; VecView halfedge; VecView vertPos; - VecView components; + std::vector* components; const float precision; void operator()(int tri) { - const int component = components[tri]; + const int component = (*components)[tri]; const int referenceTri = comp2tri[component]; if (referenceTri < 0 || referenceTri == tri) return; @@ -310,17 +309,13 @@ struct EdgeBox { int GetLabels(std::vector& components, const Vec>& edges, int numNodes) { - Graph graph; - for (int i = 0; i < numNodes; ++i) { - graph.add_nodes(i); - } - for (int i = 0; i < edges.size(); ++i) { - const thrust::pair edge = edges[i]; - if (edge.first < 0) continue; - graph.add_edge(edge.first, edge.second); + UnionFind<> uf(numNodes); + for (auto edge : edges) { + if (edge.first == -1 || edge.second == -1) continue; + uf.unionXY(edge.first, edge.second); } - return ConnectedComponents(components, graph); + return uf.connectedComponents(components); } void DedupePropVerts(manifold::Vec& triProp, @@ -639,7 +634,7 @@ void Manifold::Impl::CreateFaces(const std::vector& propertyTolerance) { std::vector components; const int numComponent = GetLabels(components, face2face, NumTri()); - std::vector comp2tri(numComponent, -1); + Vec comp2tri(numComponent, -1); for (int tri = 0; tri < NumTri(); ++tri) { const int comp = components[tri]; const int current = comp2tri[comp]; @@ -649,15 +644,13 @@ void Manifold::Impl::CreateFaces(const std::vector& propertyTolerance) { } } - Vec componentsD(components); - Vec comp2triD(comp2tri); for_each_n(autoPolicy(halfedge_.size()), countAt(0), NumTri(), CheckCoplanarity( - {comp2triD, halfedge_, vertPos_, componentsD, precision_})); + {comp2tri, halfedge_, vertPos_, &components, precision_})); Vec& triRef = meshRelation_.triRef; for (int tri = 0; tri < NumTri(); ++tri) { - const int referenceTri = comp2triD[components[tri]]; + const int referenceTri = comp2tri[components[tri]]; if (referenceTri >= 0) { triRef[tri].tri = referenceTri; } diff --git a/src/manifold/src/sort.cpp b/src/manifold/src/sort.cpp index 59cc0c3fb..acde949be 100644 --- a/src/manifold/src/sort.cpp +++ b/src/manifold/src/sort.cpp @@ -16,7 +16,6 @@ #include #include -#include "graph.h" #include "impl.h" #include "par.h" @@ -583,30 +582,20 @@ bool MeshGL::Merge() { Collider collider(vertBox, vertMorton); SparseIndices toMerge = collider.Collisions(vertBox.cview()); - Graph graph; - for (int i = 0; i < numVert; ++i) { - graph.add_nodes(i); - } + UnionFind<> uf(numVert); for (int i = 0; i < mergeFromVert.size(); ++i) { - graph.add_edge(static_cast(mergeFromVert[i]), - static_cast(mergeToVert[i])); + uf.unionXY(static_cast(mergeFromVert[i]), + static_cast(mergeToVert[i])); } for (int i = 0; i < toMerge.size(); ++i) { - graph.add_edge(openVerts[toMerge.Get(i, false)], - openVerts[toMerge.Get(i, true)]); - } - - std::vector vertLabels; - const int numLabels = ConnectedComponents(vertLabels, graph); - std::vector label2vert(numLabels); - for (int v = 0; v < numVert; ++v) { - label2vert[vertLabels[v]] = v; + uf.unionXY(openVerts[toMerge.Get(i, false)], + openVerts[toMerge.Get(i, true)]); } mergeToVert.clear(); mergeFromVert.clear(); for (int v = 0; v < numVert; ++v) { - const int mergeTo = label2vert[vertLabels[v]]; + const int mergeTo = uf.find(v); if (mergeTo != v) { mergeFromVert.push_back(v); mergeToVert.push_back(mergeTo); diff --git a/src/third_party/CMakeLists.txt b/src/third_party/CMakeLists.txt index 5b4e423bc..6ef64e832 100644 --- a/src/third_party/CMakeLists.txt +++ b/src/third_party/CMakeLists.txt @@ -1,5 +1,3 @@ -add_subdirectory(graphlite) - set(CLIPPER2_UTILS OFF) set(CLIPPER2_EXAMPLES OFF) set(CLIPPER2_TESTS OFF) diff --git a/src/third_party/graphlite/CMakeLists.txt b/src/third_party/graphlite/CMakeLists.txt deleted file mode 100644 index 30d67b005..000000000 --- a/src/third_party/graphlite/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -project (graphlite) - -add_library(${PROJECT_NAME} src/connected_components.cpp) - -target_include_directories(${PROJECT_NAME} - PUBLIC ${PROJECT_SOURCE_DIR}/include -) - -target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS}) -target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) diff --git a/src/third_party/graphlite/LICENSE.txt b/src/third_party/graphlite/LICENSE.txt deleted file mode 100644 index 8665808d7..000000000 --- a/src/third_party/graphlite/LICENSE.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright 2021 Guohao Dou - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/third_party/graphlite/include/graph.h b/src/third_party/graphlite/include/graph.h deleted file mode 100644 index 658153c37..000000000 --- a/src/third_party/graphlite/include/graph.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2022 The Manifold Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include "graph_lite.h" - -namespace manifold { - -typedef typename graph_lite::Graph< - int, void, void, graph_lite::EdgeDirection::UNDIRECTED, - graph_lite::MultiEdge::DISALLOWED, graph_lite::SelfLoop::DISALLOWED, - graph_lite::Map::UNORDERED_MAP, graph_lite::Container::VEC> - Graph; - -int ConnectedComponents(std::vector& components, const Graph& g); -} // namespace manifold \ No newline at end of file diff --git a/src/third_party/graphlite/include/graph_lite.h b/src/third_party/graphlite/include/graph_lite.h deleted file mode 100644 index 7e093781f..000000000 --- a/src/third_party/graphlite/include/graph_lite.h +++ /dev/null @@ -1,1621 +0,0 @@ -// From https://github.com/haasdo95/graphlite commit -// 26b9d8c05b5b2a93def516a1c50857695e8efe06 -#ifndef GSK_GRAPH_LITE_H -#define GSK_GRAPH_LITE_H - -#include -#include -#include // Added to fix MSVC error -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// container spec -namespace graph_lite { -// ContainerGen, supposed to be container of neighbors -enum class Container { - VEC, - LIST, - SET, - UNORDERED_SET, - MULTISET, - UNORDERED_MULTISET -}; - -// self loop permission -enum class SelfLoop { ALLOWED, DISALLOWED }; - -// multi-edge permission -enum class MultiEdge { ALLOWED, DISALLOWED }; - -// directed or undirected graph -enum class EdgeDirection { DIRECTED, UNDIRECTED }; - -// map for adj list -enum class Map { MAP, UNORDERED_MAP }; - -// Logging permission -enum class Logging { ALLOWED, DISALLOWED }; - -} // namespace graph_lite - -// type manipulation -namespace graph_lite::detail { -template -constexpr bool is_vector_v = std::is_same_v< - T, std::vector>; - -template -constexpr bool is_list_v = std::is_same_v< - T, std::vector>; - -// determine if type is map or unordered_map -template -struct is_map : std::false_type {}; -template -struct is_map< - T, std::void_t> { - static constexpr bool value = std::is_same_v< - T, std::map>; -}; -template -constexpr bool is_map_v = is_map::value; - -template -struct is_unordered_map : std::false_type {}; -template -struct is_unordered_map< - T, std::void_t> { - static constexpr bool value = std::is_same_v< - T, std::unordered_map>; -}; -template -constexpr bool is_unordered_map_v = is_unordered_map::value; -template -constexpr bool is_either_map_v = is_map_v or is_unordered_map_v; - -// CREDIT: -// https://stackoverflow.com/questions/765148/how-to-remove-constness-of-const-iterator -template -typename ContainerType::iterator const_iter_to_iter(ContainerType& c, - ConstIterator it) { - return c.erase(it, it); -} -// shorthand for turning const T& into T -template -struct remove_cv_ref { - using type = std::remove_cv_t>; -}; -template -using remove_cv_ref_t = typename remove_cv_ref::type; -// END OF shorthand for turning const T& into T - -// test if lhs==rhs and lhs!=rhs work -template > -struct is_eq_comparable : std::false_type {}; -template -struct is_eq_comparable< - T, std::void_t() == std::declval())>> - : std::true_type {}; -template -constexpr bool is_eq_comparable_v = is_eq_comparable::value; - -// END OF test if lhs==rhs work - -// test comparability; see if a < b works -template > -struct is_comparable : std::false_type {}; -template -struct is_comparable< - T, std::void_t() < std::declval())>> - : std::true_type {}; -template -constexpr bool is_comparable_v = is_comparable::value; -// END OF test comparability - -// test streamability -template > -struct is_streamable : std::false_type {}; -template -struct is_streamable() - << std::declval())>> - : std::true_type {}; -template -constexpr bool is_streamable_v = is_streamable::value; -// END OF test streamability - -// test hashability -template > -struct is_std_hashable : std::false_type {}; -template -struct is_std_hashable< - T, std::void_t>()(std::declval()))>> - : std::true_type {}; -template -constexpr bool is_std_hashable_v = is_std_hashable::value; -// END OF test hashability - -template -struct MultiEdgeTraits {}; -template <> -struct MultiEdgeTraits { - static constexpr MultiEdge value = MultiEdge::ALLOWED; -}; -template <> -struct MultiEdgeTraits { - static constexpr MultiEdge value = MultiEdge::ALLOWED; -}; -template <> -struct MultiEdgeTraits { - static constexpr MultiEdge value = MultiEdge::ALLOWED; -}; -template <> -struct MultiEdgeTraits { - static constexpr MultiEdge value = MultiEdge::ALLOWED; -}; -template <> -struct MultiEdgeTraits { - static constexpr MultiEdge value = MultiEdge::DISALLOWED; -}; -template <> -struct MultiEdgeTraits { - static constexpr MultiEdge value = MultiEdge::DISALLOWED; -}; - -template -struct OutIn { - T out; - T in; -}; -} // namespace graph_lite::detail - -// operation on containers -namespace graph_lite::detail::container { -template , typename ContainerType::value_type>>> -auto insert(ContainerType& c, ValueType&& v) { - return c.insert(c.cend(), std::forward(v)); -} - -// find the first occurrence of a value -// using different find for efficiency; std::find is always linear -template < - typename ContainerType, typename ValueType, - std::enable_if_t and !is_list_v, - bool> = true> -auto find(ContainerType& c, const ValueType& v) { - return c.find(v); -} - -template -auto find(std::vector>& c, const ValueType& v) { - return std::find_if(c.begin(), c.end(), - [&v](const auto& p) { return p.first == v; }); -} -template -auto find(std::list>& c, const ValueType& v) { - return std::find_if(c.begin(), c.end(), - [&v](const auto& p) { return p.first == v; }); -} - -template -auto find(const std::vector>& c, const ValueType& v) { - return std::find_if(c.begin(), c.end(), - [&v](const auto& p) { return p.first == v; }); -} -template -auto find(const std::list>& c, const ValueType& v) { - return std::find_if(c.begin(), c.end(), - [&v](const auto& p) { return p.first == v; }); -} - -template , - bool> = true> -auto find(std::vector& c, const ValueType& v) { - return std::find(c.begin(), c.end(), v); -} - -template , - bool> = true> -auto find(std::list& c, const ValueType& v) { - return std::find(c.begin(), c.end(), v); -} - -template , - bool> = true> -auto find(const std::vector& c, const ValueType& v) { - return std::find(c.begin(), c.end(), v); -} - -template , - bool> = true> -auto find(const std::list& c, const ValueType& v) { - return std::find(c.begin(), c.end(), v); -} -// END OF find the first occurrence of a value - -// count the occurrence of a value -template -std::enable_if_t and !is_list_v, int> -count(const ContainerType& c, const ValueType& v) { - return c.count(v); -} - -template -int count(const std::vector>& c, const ValueType& v) { - return std::count_if(c.begin(), c.end(), - [&v](const auto& e) { return e.first == v; }); -} - -template -int count(const std::list>& c, const ValueType& v) { - return std::count_if(c.begin(), c.end(), - [&v](const auto& e) { return e.first == v; }); -} - -template -std::enable_if_t, int> count( - const std::vector& c, const ValueType& v) { - return std::count(c.begin(), c.end(), v); -} - -template -std::enable_if_t, int> count( - const std::list& c, const ValueType& v) { - return std::count(c.begin(), c.end(), v); -} -// END OF count the occurrence of a value - -// remove all by value -// erase always erases all with value v; again, std::remove is linear -template -std::enable_if_t and !is_list_v, int> -erase_all(ContainerType& c, const ValueType& v) { - static_assert( - std::is_same_v>); - return c.erase(v); -} - -template -std::enable_if_t, int> -erase_all(std::vector& c, const ValueType& v) { - size_t old_size = c.size(); - c.erase(std::remove(c.begin(), c.end(), v), c.end()); - return old_size - c.size(); -} - -template -int erase_all(std::vector>& c, const ValueType& v) { - size_t old_size = c.size(); - c.erase(std::remove_if(c.begin(), c.end(), - [&v](const auto& p) { return p.first == v; }), - c.end()); - return old_size - c.size(); -} - -template -std::enable_if_t, int> -erase_all(std::list& c, const ValueType& v) { - size_t old_size = c.size(); - c.remove(v); - return old_size - c.size(); -} - -template -int erase_all(std::list>& c, const ValueType& v) { - size_t old_size = c.size(); - c.remove_if([&v](const auto& p) { return p.first == v; }); - return old_size - c.size(); -} -// END OF remove all by value - -// remove one by value or position; remove AT MOST 1 element -template -int erase_one(ContainerType& c, const ValueType& v) { - if constexpr (std::is_same_v, - typename ContainerType::iterator> or - std::is_same_v< - remove_cv_ref_t, - typename ContainerType::const_iterator>) { // remove by pos - c.erase(v); - return 1; - } else { // remove by value - auto pos = find(c, v); - if (pos == c.end()) { - return 0; - } - c.erase(pos); - return 1; - } -} -// END OF remove one -} // namespace graph_lite::detail::container - -// mixin base classes of Graph -namespace graph_lite::detail { -// EdgePropListBase provides optional member variable edge_prop_list -template -struct EdgePropListBase { - protected: - std::list edge_prop_list; -}; -template <> -struct EdgePropListBase { -}; // empty base optimization if edge prop is not needed - -// EdgeDirectionBase provides different API for directed and undirected graphs -template -struct EdgeDirectionBase {}; - -template -struct EdgeDirectionBase { // undirected graph only - template - auto neighbors(const T& node_iv) const { - const auto* self = static_cast(this); - return self->template get_neighbors_helper(node_iv); - } - template - auto neighbors(const T& node_iv) { - auto* self = static_cast(this); - return self->template get_neighbors_helper(node_iv); - } - // returns the number of neighbors of a node - template - int count_neighbors(const T& node_iv) const { - const auto* self = static_cast(this); - return self->template count_neighbors_helper(node_iv); - } - - // find a node with value tgt within the neighborhood of src - template - auto find_neighbor(const U& src_iv, V&& tgt_identifier) const { - const auto* self = static_cast(this); - return self->template find_neighbor_helper( - src_iv, std::forward(tgt_identifier)); - } - template - auto find_neighbor(const U& src_iv, - V&& tgt_identifier) { // non-const overload - auto* self = static_cast(this); - return self->template find_neighbor_helper( - src_iv, std::forward(tgt_identifier)); - } -}; - -template -struct EdgeDirectionBase { // directed graph only - template - auto out_neighbors(const T& node_iv) const { - const auto* self = static_cast(this); - return self->template get_neighbors_helper(node_iv); - } - template - auto out_neighbors(const T& node_iv) { - auto* self = static_cast(this); - return self->template get_neighbors_helper(node_iv); - } - - template - auto in_neighbors(const T& node_iv) const { - const auto* self = static_cast(this); - return self->template get_neighbors_helper(node_iv); - } - template - auto in_neighbors(const T& node_iv) { - auto* self = static_cast(this); - return self->template get_neighbors_helper(node_iv); - } - - // returns the number of out neighbors of a node - template - int count_out_neighbors(const T& node_iv) const { - const auto* self = static_cast(this); - return self->template count_neighbors_helper(node_iv); - } - - // returns the number of out neighbors of a node - template - int count_in_neighbors(const T& node_iv) const { - const auto* self = static_cast(this); - return self->template count_neighbors_helper(node_iv); - } - - // find a node with value tgt within the out-neighborhood of src - template - auto find_out_neighbor(const U& src_iv, V&& tgt_identifier) const { - const auto* self = static_cast(this); - return self->template find_neighbor_helper( - src_iv, std::forward(tgt_identifier)); - } - template - auto find_out_neighbor(const U& src_iv, - V&& tgt_identifier) { // non-const overload - auto* self = static_cast(this); - return self->template find_neighbor_helper( - src_iv, std::forward(tgt_identifier)); - } - - // find a node with value tgt within the in-neighborhood of src - template - auto find_in_neighbor(const U& src_iv, V&& tgt_identifier) const { - const auto* self = static_cast(this); - return self->template find_neighbor_helper( - src_iv, std::forward(tgt_identifier)); - } - template - auto find_in_neighbor(const U& src_iv, - V&& tgt_identifier) { // non-const overload - auto* self = static_cast(this); - return self->template find_neighbor_helper( - src_iv, std::forward(tgt_identifier)); - } -}; - -// NodePropGraphBase provides different API depending on whether node prop is -// needed -template -struct NodePropGraphBase { - public: // this can seg fault if node_identifier is invalid... - template - const NodePropType& node_prop(const T& node_iv) const { - const auto* self = static_cast(this); - auto pos = self->find_by_iter_or_by_value(node_iv); - return pos->second.prop; - } - template - NodePropType& node_prop(const T& node_iv) { - return const_cast( - static_cast(this)->node_prop(node_iv)); - } - - template - int add_node_with_prop(NT&& new_node, NPT&&... prop) noexcept { - static_assert( - std::is_same_v, typename GType::node_type>); - static_assert(std::is_constructible_v); - auto* self = static_cast(this); - if (!self->adj_list.count(new_node)) { // insert if not already existing - // this should invoke(in-place) the constructor of PropNode - self->adj_list.emplace(std::piecewise_construct, - std::forward_as_tuple(std::forward(new_node)), - std::forward_as_tuple(std::forward(prop)...)); - return 1; - } - return 0; // a no-op if already existing - } -}; -template -struct NodePropGraphBase { // no node prop - template - int add_nodes(T&& new_node) noexcept { // base case - static_assert( - std::is_same_v, typename GType::node_type>); - auto* self = static_cast(this); - int old_size = self->adj_list.size(); - self->adj_list[std::forward( - new_node)]; // insertion here; no-op if already existing - return self->adj_list.size() - old_size; - } - template - int add_nodes(T&& new_node, Args&&... args) noexcept { - static_assert( - std::is_same_v, typename GType::node_type>); - return add_nodes(std::forward(new_node)) + - add_nodes(std::forward(args)...); - } -}; - -// EdgePropGraphBase provides method "add_edge" when edge prop is not needed; -// and "add_edge_with_prop" when edge prop is needed -template -struct EdgePropGraphBase { - // extract edge property given node pair - template - EdgePropType& edge_prop(U&& source_iv, V&& target_iv) { - return const_cast( - static_cast(this)->template edge_prop( - std::forward(source_iv), std::forward(target_iv))); - } - - template - const EdgePropType& edge_prop(U&& source_iv, V&& target_iv) const { - const auto* self = static_cast(this); - const char* src_not_found_msg = "source is not found"; - const char* tgt_not_found_msg = "target is not found"; - auto find_neighbor_iv = [self, &source_iv, &target_iv]() { - auto&& tgt_val = - self->unwrap_by_iter_or_by_value(std::forward(target_iv)); - if constexpr (GType::DIRECTION == EdgeDirection::UNDIRECTED) { - return self->find_neighbor(source_iv, - std::forward(tgt_val)); - } else { - return self->find_out_neighbor( - source_iv, std::forward(tgt_val)); - } - }; - auto find_neighbor_vi = [self, &source_iv, &target_iv]() { - auto&& src_val = - self->unwrap_by_iter_or_by_value(std::forward(source_iv)); - if constexpr (GType::DIRECTION == EdgeDirection::UNDIRECTED) { - return self->find_neighbor(target_iv, - std::forward(src_val)); - } else { - return self->find_in_neighbor(target_iv, - std::forward(src_val)); - } - }; - // this ensures that no adj list lookup will happen if either is an iterator - if constexpr (GType::template is_iterator()) { // I & V or I & I - auto [found, pos] = find_neighbor_iv(); - if (not found) { - throw std::runtime_error(tgt_not_found_msg); - } - return pos->second.prop(); - } else { - // V & I or V & V - auto [found, pos] = find_neighbor_vi(); - if (not found) { - throw std::runtime_error(src_not_found_msg); - } - return pos->second.prop(); - } - } - - template - int add_edge_with_prop(U&& source_iv, V&& target_iv, EPT&&... prop) noexcept { - static_assert(std::is_constructible_v); - auto* self = static_cast(this); - auto src_pos = self->find_by_iter_or_by_value(source_iv); - auto tgt_pos = self->find_by_iter_or_by_value(target_iv); - if (src_pos == self->adj_list.end() or tgt_pos == self->adj_list.end()) { - if constexpr (GType::LOGGING == Logging::ALLOWED) { - if (src_pos == self->adj_list.end()) { - self->print_by_iter_or_by_value( - std::cerr << "(add_edge) edge involves non-existent source", - source_iv) - << "\n"; - } - if (tgt_pos == self->adj_list.end()) { - self->print_by_iter_or_by_value( - std::cerr << "(add_edge) edge involves non-existent target", - target_iv) - << "\n"; - } - } - return 0; - } - const typename GType::node_type& src_full = src_pos->first; - const typename GType::node_type& tgt_full = - tgt_pos->first; // flesh out src and tgt - if (self->check_edge_dup(src_pos, src_full, tgt_full)) { - return 0; - } - if (self->check_self_loop(src_pos, tgt_pos, src_full)) { - return 0; - } - auto prop_pos = self->insert_edge_prop(std::forward(prop)...); - container::insert(self->get_out_neighbors(src_pos), - std::make_pair(tgt_full, prop_pos)); - if (src_pos != tgt_pos or GType::DIRECTION == EdgeDirection::DIRECTED) { - container::insert(self->get_in_neighbors(tgt_pos), - std::make_pair(src_full, prop_pos)); - } - ++self->num_of_edges; - return 1; - } -}; -template -struct EdgePropGraphBase { - template - int add_edge(U&& source_iv, V&& target_iv) noexcept { - auto* self = static_cast(this); - auto src_pos = self->find_by_iter_or_by_value(source_iv); - auto tgt_pos = self->find_by_iter_or_by_value(target_iv); - if (src_pos == self->adj_list.end() or tgt_pos == self->adj_list.end()) { - if constexpr (GType::LOGGING == Logging::ALLOWED) { - if (src_pos == self->adj_list.end()) { - self->print_by_iter_or_by_value( - std::cerr << "(add_edge) edge involves non-existent source", - source_iv) - << "\n"; - } - if (tgt_pos == self->adj_list.end()) { - self->print_by_iter_or_by_value( - std::cerr << "(add_edge) edge involves non-existent target", - target_iv) - << "\n"; - } - } - return 0; - } - const typename GType::node_type& src_full = src_pos->first; - const typename GType::node_type& tgt_full = - tgt_pos->first; // flesh out src and tgt - if (self->check_edge_dup(src_pos, src_full, tgt_full)) { - return 0; - } - if (self->check_self_loop(src_pos, tgt_pos, src_full)) { - return 0; - } - container::insert(self->get_out_neighbors(src_pos), tgt_full); - if (src_pos != tgt_pos or GType::DIRECTION == EdgeDirection::DIRECTED) { - container::insert(self->get_in_neighbors(tgt_pos), src_full); - } - ++self->num_of_edges; - return 1; - } -}; -} // namespace graph_lite::detail - -// Graph class -namespace graph_lite { -template -class Graph - : private detail::EdgePropListBase, - public detail::EdgeDirectionBase< - Graph, - direction>, - public detail::NodePropGraphBase< - Graph, - NodePropType>, - public detail::EdgePropGraphBase< - Graph, - EdgePropType> { - // friend class with CRTP base classes - friend detail::EdgeDirectionBase< - Graph, - direction>; - friend detail::NodePropGraphBase< - Graph, - NodePropType>; - friend detail::EdgePropGraphBase< - Graph, - EdgePropType>; - static_assert(std::is_same_v>, - "NodeType should not be a reference"); - static_assert(std::is_same_v>, - "NodeType should not be cv-qualified"); - static_assert( - std::is_same_v> and - std::is_same_v>, - "Property types should not be references"); - static_assert( - std::is_same_v> and - std::is_same_v>, - "Property types should not be cv-qualified"); - static_assert(detail::is_eq_comparable_v, - "NodeType does not support ==; implement operator=="); - static_assert(detail::is_streamable_v, - "NodeType is not streamable; implement operator<<"); - static_assert(not((neighbors_container_spec == Container::UNORDERED_SET or - neighbors_container_spec == - Container::UNORDERED_MULTISET or - adj_list_spec == Map::UNORDERED_MAP) and - !detail::is_std_hashable_v), - "NodeType is not hashable"); - static_assert(not((neighbors_container_spec == Container::SET or - neighbors_container_spec == Container::MULTISET or - adj_list_spec == Map::MAP) and - !detail::is_comparable_v), - "NodeType does not support operator <"); - static_assert(not(detail::MultiEdgeTraits::value == - MultiEdge::DISALLOWED and - multi_edge == MultiEdge::ALLOWED), - "node container does not support multi-edge"); - static_assert(not((neighbors_container_spec == Container::MULTISET or - neighbors_container_spec == - Container::UNORDERED_MULTISET) and - multi_edge == MultiEdge::DISALLOWED), - "disallowing multi-edge yet still using multi-set; use " - "set/unordered_set instead"); - - public: // exposed types and constants - using node_type = NodeType; - using node_prop_type = NodePropType; - using edge_prop_type = EdgePropType; - static constexpr EdgeDirection DIRECTION = direction; - static constexpr MultiEdge MULTI_EDGE = multi_edge; - static constexpr SelfLoop SELF_LOOP = self_loop; - static constexpr Map ADJ_LIST_SPEC = adj_list_spec; - static constexpr Container NEIGHBORS_CONTAINER_SPEC = - neighbors_container_spec; - static constexpr Logging LOGGING = logging; - - private: // type gymnastics - // handle neighbors that may have property - // PairIterator is useful only when (1) the container is VEC or LIST and (2) - // edge prop is needed - template - class PairIterator { // always non const, since the build-in iter can handle - // const - friend class Graph; - - private: - using It = typename ContainerType::iterator; - using ConstIt = typename ContainerType::const_iterator; - It it; - using VT = typename It::value_type; - using FirstType = typename VT::first_type; - using SecondType = typename VT::second_type; - - public: // mimic the iter of a std::map - using difference_type = typename It::difference_type; - using value_type = std::pair; - using reference = std::pair; - using pointer = std::pair*; - using iterator_category = std::bidirectional_iterator_tag; - PairIterator() = default; - // can be implicitly converted FROM a non-const iter - PairIterator(const It& it) : it{it} {} - // can be implicitly converted TO a const iter - // relying on the imp conv of the underlying iter - operator ConstIt() { return it; } - - friend bool operator==(const PairIterator& lhs, const PairIterator& rhs) { - return lhs.it == rhs.it; - } - friend bool operator!=(const PairIterator& lhs, const PairIterator& rhs) { - return lhs.it != rhs.it; - } - friend bool operator==(const PairIterator& lhs, const ConstIt& rhs) { - return lhs.it == rhs; - } - friend bool operator!=(const PairIterator& lhs, const ConstIt& rhs) { - return lhs.it != rhs; - } - // symmetry - friend bool operator==(const ConstIt& lhs, const PairIterator& rhs) { - return rhs == lhs; - } - friend bool operator!=(const ConstIt& lhs, const PairIterator& rhs) { - return rhs != lhs; - } - - reference operator*() const { - return {std::cref(it->first), std::ref(it->second)}; - } - pointer operator->() const { - std::pair* ptr = it.operator->(); - using CVT = std::pair; - static_assert(offsetof(VT, first) == offsetof(CVT, first) && - offsetof(VT, second) == offsetof(CVT, second)); - return static_cast( - static_cast(ptr)); // adding constness to first - } - - PairIterator& operator++() { // prefix - ++it; - return *this; - } - PairIterator& operator--() { // prefix - --it; - return *this; - } - PairIterator operator++(int) & { // postfix - PairIterator tmp = *this; - ++(*this); - return tmp; - } - PairIterator operator--(int) & { // postfix - PairIterator tmp = *this; - --(*this); - return tmp; - } - }; - - template - struct EdgePropIterWrap { - friend class Graph; - - private: - using Iter = typename std::list::iterator; - // list iterators are NOT invalidated by insertion/removal(of others), - // making this possible - Iter pos; - - public: - EdgePropIterWrap() = default; - explicit EdgePropIterWrap(const Iter& pos) : pos{pos} {} - const EPT& prop() const { return *(this->pos); } - EPT& prop() { return *(this->pos); } - }; - - using NeighborType = - std::conditional_t, NodeType, - std::pair>>; - // type of neighbors container; the typename NT is here only because explicit - // spec is not allowed in a class... - template - struct ContainerGen {}; - template - struct ContainerGen { - using type = std::list; - }; - template - struct ContainerGen { - using type = std::vector; - }; - template - struct ContainerGen { - using type = std::map>; - }; - template - struct ContainerGen { - using type = std::set; - }; - template - struct ContainerGen { - using type = std::multimap>; - }; - template - struct ContainerGen { - using type = std::multiset; - }; - template - struct ContainerGen { - using type = std::unordered_map>; - }; - template - struct ContainerGen { - using type = std::unordered_set; - }; - template - struct ContainerGen { - using type = std::unordered_multimap>; - }; - template - struct ContainerGen { - using type = std::unordered_multiset; - }; - - public: - using NeighborsContainerType = - typename ContainerGen::type; - - private: - using NeighborsType = - std::conditional_t>; - struct PropNode { // node with property - NodePropType prop; - NeighborsType neighbors; - PropNode() = default; // needed for map/unordered map - template - explicit PropNode(NPT&&... prop) - : prop{std::forward(prop)...}, neighbors{} { - static_assert(std::is_constructible_v); - } - }; - static constexpr bool has_node_prop = not std::is_void_v; - using AdjListValueType = - std::conditional_t; - using AdjListType = - std::conditional_t, - std::unordered_map>; - - public: // iterator types - using NeighborsConstIterator = - typename NeighborsContainerType::const_iterator; - - private: - template - static constexpr bool can_construct_node = - std::is_constructible_v>; - static constexpr bool has_edge_prop = not std::is_void_v; - static constexpr bool need_pair_iter = - has_edge_prop and (neighbors_container_spec == Container::VEC or - neighbors_container_spec == Container::LIST); - - public: - using NeighborsIterator = std::conditional_t< - has_edge_prop, - std::conditional_t< - need_pair_iter, - PairIterator, // make node(aka first) - // immutable - typename NeighborsContainerType::iterator>, // the iter for - // map/multi-map works - // just fine - NeighborsConstIterator>; // if no edge prop is needed, always const - static_assert( - std::is_convertible_v); - using NeighborsView = std::pair; - using NeighborsConstView = - std::pair; - - private: - using AdjListIterType = typename AdjListType::iterator; - using AdjListConstIterType = typename AdjListType::const_iterator; - - private: - int num_of_edges{}; - AdjListType adj_list; - - private: // iterator support - template - class Iter { - public: - using difference_type = typename AdjListConstIterType::difference_type; - using value_type = const NodeType; - using reference = const NodeType&; - using pointer = const NodeType*; - using iterator_category = std::bidirectional_iterator_tag; - - private: - template - friend class Iter; - friend class Graph; - using AdjIterT = - std::conditional_t; - AdjIterT it; - - public: - Iter() = default; - Iter(AdjIterT it) : it{it} {}; - - // enables implicit conversion from non-const to const - template > - Iter(const Iter& other) : it{other.it} {} - - Iter& operator++() { // prefix - ++it; - return *this; - } - Iter operator++(int) & { // postfix - Iter tmp = *this; - ++(*this); - return tmp; - } - Iter& operator--() { // prefix - --it; - return *this; - } - Iter operator--(int) & { // postfix - Iter tmp = *this; - --(*this); - return tmp; - } - - const NodeType& operator*() const { return it->first; } - const NodeType* operator->() const { return &(it->first); } - friend bool operator==(const Iter& lhs, const Iter& rhs) { - return lhs.it == rhs.it; - } - friend bool operator!=(const Iter& lhs, const Iter& rhs) { - return lhs.it != rhs.it; - } - }; - - public: - using Iterator = Iter; - using ConstIterator = Iter; - static_assert(std::is_convertible_v); - - Iterator begin() noexcept { return Iter(adj_list.begin()); } - Iterator end() noexcept { return Iter(adj_list.end()); } - ConstIterator begin() const noexcept { return Iter(adj_list.cbegin()); } - ConstIterator end() const noexcept { return Iter(adj_list.cend()); } - // END OF iterator support - private: // neighbor access helpers - const NeighborsContainerType& get_out_neighbors( - AdjListConstIterType adj_iter) const { - if constexpr (not has_node_prop) { - if constexpr (direction == EdgeDirection::UNDIRECTED) { - return adj_iter->second; - } else { - return adj_iter->second.out; - } - } else { - if constexpr (direction == EdgeDirection::UNDIRECTED) { - return adj_iter->second.neighbors; - } else { - return adj_iter->second.neighbors.out; - } - } - } - NeighborsContainerType& get_out_neighbors(AdjListIterType adj_iter) { - return const_cast( - static_cast(this)->get_out_neighbors(adj_iter)); - } - - const NeighborsContainerType& get_in_neighbors( - AdjListConstIterType adj_iter) const { - if constexpr (not has_node_prop) { - if constexpr (direction == EdgeDirection::UNDIRECTED) { - return adj_iter->second; - } else { - return adj_iter->second.in; - } - } else { - if constexpr (direction == EdgeDirection::UNDIRECTED) { - return adj_iter->second.neighbors; - } else { - return adj_iter->second.neighbors.in; - } - } - } - NeighborsContainerType& get_in_neighbors(AdjListIterType adj_iter) { - return const_cast( - static_cast(this)->get_in_neighbors(adj_iter)); - } - - // helpers for EdgeDirectionBase - template - [[nodiscard]] int count_neighbors_helper(const T& node_iv) const { - AdjListConstIterType pos = find_by_iter_or_by_value(node_iv); - if (pos == adj_list.end()) { - std::string msg = is_out ? "out" : "in"; - if constexpr (logging == Logging::ALLOWED) { - print_by_iter_or_by_value(std::cerr - << "(count_neighbors) counting " << msg - << "-neighbors of a non-existent node", - node_iv) - << "\n"; - } - throw std::runtime_error("counting " + msg + - "-neighbors of a non-existent node"); - } - if constexpr (is_out) { - return get_out_neighbors(pos).size(); - } else { - return get_in_neighbors(pos).size(); - } - } - - template - NeighborsConstView get_neighbors_helper(const T& node_iv) const { - AdjListConstIterType pos = find_by_iter_or_by_value(node_iv); - if (pos == adj_list.end()) { - std::string msg = is_out ? "out" : "in"; - if constexpr (logging == Logging::ALLOWED) { - print_by_iter_or_by_value(std::cerr - << "(neighbors) finding " << msg - << "-neighbors of a non-existent node", - node_iv) - << "\n"; - } - throw std::runtime_error("finding " + msg + - "-neighbors of a non-existent node"); - } - const NeighborsContainerType& neighbors = [this, &pos]() -> auto& { - if constexpr (is_out) { - return get_out_neighbors(pos); - } else { - return get_in_neighbors(pos); - } - }(); - return {neighbors.begin(), neighbors.end()}; - } - template - NeighborsView get_neighbors_helper(const T& node_iv) { - AdjListIterType pos = find_by_iter_or_by_value(node_iv); - if (pos == adj_list.end()) { - std::string msg = is_out ? "out" : "in"; - if constexpr (logging == Logging::ALLOWED) { - print_by_iter_or_by_value(std::cerr - << "(neighbors) finding " << msg - << "-neighbors of a non-existent node", - node_iv) - << "\n"; - } - throw std::runtime_error("finding " + msg + - "-neighbors of a non-existent node"); - } - NeighborsContainerType& neighbors = [this, &pos]() -> auto& { - if constexpr (is_out) { - return get_out_neighbors(pos); - } else { - return get_in_neighbors(pos); - } - }(); - return {neighbors.begin(), neighbors.end()}; - } - // END OF helpers for EdgeDirectionBase - - const NodeType& get_neighbor_node( - const NeighborsConstIterator& nbr_pos) const { - if constexpr (has_edge_prop) { - return nbr_pos->first; - } else { - return *nbr_pos; - } - } - // END OF neighbor access helpers - private: // helpers for node search - // find a node by value - template - AdjListConstIterType find_node(const T& node_identifier) const { - static_assert(can_construct_node); - if constexpr (std::is_convertible_v) { // implicit conversion - return adj_list.find(node_identifier); - } else { // conversion has to be explicit - NodeType node{node_identifier}; - return adj_list.find(node); - } - } - template - AdjListIterType find_node(const T& node_identifier) { - return detail::const_iter_to_iter( - adj_list, static_cast(this)->find_node(node_identifier)); - } - - template - static constexpr bool is_iterator() { - return std::is_same_v> or - std::is_same_v>; - } - - template - decltype(auto) unwrap_by_iter_or_by_value(T&& iter_or_val) const { - if constexpr (is_iterator()) { - return iter_or_val.it->first; - } else { - static_assert(can_construct_node); - return std::forward(iter_or_val); // simply pass along - } - } - - // either unwrap the iterator, or find the node in adj_list - template - AdjListConstIterType find_by_iter_or_by_value(const T& iter_or_val) const { - if constexpr (is_iterator()) { // by iter - return iter_or_val.it; - } else { // by value - return find_node(iter_or_val); - } - } - template - AdjListIterType find_by_iter_or_by_value(const T& iter_or_val) { - return detail::const_iter_to_iter( - adj_list, - static_cast(this)->find_by_iter_or_by_value(iter_or_val)); - } - // END OF either unwrap the iterator, or find the node in adj_list - - // helper method to provide better error messages - template - std::ostream& print_by_iter_or_by_value(std::ostream& os, - const T& iter_or_val) const { - if constexpr (is_iterator()) { // by iter - return os; // no-op if by iter - } else { // by value - static_assert(can_construct_node); - os << ": " << NodeType{iter_or_val}; - return os; - } - } - - // find tgt in the neighborhood of src; returns a pair {is_found, - // neighbor_iterator} - template - std::pair find_neighbor_helper( - const U& src_iv, V&& tgt_identifier) const { - static_assert(can_construct_node); - AdjListConstIterType src_pos = find_by_iter_or_by_value(src_iv); - if (src_pos == adj_list.end()) { - if constexpr (logging == Logging::ALLOWED) { - print_by_iter_or_by_value( - std::cerr << "(find_neighbor) source node not found", src_iv) - << "\n"; - } - throw std::runtime_error{"source node is not found"}; - } - const NeighborsContainerType& src_neighbors = [this, &src_pos]() -> auto& { - if constexpr (is_out) { - return get_out_neighbors(src_pos); - } else { - return get_in_neighbors(src_pos); - } - }(); - if constexpr (std::is_same_v>) { - NeighborsConstIterator tgt_pos = - detail::container::find(src_neighbors, tgt_identifier); - return {tgt_pos != src_neighbors.end(), tgt_pos}; - } else { - NeighborsConstIterator tgt_pos = detail::container::find( - src_neighbors, NodeType{std::forward(tgt_identifier)}); - return {tgt_pos != src_neighbors.end(), tgt_pos}; - } - } - template - std::pair find_neighbor_helper(const U& src_iv, - V&& tgt_identifier) { - static_assert(can_construct_node); - AdjListIterType src_pos = find_by_iter_or_by_value(src_iv); - if (src_pos == adj_list.end()) { - if constexpr (logging == Logging::ALLOWED) { - print_by_iter_or_by_value( - std::cerr << "(find_neighbor) source node not found", src_iv) - << "\n"; - } - throw std::runtime_error{"source node is not found"}; - } - NeighborsContainerType& src_neighbors = [this, &src_pos]() -> auto& { - if constexpr (is_out) { - return get_out_neighbors(src_pos); - } else { - return get_in_neighbors(src_pos); - } - }(); - if constexpr (std::is_same_v>) { - auto tgt_pos = detail::container::find(src_neighbors, tgt_identifier); - return {tgt_pos != src_neighbors.end(), tgt_pos}; - } else { - auto tgt_pos = detail::container::find( - src_neighbors, NodeType{std::forward(tgt_identifier)}); - return {tgt_pos != src_neighbors.end(), tgt_pos}; - } - } - // END OF find tgt in the neighborhood of src; returns a pair {is_found, - // neighbor_iterator} END OF helpers for node search - public: // simple queries - [[nodiscard]] size_t size() const noexcept { return adj_list.size(); } - - [[nodiscard]] int num_edges() const noexcept { return num_of_edges; } - - template - bool has_node(const T& node_identifier) const noexcept { - auto pos = find_node(node_identifier); - return pos != adj_list.end(); - } - - // count the number of edges between src and tgt - template - int count_edges(const U& source_iv, const V& target_iv) const noexcept { - AdjListConstIterType src_pos = find_by_iter_or_by_value(source_iv); - AdjListConstIterType tgt_pos = find_by_iter_or_by_value(target_iv); - if (src_pos == adj_list.end() or tgt_pos == adj_list.end()) { - if constexpr (logging == Logging::ALLOWED) { - if (src_pos == adj_list.end()) { - print_by_iter_or_by_value( - std::cerr << "(count_edges) source node not found", source_iv) - << "\n"; - } - if (tgt_pos == adj_list.end()) { - print_by_iter_or_by_value( - std::cerr << "(count_edges) target node not found", target_iv) - << "\n"; - } - } - return 0; - } - return detail::container::count(get_out_neighbors(src_pos), tgt_pos->first); - } - - // find a node in the graph by value - template - ConstIterator find(const T& node_identifier) const noexcept { - AdjListConstIterType pos = find_node(node_identifier); - return ConstIterator{pos}; - } - template - Iterator find(const T& node_identifier) noexcept { - AdjListIterType pos = find_node(node_identifier); - return Iterator{pos}; - } - - private: // edge addition helpers - template - auto insert_edge_prop(EPT&&... prop) { - static_assert(has_edge_prop); - static_assert(std::is_constructible_v); - this->edge_prop_list.emplace_back(std::forward(prop)...); - return EdgePropIterWrap{ - std::prev(this->edge_prop_list.end())}; // iterator to the last element - } - - bool check_edge_dup(AdjListConstIterType src_pos, const NodeType& src_full, - const NodeType& tgt_full) const { - if constexpr (multi_edge == - MultiEdge::DISALLOWED) { // check is needed only when we - // disallow dup - // this catches multi-self-loop as well - const NeighborsContainerType& neighbors = get_out_neighbors(src_pos); - if (detail::container::find(neighbors, tgt_full) != neighbors.end()) { - if constexpr (logging == Logging::ALLOWED) { - std::cerr << "(add_edge) re-adding existing edge: (" << src_full - << ", " << tgt_full << ")\n"; - } - return true; - } - } - return false; - } - - bool check_self_loop(AdjListIterType src_pos, AdjListIterType tgt_pos, - const NodeType& src_full) { - if constexpr (self_loop == SelfLoop::DISALLOWED) { - if (src_pos == tgt_pos) { - if constexpr (logging == Logging::ALLOWED) { - std::cerr << "(add_edge) adding self loop on node: " << src_full - << "\n"; - } - return true; - } - } - return false; - } - // END OF edge addition helpers - private: // edge removal helpers - // because every edge has double entry, this method finds the correct double - // entry to remove - NeighborsConstIterator find_tgt_remove_pos( - AdjListIterType src_pos, NeighborsConstIterator src_remove_pos, - NeighborsContainerType& tgt_neighbors) { - if constexpr (has_edge_prop and multi_edge == MultiEdge::ALLOWED) { - auto prop_address = - &(src_remove_pos->second - .prop()); // finding the corresponding double entry - auto prop_finder = [&prop_address](const auto& tgt_nbr) { - return prop_address == &(tgt_nbr.second.prop()); - }; - if constexpr (neighbors_container_spec == Container::VEC or - neighbors_container_spec == Container::LIST) { - // NeighborsContainerType is a vector/list of pairs - // linearly search for the correct entry and remove - return std::find_if(tgt_neighbors.begin(), tgt_neighbors.end(), - prop_finder); - } else { - // NeighborsContainerType is a multi_map or unordered_multi_map - static_assert(neighbors_container_spec == Container::MULTISET or - neighbors_container_spec == - Container::UNORDERED_MULTISET); - auto [eq_begin, eq_end] = tgt_neighbors.equal_range( - src_pos->first); // slightly optimized search - return std::find_if(eq_begin, eq_end, prop_finder); - } - } else { // either multi edge is disallowed, or we don't differentiate - // multi-edges - static_assert(not has_edge_prop or multi_edge == MultiEdge::DISALLOWED); - return detail::container::find(tgt_neighbors, src_pos->first); - } - } - - // this method is useful for the "remove all" operation - std::pair find_remove_range( - NeighborsContainerType& neighbors, const NodeType& node) { - static_assert(has_edge_prop); - if constexpr (neighbors_container_spec == Container::VEC or - neighbors_container_spec == Container::LIST) { - NeighborsConstIterator partition_pos = std::partition( - neighbors.begin(), neighbors.end(), - [&node](const auto& src_nbr) { return !(src_nbr.first == node); }); - return {partition_pos, neighbors.end()}; - } else { - static_assert(neighbors_container_spec == Container::MULTISET or - neighbors_container_spec == Container::UNORDERED_MULTISET); - return neighbors.equal_range(node); - } - } - // END OF edge removal helpers - public: // edge removal - // all iterators are assumed to be valid - int remove_edge(ConstIterator source_pos, - NeighborsConstIterator target_nbr_pos) noexcept { - AdjListIterType src_pos = - detail::const_iter_to_iter(adj_list, source_pos.it); - NeighborsContainerType& src_neighbors = get_out_neighbors(src_pos); - assert(target_nbr_pos != src_neighbors.cend()); - AdjListIterType tgt_pos = adj_list.find(get_neighbor_node(target_nbr_pos)); - assert(tgt_pos != adj_list.end()); - if constexpr (self_loop == SelfLoop::DISALLOWED) { - assert(src_pos != tgt_pos); - } - NeighborsContainerType& tgt_neighbors = get_in_neighbors(tgt_pos); - NeighborsConstIterator tgt_remove_pos = - find_tgt_remove_pos(src_pos, target_nbr_pos, tgt_neighbors); - assert(tgt_remove_pos != tgt_neighbors.end()); - if constexpr (has_edge_prop) { // need to remove edge prop, as well - this->edge_prop_list.erase(target_nbr_pos->second.pos); - } - detail::container::erase_one(src_neighbors, target_nbr_pos); - if (src_pos != tgt_pos or direction == EdgeDirection::DIRECTED) { - // when src==tgt && UNDIRECTED, there is NO double entry - detail::container::erase_one(tgt_neighbors, tgt_remove_pos); - } - --num_of_edges; - return 1; - } - - // remove all edges between source and target - template - std::enable_if_t, int> - remove_edge(const U& source_iv, const V& target_iv) noexcept { - auto src_pos = find_by_iter_or_by_value(source_iv); - auto tgt_pos = find_by_iter_or_by_value(target_iv); - if (src_pos == adj_list.end() or tgt_pos == adj_list.end()) { - if constexpr (logging == Logging::ALLOWED) { - if (src_pos == adj_list.end()) { - print_by_iter_or_by_value( - std::cerr << "(remove_edge) edge involves non-existent node", - source_iv) - << "\n"; - } - if (tgt_pos == adj_list.end()) { - print_by_iter_or_by_value( - std::cerr << "(remove_edge) edge involves non-existent node", - target_iv) - << "\n"; - } - } - return 0; // no-op if nodes are not found - } - const NodeType& src_full = src_pos->first; - const NodeType& tgt_full = tgt_pos->first; - if constexpr (self_loop == SelfLoop::DISALLOWED) { - if (src_pos == tgt_pos) { // we know self loop cannot exist - if constexpr (logging == Logging::ALLOWED) { - std::cerr << "(remove_edge) cannot remove self loop on node " - << src_full << " when self loop is not even permitted\n"; - } - return 0; - } - } - NeighborsContainerType& src_neighbors = get_out_neighbors(src_pos); - if constexpr (multi_edge == MultiEdge::DISALLOWED) { // remove at most 1 - NeighborsIterator src_remove_pos = - detail::container::find(src_neighbors, tgt_full); - if (src_remove_pos == src_neighbors.cend()) { - if constexpr (logging == Logging::ALLOWED) { - std::cerr << "(remove_edge) edge (" << src_full << ", " << tgt_full - << ") not found\n"; - } - return 0; - } - remove_edge(ConstIterator{src_pos}, src_remove_pos); - --num_of_edges; - return 1; - } else { // remove all edges between src and tgt, potentially removing no - // edge at all - static_assert(multi_edge == MultiEdge::ALLOWED); - static_assert(neighbors_container_spec != Container::SET and - neighbors_container_spec != Container::UNORDERED_SET); - int num_edges_removed = 0; - if constexpr (has_edge_prop) { // remove prop too - const auto [src_remove_begin, src_remove_end] = - find_remove_range(src_neighbors, tgt_full); - // loop through this range to remove prop - for (auto it = src_remove_begin; it != src_remove_end; ++it) { - ++num_edges_removed; - this->edge_prop_list.erase(it->second.pos); - } - // erase this range itself - src_neighbors.erase(src_remove_begin, src_remove_end); - } else { // simply erase all - num_edges_removed = - detail::container::erase_all(src_neighbors, tgt_full); - } - if (src_pos != tgt_pos or direction == EdgeDirection::DIRECTED) { - int num_tgt_removed = - detail::container::erase_all(get_in_neighbors(tgt_pos), src_full); - assert(num_edges_removed == num_tgt_removed); - } - if constexpr (logging == Logging::ALLOWED) { - if (num_edges_removed == 0) { - std::cerr << "(remove_edge) edge (" << src_full << ", " << tgt_full - << ") not found\n"; - } - } - num_of_edges -= num_edges_removed; - return num_edges_removed; - } - } - - private: // node removal helper - template - void purge_edge_with(AdjListIterType pos) noexcept { - const NodeType& node = pos->first; - NeighborsContainerType& neighbors = [this, &pos]() -> auto& { - if constexpr (OutIn) { - return get_out_neighbors(pos); - } else { - return get_in_neighbors(pos); - } - }(); - NeighborsIterator nbr_begin = neighbors.begin(); - NeighborsIterator nbr_end = neighbors.end(); - // this loop should be enough for undirected graphs - for (auto it = nbr_begin; it != nbr_end; ++it) { - // purge edges that has to do with the to-be-removed node - AdjListIterType neighbor_pos = adj_list.find(get_neighbor_node(it)); - NeighborsContainerType& neighbors_of_neighbor = - [this, &neighbor_pos]() -> auto& { - if constexpr (OutIn) { - return get_in_neighbors(neighbor_pos); - } else { - return get_out_neighbors(neighbor_pos); - } - }(); - if constexpr (self_loop == SelfLoop::DISALLOWED) { - detail::container::erase_all(neighbors_of_neighbor, node); - } else { - if (neighbor_pos != pos) { // need to check for self loop - detail::container::erase_all(neighbors_of_neighbor, node); - } - } - // remove edge property if needed - if constexpr (has_edge_prop) { - this->edge_prop_list.erase(it->second.pos); - } - } - } - - public: // node removal - // we can allow removal of several nodes by iterator because erase does not - // invalidate other iterators - template - int remove_nodes(const T& node_iv) noexcept { - auto pos = find_by_iter_or_by_value(node_iv); - if (pos == adj_list.end()) { // no-op if not found - if constexpr (logging == Logging::ALLOWED) { - print_by_iter_or_by_value( - std::cerr << "(remove_nodes) removing non-existent node", node_iv) - << "\n"; - } - return 0; - } - purge_edge_with(pos); // purge all edges going out of node - if constexpr (direction == EdgeDirection::DIRECTED) { - // this loop makes it work for directed graphs as well - purge_edge_with(pos); // purge all edges coming into node - } - // count purged edges - auto& out_nbrs = get_out_neighbors(pos); - int num_edges_purged = out_nbrs.size(); - if constexpr (direction == EdgeDirection::DIRECTED) { - num_edges_purged += get_in_neighbors(pos).size(); - if constexpr (self_loop == SelfLoop::ALLOWED) { // we would be double - // counting self-edges - num_edges_purged -= detail::container::count(out_nbrs, pos->first); - } - } - num_of_edges -= num_edges_purged; - // finally erase pos - adj_list.erase(pos); - return 1; - } - template - int remove_nodes(const T& node_iv, const Args&... args) noexcept { - return remove_nodes(node_iv) + remove_nodes(args...); - } - // END OF node removal -}; -} // namespace graph_lite - -#endif // GSK_GRAPH_LITE_H diff --git a/src/third_party/graphlite/src/connected_components.cpp b/src/third_party/graphlite/src/connected_components.cpp deleted file mode 100644 index b5815eb1d..000000000 --- a/src/third_party/graphlite/src/connected_components.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2022 The Manifold Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "graph.h" - -namespace manifold { - -int ConnectedComponents(std::vector& components, const Graph& graph) { - if (!graph.size()) { - return 0; - } - components.resize(graph.size()); - std::fill(components.begin(), components.end(), -1); - - std::deque queue; - int numComponent = 0; - for (auto it = graph.begin(); it != graph.end(); ++it) { - const int& root = *it; - if (components[root] >= 0) continue; // skip visited nodes - - // new component - components[root] = numComponent; - queue.emplace_back(root); - // traverse all connected nodes - while (!queue.empty()) { - const auto [n_begin, n_end] = graph.neighbors(queue.front()); - queue.pop_front(); - for (auto n_it = n_begin; n_it != n_end; ++n_it) { - const int& neighbor = *n_it; - if (components[neighbor] < 0) { // unvisited - components[neighbor] = numComponent; - queue.emplace_back(neighbor); - } - } - } - ++numComponent; - } - return numComponent; -} -} // namespace manifold \ No newline at end of file diff --git a/src/utilities/include/utils.h b/src/utilities/include/utils.h index 738580199..498edfbb1 100644 --- a/src/utilities/include/utils.h +++ b/src/utilities/include/utils.h @@ -16,6 +16,7 @@ #pragma once #include #include +#include #ifdef MANIFOLD_DEBUG #include @@ -23,6 +24,7 @@ #endif #include "par.h" +#include "vec.h" namespace manifold { @@ -213,5 +215,62 @@ class ConcurrentSharedPtr { std::shared_ptr mutex = std::make_shared(); }; + +template +struct UnionFind { + Vec parents; + // we do union by rank + // note that we shift rank by 1, rank 0 means it is not connected to anything + // else + Vec ranks; + + UnionFind(I numNodes) : parents(numNodes), ranks(numNodes, 0) { + sequence(autoPolicy(numNodes), parents.begin(), parents.end()); + } + + I find(I x) { + while (parents[x] != x) { + parents[x] = parents[parents[x]]; + x = parents[x]; + } + return x; + } + + void unionXY(I x, I y) { + if (x == y) return; + if (ranks[x] == 0) ranks[x] = 1; + if (ranks[y] == 0) ranks[y] = 1; + x = find(x); + y = find(y); + if (x == y) return; + if (ranks[x] < ranks[y]) std::swap(x, y); + if (ranks[x] == ranks[y]) ranks[x]++; + parents[y] = x; + } + + I connectedComponents(std::vector& components) { + components.resize(parents.size()); + I lonelyNodes = 0; + std::unordered_map toLabel; + for (size_t i = 0; i < parents.size(); ++i) { + // we optimize for connected component of size 1 + // no need to put them into the hashmap + if (ranks[i] == 0) { + components[i] = static_cast(toLabel.size()) + lonelyNodes++; + continue; + } + parents[i] = find(i); + auto iter = toLabel.find(parents[i]); + if (iter == toLabel.end()) { + I s = static_cast(toLabel.size()) + lonelyNodes; + toLabel.insert(std::make_pair(parents[i], s)); + components[i] = s; + } else { + components[i] = iter->second; + } + } + return toLabel.size() + lonelyNodes; + } +}; /** @} */ } // namespace manifold