From bf5232f82f1786c13b382bec0d7926557098e3ad Mon Sep 17 00:00:00 2001 From: tonibohnlein Date: Wed, 10 Dec 2025 17:17:22 +0100 Subject: [PATCH 1/5] edge_view --- .../directed_graph_edge_view.hpp | 228 ++++++++++-------- 1 file changed, 127 insertions(+), 101 deletions(-) diff --git a/include/osp/graph_algorithms/directed_graph_edge_view.hpp b/include/osp/graph_algorithms/directed_graph_edge_view.hpp index ad63ab90..8a77bbef 100644 --- a/include/osp/graph_algorithms/directed_graph_edge_view.hpp +++ b/include/osp/graph_algorithms/directed_graph_edge_view.hpp @@ -22,137 +22,149 @@ limitations under the License. namespace osp { +/** + * @brief A view over all edges in a directed graph. + * + * This class provides an iterator-based view to iterate over all edges in a directed graph. + * The iteration order is lexicographical with respect to (source, target) pairs, determined by + * the order of vertices and their adjacency lists. + * + * @tparam Graph_t The type of the graph, which must satisfy the `is_directed_graph_v` concept. + */ template class edge_view { private: static_assert(is_directed_graph_v, "Graph_t must satisfy the directed_graph concept"); - const Graph_t &graph; + const Graph_t &graph_; template - class directed_edge_iterator { + class DirectedEdgeIterator { public: - using iterator_category = std::bidirectional_iterator_tag; + using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = directed_edge; using pointer = value_type *; using reference = value_type &; - private: - const Graph_t *graph; // Pointer to the graph - vertex_idx_t current_vertex; // Current source vertex - child_iterator_t current_child; // Iterator to the current target vertex in current_vertex's adjacency list - vertex_idx_t current_edge_idx; // Global index of the current edge in the traversal order + struct arrow_proxy { + value_type value; + const value_type *operator->() const noexcept { return &value; } + }; - public: - directed_edge_iterator() : graph(nullptr), current_vertex(0), current_edge_idx(0) {} - directed_edge_iterator(const directed_edge_iterator &other) - : graph(other.graph), current_vertex(other.current_vertex), current_child(other.current_child), - current_edge_idx(other.current_edge_idx) {} - - directed_edge_iterator operator=(const directed_edge_iterator &other) { - graph = other.graph; - current_vertex = other.current_vertex; - current_child = other.current_child; - current_edge_idx = other.current_edge_idx; - return *this; - } - - directed_edge_iterator(const Graph_t &graph_) : graph(&graph_), current_vertex(0), current_edge_idx(0) { - - while (current_vertex != graph->num_vertices()) { - if (graph->children(current_vertex).begin() != graph->children(current_vertex).end()) { - current_child = graph->children(current_vertex).begin(); + private: + const Graph_t *graph_; // Pointer to the graph + vertex_idx_t currentVertex_; // Current source vertex + child_iterator_t currentChild_; // Iterator to the current target vertex in current_vertex's adjacency list + vertex_idx_t currentEdgeIdx_; // Global index of the current edge in the traversal order + + void advanceToValid() { + while (currentVertex_ != graph_->num_vertices()) { + if (graph_->children(currentVertex_).begin() != graph_->children(currentVertex_).end()) { + currentChild_ = graph_->children(currentVertex_).begin(); break; } - current_vertex++; + currentVertex_++; } } - directed_edge_iterator(const vertex_idx_t edge_idx, const Graph_t &graph_) - : graph(&graph_), current_vertex(0), current_edge_idx(edge_idx) { - - if (current_edge_idx < graph->num_edges()) { - - vertex_idx_t tmp = 0u; + public: + DirectedEdgeIterator() noexcept : graph_(nullptr), currentVertex_(0), currentEdgeIdx_(0) {} - if (tmp < current_edge_idx) { + DirectedEdgeIterator(const DirectedEdgeIterator &other) = default; + DirectedEdgeIterator(DirectedEdgeIterator &&other) noexcept = default; - while (current_vertex != graph->num_vertices()) { + DirectedEdgeIterator &operator=(const DirectedEdgeIterator &other) = default; + DirectedEdgeIterator &operator=(DirectedEdgeIterator &&other) noexcept = default; - current_child = graph->children(current_vertex).begin(); + explicit DirectedEdgeIterator(const Graph_t &graph) : graph_(&graph), currentVertex_(0), currentEdgeIdx_(0) { + advanceToValid(); + } - while (current_child != graph->children(current_vertex).end()) { + DirectedEdgeIterator(const vertex_idx_t edge_idx, const Graph_t &graph) + : graph_(&graph), currentVertex_(0), currentEdgeIdx_(edge_idx) { + if (currentEdgeIdx_ < graph_->num_edges()) { + vertex_idx_t tmp = 0u; + advanceToValid(); - if (tmp == current_edge_idx) { - break; + while (currentVertex_ != graph_->num_vertices() && tmp < currentEdgeIdx_) { + while (currentChild_ != graph_->children(currentVertex_).end()) { + if (tmp == currentEdgeIdx_) { + return; + } + currentChild_++; + tmp++; + } + // Move to next vertex + currentVertex_++; + if (currentVertex_ != graph_->num_vertices()) { + currentChild_ = graph_->children(currentVertex_).begin(); + // Skip empty adjacency lists + while (currentVertex_ != graph_->num_vertices() && + graph_->children(currentVertex_).begin() == graph_->children(currentVertex_).end()) { + currentVertex_++; + if (currentVertex_ != graph_->num_vertices()) { + currentChild_ = graph_->children(currentVertex_).begin(); } - - current_child++; - tmp++; } - - current_vertex++; } } - } else { - current_edge_idx = graph->num_edges(); - current_vertex = graph->num_vertices(); + currentEdgeIdx_ = graph_->num_edges(); + currentVertex_ = graph_->num_vertices(); } } - inline value_type operator*() const { return {current_vertex, *current_child}; } - - inline directed_edge_iterator &operator++() { + [[nodiscard]] value_type operator*() const { return {currentVertex_, *currentChild_}; } + [[nodiscard]] arrow_proxy operator->() const { return {operator*()}; } - current_child++; - current_edge_idx++; + DirectedEdgeIterator &operator++() { + currentChild_++; + currentEdgeIdx_++; - if (current_child == graph->children(current_vertex).end()) { - - current_vertex++; - - while (current_vertex != graph->num_vertices()) { - - if (graph->children(current_vertex).begin() != graph->children(current_vertex).end()) { - current_child = graph->children(current_vertex).begin(); + if (currentChild_ == graph_->children(currentVertex_).end()) { + currentVertex_++; + // Skip empty vertices + while (currentVertex_ != graph_->num_vertices()) { + if (graph_->children(currentVertex_).begin() != graph_->children(currentVertex_).end()) { + currentChild_ = graph_->children(currentVertex_).begin(); break; } - - current_vertex++; + currentVertex_++; } } - return *this; } - inline directed_edge_iterator operator++(int) { - directed_edge_iterator temp = *this; + [[nodiscard]] DirectedEdgeIterator operator++(int) { + DirectedEdgeIterator temp = *this; ++(*this); return temp; } - inline bool operator==(const directed_edge_iterator &other) const { - return current_edge_idx == other.current_edge_idx; + [[nodiscard]] bool operator==(const DirectedEdgeIterator &other) const noexcept { + return currentEdgeIdx_ == other.currentEdgeIdx_; } - inline bool operator!=(const directed_edge_iterator &other) const { return !(*this == other); } + [[nodiscard]] bool operator!=(const DirectedEdgeIterator &other) const noexcept { return !(*this == other); } }; public: - using dir_edge_iterator = directed_edge_iterator< - decltype(std::declval().children(std::declval>()).begin())>; + using DirEdgeIterator = DirectedEdgeIterator().children(std::declval>()).begin())>; + using iterator = DirEdgeIterator; + using constIterator = DirEdgeIterator; + + explicit edge_view(const Graph_t &graph) : graph_(graph) {} - edge_view(const Graph_t &graph_) : graph(graph_) {} + [[nodiscard]] auto begin() const { return DirEdgeIterator(graph_); } + [[nodiscard]] auto cbegin() const { return DirEdgeIterator(graph_); } - inline auto begin() const { return dir_edge_iterator(graph); } - inline auto cbegin() const { return dir_edge_iterator(graph); } + [[nodiscard]] auto end() const { return DirEdgeIterator(graph_.num_edges(), graph_); } + [[nodiscard]] auto cend() const { return DirEdgeIterator(graph_.num_edges(), graph_); } - inline auto end() const { return dir_edge_iterator(graph.num_edges(), graph); } - inline auto cend() const { return dir_edge_iterator(graph.num_edges(), graph); } + [[nodiscard]] auto size() const { return graph_.num_edges(); } - inline auto size() const { return graph.num_edges(); } + [[nodiscard]] bool empty() const { return graph_.num_edges() == 0; } }; /** @@ -180,6 +192,11 @@ class out_edge_view { using pointer = value_type *; using reference = value_type &; + struct arrow_proxy { + value_type value; + const value_type *operator->() const noexcept { return &value; } + }; + private: vertex_idx_t source_vertex; child_iterator_t current_child_it; @@ -188,35 +205,36 @@ class out_edge_view { out_edge_iterator() = default; out_edge_iterator(vertex_idx_t u, child_iterator_t it) : source_vertex(u), current_child_it(it) {} - inline value_type operator*() const { return {source_vertex, *current_child_it}; } + [[nodiscard]] value_type operator*() const { return {source_vertex, *current_child_it}; } + [[nodiscard]] arrow_proxy operator->() const { return {operator*()}; } - inline out_edge_iterator &operator++() { + out_edge_iterator &operator++() { ++current_child_it; return *this; } - inline out_edge_iterator operator++(int) { + out_edge_iterator operator++(int) { out_edge_iterator temp = *this; ++(*this); return temp; } - inline out_edge_iterator &operator--() { + out_edge_iterator &operator--() { --current_child_it; return *this; } - inline out_edge_iterator operator--(int) { + out_edge_iterator operator--(int) { out_edge_iterator temp = *this; --(*this); return temp; } - inline bool operator==(const out_edge_iterator &other) const { + [[nodiscard]] bool operator==(const out_edge_iterator &other) const noexcept { return current_child_it == other.current_child_it; } - inline bool operator!=(const out_edge_iterator &other) const { return !(*this == other); } + [[nodiscard]] bool operator!=(const out_edge_iterator &other) const noexcept { return !(*this == other); } }; public: @@ -226,13 +244,14 @@ class out_edge_view { out_edge_view(const Graph_t &graph_, vertex_idx_t u) : graph(graph_), source_vertex(u) {} - inline auto begin() const { return iterator(source_vertex, graph.children(source_vertex).begin()); } - inline auto cbegin() const { return begin(); } + [[nodiscard]] auto begin() const { return iterator(source_vertex, graph.children(source_vertex).begin()); } + [[nodiscard]] auto cbegin() const { return begin(); } - inline auto end() const { return iterator(source_vertex, graph.children(source_vertex).end()); } - inline auto cend() const { return end(); } + [[nodiscard]] auto end() const { return iterator(source_vertex, graph.children(source_vertex).end()); } + [[nodiscard]] auto cend() const { return end(); } - inline auto size() const { return graph.out_degree(source_vertex); } + [[nodiscard]] auto size() const { return graph.out_degree(source_vertex); } + [[nodiscard]] bool empty() const { return graph.out_degree(source_vertex) == 0; } }; /** @@ -260,6 +279,11 @@ class in_edge_view { using pointer = value_type *; using reference = value_type &; + struct arrow_proxy { + value_type value; + const value_type *operator->() const noexcept { return &value; } + }; + private: vertex_idx_t target_vertex; parent_iterator_t current_parent_it; @@ -268,35 +292,36 @@ class in_edge_view { in_edge_iterator() = default; in_edge_iterator(vertex_idx_t v, parent_iterator_t it) : target_vertex(v), current_parent_it(it) {} - inline value_type operator*() const { return {*current_parent_it, target_vertex}; } + [[nodiscard]] value_type operator*() const { return {*current_parent_it, target_vertex}; } + [[nodiscard]] arrow_proxy operator->() const { return {operator*()}; } - inline in_edge_iterator &operator++() { + in_edge_iterator &operator++() { ++current_parent_it; return *this; } - inline in_edge_iterator operator++(int) { + in_edge_iterator operator++(int) { in_edge_iterator temp = *this; ++(*this); return temp; } - inline in_edge_iterator &operator--() { + in_edge_iterator &operator--() { --current_parent_it; return *this; } - inline in_edge_iterator operator--(int) { + in_edge_iterator operator--(int) { in_edge_iterator temp = *this; --(*this); return temp; } - inline bool operator==(const in_edge_iterator &other) const { + [[nodiscard]] bool operator==(const in_edge_iterator &other) const noexcept { return current_parent_it == other.current_parent_it; } - inline bool operator!=(const in_edge_iterator &other) const { return !(*this == other); } + [[nodiscard]] bool operator!=(const in_edge_iterator &other) const noexcept { return !(*this == other); } }; public: @@ -306,13 +331,14 @@ class in_edge_view { in_edge_view(const Graph_t &graph_, vertex_idx_t v) : graph(graph_), target_vertex(v) {} - inline auto begin() const { return iterator(target_vertex, graph.parents(target_vertex).begin()); } - inline auto cbegin() const { return begin(); } + [[nodiscard]] auto begin() const { return iterator(target_vertex, graph.parents(target_vertex).begin()); } + [[nodiscard]] auto cbegin() const { return begin(); } - inline auto end() const { return iterator(target_vertex, graph.parents(target_vertex).end()); } - inline auto cend() const { return end(); } + [[nodiscard]] auto end() const { return iterator(target_vertex, graph.parents(target_vertex).end()); } + [[nodiscard]] auto cend() const { return end(); } - inline auto size() const { return graph.in_degree(target_vertex); } + [[nodiscard]] auto size() const { return graph.in_degree(target_vertex); } + [[nodiscard]] bool empty() const { return graph.in_degree(target_vertex) == 0; } }; } // namespace osp \ No newline at end of file From d7cba6f99858a792e7101f7dff4febada9651542 Mon Sep 17 00:00:00 2001 From: tonibohnlein Date: Wed, 10 Dec 2025 17:21:17 +0100 Subject: [PATCH 2/5] out_edge --- .../directed_graph_edge_view.hpp | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/include/osp/graph_algorithms/directed_graph_edge_view.hpp b/include/osp/graph_algorithms/directed_graph_edge_view.hpp index 8a77bbef..85c2179f 100644 --- a/include/osp/graph_algorithms/directed_graph_edge_view.hpp +++ b/include/osp/graph_algorithms/directed_graph_edge_view.hpp @@ -136,7 +136,7 @@ class edge_view { return *this; } - [[nodiscard]] DirectedEdgeIterator operator++(int) { + DirectedEdgeIterator operator++(int) { DirectedEdgeIterator temp = *this; ++(*this); return temp; @@ -180,11 +180,11 @@ class out_edge_view { private: static_assert(is_directed_graph_v, "Graph_t must satisfy the directed_graph concept"); - const Graph_t &graph; - vertex_idx_t source_vertex; + const Graph_t &graph_; + vertex_idx_t sourceVertex_; template - class out_edge_iterator { + class OutEdgeIterator { public: using iterator_category = typename std::iterator_traits::iterator_category; using difference_type = std::ptrdiff_t; @@ -198,60 +198,59 @@ class out_edge_view { }; private: - vertex_idx_t source_vertex; - child_iterator_t current_child_it; + vertex_idx_t sourceVertex_; + child_iterator_t currentChildIt_; public: - out_edge_iterator() = default; - out_edge_iterator(vertex_idx_t u, child_iterator_t it) : source_vertex(u), current_child_it(it) {} + OutEdgeIterator() = default; + OutEdgeIterator(vertex_idx_t u, child_iterator_t it) : sourceVertex_(u), currentChildIt_(it) {} - [[nodiscard]] value_type operator*() const { return {source_vertex, *current_child_it}; } + [[nodiscard]] value_type operator*() const { return {sourceVertex_, *currentChildIt_}; } [[nodiscard]] arrow_proxy operator->() const { return {operator*()}; } - out_edge_iterator &operator++() { - ++current_child_it; + OutEdgeIterator &operator++() { + ++currentChildIt_; return *this; } - out_edge_iterator operator++(int) { - out_edge_iterator temp = *this; + OutEdgeIterator operator++(int) { + OutEdgeIterator temp = *this; ++(*this); return temp; } - out_edge_iterator &operator--() { - --current_child_it; + OutEdgeIterator &operator--() { + --currentChildIt_; return *this; } - out_edge_iterator operator--(int) { - out_edge_iterator temp = *this; + OutEdgeIterator operator--(int) { + OutEdgeIterator temp = *this; --(*this); return temp; } - [[nodiscard]] bool operator==(const out_edge_iterator &other) const noexcept { - return current_child_it == other.current_child_it; + [[nodiscard]] bool operator==(const OutEdgeIterator &other) const noexcept { + return currentChildIt_ == other.currentChildIt_; } - [[nodiscard]] bool operator!=(const out_edge_iterator &other) const noexcept { return !(*this == other); } + [[nodiscard]] bool operator!=(const OutEdgeIterator &other) const noexcept { return !(*this == other); } }; public: - using iterator = - out_edge_iterator().children(std::declval>()).begin())>; - using const_iterator = iterator; + using iterator = OutEdgeIterator().children(std::declval>()).begin())>; + using constIterator = iterator; - out_edge_view(const Graph_t &graph_, vertex_idx_t u) : graph(graph_), source_vertex(u) {} + out_edge_view(const Graph_t &graph, vertex_idx_t u) : graph_(graph), sourceVertex_(u) {} - [[nodiscard]] auto begin() const { return iterator(source_vertex, graph.children(source_vertex).begin()); } + [[nodiscard]] auto begin() const { return iterator(sourceVertex_, graph_.children(sourceVertex_).begin()); } [[nodiscard]] auto cbegin() const { return begin(); } - [[nodiscard]] auto end() const { return iterator(source_vertex, graph.children(source_vertex).end()); } + [[nodiscard]] auto end() const { return iterator(sourceVertex_, graph_.children(sourceVertex_).end()); } [[nodiscard]] auto cend() const { return end(); } - [[nodiscard]] auto size() const { return graph.out_degree(source_vertex); } - [[nodiscard]] bool empty() const { return graph.out_degree(source_vertex) == 0; } + [[nodiscard]] auto size() const { return graph_.out_degree(sourceVertex_); } + [[nodiscard]] bool empty() const { return graph_.out_degree(sourceVertex_) == 0; } }; /** From d74cdc5d398be394987235d81927ed35b6dddaee Mon Sep 17 00:00:00 2001 From: tonibohnlein Date: Wed, 10 Dec 2025 17:57:57 +0100 Subject: [PATCH 3/5] in_edge --- .../directed_graph_edge_view.hpp | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/include/osp/graph_algorithms/directed_graph_edge_view.hpp b/include/osp/graph_algorithms/directed_graph_edge_view.hpp index 85c2179f..252d934c 100644 --- a/include/osp/graph_algorithms/directed_graph_edge_view.hpp +++ b/include/osp/graph_algorithms/directed_graph_edge_view.hpp @@ -266,11 +266,11 @@ class in_edge_view { private: static_assert(is_directed_graph_v, "Graph_t must satisfy the directed_graph concept"); - const Graph_t &graph; - vertex_idx_t target_vertex; + const Graph_t &graph_; + vertex_idx_t targetVertex_; template - class in_edge_iterator { + class InEdgeIterator { public: using iterator_category = typename std::iterator_traits::iterator_category; using difference_type = std::ptrdiff_t; @@ -284,60 +284,59 @@ class in_edge_view { }; private: - vertex_idx_t target_vertex; - parent_iterator_t current_parent_it; + vertex_idx_t targetVertex_; + parent_iterator_t currentParentIt_; public: - in_edge_iterator() = default; - in_edge_iterator(vertex_idx_t v, parent_iterator_t it) : target_vertex(v), current_parent_it(it) {} + InEdgeIterator() = default; + InEdgeIterator(vertex_idx_t v, parent_iterator_t it) : targetVertex_(v), currentParentIt_(it) {} - [[nodiscard]] value_type operator*() const { return {*current_parent_it, target_vertex}; } + [[nodiscard]] value_type operator*() const { return {*currentParentIt_, targetVertex_}; } [[nodiscard]] arrow_proxy operator->() const { return {operator*()}; } - in_edge_iterator &operator++() { - ++current_parent_it; + InEdgeIterator &operator++() { + ++currentParentIt_; return *this; } - in_edge_iterator operator++(int) { - in_edge_iterator temp = *this; + InEdgeIterator operator++(int) { + InEdgeIterator temp = *this; ++(*this); return temp; } - in_edge_iterator &operator--() { - --current_parent_it; + InEdgeIterator &operator--() { + --currentParentIt_; return *this; } - in_edge_iterator operator--(int) { - in_edge_iterator temp = *this; + InEdgeIterator operator--(int) { + InEdgeIterator temp = *this; --(*this); return temp; } - [[nodiscard]] bool operator==(const in_edge_iterator &other) const noexcept { - return current_parent_it == other.current_parent_it; + [[nodiscard]] bool operator==(const InEdgeIterator &other) const noexcept { + return currentParentIt_ == other.currentParentIt_; } - [[nodiscard]] bool operator!=(const in_edge_iterator &other) const noexcept { return !(*this == other); } + [[nodiscard]] bool operator!=(const InEdgeIterator &other) const noexcept { return !(*this == other); } }; public: - using iterator = - in_edge_iterator().parents(std::declval>()).begin())>; - using const_iterator = iterator; + using iterator = InEdgeIterator().parents(std::declval>()).begin())>; + using constIterator = iterator; - in_edge_view(const Graph_t &graph_, vertex_idx_t v) : graph(graph_), target_vertex(v) {} + in_edge_view(const Graph_t &graph, vertex_idx_t v) : graph_(graph), targetVertex_(v) {} - [[nodiscard]] auto begin() const { return iterator(target_vertex, graph.parents(target_vertex).begin()); } + [[nodiscard]] auto begin() const { return iterator(targetVertex_, graph_.parents(targetVertex_).begin()); } [[nodiscard]] auto cbegin() const { return begin(); } - [[nodiscard]] auto end() const { return iterator(target_vertex, graph.parents(target_vertex).end()); } + [[nodiscard]] auto end() const { return iterator(targetVertex_, graph_.parents(targetVertex_).end()); } [[nodiscard]] auto cend() const { return end(); } - [[nodiscard]] auto size() const { return graph.in_degree(target_vertex); } - [[nodiscard]] bool empty() const { return graph.in_degree(target_vertex) == 0; } + [[nodiscard]] auto size() const { return graph_.in_degree(targetVertex_); } + [[nodiscard]] bool empty() const { return graph_.in_degree(targetVertex_) == 0; } }; } // namespace osp \ No newline at end of file From ed95050650001f60ca18d3d7330b83e18d8b30d7 Mon Sep 17 00:00:00 2001 From: tonibohnlein Date: Thu, 11 Dec 2025 10:02:32 +0100 Subject: [PATCH 4/5] merged in/ou_view --- .../directed_graph_edge_desc_concept.hpp | 8 +- .../directed_graph_edge_view.hpp | 182 +++++++----------- 2 files changed, 76 insertions(+), 114 deletions(-) diff --git a/include/osp/concepts/directed_graph_edge_desc_concept.hpp b/include/osp/concepts/directed_graph_edge_desc_concept.hpp index eb6672ef..dadd827b 100644 --- a/include/osp/concepts/directed_graph_edge_desc_concept.hpp +++ b/include/osp/concepts/directed_graph_edge_desc_concept.hpp @@ -83,8 +83,8 @@ inline edge_view edges(const Graph_t &graph) { * @return An `out_edge_view` allowing iteration over outgoing edges from `u`. */ template -inline out_edge_view out_edges(vertex_idx_t u, const Graph_t &graph) { - return out_edge_view(graph, u); +inline OutEdgeView out_edges(vertex_idx_t u, const Graph_t &graph) { + return OutEdgeView(graph, u); } /** @@ -96,8 +96,8 @@ inline out_edge_view out_edges(vertex_idx_t u, const Graph_t & * @return An `in_edge_view` allowing iteration over incoming edges to `v`. */ template -inline in_edge_view in_edges(vertex_idx_t v, const Graph_t &graph) { - return in_edge_view(graph, v); +inline InEdgeView in_edges(vertex_idx_t v, const Graph_t &graph) { + return InEdgeView(graph, v); } /** diff --git a/include/osp/graph_algorithms/directed_graph_edge_view.hpp b/include/osp/graph_algorithms/directed_graph_edge_view.hpp index 252d934c..a4f5e23c 100644 --- a/include/osp/graph_algorithms/directed_graph_edge_view.hpp +++ b/include/osp/graph_algorithms/directed_graph_edge_view.hpp @@ -168,23 +168,24 @@ class edge_view { }; /** - * @brief A view over the outgoing edges of a specific vertex in a directed graph. + * @brief A view over the incident edges of a specific vertex in a directed graph. * - * This class provides an iterator-based view to iterate over the outgoing edges - * of a given vertex `u`. It is a lightweight, non-owning view. + * This class provides an iterator-based view to iterate over either outgoing or incoming edges + * of a given vertex. It is a lightweight, non-owning view. * * @tparam Graph_t The type of the graph, which must satisfy the `is_directed_graph_v` concept. + * @tparam IsOutgoing If true, iterates over outgoing edges; otherwise, incoming edges. */ -template -class out_edge_view { +template +class IncidentEdgeView { private: static_assert(is_directed_graph_v, "Graph_t must satisfy the directed_graph concept"); const Graph_t &graph_; - vertex_idx_t sourceVertex_; + vertex_idx_t anchorVertex_; template - class OutEdgeIterator { + class IncidentEdgeIterator { public: using iterator_category = typename std::iterator_traits::iterator_category; using difference_type = std::ptrdiff_t; @@ -198,145 +199,106 @@ class out_edge_view { }; private: - vertex_idx_t sourceVertex_; - child_iterator_t currentChildIt_; + vertex_idx_t anchorVertex_; + child_iterator_t currentIt_; public: - OutEdgeIterator() = default; - OutEdgeIterator(vertex_idx_t u, child_iterator_t it) : sourceVertex_(u), currentChildIt_(it) {} + IncidentEdgeIterator() = default; + IncidentEdgeIterator(vertex_idx_t u, child_iterator_t it) : anchorVertex_(u), currentIt_(it) {} - [[nodiscard]] value_type operator*() const { return {sourceVertex_, *currentChildIt_}; } + [[nodiscard]] value_type operator*() const { + if constexpr (IsOutgoing) { + return {anchorVertex_, *currentIt_}; + } else { + return {*currentIt_, anchorVertex_}; + } + } [[nodiscard]] arrow_proxy operator->() const { return {operator*()}; } - OutEdgeIterator &operator++() { - ++currentChildIt_; + IncidentEdgeIterator &operator++() { + ++currentIt_; return *this; } - OutEdgeIterator operator++(int) { - OutEdgeIterator temp = *this; + IncidentEdgeIterator operator++(int) { + IncidentEdgeIterator temp = *this; ++(*this); return temp; } - OutEdgeIterator &operator--() { - --currentChildIt_; + IncidentEdgeIterator &operator--() { + --currentIt_; return *this; } - OutEdgeIterator operator--(int) { - OutEdgeIterator temp = *this; + IncidentEdgeIterator operator--(int) { + IncidentEdgeIterator temp = *this; --(*this); return temp; } - [[nodiscard]] bool operator==(const OutEdgeIterator &other) const noexcept { - return currentChildIt_ == other.currentChildIt_; + [[nodiscard]] bool operator==(const IncidentEdgeIterator &other) const noexcept { + return currentIt_ == other.currentIt_; } - [[nodiscard]] bool operator!=(const OutEdgeIterator &other) const noexcept { return !(*this == other); } + [[nodiscard]] bool operator!=(const IncidentEdgeIterator &other) const noexcept { return !(*this == other); } }; + // Helper to deduce iterator type based on direction + using base_iterator_type = + std::conditional_t().children(std::declval>()).begin()), + decltype(std::declval().parents(std::declval>()).begin())>; + public: - using iterator = OutEdgeIterator().children(std::declval>()).begin())>; + using iterator = IncidentEdgeIterator; using constIterator = iterator; - out_edge_view(const Graph_t &graph, vertex_idx_t u) : graph_(graph), sourceVertex_(u) {} + IncidentEdgeView(const Graph_t &graph, vertex_idx_t u) : graph_(graph), anchorVertex_(u) {} - [[nodiscard]] auto begin() const { return iterator(sourceVertex_, graph_.children(sourceVertex_).begin()); } + [[nodiscard]] auto begin() const { + if constexpr (IsOutgoing) { + return iterator(anchorVertex_, graph_.children(anchorVertex_).begin()); + } else { + return iterator(anchorVertex_, graph_.parents(anchorVertex_).begin()); + } + } [[nodiscard]] auto cbegin() const { return begin(); } - [[nodiscard]] auto end() const { return iterator(sourceVertex_, graph_.children(sourceVertex_).end()); } + [[nodiscard]] auto end() const { + if constexpr (IsOutgoing) { + return iterator(anchorVertex_, graph_.children(anchorVertex_).end()); + } else { + return iterator(anchorVertex_, graph_.parents(anchorVertex_).end()); + } + } [[nodiscard]] auto cend() const { return end(); } - [[nodiscard]] auto size() const { return graph_.out_degree(sourceVertex_); } - [[nodiscard]] bool empty() const { return graph_.out_degree(sourceVertex_) == 0; } + [[nodiscard]] auto size() const { + if constexpr (IsOutgoing) { + return graph_.out_degree(anchorVertex_); + } else { + return graph_.in_degree(anchorVertex_); + } + } + [[nodiscard]] bool empty() const { + if constexpr (IsOutgoing) { + return graph_.out_degree(anchorVertex_) == 0; + } else { + return graph_.in_degree(anchorVertex_) == 0; + } + } }; /** - * @brief A view over the incoming edges of a specific vertex in a directed graph. - * - * This class provides an iterator-based view to iterate over the incoming edges - * of a given vertex `v`. It is a lightweight, non-owning view. - * - * @tparam Graph_t The type of the graph, which must satisfy the `is_directed_graph_v` concept. + * @brief A view over the outgoing edges of a specific vertex in a directed graph. */ template -class in_edge_view { - private: - static_assert(is_directed_graph_v, "Graph_t must satisfy the directed_graph concept"); - - const Graph_t &graph_; - vertex_idx_t targetVertex_; - - template - class InEdgeIterator { - public: - using iterator_category = typename std::iterator_traits::iterator_category; - using difference_type = std::ptrdiff_t; - using value_type = directed_edge; - using pointer = value_type *; - using reference = value_type &; - - struct arrow_proxy { - value_type value; - const value_type *operator->() const noexcept { return &value; } - }; - - private: - vertex_idx_t targetVertex_; - parent_iterator_t currentParentIt_; - - public: - InEdgeIterator() = default; - InEdgeIterator(vertex_idx_t v, parent_iterator_t it) : targetVertex_(v), currentParentIt_(it) {} +using OutEdgeView = IncidentEdgeView; - [[nodiscard]] value_type operator*() const { return {*currentParentIt_, targetVertex_}; } - [[nodiscard]] arrow_proxy operator->() const { return {operator*()}; } - - InEdgeIterator &operator++() { - ++currentParentIt_; - return *this; - } - - InEdgeIterator operator++(int) { - InEdgeIterator temp = *this; - ++(*this); - return temp; - } - - InEdgeIterator &operator--() { - --currentParentIt_; - return *this; - } - - InEdgeIterator operator--(int) { - InEdgeIterator temp = *this; - --(*this); - return temp; - } - - [[nodiscard]] bool operator==(const InEdgeIterator &other) const noexcept { - return currentParentIt_ == other.currentParentIt_; - } - - [[nodiscard]] bool operator!=(const InEdgeIterator &other) const noexcept { return !(*this == other); } - }; - - public: - using iterator = InEdgeIterator().parents(std::declval>()).begin())>; - using constIterator = iterator; - - in_edge_view(const Graph_t &graph, vertex_idx_t v) : graph_(graph), targetVertex_(v) {} - - [[nodiscard]] auto begin() const { return iterator(targetVertex_, graph_.parents(targetVertex_).begin()); } - [[nodiscard]] auto cbegin() const { return begin(); } - - [[nodiscard]] auto end() const { return iterator(targetVertex_, graph_.parents(targetVertex_).end()); } - [[nodiscard]] auto cend() const { return end(); } - - [[nodiscard]] auto size() const { return graph_.in_degree(targetVertex_); } - [[nodiscard]] bool empty() const { return graph_.in_degree(targetVertex_) == 0; } -}; +/** + * @brief A view over the incoming edges of a specific vertex in a directed graph. + */ +template +using InEdgeView = IncidentEdgeView; } // namespace osp \ No newline at end of file From 887367ee62d8104ffd76c2f2d1bd1675ae55ed93 Mon Sep 17 00:00:00 2001 From: tonibohnlein Date: Thu, 11 Dec 2025 11:08:22 +0100 Subject: [PATCH 5/5] added test, improve implementaion --- .../directed_graph_edge_view.hpp | 55 +++++++------------ tests/directed_graph_util.cpp | 34 +++++++++++- 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/include/osp/graph_algorithms/directed_graph_edge_view.hpp b/include/osp/graph_algorithms/directed_graph_edge_view.hpp index a4f5e23c..cf2829a5 100644 --- a/include/osp/graph_algorithms/directed_graph_edge_view.hpp +++ b/include/osp/graph_algorithms/directed_graph_edge_view.hpp @@ -83,35 +83,29 @@ class edge_view { DirectedEdgeIterator(const vertex_idx_t edge_idx, const Graph_t &graph) : graph_(&graph), currentVertex_(0), currentEdgeIdx_(edge_idx) { - if (currentEdgeIdx_ < graph_->num_edges()) { - vertex_idx_t tmp = 0u; - advanceToValid(); - while (currentVertex_ != graph_->num_vertices() && tmp < currentEdgeIdx_) { - while (currentChild_ != graph_->children(currentVertex_).end()) { - if (tmp == currentEdgeIdx_) { - return; - } - currentChild_++; - tmp++; - } - // Move to next vertex - currentVertex_++; - if (currentVertex_ != graph_->num_vertices()) { - currentChild_ = graph_->children(currentVertex_).begin(); - // Skip empty adjacency lists - while (currentVertex_ != graph_->num_vertices() && - graph_->children(currentVertex_).begin() == graph_->children(currentVertex_).end()) { - currentVertex_++; - if (currentVertex_ != graph_->num_vertices()) { - currentChild_ = graph_->children(currentVertex_).begin(); - } - } - } - } - } else { + if (currentEdgeIdx_ >= graph_->num_edges()) { currentEdgeIdx_ = graph_->num_edges(); currentVertex_ = graph_->num_vertices(); + return; + } + + vertex_idx_t currentAccumulatedEdges = 0; + + // Optimization: Skip vertices entirely if their degree is small enough + while (currentVertex_ < graph_->num_vertices()) { + const auto degree = graph_->out_degree(currentVertex_); + if (currentAccumulatedEdges + degree > currentEdgeIdx_) { + break; + } + currentAccumulatedEdges += degree; + currentVertex_++; + } + + // Initialize child iterator and advance within the specific vertex + if (currentVertex_ < graph_->num_vertices()) { + currentChild_ = graph_->children(currentVertex_).begin(); + std::advance(currentChild_, currentEdgeIdx_ - currentAccumulatedEdges); } } @@ -124,14 +118,7 @@ class edge_view { if (currentChild_ == graph_->children(currentVertex_).end()) { currentVertex_++; - // Skip empty vertices - while (currentVertex_ != graph_->num_vertices()) { - if (graph_->children(currentVertex_).begin() != graph_->children(currentVertex_).end()) { - currentChild_ = graph_->children(currentVertex_).begin(); - break; - } - currentVertex_++; - } + advanceToValid(); } return *this; } diff --git a/tests/directed_graph_util.cpp b/tests/directed_graph_util.cpp index 353e202c..492f61e8 100644 --- a/tests/directed_graph_util.cpp +++ b/tests/directed_graph_util.cpp @@ -602,7 +602,13 @@ BOOST_AUTO_TEST_CASE(ComputationalDagConstructor) { BOOST_CHECK(get_bottom_node_distance(graph) == bottom_dist); const std::vector> graph_second_Out = { - {1, 2}, {3, 4}, {4, 5}, {6}, {}, {6}, {}, + {1, 2}, + {3, 4}, + {4, 5}, + {6}, + {}, + {6}, + {}, }; const std::vector graph_second_workW = {1, 1, 1, 1, 1, 1, 3}; const std::vector graph_second_commW = graph_second_workW; @@ -675,4 +681,30 @@ BOOST_AUTO_TEST_CASE(ComputationalDagConstructor) { // rev_edge_in_rev_graph); // } // } +} + +BOOST_AUTO_TEST_CASE(test_edge_view_indexed_access) { + computational_dag_vector_impl_def_t graph = constr_graph_1(); + auto all_edges = edge_view(graph); + + // Check initial iterator + auto it = all_edges.begin(); + + // Check each edge by index + for (size_t i = 0; i < graph.num_edges(); ++i) { + // Construct iterator directly to index i + auto indexed_it = decltype(all_edges)::iterator(i, graph); + BOOST_CHECK(indexed_it == it); + BOOST_CHECK(*indexed_it == *it); + + ++it; + } + + // Check end condition + auto end_it = decltype(all_edges)::iterator(graph.num_edges(), graph); + BOOST_CHECK(end_it == all_edges.end()); + + // Check out of bounds + auto oob_it = decltype(all_edges)::iterator(graph.num_edges() + 5, graph); + BOOST_CHECK(oob_it == all_edges.end()); } \ No newline at end of file