diff --git a/CHANGELOG.md b/CHANGELOG.md index 199d1f5..b6c06b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## [Unreleased] ### Added +- **Container mutation API** — BGL-style member functions for incrementally building and editing graphs: + - **`dynamic_graph`**: `add_vertex()` / `add_vertex(val)` (sequential), `add_vertex(id)` / `add_vertex(id, val)` (associative, returns `bool`), `add_edge(u, v[, val])` (throws `std::out_of_range` if either endpoint is missing; maintains in-edges when bidirectional), `remove_edge(u, v)` (returns count removed), `remove_vertex(u)` (sequential renumbers higher ids; associative keeps stable keys). + - **`undirected_adjacency_list`**: `remove_edge(uid, vid)` (returns count removed), `remove_vertex(uid)` (O(V+E), renumbers higher ids). Both throw `std::out_of_range` on invalid ids. - **Composable visitor toolkit** (`algorithm/visitor_factory.hpp`, included via `algorithms.hpp` umbrella) — BGL-style event-visitor combinators built on the existing duck-typed `on_*` callback model, without changing any traversal algorithm: - **Layer 1 — single-event adaptors** (`on_discover_vertex(f)`, `on_tree_edge(f)`, …): wrap a callable so it fires for exactly one traversal event. Generated for all 14 event names (5 vertex + 9 edge) via a macro. Analogous to BGL event tags. - **Layer 2 — `composite_visitor` / `make_visitor(...)`**: fan a single traversal out to multiple sub-visitors. Each event method is constrained by a fold over the child pack, so a composite exposes `on_X` only when at least one child handles it — keeping `has_on_*` / `valid_visitor` detection accurate and preserving zero-overhead event skipping. Bridges descriptor- and id-form children automatically via `vertex_id()`. @@ -59,6 +62,7 @@ - `germany_routes_example.cpp` — builds a Germany routes graph, traverses it, and runs Dijkstra twice (segment count and km distance) ### Changed +- **`undirected_adjacency_list` mutation API renamed** to match `dynamic_graph` and BGL conventions: `create_vertex` → `add_vertex`, `create_edge` → `add_edge`, `erase_edge` → `remove_edge`. The old member names were removed (no backward-compatible aliases); update call sites accordingly. - **`edge_descriptor` simplified to iterator-only storage** — removed the `conditional_t` dual-storage path; edges always store the iterator directly since edges always have physical containers. Eliminates 38 `if constexpr` branches across 6 files (~500 lines removed). - **`compressed_graph::vertices(g)` returns `iota_view`** — simplified to `std::ranges::iota_view(0, num_vertices())`, which the `vertices` CPO wraps automatically via `_wrap_if_needed`. - **`vertex_descriptor_view` CTAD deduction guides** — updated from `Container::iterator`/`const_iterator` to `std::ranges::iterator_t<>` for compatibility with views like `iota_view`. diff --git a/agents/bgl_migration_strategy.md b/agents/bgl_migration_strategy.md index 7000a36..5d3f109 100644 --- a/agents/bgl_migration_strategy.md +++ b/agents/bgl_migration_strategy.md @@ -46,7 +46,7 @@ graph-v3 is a ground-up C++20 redesign targeting ISO standardization (P3126–P3 - No `subgraph` hierarchy with descriptor mapping - No DIMACS or METIS I/O - Graph generators partially implemented (Erdős-Rényi G(n,p), Barabási-Albert, 2D grid, path available; Watts-Strogatz, R-MAT, complete graph still missing) -- `dynamic_graph` lacks individual mutation (`add_vertex`, `add_edge`, `remove_vertex`, `remove_edge`); `undirected_adjacency_list` provides `create_vertex` / `create_edge` / `erase_edge` but no `erase_vertex` +- Both `dynamic_graph` and `undirected_adjacency_list` now support full mutation (`add_vertex`, `add_edge`, `remove_edge`, `remove_vertex`); no major mutation gaps remain - No `adjacency_matrix` container - No `copy_graph` utility with cross-type and property mapping support - No `labeled_graph` adaptor (string labels → vertex mapping) @@ -138,11 +138,11 @@ These are behavioral analogues, not strict one-to-one translations. In particula | `BidirectionalGraph` | `bidirectional_adjacency_list` | `in_edges(g, u)` available | | `AdjacencyGraph` | Part of `adjacency_list` | `neighbors(g, u)` view | | `VertexListGraph` | `vertex_range` | `vertices(g)` returns range | -| `EdgeListGraph` | `edgelist(g)` view | Not a concept — a view function | +| `EdgeListGraph` | `basic_sourced_edgelist` / `basic_sourced_index_edgelist` | Concepts for an edgelist (range of edges); `edgelist(g)` is a *view* that produces such a range from an adjacency list | | `VertexAndEdgeListGraph` | `adjacency_list` | Combined by default | | `AdjacencyMatrix` | ❌ Not available | No O(1) edge lookup concept | -| `MutableGraph` | ❌ No unified concept | `dynamic_graph` is batch-load oriented; `undirected_adjacency_list` supports create-vertex/create-edge and edge erasure | -| `PropertyGraph` | ❌ No formal concept | CPOs `vertex_value`/`edge_value` serve the role | +| `MutableGraph` | ❌ No unified concept | No mutating CPOs are defined in the current design | +| `PropertyGraph` | ❌ No formal concept | CPOs `vertex_value`/`edge_value` and their matching `vertex_value_t/edge_value_t` serve the role | | `ColorValue` | ❌ Internal only | Hidden inside algorithms | ### Key Concept Differences @@ -162,8 +162,8 @@ These are behavioral analogues, not strict one-to-one translations. In particula | `graph_traits::vertex_descriptor` | `vertex_t` (descriptor-like handle); use `vertex_id_t` when you specifically need the key/index | | `graph_traits::edge_descriptor` | `edge_t` | | `graph_traits::vertex_iterator` | `vertex_iterator_t` | -| `graph_traits::out_edge_iterator` | `iterator_t>` | -| `graph_traits::in_edge_iterator` | `iterator_t>` | +| `graph_traits::out_edge_iterator` | `out_edge_iterator_t` | +| `graph_traits::in_edge_iterator` | `in_edge_iterator_t` | | `graph_traits::adjacency_iterator` | (use `neighbors(g, u)` view) | | `graph_traits::directed_category` | (implicit in type: `dynamic_graph` vs `undirected_adjacency_list`) | | `graph_traits::traversal_category` | (replaced by concept constraints) | @@ -717,21 +717,40 @@ To achieve full BGL parity, the following generators are still needed: ```cpp typedef boost::adjacency_list> Graph; -typedef graph_traits::vertex_descriptor Vertex; -typedef graph_traits::edge_descriptor Edge; +typedef graph_traits::vertex_descriptor Vertex; // integral (size_t) for vecS +typedef graph_traits::edge_descriptor Edge; +typedef graph_traits::vertices_size_type VId; // same as vertex_descriptor for vecS ``` **graph-v3:** + +Define a graph based on `dynamic_graph`. This is similar to the graph used by BGL, but it's +not the only graph type supported. +```cpp +#include +using Graph = graph::container::vov_graph; // vecS vertices, vecS edges, EV=double +using Vertex = vertex_t; +using Edge = edge_t; +using VId = graph::vertex_id_t; // integral in this example, but may be non-integral +``` + +Graphs may also be defined using standard containers. The following has the same characteristics +as the `vov_graph` above. Construction and mutation functions (e.g. add/remove for vertices and +edges) are unique to a graph type. ```cpp -using Graph = graph::dynamic_graph; // EV=double, VV=void -using VId = graph::vertex_id_t; +using Graph = vector<>>; // vecS vertices, vecS edges, EV=double +using Vertex = vertex_t; +using Edge = edge_t; +using VId = graph::vertex_id_t; // integral in this example, but may be non-integral ``` +User-defined graphs can easily be used by by overriding 3-7 CPO functions for the graph. + #### Pattern 2: Graph Construction with add_edge **BGL:** ```cpp -Graph g(5); +Graph g(5); // 5 vertices, no edges add_edge(0, 1, 10.0, g); add_edge(1, 2, 20.0, g); add_edge(2, 3, 30.0, g); @@ -739,17 +758,17 @@ add_edge(2, 3, 30.0, g); **graph-v3:** ```cpp -Graph g({{0,1,10.0}, {0,2,20.0}, {2,3,30.0}}); -// Or with undirected_adjacency_list for individual mutation: -undirected_adjacency_list g; -g.create_vertex(); g.create_vertex(); // ... -g.create_edge(0, 1, 10.0); +Graph g(5); // 5 vertices, no edges +g.add_edge(0, 1, 10.0); +g.add_edge(1, 2, 20.0); +g.add_edge(2, 3, 30.0); ``` +* Note that add_edge is a member function of g. There are no mutating CPOs at this time. -> ⚠️ **Migration friction:** `dynamic_graph` does not support incremental `add_edge`. Code that builds graphs edge-by-edge must either: -> 1. Collect edges into a vector first, then construct -> 2. Use `undirected_adjacency_list` (undirected only) -> 3. Wait for mutation API to be added to `dynamic_graph` +Or with an _Initializer list_ +```cpp +Graph g({{0,1,10.0}, {1,2,20.0}, {2,3,30.0}}); +``` #### Pattern 3: Vertex/Edge Iteration @@ -765,9 +784,20 @@ for (tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) { ``` **graph-v3:** + +Using views: ```cpp for (auto&& [uid, u] : vertexlist(g)) { - for (auto&& [tid, uv] : incidence(g, u)) { + for (auto&& [vid, uv] : incidence(g, u)) { + double w = edge_value(g, uv); + } +} +``` + +Using lower-level CPO functions directly: +```cpp +for (auto u : vertices(g)) { + for (auto uv : out_edges(g, u)) { double w = edge_value(g, uv); } } @@ -787,11 +817,16 @@ dijkstra_shortest_paths(g, s, **graph-v3:** ```cpp vector pred(num_vertices(g)); -vector dist(num_vertices(g), numeric_limits::max()); +vector dist(num_vertices(g)); +init_shortest_paths(g, dist, pred); // dist -> infinite_distance, pred -> self-id dijkstra_shortest_paths(g, {s}, container_value_fn(dist), container_value_fn(pred), [](const auto& g, const auto& uv) { return edge_value(g, uv); }); ``` +> **Note:** BGL's full `dijkstra_shortest_paths` initializes the distance and predecessor +> maps internally. graph-v3 does not auto-initialize, so the caller must call +> `init_shortest_paths(g, dist, pred)` first (or pre-fill `dist` with `infinite_distance()`). + #### Pattern 5: Custom BFS Visitor **BGL:** @@ -821,7 +856,6 @@ dijkstra_shortest_paths(fg, s, ...); **graph-v3:** ```cpp -#include auto ep = [&](auto&& uv) { return edge_value(g, uv) > 5.0; }; auto fg = graph::adaptors::filtered_graph(g, graph::adaptors::keep_all{}, ep); dijkstra_shortest_paths(fg, {s}, dist_fn, pred_fn, weight_fn); @@ -888,6 +922,15 @@ There is **no `graph_traits` to specialise**. A CPO is satisfied in one of three 2. A free function found by ADL in the namespace of `G` (e.g. `vertices(g)` in namespace `boost`). 3. A built-in default that works when `G` matches a recognised pattern (e.g. random-access range of inner ranges of integers). +> **Note:** This refers to *adapting* an external graph type — there is no trait to +> specialise to teach graph-v3 about your type; you satisfy the CPOs instead. This is +> distinct from `dynamic_graph`, the container used in many examples, which *does* take a +> traits template parameter that selects the container types for vertices and edges. +> Pre-defined combinations are provided as aliases in their own headers (e.g. `vov_graph` +> = vector-of-vector). Because those traits constrain containers via *concepts* (not +> concrete standard-library types), containers from other libraries can be used as long +> as they model the required concepts. + For BGL types you will use **option 2**: add free functions in `namespace boost`. ADL will find them because `boost::adjacency_list` lives in that namespace. ### 12.2 Recommended Layout @@ -908,6 +951,16 @@ All adapter functions go inside `namespace boost { ... }` so ADL picks them up. ### 12.3 Minimal Adapter for `boost::adjacency_list` +> **Note:** graph-v3 already ships a complete, production-ready BGL adaptor — prefer it +> over hand-rolling your own. Include `` (and +> `` for property maps). It supports `vecS`/`listS`/`setS` +> storage, `directedS`/`undirectedS`/`bidirectionalS`, `in_edges`, and BGL property-map bridging. +> See [docs/user-guide/bgl-adaptor.md](../docs/user-guide/bgl-adaptor.md) and +> [examples/bgl_adaptor_example.cpp](../examples/bgl_adaptor_example.cpp). +> +> The minimal example below is kept for reference: it shows *how* CPO-based adaptation works +> and serves as a template for adapting other third-party graph types. + The example below adapts `boost::adjacency_list` — the most common BGL configuration. It is the analogue of graph-v3's `vov_graph_traits`-backed `dynamic_graph`. ```cpp @@ -1031,6 +1084,25 @@ graph::dijkstra_shortest_paths(g, {source}, distances, predecessors, weight); **Exterior property maps.** Pass them as ordinary lambdas — graph-v3 algorithms accept any invocable taking an edge or vertex descriptor. +**No bundled properties.** When the BGL `adjacency_list` carries no bundled properties, you don't +need `vertex_value` / `edge_value` at all. Instead, define a separate function object per property +whose call interface takes the graph plus a descriptor — `prop(g, u)` for vertex properties or +`prop(g, uv)` for edge properties — and hand each one to the algorithms that consume it: + +```cpp +// Pull values from an interior or exterior BGL property map. +auto weight = [wmap = ::boost::get(::boost::edge_weight, g)](const auto& g, auto uv) -> double { + return ::boost::get(wmap, uv); // prop(g, uv) +}; +auto vcolor = [cmap = ::boost::get(::boost::vertex_color, g)](const auto& g, auto u) { + return ::boost::get(cmap, u); // prop(g, u) +}; + +graph::dijkstra_shortest_paths(g, {source}, distances, predecessors, weight); +``` + +Each property is an independent callable, so you only define the ones a given algorithm requires. + ### 12.6 Other BGL Container Configurations | BGL `OutEdgeS` / `VertexS` | Vertex descriptor | Adapter notes | @@ -1086,8 +1158,7 @@ These items block migration for the largest number of BGL users: | Item | Type | Effort | Rationale | |------|------|--------|-----------| -| **Individual mutation on directed graph** | Container | High | `add_vertex()`, `add_edge()`, `remove_vertex()`, `remove_edge()` on `dynamic_graph`. Most BGL code builds graphs incrementally. | -| **`erase_vertex` on `undirected_adjacency_list`** | Container | Medium | Currently only `erase_edge` is supported on the mutation-friendly container | +| **`adjacency_matrix` container** | Container | High | BGL users relying on matrix storage have no direct equivalent | | **A\* Search** | Algorithm | Medium | Heavily used in pathfinding, robotics, game AI | | **`copy_graph` utility** | Utility | Low | Cross-type graph copy with property mapping | | **Betweenness Centrality** | Algorithm | Medium | Core network analysis metric | diff --git a/docs/migration-from-v2.md b/docs/migration-from-v2.md index bb1db8b..a8a18eb 100644 --- a/docs/migration-from-v2.md +++ b/docs/migration-from-v2.md @@ -77,9 +77,11 @@ class undirected_adjacency_list; | Operation | Complexity | |-----------|-----------| | Vertex access by id | O(1) | -| `create_vertex()` | O(1) amortized | -| `create_edge(u, v)` | O(1) | -| `erase_edge(pos)` | O(1) — unlinks from both vertices' lists | +| `add_vertex()` | O(1) amortized | +| `add_edge(u, v)` | O(1) | +| `remove_edge(pos)` | O(1) — unlinks from both vertices' lists | +| `remove_edge(uid, vid)` | O(degree(uid)) | +| `remove_vertex(uid)` | O(V + E) — renumbers higher vertex ids | | `degree(v)` | O(1) — cached per vertex | | Iterate edges from vertex | O(degree) | | Iterate all edges | O(V + E) | @@ -88,8 +90,8 @@ class undirected_adjacency_list; from each endpoint. Use `edges_size() / 2` to get the unique edge count. **Iterator invalidation:** -- Vertex iterators: invalidated by `create_vertex()` if reallocation occurs, and `clear()`. -- Vertex iterators: **not** invalidated by `create_edge()` or `erase_edge()`. +- Vertex iterators: invalidated by `add_vertex()` if reallocation occurs, by `remove_vertex()`, and by `clear()`. +- Vertex iterators: **not** invalidated by `add_edge()` or `remove_edge()`. **Basic usage:** @@ -100,9 +102,9 @@ using namespace graph::container; // Edge value = int (weight), vertex value = std::string (name) undirected_adjacency_list g; -auto u = g.create_vertex("Alice"); -auto v = g.create_vertex("Bob"); -auto e = g.create_edge(u, v, 42); // weight 42 +auto u = g.add_vertex("Alice"); +auto v = g.add_vertex("Bob"); +auto e = g.add_edge(u, v, 42); // weight 42 // Iterate incident edges of u for (auto&& [uid, vid, uv] : edges(g, *u)) { diff --git a/docs/user-guide/algorithms.md b/docs/user-guide/algorithms.md index 39d7dec..0df839d 100644 --- a/docs/user-guide/algorithms.md +++ b/docs/user-guide/algorithms.md @@ -44,7 +44,8 @@ Algorithms follow a consistent pattern: - **Output**: filled via caller-provided output ranges (distances, predecessors, component labels) — not returned by value - **Weight functions**: passed as callable `WF(g, uv)` returning edge weight -- **Visitors**: optional structs with callback methods for fine-grained event hooks +- **Visitors**: optional structs with callback methods for fine-grained event hooks, + or assemble one from reusable pieces with the [composable visitor toolkit](#composable-visitors) - **Initialization**: use `init_shortest_paths()` to properly set up distance and predecessor vectors before calling shortest-path algorithms @@ -336,6 +337,82 @@ callbacks you define are called — unimplemented callbacks are silently skipped Each callback also has an `_id` variant that receives vertex/edge IDs instead of descriptors. +### Composable visitors + +Instead of hand-writing a visitor struct, you can assemble one from reusable +pieces with the toolkit in `` (also pulled +in by the `algorithms.hpp` umbrella header). It is the graph-v3 analogue of +Boost.Graph's `make_bfs_visitor` / event-tag combinators. There are three layers: + +**1. Single-event adaptors** — wrap any `(g, x)` callable so it fires for exactly +one event. One adaptor exists per event name (`on_discover_vertex`, +`on_tree_edge`, `on_edge_relaxed`, …): + +```cpp +#include +using namespace graph; + +std::vector> order; +breadth_first_search(g, s, + on_discover_vertex([&](auto& g, auto u) { order.push_back(vertex_id(g, u)); })); +``` + +**2. `make_visitor(...)`** — fan a single traversal out to several sub-visitors. +A composite only exposes an `on_X` method when at least one child handles event +`X`, so unused events still compile away to nothing. Descriptor-form and id-form +children can be mixed freely: + +```cpp +auto pred = make_vertex_property_map>(g); +auto dist = make_vertex_property_map(g); +std::vector> order; + +breadth_first_search(g, s, + make_visitor( + on_tree_edge(predecessor_recorder(pred)), + on_tree_edge(distance_recorder(dist)), // hop-count layering + on_discover_vertex([&](auto& g, auto u) { order.push_back(vertex_id(g, u)); }))); +``` + +**3. Prebuilt recorders** — ready-made `(g, x) -> void` callables for the most +common bookkeeping; you choose the event to bind them to: + +| Recorder | Records | Typical event | +|----------|---------|---------------| +| `predecessor_recorder(pred)` | `pred[target] = source` | `on_tree_edge` (BFS/DFS), `on_edge_relaxed` (Dijkstra/Bellman-Ford) | +| `distance_recorder(dist, weight_fn)` | `dist[target] = dist[source] + weight(g, uv)` | `on_edge_relaxed` (weighted) | +| `distance_recorder(dist)` | `dist[target] = dist[source] + 1` | `on_tree_edge` (hop-count layering) | +| `time_stamper(time_map, clock)` | `time[u] = clock++` | `on_discover_vertex` / `on_finish_vertex` | + +Because the recorders are event-agnostic, the same pieces work for Dijkstra by +binding them to the relaxation event: + +```cpp +auto pred = make_vertex_property_map>(g); +dijkstra_shortest_paths(g, s, dist, weight, + make_visitor( + on_edge_relaxed(predecessor_recorder(pred)), + on_edge_relaxed(distance_recorder(dist, weight)))); +``` + +A `time_stamper` advances its `clock` by reference, so two stampers sharing one +counter produce interleaved discover/finish timestamps: + +```cpp +auto discover = make_vertex_property_map(g); +auto finish = make_vertex_property_map(g); +int clock = 0; +depth_first_search(g, s, + make_visitor( + on_discover_vertex(time_stamper(discover, clock)), + on_finish_vertex(time_stamper(finish, clock)))); +``` + +> Misspelled event names are caught at compile time: BFS, DFS, Dijkstra, and +> Bellman-Ford `static_assert` on `valid_visitor`, so a visitor with no +> recognized `on_*` method produces a clear diagnostic instead of silently doing +> nothing. + --- ## Roadmap diff --git a/docs/user-guide/containers.md b/docs/user-guide/containers.md index 5d5a9cd..4e30db8 100644 --- a/docs/user-guide/containers.md +++ b/docs/user-guide/containers.md @@ -300,6 +300,58 @@ G g; > `include/graph/container/traits/`. The simplest starting point is > `vov_graph_traits.hpp`. +### Mutation API + +`dynamic_graph` supports incremental, BGL-style mutation after construction. The +`add_vertex` overloads differ between **sequential** (`v`/`d`) and **associative** +(`m`/`u`) vertex containers: + +```cpp +using namespace graph::container; +using G = vov_graph; // sequential vertices, EV=double, VV=string +G g; + +// Sequential containers: ids are positional; add_vertex appends and returns the new id +auto a = g.add_vertex("Alice"); // returns vertex_id_type 0 +auto b = g.add_vertex("Bob"); // returns 1 +auto c = g.add_vertex(); // default-constructed value, returns 2 + +g.add_edge(a, b, 1.5); // both endpoints must already exist +g.add_edge(a, c); // EV default-constructed when value omitted + +auto removed = g.remove_edge(a, b); // returns number of edges removed +g.remove_vertex(c); // renumbers higher vertex/edge ids +``` + +For **associative** vertex containers (`m*` / `u*` traits) the caller supplies the +id (key), and `add_vertex` returns a `bool` indicating whether a new vertex was +inserted: + +```cpp +using G = mov_graph; // map +G g; + +bool inserted = g.add_vertex("alice", "Alice"); // true (new), false if key existed +g.add_vertex("bob", "Bob"); +g.add_edge("alice", "bob", 2.0); + +g.remove_edge("alice", "bob"); +g.remove_vertex("bob"); // remaining keys stay stable (no renumbering) +``` + +| Operation | Sequential (`v`/`d`) | Associative (`m`/`u`) | +|-----------|----------------------|------------------------| +| `add_vertex()` / `add_vertex(val)` | Appends, returns new id | n/a (id required) | +| `add_vertex(id)` / `add_vertex(id, val)` | n/a | Keyed insert, returns `bool` | +| `add_edge(u, v)` / `add_edge(u, v, val)` | Both endpoints must exist (`std::out_of_range` otherwise) | same | +| `remove_edge(u, v)` | Returns count removed | same | +| `remove_vertex(u)` | Renumbers higher ids; invalidates descriptors | Stable keys; only removed id invalidated | + +> **Note:** `add_edge` requires that both vertices already exist — it does not +> auto-create them. When `Bidirectional` is `true`, edge mutations also maintain +> the matching in-edge lists. After `remove_vertex` on a sequential container, all +> existing vertex and edge descriptors are invalidated because ids are renumbered. + --- ## 2. `compressed_graph` @@ -440,6 +492,41 @@ endpoint). Use `edges_size() / 2` for the unique edge count. | `VContainer` | `std::vector` | Vertex storage container template | | `Alloc` | `std::allocator` | Allocator | +### Mutation API + +`undirected_adjacency_list` is built incrementally with the same BGL-style names +as `dynamic_graph`. Vertex ids are positional indices into the vertex container. + +```cpp +#include +using namespace graph::container; + +undirected_adjacency_list g; // EV=int (weight), VV=string (name) + +auto u = g.add_vertex("Alice"); // returns vertex_iterator +auto v = g.add_vertex("Bob"); +auto w = g.add_vertex(); // default-constructed value + +g.add_edge(u, v, 42); // by iterator, with edge value +g.add_edge(0, 2); // by id, edge value default-constructed + +auto n = g.remove_edge(0u, 1u); // by id: returns number of edges removed +g.remove_vertex(2u); // O(V+E): renumbers higher vertex ids +``` + +| Operation | Complexity | Notes | +|-----------|------------|-------| +| `add_vertex()` / `add_vertex(val)` | O(1) amortized | Appends, returns `vertex_iterator` | +| `add_edge(u, v)` / `add_edge(u, v, val)` | O(1) | By iterator or by id; with or without edge value | +| `remove_edge(pos)` | O(1) | By `edge_iterator`; unlinks from both endpoints | +| `remove_edge(uid, vid)` | O(degree(uid)) | Returns count removed; `std::out_of_range` on invalid id | +| `remove_vertex(uid)` | O(V + E) | Renumbers higher vertex ids; `std::out_of_range` on invalid id | + +> **Iterator invalidation:** `add_vertex` may invalidate vertex iterators if the +> vertex container reallocates; `remove_vertex` invalidates them because higher +> ids are renumbered. `add_edge` and `remove_edge` do **not** invalidate vertex +> iterators. + --- ## 4. Range-of-Ranges Graphs (No Library Graph Container Required) diff --git a/include/graph/container/detail/undirected_adjacency_list_impl.hpp b/include/graph/container/detail/undirected_adjacency_list_impl.hpp index 647cd8f..6e55ecb 100644 --- a/include/graph/container/detail/undirected_adjacency_list_impl.hpp +++ b/include/graph/container/detail/undirected_adjacency_list_impl.hpp @@ -6,6 +6,7 @@ #define UNDIRECTED_ADJ_LIST_IMPL_HPP #include +#include namespace graph::container { @@ -1015,7 +1016,7 @@ ual_vertex::erase_edge(graph_type& #if 0 template class VContainer, typename Alloc> typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v) { +ual_vertex::add_edge(graph_type& g, vertex_type& v) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v); ++g.edges_size_; @@ -1024,7 +1025,7 @@ ual_vertex::create_edge(graph_type& g, verte template class VContainer, typename Alloc> typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v, edge_value_type&& val) { +ual_vertex::add_edge(graph_type& g, vertex_type& v, edge_value_type&& val) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v, move(val)); ++g.edges_size_; @@ -1033,7 +1034,7 @@ ual_vertex::create_edge(graph_type& g, verte template class VContainer, typename Alloc> typename ual_vertex::vertex_edge_iterator -ual_vertex::create_edge(graph_type& g, vertex_type& v, const edge_value_type& val) { +ual_vertex::add_edge(graph_type& g, vertex_type& v, const edge_value_type& val) { edge_type* uv = g.edge_alloc_.allocate(1); new (uv) edge_type(g, *this, v, val); ++g.edges_size_; @@ -1115,9 +1116,9 @@ base_undirected_adjacency_list::base_undirec // Only copy each edge once: when uid matches source and source <= target if (uid == src_id && src_id <= tgt_id) { if constexpr (std::is_void_v) { - g.create_edge(src_id, tgt_id); + g.add_edge(src_id, tgt_id); } else { - g.create_edge(src_id, tgt_id, uv->value()); + g.add_edge(src_id, tgt_id, uv->value()); } } } @@ -1160,7 +1161,7 @@ base_undirected_adjacency_list::base_undirec } vertices_.resize(max_vtx_id + 1); // assure expected vertices exist - // Downcast to graph_type to access create_edge + // Downcast to graph_type to access add_edge auto& g = static_cast(*this); // add edges @@ -1173,9 +1174,9 @@ base_undirected_adjacency_list::base_undirec g.throw_unordered_edges(); if constexpr (std::is_void_v) { - g.create_edge(ed.source_id, ed.target_id); + g.add_edge(ed.source_id, ed.target_id); } else { - g.create_edge(ed.source_id, ed.target_id, ed.value); + g.add_edge(ed.source_id, ed.target_id, ed.value); } tid = ed.source_id; } @@ -1201,13 +1202,13 @@ base_undirected_adjacency_list::base_undirec } vertices_.resize(max_vtx_id + 1); // assure expected vertices exist - // Downcast to graph_type to access create_edge + // Downcast to graph_type to access add_edge auto& g = static_cast(*this); // add edges - no ordering requirement, just insert them for (auto& edge_data : ilist) { const auto& [uid, vid, uv_val] = edge_data; - g.create_edge(uid, vid, uv_val); + g.add_edge(uid, vid, uv_val); } } @@ -1229,13 +1230,13 @@ base_undirected_adjacency_list::base_undirec } vertices_.resize(max_vtx_id + 1); // assure expected vertices exist - // Downcast to graph_type to access create_edge + // Downcast to graph_type to access add_edge auto& g = static_cast(*this); // add edges - no ordering requirement, just insert them for (auto& edge_data : ilist) { const auto& [uid, vid] = edge_data; - g.create_edge(uid, vid); + g.add_edge(uid, vid); } } @@ -1436,15 +1437,80 @@ template class VContainer, typename Alloc> typename base_undirected_adjacency_list::edge_iterator -base_undirected_adjacency_list::erase_edge(edge_iterator pos) { +base_undirected_adjacency_list::remove_edge(edge_iterator pos) { edge_type* uv = &*pos; ++pos; - uv->~edge_type(); // unlinks from vertices + vertex_type& u = this->vertices_[uv->list_owner_id()]; + vertex_type& v = this->vertices_[uv->list_target_id()]; + uv->unlink(u, v); // unlink from both endpoints' edge lists + uv->~edge_type(); this->edge_alloc_.deallocate(uv, 1); --this->edges_size_; return pos; } +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::vertex_size_type +base_undirected_adjacency_list::remove_edge(vertex_id_type uid, + vertex_id_type vid) { + auto& derived = static_cast(*this); + if (uid >= this->vertices_.size() || vid >= this->vertices_.size()) + throw std::out_of_range("remove_edge: vertex id out of range"); + + vertex_type& u = this->vertices_[uid]; + vertex_size_type count = 0; + vertex_edge_iterator it = u.edges_begin(derived, uid); + vertex_edge_iterator last = u.edges_end(derived, uid); + while (it != last) { + edge_type& uv = *it; + vertex_id_type other = (uv.list_owner_id() == uid) ? uv.list_target_id() : uv.list_owner_id(); + if (other == vid) { + it = u.erase_edge(derived, it); // unlinks from both endpoints, returns next in u's list + ++count; + } else { + ++it; + } + } + return count; +} + +//------------------------------------------------------------------------------------- +// Vertex removal methods +//------------------------------------------------------------------------------------- + +template class VContainer, + typename Alloc> +void base_undirected_adjacency_list::remove_vertex(vertex_id_type uid) { + auto& derived = static_cast(*this); + if (uid >= this->vertices_.size()) + throw std::out_of_range("remove_vertex: vertex id out of range"); + + // 1. Remove all edges incident to the vertex being erased. + this->vertices_[uid].clear_edges(derived); + + // 2. Collect the remaining (unique) physical edges. Each undirected edge appears twice + // when iterating, so deduplicate by address. Ids are still consistent at this point. + std::unordered_set remaining; + for (edge_type& e : derived.edges()) + remaining.insert(&e); + + // 3. Erase the vertex; vertices with a higher id shift down by one position. + this->vertices_.erase(this->vertices_.begin() + static_cast(uid)); + + // 4. Renumber the stored endpoint ids of every remaining edge. + for (edge_type* e : remaining) + e->renumber_after_vertex_erase(uid); +} + //------------------------------------------------------------------------------------- // Graph modification methods //------------------------------------------------------------------------------------- @@ -1619,7 +1685,7 @@ undirected_adjacency_list::undirected_adjace if constexpr (!std::is_void_v) { for (auto& vtx : vrng) { auto&& [id, value] = vproj(vtx); // copyable_vertex_t - create_vertex(value); + add_vertex(value); } } this->vertices_.resize(max_vtx_id + 1); // assure expected vertices exist @@ -1635,9 +1701,9 @@ undirected_adjacency_list::undirected_adjace vertex_edge_iterator uv; if constexpr (std::is_void_v) { - uv = create_edge(ed.source_id, ed.target_id); + uv = add_edge(ed.source_id, ed.target_id); } else { - uv = create_edge(ed.source_id, ed.target_id, ed.value); + uv = add_edge(ed.source_id, ed.target_id, ed.value); } tid = ed.source_id; } @@ -2041,6 +2107,853 @@ undirected_adjacency_list::operator=(const return *this; } +///------------------------------------------------------------------------------------- +/// ual_edge_value +/// + +template class VContainer, + typename Alloc> +constexpr typename ual_edge_value::value_type& +ual_edge_value::value() noexcept { + return value_; +} + +template class VContainer, + typename Alloc> +constexpr const typename ual_edge_value::value_type& +ual_edge_value::value() const noexcept { + return value_; +} + +///------------------------------------------------------------------------------------- +/// ual_vertex_value +/// + +template class VContainer, + typename Alloc> +constexpr typename ual_vertex_value::value_type& +ual_vertex_value::value() noexcept { + return value_; +} + +template class VContainer, + typename Alloc> +constexpr const typename ual_vertex_value::value_type& +ual_vertex_value::value() const noexcept { + return value_; +} + +///------------------------------------------------------------------------------------- +/// ual_vertex_edge_list::const_iterator (new constructors and graph/source_id) +/// + +template class VContainer, + typename Alloc> +ual_vertex_edge_list::const_iterator::const_iterator( + const graph_type& g, vertex_id_type uid, const edge_type* uv) noexcept + : vertex_id_(uid), edge_(const_cast(uv)), graph_(const_cast(&g)) {} + +template class VContainer, + typename Alloc> +typename ual_vertex_edge_list::const_iterator::graph_type& +ual_vertex_edge_list::const_iterator::graph() { + return *graph_; +} + +template class VContainer, + typename Alloc> +const typename ual_vertex_edge_list::const_iterator::graph_type& +ual_vertex_edge_list::const_iterator::graph() const { + return *graph_; +} + +template class VContainer, + typename Alloc> +typename ual_vertex_edge_list::vertex_id_type +ual_vertex_edge_list::const_iterator::source_id() const { + return vertex_id_; +} + +///------------------------------------------------------------------------------------- +/// ual_vertex_edge_list::iterator (new constructor) +/// + +template class VContainer, + typename Alloc> +ual_vertex_edge_list::iterator::iterator( + const graph_type& g, vertex_id_type uid, const edge_type* uv) + : const_iterator(g, uid, uv) {} + +///------------------------------------------------------------------------------------- +/// ual_vertex_edge_list move constructor +/// + +template class VContainer, + typename Alloc> +ual_vertex_edge_list::ual_vertex_edge_list( + ual_vertex_edge_list&& rhs) noexcept + : head_(move(rhs.head_)), tail_(move(rhs.tail_)), size_(move(rhs.size_)) { + rhs.head_ = rhs.tail_ = nullptr; + rhs.size_ = 0; +} + +///------------------------------------------------------------------------------------- +/// ual_vertex_edge_list_link +/// + +template class VContainer, + typename Alloc, + typename ListT> +ual_vertex_edge_list_link::ual_vertex_edge_list_link( + vertex_id_type uid) noexcept + : vertex_id_(uid) {} + +template class VContainer, + typename Alloc, + typename ListT> +typename ual_vertex_edge_list_link::vertex_id_type +ual_vertex_edge_list_link::vertex_id() const noexcept { + return vertex_id_; +} + +template class VContainer, + typename Alloc, + typename ListT> +typename ual_vertex_edge_list_link::const_vertex_iterator +ual_vertex_edge_list_link::vertex(const graph_type& g) const { + return g.vertices().begin() + vertex_id_; +} + +template class VContainer, + typename Alloc, + typename ListT> +typename ual_vertex_edge_list_link::vertex_iterator +ual_vertex_edge_list_link::vertex(graph_type& g) { + return g.vertices().begin() + vertex_id_; +} + +template class VContainer, + typename Alloc, + typename ListT> +typename ual_vertex_edge_list_link::edge_type* +ual_vertex_edge_list_link::next() noexcept { + return next_; +} + +template class VContainer, + typename Alloc, + typename ListT> +const typename ual_vertex_edge_list_link::edge_type* +ual_vertex_edge_list_link::next() const noexcept { + return next_; +} + +template class VContainer, + typename Alloc, + typename ListT> +typename ual_vertex_edge_list_link::edge_type* +ual_vertex_edge_list_link::prev() noexcept { + return prev_; +} + +template class VContainer, + typename Alloc, + typename ListT> +const typename ual_vertex_edge_list_link::edge_type* +ual_vertex_edge_list_link::prev() const noexcept { + return prev_; +} + +///------------------------------------------------------------------------------------- +/// ual_edge::renumber_after_vertex_erase +/// + +template class VContainer, + typename Alloc> +void ual_edge::renumber_after_vertex_erase( + vertex_id_type removed_id) noexcept { + auto& in_link = static_cast(*this); + auto& out_link = static_cast(*this); + if (in_link.vertex_id_ > removed_id) + --in_link.vertex_id_; + if (out_link.vertex_id_ > removed_id) + --out_link.vertex_id_; +} + +///------------------------------------------------------------------------------------- +/// base_undirected_adjacency_list::const_edge_iterator +/// + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::const_edge_iterator::const_edge_iterator( + const graph_type& g, vertex_iterator u) + : g_(&const_cast(g)), u_(u) { + advance_vertex(); +} + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::const_edge_iterator::const_edge_iterator( + const graph_type& g, const_vertex_iterator u) + : g_(&const_cast(g)), + u_(g_->vertices().begin() + (u - g.vertices().begin())) { + advance_vertex(); +} + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::const_edge_iterator::const_edge_iterator( + const graph_type& g, vertex_iterator u, vertex_edge_iterator uv) + : g_(&const_cast(g)), u_(u), uv_(uv) {} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::const_edge_iterator::reference +base_undirected_adjacency_list::const_edge_iterator::operator*() const { + return *uv_; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::const_edge_iterator::pointer +base_undirected_adjacency_list::const_edge_iterator::operator->() const { + return &*uv_; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::const_edge_iterator& +base_undirected_adjacency_list::const_edge_iterator::operator++() { + advance_edge(); + return *this; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::const_edge_iterator +base_undirected_adjacency_list::const_edge_iterator::operator++(int) { + const_edge_iterator tmp(*this); + ++*this; + return tmp; +} + +template class VContainer, + typename Alloc> +bool base_undirected_adjacency_list::const_edge_iterator::operator==( + const const_edge_iterator& rhs) const noexcept { + return uv_ == rhs.uv_ && u_ == rhs.u_; +} + +template class VContainer, + typename Alloc> +bool base_undirected_adjacency_list::const_edge_iterator::operator!=( + const const_edge_iterator& rhs) const noexcept { + return !operator==(rhs); +} + +template class VContainer, + typename Alloc> +void base_undirected_adjacency_list::const_edge_iterator::advance_edge() { + vertex_id_type uid = static_cast(u_ - g_->vertices().begin()); + if (++uv_ != u_->edges_end(*g_, uid)) + return; + ++u_; + advance_vertex(); +} + +template class VContainer, + typename Alloc> +void base_undirected_adjacency_list::const_edge_iterator::advance_vertex() { + for (; u_ != g_->vertices().end(); ++u_) { + if (u_->num_edges() > 0) { + vertex_id_type uid = static_cast(u_ - g_->vertices().begin()); + uv_ = u_->edges_begin(*g_, uid); + return; + } + } +} + +///------------------------------------------------------------------------------------- +/// base_undirected_adjacency_list::edge_iterator +/// + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::edge_iterator::edge_iterator( + graph_type& g, vertex_iterator u) noexcept + : const_edge_iterator(g, u) {} + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::edge_iterator::edge_iterator( + graph_type& g, vertex_iterator u, vertex_edge_iterator uv) + : const_edge_iterator(g, u, uv) {} + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::edge_iterator::edge_iterator() noexcept + : const_edge_iterator() {} + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::edge_iterator::edge_iterator( + const edge_iterator& rhs) noexcept + : const_edge_iterator(rhs) {} + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::edge_iterator::edge_iterator( + const_edge_iterator& rhs) + : const_edge_iterator(rhs) {} + +template class VContainer, + typename Alloc> +base_undirected_adjacency_list::edge_iterator::~edge_iterator() {} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::edge_iterator& +base_undirected_adjacency_list::edge_iterator::operator=( + const edge_iterator& rhs) noexcept { + const_edge_iterator::operator=(rhs); + return *this; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::edge_iterator::reference +base_undirected_adjacency_list::edge_iterator::operator*() const { + return *this->uv_; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::edge_iterator::pointer +base_undirected_adjacency_list::edge_iterator::operator->() const { + return &*this->uv_; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::edge_iterator& +base_undirected_adjacency_list::edge_iterator::operator++() { + this->advance_edge(); + return *this; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::edge_iterator +base_undirected_adjacency_list::edge_iterator::operator++(int) { + edge_iterator tmp(*this); + ++*this; + return tmp; +} + +///------------------------------------------------------------------------------------- +/// base_undirected_adjacency_list: num_vertices, num_edges, has_edge, edges +/// + +template class VContainer, + typename Alloc> +constexpr auto +base_undirected_adjacency_list::num_vertices() const noexcept { + return vertices_.size(); +} + +template class VContainer, + typename Alloc> +constexpr typename base_undirected_adjacency_list::edge_size_type +base_undirected_adjacency_list::num_edges() const noexcept { + return edges_size_; +} + +template class VContainer, + typename Alloc> +constexpr bool +base_undirected_adjacency_list::has_edge() const noexcept { + return edges_size_ > 0; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::edge_range +base_undirected_adjacency_list::edges() { + auto& self = static_cast(*this); + return {edge_iterator(self, begin()), edge_iterator(self, end()), this->edges_size_}; +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::const_edge_range +base_undirected_adjacency_list::edges() const { + auto& self = static_cast(*this); + return {const_edge_iterator(self, const_cast(self).begin()), + const_edge_iterator(self, const_cast(self).end()), this->edges_size_}; +} + +///------------------------------------------------------------------------------------- +/// base_undirected_adjacency_list: add_vertex +/// + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::add_vertex() { + this->vertices_.push_back( + vertex_type(this->vertices_, static_cast(this->vertices_.size()))); + return this->vertices_.begin() + static_cast(this->vertices_.size() - 1); +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::add_vertex(vertex_value_type&& val) { + this->vertices_.push_back( + vertex_type(this->vertices_, static_cast(this->vertices_.size()), std::move(val))); + return this->vertices_.begin() + static_cast(this->vertices_.size() - 1); +} + +template class VContainer, + typename Alloc> +template +requires std::constructible_from +typename base_undirected_adjacency_list::vertex_iterator +base_undirected_adjacency_list::add_vertex(const VV2& val) { + this->vertices_.push_back( + vertex_type(this->vertices_, static_cast(this->vertices_.size()), val)); + return this->vertices_.begin() + static_cast(this->vertices_.size() - 1); +} + +///------------------------------------------------------------------------------------- +/// base_undirected_adjacency_list: add_edge +/// + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::vertex_edge_iterator +base_undirected_adjacency_list::add_edge(vertex_id_type uid, + vertex_id_type vid) { + vertex_iterator ui = try_find_vertex(uid); + vertex_iterator vi = try_find_vertex(vid); + return add_edge(ui, vi); +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::vertex_edge_iterator +base_undirected_adjacency_list::add_edge(vertex_id_type uid, + vertex_id_type vid, + edge_value_type&& val) { + vertex_iterator ui = this->vertices_.begin() + uid; + vertex_iterator vi = this->vertices_.begin() + vid; + return add_edge(ui, vi, std::move(val)); +} + +template class VContainer, + typename Alloc> +template +requires std::constructible_from +typename base_undirected_adjacency_list::vertex_edge_iterator +base_undirected_adjacency_list::add_edge(vertex_id_type uid, + vertex_id_type vid, + const EV2& val) { + vertex_iterator ui = this->vertices_.begin() + uid; + vertex_iterator vi = this->vertices_.begin() + vid; + return add_edge(ui, vi, val); +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::vertex_edge_iterator +base_undirected_adjacency_list::add_edge(vertex_iterator u, + vertex_iterator v) { + vertex_id_type uid = static_cast(u - this->vertices_.begin()); + edge_type* uv = this->edge_alloc_.allocate(1); + new (uv) edge_type(static_cast(*this), u, v); + ++this->edges_size_; + return vertex_edge_iterator(static_cast(*this), uid, uv); +} + +template class VContainer, + typename Alloc> +typename base_undirected_adjacency_list::vertex_edge_iterator +base_undirected_adjacency_list::add_edge(vertex_iterator u, + vertex_iterator v, + edge_value_type&& val) { + vertex_id_type uid = static_cast(u - this->vertices_.begin()); + edge_type* uv = this->edge_alloc_.allocate(1); + new (uv) edge_type(static_cast(*this), u, v, std::move(val)); + ++this->edges_size_; + return vertex_edge_iterator(static_cast(*this), uid, uv); +} + +template class VContainer, + typename Alloc> +template +requires std::constructible_from +typename base_undirected_adjacency_list::vertex_edge_iterator +base_undirected_adjacency_list::add_edge(vertex_iterator u, + vertex_iterator v, + const EV2& val) { + vertex_id_type uid = static_cast(u - this->vertices_.begin()); + edge_type* uv = this->edge_alloc_.allocate(1); + new (uv) edge_type(static_cast(*this), u, v, val); + ++this->edges_size_; + return vertex_edge_iterator(static_cast(*this), uid, uv); +} + +///------------------------------------------------------------------------------------- +/// undirected_adjacency_list::graph_value (GV != void) +/// + +template class VContainer, + typename Alloc> +typename undirected_adjacency_list::graph_value_type& +undirected_adjacency_list::graph_value() noexcept { + return graph_value_; +} + +template class VContainer, + typename Alloc> +const typename undirected_adjacency_list::graph_value_type& +undirected_adjacency_list::graph_value() const noexcept { + return graph_value_; +} + +///------------------------------------------------------------------------------------- +/// CPO non-member free functions (find_vertex, target_id, source_id, edge_value) +/// +/// These are defined here (in impl.hpp) so that they are available to all code +/// that includes undirected_adjacency_list.hpp (which #includes this file). +///------------------------------------------------------------------------------------- + +/// find_vertex(g, id) - returns view iterator yielding vertex_descriptor +/// REQUIRED: Provides bounds checking - returns end() if id >= size() +template class VContainer, + typename Alloc> +constexpr auto find_vertex(undirected_adjacency_list& g, VId id) noexcept { + using graph_type = undirected_adjacency_list; + using vertex_set = typename graph_type::vertex_set; + using container_iter = typename vertex_set::iterator; + using view_type = vertex_descriptor_view; + using view_iterator = typename view_type::iterator; + using storage_type = typename view_iterator::value_type::storage_type; + + if (id >= static_cast(g.vertices().size())) { + return view_iterator{static_cast(g.vertices().size())}; + } + return view_iterator{static_cast(id)}; +} + +template class VContainer, + typename Alloc> +constexpr auto find_vertex(const undirected_adjacency_list& g, VId id) noexcept { + using graph_type = undirected_adjacency_list; + using vertex_set = typename graph_type::vertex_set; + using container_iter = typename vertex_set::const_iterator; + using view_type = vertex_descriptor_view; + using view_iterator = typename view_type::iterator; + using storage_type = typename view_iterator::value_type::storage_type; + + if (id >= static_cast(g.vertices().size())) { + return view_iterator{static_cast(g.vertices().size())}; + } + return view_iterator{static_cast(id)}; +} + +/// target_id(g, edge_descriptor) - get target vertex id from edge descriptor (iteration perspective) +template class VContainer, + typename Alloc, + typename E> +requires edge_descriptor_type +constexpr VId target_id(const undirected_adjacency_list& g, + const E& e) noexcept { + return e.value()->other_vertex_id(g, static_cast(e.source_id())); +} + +/// source_id(g, edge_descriptor) - get source vertex id from edge descriptor (iteration perspective) +template class VContainer, + typename Alloc, + typename E> +requires edge_descriptor_type +constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list& g, + const E& e) noexcept { + return static_cast(e.source_id()); +} + +/// edge_value(g, edge_descriptor) - get edge value from edge descriptor (non-void EV only) +template class VContainer, + typename Alloc, + typename E> +requires edge_descriptor_type && (!std::is_void_v) +constexpr decltype(auto) + edge_value(undirected_adjacency_list&, const E& e) noexcept { + return e.value()->value(); +} + +template class VContainer, + typename Alloc, + typename E> +requires edge_descriptor_type && (!std::is_void_v) +constexpr decltype(auto) + edge_value(const undirected_adjacency_list&, const E& e) noexcept { + return e.value()->value(); +} + } // namespace graph::container #endif // UNDIRECTED_ADJ_LIST_IMPL_HPP diff --git a/include/graph/container/dynamic_graph.hpp b/include/graph/container/dynamic_graph.hpp index b138a80..9921f0d 100644 --- a/include/graph/container/dynamic_graph.hpp +++ b/include/graph/container/dynamic_graph.hpp @@ -225,6 +225,13 @@ class dynamic_edge_target { private: vertex_id_type target_id_ = vertex_id_type(); + // Mutation support for dynamic_graph_base::remove_vertex on sequential containers. + // Allows decrementing stored target_id after a vertex is erased from a vector/deque. + template + friend class dynamic_graph_base; + + constexpr void set_target_id(const vertex_id_type& id) noexcept { target_id_ = id; } + private: // target_id(g,uv) - ADL customization point for CPO friend constexpr decltype(auto) target_id(const graph_type&, const edge_type& uv) noexcept { @@ -289,6 +296,12 @@ class dynamic_edge_source { private: vertex_id_type source_id_ = vertex_id_type(); + // Mutation support for dynamic_graph_base::remove_vertex on sequential containers. + template + friend class dynamic_graph_base; + + constexpr void set_source_id(const vertex_id_type& id) noexcept { source_id_ = id; } + private: // source_id(g,uv), source(g) // friend constexpr vertex_id_type source_id(const graph_type& g, const edge_type& uv) noexcept { return uv.source_id_; } @@ -977,6 +990,25 @@ class dynamic_graph_base { */ dynamic_graph_base(vertex_allocator_type alloc) : vertices_(alloc), partition_(alloc) { terminate_partitions(); } + /** + * @brief Construct a graph with a fixed number of vertices and no edges. + * + * Pre-creates @p vertex_count default-constructed vertices (ids @c 0 to @c vertex_count-1), analogous to + * Boost.Graph's @c adjacency_list(n) constructor. Edges can then be added incrementally with @c add_edge(). + * If vertices have a user-defined value (e.g. VV not void), the value must be default-constructable. + * + * @note Only supported for resizable (sequential) vertex containers; for associative vertex containers no + * vertices are created. + * + * @param vertex_count The number of vertices to create. + * @param alloc Used to allocate vertices and edges. + */ + dynamic_graph_base(size_type vertex_count, vertex_allocator_type alloc) + : vertices_(alloc), partition_(alloc) { + resize_vertices(vertex_count); + terminate_partitions(); + } + /** * @brief Construct the graph using edge and vertex ranges. * @@ -1552,6 +1584,378 @@ class dynamic_graph_base { edge_count_ = 0; } +public: // Vertex and Edge Mutation + + // --- add_vertex: sequential containers (vector/deque) --- + // Vertex ids are positional indices, so a new vertex is appended and assigned the next + // sequential id. These overloads are disabled for associative containers, where ids are + // user-supplied keys that may be sparse and/or non-integral. + + /// @brief Add a new vertex with a default-constructed value (sequential containers only). + /// + /// The vertex is appended and assigned the next sequential id. + /// + /// @return The id of the newly created vertex. + /// @complexity O(1) amortized. + vertex_id_type add_vertex() + requires (!is_associative_container) { + vertices_.push_back(vertex_type(vertices_.get_allocator())); + return static_cast(vertices_.size() - 1); + } + + /// @brief Add a new vertex with a value (sequential containers only). + /// Only available when VV is not void. + /// + /// The vertex is appended and assigned the next sequential id. + /// + /// @param val Value to forward into the new vertex. + /// @return The id of the newly created vertex. + /// @complexity O(1) amortized. + template + vertex_id_type add_vertex(V&& val) + requires (!std::is_void_v && std::same_as, VV> && + !is_associative_container) { + vertices_.emplace_back(std::forward(val), vertices_.get_allocator()); + return static_cast(vertices_.size() - 1); + } + + // --- add_vertex: associative containers (map/unordered_map) --- + // Vertex ids are user-supplied keys that may be sparse and/or non-integral, so the caller + // must provide the id explicitly. Insertion is idempotent: if a vertex with the given id + // already exists it is preserved (its edges are not disturbed). + + /// @brief Add a vertex with the given id (associative containers only). + /// + /// If a vertex with @p id already exists it is left unchanged. + /// + /// @param id The id (key) of the vertex to add. + /// @return true if a new vertex was inserted, false if one already existed. + /// @complexity O(log n) for ordered maps, O(1) average for unordered maps. + bool add_vertex(const vertex_id_type& id) + requires (is_associative_container) { + return vertices_.try_emplace(id).second; + } + + /// @brief Add a vertex with the given id and value (associative containers only). + /// Only available when VV is not void. + /// + /// If a vertex with @p id already exists its value is assigned from @p val and its edges + /// are left unchanged; otherwise a new vertex is inserted with the value. + /// + /// @param id The id (key) of the vertex to add. + /// @param val Value to forward into the vertex. + /// @return true if a new vertex was inserted, false if an existing vertex's value was updated. + /// @complexity O(log n) for ordered maps, O(1) average for unordered maps. + template + bool add_vertex(const vertex_id_type& id, V&& val) + requires (!std::is_void_v && std::same_as, VV> && + is_associative_container) { + auto it = vertices_.find(id); + if (it == vertices_.end()) { + vertices_.emplace(id, vertex_type(std::forward(val))); + return true; + } + it->second.value() = std::forward(val); + return false; + } + + /// @brief Add a directed edge from uid to vid. + /// + /// When EV is not void the edge value is default-constructed; use the value overload to + /// supply an explicit value. + /// + /// Both vertices must already exist. For sequential containers the ids are positional + /// indices; for associative containers they are user-supplied keys. A missing vertex + /// throws @c std::out_of_range in either case. + /// + /// @param uid Source vertex id. + /// @param vid Target vertex id. + /// @complexity O(1) amortized for most containers; O(log n) for ordered set/map containers. + void add_edge(const vertex_id_type& uid, const vertex_id_type& vid) { + if constexpr (Bidirectional) { + emplace_edge(vertex_at(vid).in_edges(), uid, in_edge_type(uid)); + } else { + (void)vertex_at(vid); // validate target exists (throws std::out_of_range otherwise) + } + emplace_edge(vertex_at(uid).edges(), vid, edge_type(vid)); + ++edge_count_; + } + + /// @brief Add a directed edge from uid to vid with a value (copy or move). + /// Only available when EV is not void. + /// + /// Both vertices must already exist; a missing vertex throws @c std::out_of_range. + /// + /// @param uid Source vertex id. + /// @param vid Target vertex id. + /// @param val Edge value to forward into the out-edge; in-edge (if bidirectional) receives a copy. + /// @complexity O(1) amortized for most containers; O(log n) for ordered set/map containers. + template + void add_edge(const vertex_id_type& uid, const vertex_id_type& vid, E&& val) + requires (!std::is_void_v && std::same_as, EV>) { + if constexpr (Bidirectional) { + emplace_edge(vertex_at(vid).in_edges(), uid, in_edge_type(uid, val)); // copy before move + } else { + (void)vertex_at(vid); // validate target exists (throws std::out_of_range otherwise) + } + emplace_edge(vertex_at(uid).edges(), vid, edge_type(vid, std::forward(val))); + ++edge_count_; + } + + /// @brief Remove all directed edges from uid to vid. + /// + /// @param uid Source vertex id. + /// @param vid Target vertex id. + /// @return Number of edges removed. + /// @complexity O(degree(uid)) for most containers; O(1) average for unordered_set/map containers. + size_type remove_edge(const vertex_id_type& uid, const vertex_id_type& vid) { + if (!contains_vertex(uid)) + return 0; + size_type count = remove_out_edges_targeting(vertex_at(uid).edges(), vid); + if constexpr (Bidirectional) { + if (count > 0 && contains_vertex(vid)) + remove_in_edges_for(vertex_at(vid).in_edges(), uid); + } + edge_count_ -= count; + return count; + } + + /// @brief Remove a vertex and all edges associated with it. + /// + /// For sequential containers (vector/deque), erasing a vertex renumbers all subsequent + /// vertex ids (decrement by one) and updates all stored edge ids accordingly. + /// All existing vertex and edge descriptors become invalid after this call. + /// + /// For associative containers (map/unordered_map), only the removed vertex's id is + /// invalidated; remaining vertex ids are stable. + /// + /// @param uid The vertex id to remove. + /// @complexity O(V + E) for sequential containers; O(V + degree(uid)) for associative containers. + void remove_vertex(const vertex_id_type& uid) { + if (!contains_vertex(uid)) + return; + + if constexpr (Bidirectional) { + // (1) Remove each out-edge uid→vid: unlink the corresponding in-edge at vid. + { + auto& out = vertex_at(uid).edges(); + for (const auto& e : out) { + vertex_id_type vid = e.target_id(); + if (vid != uid && contains_vertex(vid)) + remove_in_edges_for(vertex_at(vid).in_edges(), uid); + } + edge_count_ -= static_cast(std::ranges::distance(out)); + } + + // (2) Remove each in-edge src→uid: unlink the corresponding out-edge at src. + { + auto& in = vertex_at(uid).in_edges(); + for (const auto& e : in) { + vertex_id_type src = get_in_edge_id(e); + if (src != uid && contains_vertex(src)) { + size_type n = remove_out_edges_targeting(vertex_at(src).edges(), uid); + edge_count_ -= n; + } + } + } + } else { + // Non-bidirectional: count uid's out-edges (they go away with the vertex). + edge_count_ -= static_cast(std::ranges::distance(vertex_at(uid).edges())); + + // Remove edges pointing to uid from every other vertex. + if constexpr (is_associative_container) { + for (auto& [key, vtx] : vertices_) { + if (key == uid) + continue; + edge_count_ -= remove_out_edges_targeting(vtx.edges(), uid); + } + } else { + for (size_type i = 0; i < static_cast(vertices_.size()); ++i) { + if (static_cast(i) == uid) + continue; + edge_count_ -= remove_out_edges_targeting(vertices_[i].edges(), uid); + } + } + } + + // (3) For sequential containers: renumber all stored edge ids that are > uid. + if constexpr (!is_associative_container) { + for (auto& vtx : vertices_) { + adjust_out_edge_ids(vtx.edges(), uid); + if constexpr (Bidirectional) + adjust_in_edge_ids(vtx.in_edges(), uid); + } + } + + // (4) Erase the vertex. + if constexpr (is_associative_container) { + vertices_.erase(uid); + } else { + vertices_.erase(vertices_.begin() + static_cast(uid)); + } + } + +private: // Mutation helpers + + // Extract the stored vertex id from an in-edge element: + // dynamic_in_edge (non-uniform bidir traits): stored in source_id_ + // edge_type as in_edge_type (standard bidir traits): stored in target_id_ + template + static constexpr vertex_id_type get_in_edge_id(const InEdgeT& e) noexcept { + if constexpr (requires { e.source_id(); }) + return e.source_id(); + else + return e.target_id(); + } + + // Set the stored vertex id on an in-edge element. + template + static constexpr void set_in_edge_id(InEdgeT& e, vertex_id_type id) noexcept { + if constexpr (requires { e.source_id(); }) + e.set_source_id(id); + else + e.set_target_id(id); + } + + // Remove all out-edges whose target_id equals tid. Returns the count removed. + template + static size_type remove_out_edges_targeting(EdgeContainer& edges, vertex_id_type tid) { + if constexpr (is_map_based_edge_container) { + return static_cast(edges.erase(tid)); + } else if constexpr (has_emplace) { + // set / unordered_set: at most one match (comparison is by target_id) + auto it = edges.find(edge_type(tid)); + if (it != edges.end()) { + edges.erase(it); + return 1; + } + return 0; + } else if constexpr (requires(EdgeContainer& c) { + c.remove_if([](const typename EdgeContainer::value_type&) { return true; }); + }) { + // list / forward_list: C++20 remove_if returns count + return static_cast( + edges.remove_if([tid](const auto& e) { return e.target_id() == tid; })); + } else { + // vector / deque: erase-remove + auto new_end = + std::remove_if(edges.begin(), edges.end(), [tid](const auto& e) { return e.target_id() == tid; }); + size_type count = static_cast(std::distance(new_end, edges.end())); + edges.erase(new_end, edges.end()); + return count; + } + } + + // Remove all in-edges whose stored vertex id equals src_id. Returns the count removed. + template + static size_type remove_in_edges_for(InEdgesContainer& in_edges, vertex_id_type src_id) { + using elem_t = typename InEdgesContainer::value_type; + if constexpr (is_map_based_edge_container) { + return static_cast(in_edges.erase(src_id)); + } else if constexpr (has_emplace) { + auto it = in_edges.find(elem_t(src_id)); + if (it != in_edges.end()) { + in_edges.erase(it); + return 1; + } + return 0; + } else if constexpr (requires(InEdgesContainer& c) { + c.remove_if([](const elem_t&) { return true; }); + }) { + return static_cast(in_edges.remove_if([src_id](const auto& e) { + if constexpr (requires { e.source_id(); }) + return e.source_id() == src_id; + else + return e.target_id() == src_id; + })); + } else { + auto new_end = std::remove_if(in_edges.begin(), in_edges.end(), [src_id](const auto& e) { + if constexpr (requires { e.source_id(); }) + return e.source_id() == src_id; + else + return e.target_id() == src_id; + }); + size_type count = static_cast(std::distance(new_end, in_edges.end())); + in_edges.erase(new_end, in_edges.end()); + return count; + } + } + + // Decrement the target_id of all out-edges with target_id > uid. + // For set/unordered_set and map-based containers the affected elements must be + // erased and re-inserted because the key (target_id) would change. + template + void adjust_out_edge_ids(EdgeContainer& edges, vertex_id_type uid) { + if constexpr (is_map_based_edge_container) { + std::vector> pending; + for (auto it = edges.upper_bound(uid); it != edges.end();) { + pending.emplace_back(static_cast(it->first - 1), std::move(it->second)); + it = edges.erase(it); + } + for (auto& [k, v] : pending) { + v.set_target_id(k); + edges.emplace(k, std::move(v)); + } + } else if constexpr (has_emplace) { + // set / unordered_set: elements are immutable — copy out, erase, decrement, re-insert + std::vector pending; + for (auto it = edges.begin(); it != edges.end();) { + if (it->target_id() > uid) { + pending.push_back(*it); + it = edges.erase(it); + } else { + ++it; + } + } + for (auto& e : pending) { + e.set_target_id(e.target_id() - 1); + edges.emplace(std::move(e)); + } + } else { + // vector / deque / list / forward_list: modify in-place + for (auto& e : edges) + if (e.target_id() > uid) + e.set_target_id(e.target_id() - 1); + } + } + + // Decrement the stored vertex id of all in-edges with stored id > uid. + template + void adjust_in_edge_ids(InEdgesContainer& in_edges, vertex_id_type uid) { + using elem_t = typename InEdgesContainer::value_type; + if constexpr (is_map_based_edge_container) { + std::vector> pending; + for (auto it = in_edges.upper_bound(uid); it != in_edges.end();) { + pending.emplace_back(static_cast(it->first - 1), std::move(it->second)); + it = in_edges.erase(it); + } + for (auto& [k, v] : pending) { + set_in_edge_id(v, k); + in_edges.emplace(k, std::move(v)); + } + } else if constexpr (has_emplace) { + std::vector pending; + for (auto it = in_edges.begin(); it != in_edges.end();) { + if (get_in_edge_id(*it) > uid) { + pending.push_back(*it); + it = in_edges.erase(it); + } else { + ++it; + } + } + for (auto& e : pending) { + set_in_edge_id(e, get_in_edge_id(e) - 1); + in_edges.emplace(std::move(e)); + } + } else { + for (auto& e : in_edges) + if (get_in_edge_id(e) > uid) + set_in_edge_id(e, get_in_edge_id(e) - 1); + } + } + +public: // Vertex Query and Access + /** * @brief Check if a vertex with the given id exists in the graph. * @@ -1917,6 +2321,18 @@ class dynamic_graph : public dynamic_graph_base */ dynamic_graph(allocator_type alloc) : base_type(alloc) {} + /** + * @brief Construct a dynamic_graph with a fixed number of vertices and no edges. + * + * Pre-creates @p vertex_count default-constructed vertices (ids @c 0 to @c vertex_count-1), analogous to + * Boost.Graph's @c adjacency_list(n) constructor. Edges can then be added incrementally with @c add_edge(). + * + * @param vertex_count The number of vertices to create. + * @param alloc Used to allocate vertices and edges. + */ + dynamic_graph(typename base_type::size_type vertex_count, allocator_type alloc = allocator_type()) + : base_type(vertex_count, alloc) {} + /** * @brief Constructs the graph from a range of edge data and range of vertex data. * diff --git a/include/graph/container/undirected_adjacency_list.hpp b/include/graph/container/undirected_adjacency_list.hpp index 8ae18cc..6fbbfec 100644 --- a/include/graph/container/undirected_adjacency_list.hpp +++ b/include/graph/container/undirected_adjacency_list.hpp @@ -123,9 +123,9 @@ namespace ranges = std::ranges; /// using Graph = undirected_adjacency_list; // edge_value=int, vertex_value=string /// /// Graph g; -/// auto u = g.create_vertex("Alice"); -/// auto v = g.create_vertex("Bob"); -/// auto uv = g.create_edge(u, v, 100); // edge from Alice to Bob with value 100 +/// auto u = g.add_vertex("Alice"); +/// auto v = g.add_vertex("Bob"); +/// auto uv = g.add_edge(u, v, 100); // edge from Alice to Bob with value 100 /// /// // Iterate neighbors /// for (auto&& [uid, vid, uv] : g.edges(u)) { @@ -252,8 +252,8 @@ class ual_edge_value { constexpr ual_edge_value& operator=(const ual_edge_value&) = default; constexpr ual_edge_value& operator=(ual_edge_value&&) = default; - constexpr value_type& value() noexcept { return value_; } - constexpr const value_type& value() const noexcept { return value_; } + constexpr value_type& value() noexcept; + constexpr const value_type& value() const noexcept; private: // CPO support via ADL (friend functions) // edge_value(g, e) - get edge value (only when EV is not void) @@ -311,8 +311,8 @@ class ual_vertex_value { constexpr ual_vertex_value& operator=(const ual_vertex_value&) = default; constexpr ual_vertex_value& operator=(ual_vertex_value&&) = default; - constexpr value_type& value() noexcept { return value_; } - constexpr const value_type& value() const noexcept { return value_; } + constexpr value_type& value() noexcept; + constexpr const value_type& value() const noexcept; private: // CPO support via ADL (friend functions) // vertex_value(g, u) - get vertex value (only when VV is not void) @@ -410,8 +410,7 @@ class ual_vertex_edge_list { using vertex_type = ual_vertex_edge_list::vertex_type; using edge_type = ual_vertex_edge_list::edge_type; - const_iterator(const graph_type& g, vertex_id_type uid, const edge_type* uv = nullptr) noexcept - : vertex_id_(uid), edge_(const_cast(uv)), graph_(const_cast(&g)) {} + const_iterator(const graph_type& g, vertex_id_type uid, const edge_type* uv = nullptr) noexcept; const_iterator() noexcept = default; const_iterator(const const_iterator& rhs) noexcept = default; @@ -438,10 +437,10 @@ class ual_vertex_edge_list { swap(lhs.edge_, rhs.edge_); } - graph_type& graph() { return *graph_; } - const graph_type& graph() const { return *graph_; } + graph_type& graph(); + const graph_type& graph() const; - vertex_id_type source_id() const { return vertex_id_; } + vertex_id_type source_id() const; protected: void advance(); @@ -477,7 +476,7 @@ class ual_vertex_edge_list { iterator& operator=(const iterator&) = default; iterator& operator=(iterator&&) = default; - iterator(const graph_type& g, vertex_id_type uid, const edge_type* uv = nullptr) : const_iterator(g, uid, uv) {} + iterator(const graph_type& g, vertex_id_type uid, const edge_type* uv = nullptr); reference operator*() const; pointer operator->() const; @@ -500,11 +499,7 @@ class ual_vertex_edge_list { ~ual_vertex_edge_list() noexcept = default; ual_vertex_edge_list& operator=(const ual_vertex_edge_list&) noexcept = default; - ual_vertex_edge_list(ual_vertex_edge_list&& rhs) noexcept - : head_(move(rhs.head_)), tail_(move(rhs.tail_)), size_(move(rhs.size_)) { - rhs.head_ = rhs.tail_ = nullptr; - rhs.size_ = 0; - } + ual_vertex_edge_list(ual_vertex_edge_list&& rhs) noexcept; ual_vertex_edge_list& operator=(ual_vertex_edge_list&& rhs) noexcept = default; size_type size() const noexcept; @@ -578,7 +573,7 @@ class ual_vertex_edge_list_link { using edge_list_link_type = ual_vertex_edge_list_link; public: - ual_vertex_edge_list_link(vertex_id_type uid) noexcept : vertex_id_(uid) {} + ual_vertex_edge_list_link(vertex_id_type uid) noexcept; ual_vertex_edge_list_link() noexcept = default; ual_vertex_edge_list_link(const ual_vertex_edge_list_link&) noexcept = default; @@ -588,14 +583,14 @@ class ual_vertex_edge_list_link { ual_vertex_edge_list_link& operator=(ual_vertex_edge_list_link&&) noexcept = default; public: - vertex_id_type vertex_id() const noexcept { return vertex_id_; } - const_vertex_iterator vertex(const graph_type& g) const { return g.vertices().begin() + vertex_id_; } - vertex_iterator vertex(graph_type& g) { return g.vertices().begin() + vertex_id_; } + vertex_id_type vertex_id() const noexcept; + const_vertex_iterator vertex(const graph_type& g) const; + vertex_iterator vertex(graph_type& g); - edge_type* next() noexcept { return next_; } - const edge_type* next() const noexcept { return next_; } - edge_type* prev() noexcept { return prev_; } - const edge_type* prev() const noexcept { return prev_; } + edge_type* next() noexcept; + const edge_type* next() const noexcept; + edge_type* prev() noexcept; + const edge_type* prev() const noexcept; private: vertex_id_type vertex_id_ = numeric_limits::max(); @@ -723,6 +718,10 @@ class ual_edge edge_id_type edge_id(const graph_type& g) const noexcept; + // Adjust the stored endpoint ids after the vertex at @p removed_id was erased and all + // higher-numbered vertices were shifted down by one position. + void renumber_after_vertex_erase(vertex_id_type removed_id) noexcept; + friend graph_type; // the graph is the one to create & destroy edges because it owns the allocator friend base_undirected_adjacency_list; // base class also creates edges friend vertex_type; // vertex can also destroy its own edges @@ -819,59 +818,27 @@ class base_undirected_adjacency_list { using reference = const value_type&; public: - const_edge_iterator(const graph_type& g, vertex_iterator u) : g_(&const_cast(g)), u_(u) { - advance_vertex(); - } - const_edge_iterator(const graph_type& g, const_vertex_iterator u) - : g_(&const_cast(g)), u_(g_->vertices().begin() + (u - g.vertices().begin())) { - advance_vertex(); - } - const_edge_iterator(const graph_type& g, vertex_iterator u, vertex_edge_iterator uv) - : g_(&const_cast(g)), u_(u), uv_(uv) {} + const_edge_iterator(const graph_type& g, vertex_iterator u); + const_edge_iterator(const graph_type& g, const_vertex_iterator u); + const_edge_iterator(const graph_type& g, vertex_iterator u, vertex_edge_iterator uv); const_edge_iterator() noexcept = default; const_edge_iterator(const const_edge_iterator& rhs) noexcept = default; ~const_edge_iterator() noexcept = default; const_edge_iterator& operator=(const const_edge_iterator& rhs) = default; - reference operator*() const { return *uv_; } - pointer operator->() const { return &*uv_; } + reference operator*() const; + pointer operator->() const; - const_edge_iterator& operator++() { - advance_edge(); - return *this; - } - const_edge_iterator operator++(int) { - const_edge_iterator tmp(*this); - ++*this; - return tmp; - } + const_edge_iterator& operator++(); + const_edge_iterator operator++(int); - bool operator==(const const_edge_iterator& rhs) const noexcept { return uv_ == rhs.uv_ && u_ == rhs.u_; } - bool operator!=(const const_edge_iterator& rhs) const noexcept { return !operator==(rhs); } + bool operator==(const const_edge_iterator& rhs) const noexcept; + bool operator!=(const const_edge_iterator& rhs) const noexcept; protected: - void advance_edge() { - // next edge for current vertex - vertex_id_type uid = static_cast(u_ - g_->vertices().begin()); - if (++uv_ != u_->edges_end(*g_, uid)) - return; - - // find next vertex with edge(s) - ++u_; - advance_vertex(); - } - - void advance_vertex() { - // at exit, if u_ != g.vertices().end() then uv_ will refer to a valid edge - for (; u_ != g_->vertices().end(); ++u_) { - if (u_->num_edges() > 0) { - vertex_id_type uid = static_cast(u_ - g_->vertices().begin()); - uv_ = u_->edges_begin(*g_, uid); - return; - } - } - } + void advance_edge(); + void advance_vertex(); protected: graph_type* g_; @@ -890,30 +857,20 @@ class base_undirected_adjacency_list { using reference = value_type&; public: - edge_iterator(graph_type& g, vertex_iterator u) noexcept : const_edge_iterator(g, u) {} - edge_iterator(graph_type& g, vertex_iterator u, vertex_edge_iterator uv) : const_edge_iterator(g, u, uv) {} - - edge_iterator() noexcept : const_edge_iterator() {}; - edge_iterator(const edge_iterator& rhs) noexcept : const_edge_iterator(rhs) {} - edge_iterator(const_edge_iterator& rhs) : const_edge_iterator(rhs) {} - ~edge_iterator() {} - edge_iterator& operator=(const edge_iterator& rhs) noexcept { - const_edge_iterator::operator=(rhs); - return *this; - } + edge_iterator(graph_type& g, vertex_iterator u) noexcept; + edge_iterator(graph_type& g, vertex_iterator u, vertex_edge_iterator uv); - reference operator*() const { return *this->uv_; } - pointer operator->() const { return &*this->uv_; } + edge_iterator() noexcept; + edge_iterator(const edge_iterator& rhs) noexcept; + edge_iterator(const_edge_iterator& rhs); + ~edge_iterator(); + edge_iterator& operator=(const edge_iterator& rhs) noexcept; - edge_iterator& operator++() { - this->advance_edge(); - return *this; - } - edge_iterator operator++(int) { - edge_iterator tmp(*this); - ++*this; - return tmp; - } + reference operator*() const; + pointer operator->() const; + + edge_iterator& operator++(); + edge_iterator operator++(int); }; protected: // Data Members (protected for derived class access) @@ -976,7 +933,7 @@ class base_undirected_adjacency_list { /// @brief Get the number of vertices in the graph (CPO-compatible name). /// @return Number of vertices in the graph. /// @complexity O(1) - constexpr auto num_vertices() const noexcept { return vertices_.size(); } + constexpr auto num_vertices() const noexcept; /// @brief Get iterator to first vertex. /// @complexity O(1) @@ -1002,12 +959,12 @@ class base_undirected_adjacency_list { /// @return Edge count (each undirected edge counted TWICE - once from each endpoint). /// @note For unique edge count, divide by 2. /// @complexity O(1) - constexpr edge_size_type num_edges() const noexcept { return edges_size_; } + constexpr edge_size_type num_edges() const noexcept; /// @brief Check if the graph has any edges (CPO-compatible name). /// @return true if the graph has at least one edge, false otherwise. /// @complexity O(1) - constexpr bool has_edge() const noexcept { return edges_size_ > 0; } + constexpr bool has_edge() const noexcept; private: // CPO support via ADL (friend functions) /// @brief Get edges from a vertex descriptor (CPO: edges(g, u)). @@ -1055,30 +1012,20 @@ class base_undirected_adjacency_list { /// @brief Get range of all edges. /// @note Each undirected edge appears twice in iteration (once from each endpoint). /// @complexity O(1) to create range, O(V+E) to iterate. - edge_range edges() { return {edge_iterator(*this, begin()), edge_iterator(*this, end()), this->edges_size_}; } - const_edge_range edges() const { - return {const_edge_iterator(*this, const_cast(*this).begin()), - const_edge_iterator(*this, const_cast(*this).end()), this->edges_size_}; - } + edge_range edges(); + const_edge_range edges() const; public: // Vertex Creation /// @brief Create a new vertex with default value. /// @return Iterator to the newly created vertex. /// @complexity O(1) amortized - vertex_iterator create_vertex() { - this->vertices_.push_back(vertex_type(this->vertices_, static_cast(this->vertices_.size()))); - return this->vertices_.begin() + static_cast(this->vertices_.size() - 1); -} + vertex_iterator add_vertex(); /// @brief Create a new vertex with moved value. /// @param val Value to move into the vertex. /// @return Iterator to the newly created vertex. /// @complexity O(1) amortized - vertex_iterator create_vertex(vertex_value_type&& val) { - this->vertices_.push_back( - vertex_type(this->vertices_, static_cast(this->vertices_.size()), std::move(val))); - return this->vertices_.begin() + static_cast(this->vertices_.size() - 1); - } + vertex_iterator add_vertex(vertex_value_type&& val); /// @brief Create a new vertex with copied value. /// @tparam VV2 Type convertible to vertex_value_type. @@ -1087,11 +1034,7 @@ class base_undirected_adjacency_list { /// @complexity O(1) amortized template requires std::constructible_from - vertex_iterator create_vertex(const VV2& val) { - this->vertices_.push_back( - vertex_type(this->vertices_, static_cast(this->vertices_.size()), val)); - return this->vertices_.begin() + static_cast(this->vertices_.size() - 1); - } + vertex_iterator add_vertex(const VV2& val); public: // Edge Creation /// @brief Create an edge between two vertices (by id). @@ -1099,11 +1042,7 @@ class base_undirected_adjacency_list { /// @param vid Target vertex id. /// @return Iterator to the newly created edge. /// @complexity O(1). - vertex_edge_iterator create_edge(vertex_id_type uid, vertex_id_type vid) { - vertex_iterator ui = try_find_vertex(uid); - vertex_iterator vi = try_find_vertex(vid); - return create_edge(ui, vi); - } + vertex_edge_iterator add_edge(vertex_id_type uid, vertex_id_type vid); /// @brief Create an edge with value between two vertices (by id, move value). /// @param uid Source vertex id. @@ -1111,11 +1050,7 @@ class base_undirected_adjacency_list { /// @param val Edge value to move. /// @return Iterator to the newly created edge. /// @complexity O(1). - vertex_edge_iterator create_edge(vertex_id_type uid, vertex_id_type vid, edge_value_type&& val) { - vertex_iterator ui = this->vertices_.begin() + uid; - vertex_iterator vi = this->vertices_.begin() + vid; - return create_edge(ui, vi, std::move(val)); - } + vertex_edge_iterator add_edge(vertex_id_type uid, vertex_id_type vid, edge_value_type&& val); /// @brief Create an edge with value between two vertices (by id, copy value). /// @tparam EV2 Type convertible to edge_value_type. @@ -1126,24 +1061,14 @@ class base_undirected_adjacency_list { /// @complexity O(1). template requires std::constructible_from - vertex_edge_iterator create_edge(vertex_id_type uid, vertex_id_type vid, const EV2& val) { - vertex_iterator ui = this->vertices_.begin() + uid; - vertex_iterator vi = this->vertices_.begin() + vid; - return create_edge(ui, vi, val); - } + vertex_edge_iterator add_edge(vertex_id_type uid, vertex_id_type vid, const EV2& val); /// @brief Create an edge between two vertices (by iterator). /// @param u Source vertex iterator. /// @param v Target vertex iterator. /// @return Iterator to the newly created edge. /// @complexity O(1). - vertex_edge_iterator create_edge(vertex_iterator u, vertex_iterator v) { - vertex_id_type uid = static_cast(u - this->vertices_.begin()); - edge_type* uv = this->edge_alloc_.allocate(1); - new (uv) edge_type(static_cast(*this), u, v); - ++this->edges_size_; - return vertex_edge_iterator(static_cast(*this), uid, uv); - } + vertex_edge_iterator add_edge(vertex_iterator u, vertex_iterator v); /// @brief Create an edge with value between two vertices (by iterator, move value). /// @param u Source vertex iterator. @@ -1151,13 +1076,7 @@ class base_undirected_adjacency_list { /// @param val Edge value to move. /// @return Iterator to the newly created edge. /// @complexity O(1). - vertex_edge_iterator create_edge(vertex_iterator u, vertex_iterator v, edge_value_type&& val) { - vertex_id_type uid = static_cast(u - this->vertices_.begin()); - edge_type* uv = this->edge_alloc_.allocate(1); - new (uv) edge_type(static_cast(*this), u, v, std::move(val)); - ++this->edges_size_; - return vertex_edge_iterator(static_cast(*this), uid, uv); - } + vertex_edge_iterator add_edge(vertex_iterator u, vertex_iterator v, edge_value_type&& val); /// @brief Create an edge with value between two vertices (by iterator, copy value). /// @tparam EV2 Type convertible to edge_value_type. @@ -1168,20 +1087,30 @@ class base_undirected_adjacency_list { /// @complexity O(1). template requires std::constructible_from - vertex_edge_iterator create_edge(vertex_iterator u, vertex_iterator v, const EV2& val) { - vertex_id_type uid = static_cast(u - this->vertices_.begin()); - edge_type* uv = this->edge_alloc_.allocate(1); - new (uv) edge_type(static_cast(*this), u, v, val); - ++this->edges_size_; - return vertex_edge_iterator(static_cast(*this), uid, uv); - } + vertex_edge_iterator add_edge(vertex_iterator u, vertex_iterator v, const EV2& val); public: // Edge Removal - /// @brief Erase an edge from the graph. - /// @param pos Iterator to the edge to erase. + /// @brief Remove an edge from the graph. + /// @param pos Iterator to the edge to remove. /// @return Iterator to the next edge. /// @complexity O(1) to unlink from both vertex edge lists. - edge_iterator erase_edge(edge_iterator pos); + edge_iterator remove_edge(edge_iterator pos); + + /// @brief Remove the edge(s) between two vertices (by id). + /// @param uid One endpoint vertex id. + /// @param vid The other endpoint vertex id. + /// @return The number of edges removed. + /// @throws std::out_of_range if either vertex id is out of range. + /// @complexity O(degree(uid)). + vertex_size_type remove_edge(vertex_id_type uid, vertex_id_type vid); + +public: // Vertex Removal + /// @brief Remove a vertex and all of its incident edges from the graph. + /// @param uid The id of the vertex to remove. + /// @note Vertices with an id greater than @p uid are renumbered (shifted down by one). + /// @throws std::out_of_range if @p uid is out of range. + /// @complexity O(V + E). + void remove_vertex(vertex_id_type uid); public: // Graph Modification /// @brief Remove all vertices and edges from the graph. @@ -1215,115 +1144,9 @@ class base_undirected_adjacency_list { /// These functions provide CPO customization via ADL for all undirected_adjacency_list /// specializations. They are defined once as non-member templates to avoid redefinition /// errors when multiple instantiations of base_undirected_adjacency_list exist. -///------------------------------------------------------------------------------------- - -/// find_vertex(g, id) - returns view iterator yielding vertex_descriptor -/// REQUIRED: Provides bounds checking - returns end() if id >= size() -/// The default CPO implementation lacks this bounds checking. -template class VContainer, - typename Alloc> -constexpr auto find_vertex(undirected_adjacency_list& g, VId id) noexcept { - using graph_type = undirected_adjacency_list; - using vertex_set = typename graph_type::vertex_set; - using container_iter = typename vertex_set::iterator; - using view_type = vertex_descriptor_view; - using view_iterator = typename view_type::iterator; - using storage_type = typename view_iterator::value_type::storage_type; - - if (id >= static_cast(g.vertices().size())) { - return view_iterator{static_cast(g.vertices().size())}; - } - return view_iterator{static_cast(id)}; -} - -template class VContainer, - typename Alloc> -constexpr auto find_vertex(const undirected_adjacency_list& g, VId id) noexcept { - using graph_type = undirected_adjacency_list; - using vertex_set = typename graph_type::vertex_set; - using container_iter = typename vertex_set::const_iterator; - using view_type = vertex_descriptor_view; - using view_iterator = typename view_type::iterator; - using storage_type = typename view_iterator::value_type::storage_type; - - if (id >= static_cast(g.vertices().size())) { - return view_iterator{static_cast(g.vertices().size())}; - } - return view_iterator{static_cast(id)}; -} - -/// target_id(g, edge_descriptor) - get target vertex id from edge descriptor (iteration perspective) -/// For undirected graphs, the target is the "other" vertex relative to the source we're iterating from -/// This provides the ITERATION perspective, not the STORAGE perspective. -/// See ual_edge::list_owner_id()/list_target_id() documentation for the distinction. -template class VContainer, - typename Alloc, - typename E> -requires edge_descriptor_type -constexpr VId target_id(const undirected_adjacency_list& g, const E& e) noexcept { - return e.value()->other_vertex_id(g, static_cast(e.source_id())); -} - -/// source_id(g, edge_descriptor) - get source vertex id from edge descriptor (iteration perspective) -/// For undirected graphs, the source is the vertex we're iterating from (stored in descriptor) -/// This provides the ITERATION perspective, not the STORAGE perspective. /// -/// Uses edge_descriptor.source_id() which returns the source from iteration context, -/// NOT ual_edge.list_owner_id() which returns the storage location. -/// See ual_edge::list_owner_id()/list_target_id() documentation for the distinction. -template class VContainer, - typename Alloc, - typename E> -requires edge_descriptor_type -constexpr VId source_id([[maybe_unused]] const undirected_adjacency_list& g, - const E& e) noexcept { - return static_cast(e.source_id()); -} - -/// edge_value(g, edge_descriptor) - get edge value from edge descriptor -/// Extracts the edge value from the underlying edge pointed to by the descriptor. -/// Only enabled when EV is not void. -template class VContainer, - typename Alloc, - typename E> -requires edge_descriptor_type && (!std::is_void_v) -constexpr decltype(auto) - edge_value(undirected_adjacency_list&, const E& e) noexcept { - return e.value()->value(); -} - -template class VContainer, - typename Alloc, - typename E> -requires edge_descriptor_type && (!std::is_void_v) -constexpr decltype(auto) - edge_value(const undirected_adjacency_list&, const E& e) noexcept { - return e.value()->value(); -} +/// Definitions are in undirected_adjacency_list_impl.hpp (included below). +///------------------------------------------------------------------------------------- ///------------------------------------------------------------------------------------- /// ual_vertex @@ -1576,24 +1399,24 @@ class ual_neighbor_iterator : public ual_const_neighbor_iterator public: // Forward declare all the same public methods as the primary template - // (constructor declarations, accessors, create_vertex, create_edge, etc.) + // (constructor declarations, accessors, add_vertex, add_edge, etc.) // The implementations will be shared via undirected_adjacency_list_impl.hpp undirected_adjacency_list() = default; @@ -2033,15 +1860,19 @@ class undirected_adjacency_list public: // Vertex creation // Base class vertex creation methods - using base_type::create_vertex; + using base_type::add_vertex; public: // Edge creation // Base class edge creation methods - using base_type::create_edge; + using base_type::add_edge; public: // Edge removal // Base class edge removal methods - using base_type::erase_edge; + using base_type::remove_edge; + +public: // Vertex removal + // Base class vertex removal method + using base_type::remove_vertex; public: // Graph operations // Base class graph operations diff --git a/tests/algorithms/test_connected_components.cpp b/tests/algorithms/test_connected_components.cpp index 45ba739..9d6dd3f 100644 --- a/tests/algorithms/test_connected_components.cpp +++ b/tests/algorithms/test_connected_components.cpp @@ -423,7 +423,7 @@ TEST_CASE("connected_components (UAL) - single vertex", "[algorithm][connected_c using Graph = undirected_adjacency_list; Graph g; - g.create_vertex(0); + g.add_vertex(0); std::vector component(num_vertices(g)); size_t num_components = connected_components(g, container_value_fn(component)); @@ -491,7 +491,7 @@ TEST_CASE("connected_components (UAL) - isolated vertices", "[algorithm][connect // Five isolated vertices Graph g; for (int i = 0; i < 5; ++i) { - g.create_vertex(i); + g.add_vertex(i); } std::vector component(num_vertices(g)); @@ -602,10 +602,10 @@ TEST_CASE("connected_components (UAL) - with vertex values", "[algorithm][connec // Disconnected with vertex values Graph g; - g.create_vertex(100); - g.create_vertex(200); - g.create_vertex(300); - g.create_edge(0, 1, 1); + g.add_vertex(100); + g.add_vertex(200); + g.add_vertex(300); + g.add_edge(0, 1, 1); std::vector component(num_vertices(g)); size_t num_components = connected_components(g, container_value_fn(component)); diff --git a/tests/container/CMakeLists.txt b/tests/container/CMakeLists.txt index 73f53a4..fced52c 100644 --- a/tests/container/CMakeLists.txt +++ b/tests/container/CMakeLists.txt @@ -35,6 +35,7 @@ add_executable(graph3_container_tests dynamic_graph/test_dynamic_graph_common.cpp dynamic_graph/test_dynamic_edge_comparison.cpp dynamic_graph/test_dynamic_graph_nonintegral_ids.cpp + dynamic_graph/test_dynamic_graph_mutation.cpp dynamic_graph/test_dynamic_graph_integration.cpp dynamic_graph/test_dynamic_graph_stl_algorithms.cpp dynamic_graph/test_dynamic_graph_generic_queries.cpp @@ -63,6 +64,7 @@ add_executable(graph3_container_tests # undirected_adjacency_list undirected_adjacency_list/test_undirected_adjacency_list.cpp undirected_adjacency_list/test_undirected_adjacency_list_cpo.cpp + undirected_adjacency_list/test_undirected_adjacency_list_mutation.cpp undirected_adjacency_list/test_undirected_bidirectional.cpp ) diff --git a/tests/container/dynamic_graph/test_dynamic_graph_mutation.cpp b/tests/container/dynamic_graph/test_dynamic_graph_mutation.cpp new file mode 100644 index 0000000..f60139c --- /dev/null +++ b/tests/container/dynamic_graph/test_dynamic_graph_mutation.cpp @@ -0,0 +1,521 @@ +/** + * @file test_dynamic_graph_mutation.cpp + * @brief Comprehensive tests for the dynamic_graph mutation API. + * + * Exercises the BGL-style mutation members added to dynamic_graph_base: + * - add_vertex() (sequential containers: append, return new id) + * - add_vertex(val) (sequential containers, VV != void) + * - add_vertex(id) (associative containers: keyed, returns bool) + * - add_vertex(id, val) (associative containers, VV != void) + * - add_edge(u, v) / add_edge(u, v, val) + * - remove_edge(u, v) + * - remove_vertex(u) + * + * Coverage spans: + * - Sequential vertex containers (vector / deque) vs. associative (map / unordered_map) + * - Edge containers: vector, list, forward_list, set, map (rekeying paths) + * - Integral and non-integral (std::string) vertex ids (sparse keys) + * - Edge value present (EV != void) and absent (EV == void) + * - Vertex value present (VV != void) and absent + * - Bidirectional (in_edges) maintenance + * + * Duplication is minimized via TEMPLATE_TEST_CASE and a couple of container-category + * aware helpers (add_n_vertices / sequential_vertices concept). + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace graph::container; + +using graph::adj_list::num_vertices; +using graph::adj_list::num_edges; +using graph::adj_list::find_vertex; +using graph::adj_list::vertex_id; +using graph::adj_list::vertex_value; +using graph::adj_list::edges; +using graph::adj_list::degree; +using graph::adj_list::target_id; +using graph::adj_list::edge_value; +using graph::adj_list::in_degree; + +//================================================================================================== +// Container-category helpers +//================================================================================================== + +// A graph has sequential vertices iff the no-argument add_vertex() overload is available. +template +concept sequential_vertices = requires(G g) { + { g.add_vertex() } -> std::same_as; +}; + +// Create n vertices with ids 0..n-1 and return them. Works for both container categories: +// sequential containers append and return the generated id; associative containers receive +// the explicit integral key. +template +std::vector add_n_vertices(G& g, std::size_t n) { + using vid_t = typename G::vertex_id_type; + std::vector ids; + ids.reserve(n); + for (std::size_t i = 0; i < n; ++i) { + if constexpr (sequential_vertices) { + ids.push_back(g.add_vertex()); + } else { + vid_t id = static_cast(i); + g.add_vertex(id); + ids.push_back(id); + } + } + return ids; +} + +// Collect the set of out-edge target ids for a given source vertex id. +template +std::multiset out_targets(G& g, typename G::vertex_id_type uid) { + std::multiset t; + for (auto e : edges(g, uid)) + t.insert(target_id(g, e)); + return t; +} + +//================================================================================================== +// Type aliases +//================================================================================================== + +// --- Sequential, weighted (EV=int), no vertex value --- +using seq_vov_w = vov_graph; +using seq_vol_w = vol_graph; +using seq_vofl_w = vofl_graph; +using seq_vos_w = vos_graph; // set edges: rekey path +using seq_vom_w = vom_graph; // map edges: rekey path +using seq_dov_w = dov_graph; // deque vertices + +// --- Sequential, unweighted (EV=void) --- +using seq_vov_u = vov_graph; +using seq_vol_u = vol_graph; +using seq_dov_u = dov_graph; + +// --- Sequential, with vertex value (VV=int) --- +using seq_vov_vv = vov_graph; +using seq_vol_vv = vol_graph; +using seq_dov_vv = dov_graph; + +// --- Associative, weighted (EV=int), integral keys --- +using assoc_mov_w = mov_graph; +using assoc_mol_w = mol_graph; +using assoc_uov_w = uov_graph; + +// --- Associative, with vertex value (VV=int) --- +using assoc_mov_vv = mov_graph; +using assoc_uov_vv = uov_graph; + +// --- Associative, non-integral (std::string) keys --- +using assoc_str_w = mol_graph; + +//================================================================================================== +// Non-uniform bidirectional traits (so in-edges store source_id and in_degree works) +//================================================================================================== + +template +struct vov_bidir_traits { + using edge_value_type = EV; + using vertex_value_type = VV; + using graph_value_type = GV; + using vertex_id_type = VId; + static constexpr bool bidirectional = true; + + using edge_type = dynamic_out_edge; + using in_edge_type = dynamic_in_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; + + using edges_type = std::vector; + using in_edges_type = std::vector; + using vertices_type = std::vector; +}; + +template +struct vol_bidir_traits { + using edge_value_type = EV; + using vertex_value_type = VV; + using graph_value_type = GV; + using vertex_id_type = VId; + static constexpr bool bidirectional = true; + + using edge_type = dynamic_out_edge; + using in_edge_type = dynamic_in_edge; + using vertex_type = dynamic_vertex; + using graph_type = dynamic_graph; + + using edges_type = std::list; + using in_edges_type = std::list; + using vertices_type = std::vector; +}; + +using bidir_vov = dynamic_graph>; +using bidir_vol = dynamic_graph>; + +//================================================================================================== +// 1. add_vertex — sequential containers +//================================================================================================== + +TEMPLATE_TEST_CASE("add_vertex appends and returns sequential ids", "[mutation][add_vertex][sequential]", + seq_vov_w, seq_vol_w, seq_vofl_w, seq_vos_w, seq_vom_w, seq_dov_w, + seq_vov_u, seq_dov_u) { + TestType g; + REQUIRE(num_vertices(g) == 0); + + auto id0 = g.add_vertex(); + auto id1 = g.add_vertex(); + auto id2 = g.add_vertex(); + + REQUIRE(id0 == 0); + REQUIRE(id1 == 1); + REQUIRE(id2 == 2); + REQUIRE(num_vertices(g) == 3); + REQUIRE(num_edges(g) == 0); + REQUIRE(g.contains_vertex(0)); + REQUIRE(g.contains_vertex(2)); + REQUIRE_FALSE(g.contains_vertex(3)); +} + +//================================================================================================== +// 2. add_vertex(val) — sequential containers with vertex value +//================================================================================================== + +TEMPLATE_TEST_CASE("add_vertex with value stores the vertex value", "[mutation][add_vertex][value][sequential]", + seq_vov_vv, seq_vol_vv, seq_dov_vv) { + TestType g; + + auto id0 = g.add_vertex(100); + auto id1 = g.add_vertex(200); + + REQUIRE(id0 == 0); + REQUIRE(id1 == 1); + REQUIRE(num_vertices(g) == 2); + + REQUIRE(vertex_value(g, *find_vertex(g, id0)) == 100); + REQUIRE(vertex_value(g, *find_vertex(g, id1)) == 200); +} + +//================================================================================================== +// 3. add_vertex(id) — associative containers (sparse / idempotent) +//================================================================================================== + +TEMPLATE_TEST_CASE("add_vertex with key inserts sparse vertices", "[mutation][add_vertex][associative]", + assoc_mov_w, assoc_mol_w, assoc_uov_w) { + TestType g; + using vid_t = typename TestType::vertex_id_type; + + REQUIRE(g.add_vertex(vid_t{5}) == true); + REQUIRE(g.add_vertex(vid_t{100}) == true); + REQUIRE(g.add_vertex(vid_t{7}) == true); + REQUIRE(num_vertices(g) == 3); + REQUIRE(g.contains_vertex(vid_t{5})); + REQUIRE(g.contains_vertex(vid_t{100})); + REQUIRE(g.contains_vertex(vid_t{7})); + REQUIRE_FALSE(g.contains_vertex(vid_t{6})); + + SECTION("re-adding an existing key is a no-op returning false") { + REQUIRE(g.add_vertex(vid_t{5}) == false); + REQUIRE(num_vertices(g) == 3); + } +} + +TEST_CASE("add_vertex preserves edges of an existing key", "[mutation][add_vertex][associative]") { + assoc_mov_w g; + REQUIRE(g.add_vertex(1u)); + REQUIRE(g.add_vertex(2u)); + g.add_edge(1u, 2u, 42); + REQUIRE(num_edges(g) == 1); + + // Re-adding key 1 must not disturb its existing edge. + REQUIRE(g.add_vertex(1u) == false); + REQUIRE(num_edges(g) == 1); + REQUIRE(degree(g, 1u) == 1); +} + +//================================================================================================== +// 4. add_vertex(id, val) — associative containers with vertex value +//================================================================================================== + +TEMPLATE_TEST_CASE("add_vertex with key and value", "[mutation][add_vertex][value][associative]", + assoc_mov_vv, assoc_uov_vv) { + TestType g; + using vid_t = typename TestType::vertex_id_type; + + REQUIRE(g.add_vertex(vid_t{10}, 111) == true); + REQUIRE(g.add_vertex(vid_t{20}, 222) == true); + REQUIRE(num_vertices(g) == 2); + REQUIRE(vertex_value(g, *find_vertex(g, vid_t{10})) == 111); + REQUIRE(vertex_value(g, *find_vertex(g, vid_t{20})) == 222); + + SECTION("re-adding an existing key updates the value and returns false") { + REQUIRE(g.add_vertex(vid_t{10}, 999) == false); + REQUIRE(num_vertices(g) == 2); + REQUIRE(vertex_value(g, *find_vertex(g, vid_t{10})) == 999); + } +} + +//================================================================================================== +// 5. Non-integral (std::string) vertex ids +//================================================================================================== + +TEST_CASE("add_vertex / add_edge with non-integral string ids", "[mutation][add_vertex][nonintegral]") { + assoc_str_w g; + + REQUIRE(g.add_vertex(std::string{"alice"}) == true); + REQUIRE(g.add_vertex(std::string{"bob"}) == true); + REQUIRE(g.add_vertex(std::string{"carol"}) == true); + REQUIRE(g.add_vertex(std::string{"alice"}) == false); // duplicate + REQUIRE(num_vertices(g) == 3); + + g.add_edge(std::string{"alice"}, std::string{"bob"}, 7); + g.add_edge(std::string{"alice"}, std::string{"carol"}, 9); + REQUIRE(num_edges(g) == 2); + REQUIRE(degree(g, std::string{"alice"}) == 2); + + auto t = out_targets(g, std::string{"alice"}); + REQUIRE(t.count("bob") == 1); + REQUIRE(t.count("carol") == 1); + + SECTION("remove_vertex keeps remaining string keys stable") { + g.remove_vertex(std::string{"bob"}); + REQUIRE_FALSE(g.contains_vertex(std::string{"bob"})); + REQUIRE(g.contains_vertex(std::string{"alice"})); + REQUIRE(g.contains_vertex(std::string{"carol"})); + REQUIRE(num_edges(g) == 1); // alice->bob removed, alice->carol remains + REQUIRE(degree(g, std::string{"alice"}) == 1); + } +} + +//================================================================================================== +// 6. add_edge — weighted, all container categories +//================================================================================================== + +TEMPLATE_TEST_CASE("add_edge with value builds a weighted graph", "[mutation][add_edge][weighted]", + seq_vov_w, seq_vol_w, seq_vofl_w, seq_vos_w, seq_vom_w, seq_dov_w, + assoc_mov_w, assoc_mol_w, assoc_uov_w) { + TestType g; + auto ids = add_n_vertices(g, 3); // 0, 1, 2 + + g.add_edge(ids[0], ids[1], 10); + g.add_edge(ids[0], ids[2], 30); + g.add_edge(ids[1], ids[2], 20); + + REQUIRE(num_edges(g) == 3); + REQUIRE(degree(g, ids[0]) == 2); + REQUIRE(degree(g, ids[1]) == 1); + REQUIRE(degree(g, ids[2]) == 0); + + REQUIRE(out_targets(g, ids[0]) == std::multiset{ids[1], ids[2]}); + + SECTION("edge values are stored") { + int sum = 0; + for (auto e : edges(g, ids[0])) + sum += edge_value(g, e); + REQUIRE(sum == 40); + } +} + +//================================================================================================== +// 7. add_edge — unweighted +//================================================================================================== + +TEMPLATE_TEST_CASE("add_edge without value builds an unweighted graph", "[mutation][add_edge][unweighted]", + seq_vov_u, seq_vol_u, seq_dov_u) { + TestType g; + auto ids = add_n_vertices(g, 3); + + g.add_edge(ids[0], ids[1]); + g.add_edge(ids[0], ids[2]); + + REQUIRE(num_edges(g) == 2); + REQUIRE(degree(g, ids[0]) == 2); + REQUIRE(degree(g, ids[1]) == 0); +} + +//================================================================================================== +// 8. add_edge throws when a vertex is missing +//================================================================================================== + +TEST_CASE("add_edge throws std::out_of_range for a missing vertex", "[mutation][add_edge][error]") { + SECTION("sequential container") { + seq_vov_w g; + add_n_vertices(g, 2); // ids 0, 1 + REQUIRE_THROWS_AS(g.add_edge(0u, 5u, 1), std::out_of_range); + REQUIRE_THROWS_AS(g.add_edge(5u, 0u, 1), std::out_of_range); + } + SECTION("associative container") { + assoc_mov_w g; + g.add_vertex(0u); + REQUIRE_THROWS_AS(g.add_edge(0u, 99u, 1), std::out_of_range); + REQUIRE_THROWS_AS(g.add_edge(99u, 0u, 1), std::out_of_range); + } +} + +//================================================================================================== +// 9. remove_edge +//================================================================================================== + +TEMPLATE_TEST_CASE("remove_edge removes edges and reports the count", "[mutation][remove_edge]", + seq_vov_w, seq_vol_w, seq_vos_w, seq_vom_w, seq_dov_w, + assoc_mov_w, assoc_mol_w, assoc_uov_w) { + TestType g; + auto ids = add_n_vertices(g, 3); + g.add_edge(ids[0], ids[1], 10); + g.add_edge(ids[0], ids[2], 30); + g.add_edge(ids[1], ids[2], 20); + REQUIRE(num_edges(g) == 3); + + SECTION("removing an existing edge") { + REQUIRE(g.remove_edge(ids[0], ids[1]) == 1); + REQUIRE(num_edges(g) == 2); + REQUIRE(degree(g, ids[0]) == 1); + REQUIRE(out_targets(g, ids[0]) == std::multiset{ids[2]}); + } + + SECTION("removing a non-existent edge returns 0") { + REQUIRE(g.remove_edge(ids[2], ids[0]) == 0); + REQUIRE(num_edges(g) == 3); + } + + SECTION("removing an edge from a missing source returns 0") { + REQUIRE(g.remove_edge(typename TestType::vertex_id_type{99}, ids[0]) == 0); + REQUIRE(num_edges(g) == 3); + } +} + +//================================================================================================== +// 10. remove_vertex — sequential containers (id renumbering) +//================================================================================================== + +TEMPLATE_TEST_CASE("remove_vertex renumbers ids on sequential containers", "[mutation][remove_vertex][sequential]", + seq_vov_w, seq_vol_w, seq_vos_w, seq_vom_w, seq_dov_w) { + TestType g; + add_n_vertices(g, 4); // ids 0,1,2,3 + // path 0 -> 1 -> 2 -> 3 + g.add_edge(0u, 1u, 1); + g.add_edge(1u, 2u, 2); + g.add_edge(2u, 3u, 3); + REQUIRE(num_edges(g) == 3); + + g.remove_vertex(1u); + + // Vertex 1 gone: its out-edge (1->2) and the in-edge (0->1) disappear. + // The surviving edge (2->3) is renumbered to (1->2). + REQUIRE(num_vertices(g) == 3); + REQUIRE(num_edges(g) == 1); + REQUIRE(degree(g, 0u) == 0); + REQUIRE(degree(g, 1u) == 1); // old vertex 2 + REQUIRE(degree(g, 2u) == 0); // old vertex 3 + REQUIRE(out_targets(g, 1u) == std::multiset{2u}); +} + +//================================================================================================== +// 11. remove_vertex — associative containers (stable ids) +//================================================================================================== + +TEMPLATE_TEST_CASE("remove_vertex keeps ids stable on associative containers", + "[mutation][remove_vertex][associative]", + assoc_mov_w, assoc_mol_w, assoc_uov_w) { + TestType g; + using vid_t = typename TestType::vertex_id_type; + g.add_vertex(vid_t{10}); + g.add_vertex(vid_t{20}); + g.add_vertex(vid_t{30}); + g.add_edge(vid_t{10}, vid_t{20}, 1); + g.add_edge(vid_t{10}, vid_t{30}, 2); + g.add_edge(vid_t{20}, vid_t{30}, 3); + REQUIRE(num_edges(g) == 3); + + g.remove_vertex(vid_t{20}); + + REQUIRE(num_vertices(g) == 2); + REQUIRE_FALSE(g.contains_vertex(vid_t{20})); + REQUIRE(g.contains_vertex(vid_t{10})); + REQUIRE(g.contains_vertex(vid_t{30})); + // (10->20) removed and (20->30) gone with the vertex; (10->30) remains. + REQUIRE(num_edges(g) == 1); + REQUIRE(degree(g, vid_t{10}) == 1); + REQUIRE(out_targets(g, vid_t{10}) == std::multiset{vid_t{30}}); +} + +TEST_CASE("remove_vertex on a missing id is a no-op", "[mutation][remove_vertex]") { + seq_vov_w g; + add_n_vertices(g, 2); + g.add_edge(0u, 1u, 5); + g.remove_vertex(99u); + REQUIRE(num_vertices(g) == 2); + REQUIRE(num_edges(g) == 1); +} + +//================================================================================================== +// 12. Bidirectional in_edge maintenance +//================================================================================================== + +TEMPLATE_TEST_CASE("bidirectional add_edge maintains in_edges", "[mutation][bidirectional][add_edge]", + bidir_vov, bidir_vol) { + TestType g; + add_n_vertices(g, 3); + g.add_edge(0u, 1u, 10); + g.add_edge(0u, 2u, 30); + g.add_edge(1u, 2u, 20); + + REQUIRE(num_edges(g) == 3); + REQUIRE(in_degree(g, 0u) == 0); + REQUIRE(in_degree(g, 1u) == 1); + REQUIRE(in_degree(g, 2u) == 2); +} + +TEMPLATE_TEST_CASE("bidirectional remove_edge unlinks in_edges", "[mutation][bidirectional][remove_edge]", + bidir_vov, bidir_vol) { + TestType g; + add_n_vertices(g, 3); + g.add_edge(0u, 2u, 30); + g.add_edge(1u, 2u, 20); + REQUIRE(in_degree(g, 2u) == 2); + + REQUIRE(g.remove_edge(0u, 2u) == 1); + REQUIRE(num_edges(g) == 1); + REQUIRE(in_degree(g, 2u) == 1); + REQUIRE(degree(g, 0u) == 0); +} + +TEMPLATE_TEST_CASE("bidirectional remove_vertex unlinks neighbor in/out edges", + "[mutation][bidirectional][remove_vertex]", + bidir_vov, bidir_vol) { + TestType g; + add_n_vertices(g, 4); // 0,1,2,3 + g.add_edge(0u, 1u, 1); + g.add_edge(1u, 2u, 2); + g.add_edge(2u, 3u, 3); + REQUIRE(num_edges(g) == 3); + + g.remove_vertex(1u); + + // Surviving edge (2->3) renumbers to (1->2). + REQUIRE(num_vertices(g) == 3); + REQUIRE(num_edges(g) == 1); + REQUIRE(in_degree(g, 0u) == 0); + REQUIRE(in_degree(g, 2u) == 1); // old vertex 3 keeps one incoming edge + REQUIRE(degree(g, 1u) == 1); // old vertex 2 +} diff --git a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp index 219b069..b62c889 100644 --- a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp +++ b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list.cpp @@ -56,7 +56,7 @@ TEST_CASE("empty graph properties", "[undirected_adjacency_list][empty]") { TEST_CASE("create single vertex", "[undirected_adjacency_list][vertex][create]") { undirected_adjacency_list g; - auto v_it = g.create_vertex(42); + auto v_it = g.add_vertex(42); SECTION("graph has one vertex") { REQUIRE(g.vertices().size() == 1); @@ -79,11 +79,11 @@ TEST_CASE("create single vertex", "[undirected_adjacency_list][vertex][create]") TEST_CASE("create multiple vertices", "[undirected_adjacency_list][vertex][create]") { undirected_adjacency_list g; - // Note: create_vertex may invalidate iterators due to vector reallocation + // Note: add_vertex may invalidate iterators due to vector reallocation // Store keys (indices) instead of iterators, or re-fetch after all insertions - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); // Re-fetch iterators after all vertices are created auto v1 = g.begin(); @@ -108,12 +108,12 @@ TEST_CASE("create multiple vertices", "[undirected_adjacency_list][vertex][creat TEST_CASE("create single edge", "[undirected_adjacency_list][edge][create]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto e_it = g.create_edge(k1, k2, 100); + auto e_it = g.add_edge(k1, k2, 100); SECTION("graph has one edge") { REQUIRE(g.num_edges() == 1); } @@ -133,16 +133,16 @@ TEST_CASE("create single edge", "[undirected_adjacency_list][edge][create]") { TEST_CASE("create multiple edges", "[undirected_adjacency_list][edge][create]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k2, k3, 200); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k2, k3, 200); + g.add_edge(k1, k3, 300); SECTION("graph has three edges") { REQUIRE(g.num_edges() == 3); } @@ -156,16 +156,16 @@ TEST_CASE("create multiple edges", "[undirected_adjacency_list][edge][create]") TEST_CASE("remove edge", "[undirected_adjacency_list][edge][erase]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k2, k3, 200); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k2, k3, 200); + g.add_edge(k1, k3, 300); REQUIRE(g.num_edges() == 3); @@ -186,7 +186,7 @@ TEST_CASE("remove edge", "[undirected_adjacency_list][edge][erase]") { TEST_CASE("modify vertex value", "[undirected_adjacency_list][vertex][modify]") { undirected_adjacency_list g; - auto v = g.create_vertex(10); + auto v = g.add_vertex(10); REQUIRE(v->value() == 10); v->value() = 99; @@ -197,12 +197,12 @@ TEST_CASE("modify vertex value", "[undirected_adjacency_list][vertex][modify]") TEST_CASE("modify edge value", "[undirected_adjacency_list][edge][modify]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto e = g.create_edge(k1, k2, 100); + auto e = g.add_edge(k1, k2, 100); REQUIRE(e->value() == 100); e->value() = 999; @@ -219,9 +219,9 @@ TEST_CASE("modify edge value", "[undirected_adjacency_list][edge][modify]") { TEST_CASE("iterate vertices", "[undirected_adjacency_list][vertex][iterate]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); SECTION("range-for iteration") { vector values; @@ -250,15 +250,15 @@ TEST_CASE("iterate vertices", "[undirected_adjacency_list][vertex][iterate]") { TEST_CASE("iterate edges from vertex", "[undirected_adjacency_list][edge][iterate]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k1, k3, 300); auto& v = g.vertices()[k1]; vector edge_values; @@ -275,10 +275,10 @@ TEST_CASE("iterate edges from vertex", "[undirected_adjacency_list][edge][iterat TEST_CASE("self-loop value storage", "[undirected_adjacency_list][edge][self_loop]") { undirected_adjacency_list g; - auto v_it = g.create_vertex(10); + auto v_it = g.add_vertex(10); VKey k = vertex_id(v_it, g); - g.create_edge(k, k, 100); + g.add_edge(k, k, 100); SECTION("graph recognizes edge") { REQUIRE(g.num_edges() == 1); } } @@ -305,9 +305,9 @@ TEST_CASE("graph value access", "[undirected_adjacency_list][graph_value]") { TEST_CASE("vertex_iterator basic", "[undirected_adjacency_list][iterators][vertex]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); auto it = g.begin(); REQUIRE(it != g.end()); @@ -325,8 +325,8 @@ TEST_CASE("vertex_iterator basic", "[undirected_adjacency_list][iterators][verte TEST_CASE("vertex_iterator postincrement", "[undirected_adjacency_list][iterators][vertex]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); + g.add_vertex(10); + g.add_vertex(20); auto it = g.begin(); auto old_it = it++; @@ -337,7 +337,7 @@ TEST_CASE("vertex_iterator postincrement", "[undirected_adjacency_list][iterator TEST_CASE("vertex_iterator dereference", "[undirected_adjacency_list][iterators][vertex]") { undirected_adjacency_list g; - g.create_vertex(42); + g.add_vertex(42); auto it = g.begin(); auto& vertex = *it; @@ -349,8 +349,8 @@ TEST_CASE("vertex_iterator dereference", "[undirected_adjacency_list][iterators] TEST_CASE("vertex_iterator comparison", "[undirected_adjacency_list][iterators][vertex]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); + g.add_vertex(10); + g.add_vertex(20); auto it1 = g.begin(); auto it2 = g.begin(); @@ -365,9 +365,9 @@ TEST_CASE("vertex_iterator comparison", "[undirected_adjacency_list][iterators][ TEST_CASE("vertex_iterator range-for", "[undirected_adjacency_list][iterators][vertex]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); vector values; for (auto& v : g.vertices()) { @@ -379,8 +379,8 @@ TEST_CASE("vertex_iterator range-for", "[undirected_adjacency_list][iterators][v TEST_CASE("const_vertex_iterator basic", "[undirected_adjacency_list][iterators][vertex][const]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); + g.add_vertex(10); + g.add_vertex(20); const auto& cg = g; auto it = cg.begin(); @@ -394,8 +394,8 @@ TEST_CASE("const_vertex_iterator basic", "[undirected_adjacency_list][iterators] TEST_CASE("const_vertex_iterator cbegin/cend", "[undirected_adjacency_list][iterators][vertex][const]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); + g.add_vertex(10); + g.add_vertex(20); auto it = g.cbegin(); REQUIRE(it->value() == 10); @@ -407,9 +407,9 @@ TEST_CASE("const_vertex_iterator cbegin/cend", "[undirected_adjacency_list][iter TEST_CASE("const_vertex_iterator range-for", "[undirected_adjacency_list][iterators][vertex][const]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); const auto& cg = g; vector values; @@ -423,13 +423,13 @@ TEST_CASE("const_vertex_iterator range-for", "[undirected_adjacency_list][iterat TEST_CASE("edge_iterator basic", "[undirected_adjacency_list][iterators][edge]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - g.create_edge(k1, k2, 100); - g.create_edge(k1, k2, 200); + g.add_edge(k1, k2, 100); + g.add_edge(k1, k2, 200); auto& vertex = g.vertices()[k1]; auto it = vertex.edges(g, k1).begin(); @@ -446,12 +446,12 @@ TEST_CASE("edge_iterator basic", "[undirected_adjacency_list][iterators][edge]") TEST_CASE("edge_iterator dereference", "[undirected_adjacency_list][iterators][edge]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - g.create_edge(k1, k2, 100); + g.add_edge(k1, k2, 100); auto& vertex = g.vertices()[k1]; auto it = vertex.edges(g, k1).begin(); @@ -466,12 +466,12 @@ TEST_CASE("edge_iterator dereference", "[undirected_adjacency_list][iterators][e TEST_CASE("edge_iterator comparison", "[undirected_adjacency_list][iterators][edge]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - g.create_edge(k1, k2, 100); + g.add_edge(k1, k2, 100); auto& vertex = g.vertices()[k1]; auto it1 = vertex.edges(g, k1).begin(); @@ -485,15 +485,15 @@ TEST_CASE("edge_iterator comparison", "[undirected_adjacency_list][iterators][ed TEST_CASE("edge_iterator range-for", "[undirected_adjacency_list][iterators][edge]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k1, k3, 300); auto& vertex = g.vertices()[k1]; vector values; @@ -509,15 +509,15 @@ TEST_CASE("edge_iterator range-for", "[undirected_adjacency_list][iterators][edg TEST_CASE("vertex_edge_iterator basic", "[undirected_adjacency_list][iterators][vertex_edge]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k1, k3, 300); auto& vertex = g.vertices()[k1]; auto range = vertex.edges(g, k1); @@ -528,7 +528,7 @@ TEST_CASE("vertex_edge_iterator basic", "[undirected_adjacency_list][iterators][ TEST_CASE("vertex_edge_iterator empty range", "[undirected_adjacency_list][iterators][vertex_edge]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); auto& vertex = g.vertices()[k1]; @@ -540,15 +540,15 @@ TEST_CASE("vertex_edge_iterator empty range", "[undirected_adjacency_list][itera TEST_CASE("neighbor_iterator basic", "[undirected_adjacency_list][iterators][vertex_vertex]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k1, k3, 300); auto& vertex = g.vertices()[k1]; auto range = vertex.neighbors(g, k1); @@ -565,7 +565,7 @@ TEST_CASE("neighbor_iterator basic", "[undirected_adjacency_list][iterators][ver TEST_CASE("neighbor_iterator empty", "[undirected_adjacency_list][iterators][vertex_vertex]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); auto& vertex = g.vertices()[k1]; @@ -576,12 +576,12 @@ TEST_CASE("neighbor_iterator empty", "[undirected_adjacency_list][iterators][ver TEST_CASE("neighbor_iterator dereference", "[undirected_adjacency_list][iterators][vertex_vertex]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - g.create_edge(k1, k2, 100); + g.add_edge(k1, k2, 100); auto& vertex = g.vertices()[k1]; auto it = vertex.neighbors(g, k1).begin(); @@ -594,9 +594,9 @@ TEST_CASE("neighbor_iterator dereference", "[undirected_adjacency_list][iterator TEST_CASE("std::find with vertex_iterator", "[undirected_adjacency_list][iterators][algorithms]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); auto it = std::find_if(g.begin(), g.end(), [](const auto& v) { return v.value() == 20; }); @@ -606,10 +606,10 @@ TEST_CASE("std::find with vertex_iterator", "[undirected_adjacency_list][iterato TEST_CASE("std::count_if with vertex_iterator", "[undirected_adjacency_list][iterators][algorithms]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); - g.create_vertex(40); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_vertex(40); auto count = std::count_if(g.begin(), g.end(), [](const auto& v) { return v.value() > 15; }); @@ -618,9 +618,9 @@ TEST_CASE("std::count_if with vertex_iterator", "[undirected_adjacency_list][ite TEST_CASE("std::for_each with vertex_iterator", "[undirected_adjacency_list][iterators][algorithms]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); int sum = 0; std::for_each(g.begin(), g.end(), [&sum](const auto& v) { sum += v.value(); }); @@ -630,9 +630,9 @@ TEST_CASE("std::for_each with vertex_iterator", "[undirected_adjacency_list][ite TEST_CASE("std::all_of with vertex_iterator", "[undirected_adjacency_list][iterators][algorithms]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); bool all_positive = std::all_of(g.begin(), g.end(), [](const auto& v) { return v.value() > 0; }); @@ -641,9 +641,9 @@ TEST_CASE("std::all_of with vertex_iterator", "[undirected_adjacency_list][itera TEST_CASE("std::any_of with vertex_iterator", "[undirected_adjacency_list][iterators][algorithms]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); bool has_twenty = std::any_of(g.begin(), g.end(), [](const auto& v) { return v.value() == 20; }); @@ -652,9 +652,9 @@ TEST_CASE("std::any_of with vertex_iterator", "[undirected_adjacency_list][itera TEST_CASE("std::none_of with vertex_iterator", "[undirected_adjacency_list][iterators][algorithms]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); bool none_negative = std::none_of(g.begin(), g.end(), [](const auto& v) { return v.value() < 0; }); @@ -663,15 +663,15 @@ TEST_CASE("std::none_of with vertex_iterator", "[undirected_adjacency_list][iter TEST_CASE("std::find with edge_iterator", "[undirected_adjacency_list][iterators][algorithms]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k1, k3, 300); auto& vertex = g.vertices()[k1]; auto range = vertex.edges(g, k1); @@ -684,11 +684,11 @@ TEST_CASE("std::find with edge_iterator", "[undirected_adjacency_list][iterators TEST_CASE("iterator subtraction", "[undirected_adjacency_list][iterators][vertex]") { undirected_adjacency_list g; - // Note: create_vertex may invalidate iterators due to vector reallocation + // Note: add_vertex may invalidate iterators due to vector reallocation // Create all vertices first, then get iterators - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); // Re-fetch iterators after all vertices are created auto v1 = g.begin(); @@ -712,11 +712,11 @@ TEST_CASE("self-loops behavior", "[undirected_adjacency_list][edge_cases][self_l // Self-loops are correctly handled by cycle detection in the iterator's advance() method. // The iterator detects when it returns to its starting edge and terminates iteration. undirected_adjacency_list g; - auto v_it = g.create_vertex(10); + auto v_it = g.add_vertex(10); VKey k = vertex_id(v_it, g); // Create self-loop - g.create_edge(k, k, 100); + g.add_edge(k, k, 100); SECTION("self-loop increases edges_size by 1") { REQUIRE(g.num_edges() == 1); } @@ -739,14 +739,14 @@ TEST_CASE("self-loops behavior", "[undirected_adjacency_list][edge_cases][self_l TEST_CASE("self-loop with regular edges", "[undirected_adjacency_list][edge_cases][self_loop]") { // Verify self-loops work correctly when mixed with regular edges undirected_adjacency_list g; - auto v0_it = g.create_vertex(10); + auto v0_it = g.add_vertex(10); VKey k0 = vertex_id(v0_it, g); - auto v1_it = g.create_vertex(20); + auto v1_it = g.add_vertex(20); VKey k1 = vertex_id(v1_it, g); // Create a regular edge and a self-loop on v0 - g.create_edge(k0, k1, 100); // Regular edge between v0 and v1 - g.create_edge(k0, k0, 200); // Self-loop on v0 + g.add_edge(k0, k1, 100); // Regular edge between v0 and v1 + g.add_edge(k0, k0, 200); // Self-loop on v0 SECTION("edges_size reflects both edges") { REQUIRE(g.num_edges() == 2); } @@ -777,13 +777,13 @@ TEST_CASE("self-loop with regular edges", "[undirected_adjacency_list][edge_case TEST_CASE("multiple self-loops on same vertex", "[undirected_adjacency_list][edge_cases][self_loop]") { // Verify multiple self-loops can exist on the same vertex undirected_adjacency_list g; - auto v_it = g.create_vertex(10); + auto v_it = g.add_vertex(10); VKey k = vertex_id(v_it, g); // Create multiple self-loops - g.create_edge(k, k, 100); - g.create_edge(k, k, 200); - g.create_edge(k, k, 300); + g.add_edge(k, k, 100); + g.add_edge(k, k, 200); + g.add_edge(k, k, 300); SECTION("edges_size reflects all self-loops") { REQUIRE(g.num_edges() == 3); } @@ -804,11 +804,11 @@ TEST_CASE("multiple self-loops on same vertex", "[undirected_adjacency_list][edg TEST_CASE("self-loop erasure", "[undirected_adjacency_list][edge_cases][self_loop]") { // Verify self-loops can be erased correctly undirected_adjacency_list g; - auto v_it = g.create_vertex(10); + auto v_it = g.add_vertex(10); VKey k = vertex_id(v_it, g); - g.create_edge(k, k, 100); - g.create_edge(k, k, 200); + g.add_edge(k, k, 100); + g.add_edge(k, k, 200); REQUIRE(g.num_edges() == 2); @@ -834,15 +834,15 @@ TEST_CASE("self-loop erasure", "[undirected_adjacency_list][edge_cases][self_loo TEST_CASE("parallel edges", "[undirected_adjacency_list][edge_cases][parallel]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); // Create 3 parallel edges - g.create_edge(k1, k2, 100); - g.create_edge(k1, k2, 200); - g.create_edge(k1, k2, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k1, k2, 200); + g.add_edge(k1, k2, 300); SECTION("all edges exist") { REQUIRE(g.num_edges() == 3); } @@ -887,12 +887,12 @@ TEST_CASE("parallel edges", "[undirected_adjacency_list][edge_cases][parallel]") TEST_CASE("edge erasure consistency", "[undirected_adjacency_list][edge_cases][erase][consistency]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - g.create_edge(k1, k2, 100); + g.add_edge(k1, k2, 100); REQUIRE(g.vertices()[k1].num_edges() == 1); REQUIRE(g.vertices()[k2].num_edges() == 1); @@ -921,16 +921,16 @@ TEST_CASE("high degree vertex", "[undirected_adjacency_list][edge_cases][stress] undirected_adjacency_list g; // Create center vertex - auto center_it = g.create_vertex(0); + auto center_it = g.add_vertex(0); VKey center_k = vertex_id(center_it, g); // Create 100 satellite vertices const int NUM_SATELLITES = 100; vector satellite_keys; for (int i = 0; i < NUM_SATELLITES; ++i) { - auto v = g.create_vertex(i + 1); + auto v = g.add_vertex(i + 1); satellite_keys.push_back(vertex_id(v, g)); - g.create_edge(center_k, satellite_keys.back(), i * 10); + g.add_edge(center_k, satellite_keys.back(), i * 10); } SECTION("center has correct degree") { REQUIRE(g.vertices()[center_k].num_edges() == NUM_SATELLITES); } @@ -957,14 +957,14 @@ TEST_CASE("high degree vertex", "[undirected_adjacency_list][edge_cases][stress] TEST_CASE("edge deletion during iteration", "[undirected_adjacency_list][edge_cases][erase][iteration]") { undirected_adjacency_list g; - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); // Create multiple edges for (int i = 0; i < 5; ++i) { - g.create_edge(k1, k2, i); + g.add_edge(k1, k2, i); } REQUIRE(g.num_edges() == 5); @@ -1006,11 +1006,11 @@ TEST_CASE("edge deletion during iteration", "[undirected_adjacency_list][edge_ca TEST_CASE("move constructor", "[undirected_adjacency_list][memory][move]") { undirected_adjacency_list g1; - auto v1 = g1.create_vertex(10); + auto v1 = g1.add_vertex(10); VKey k1 = vertex_id(v1, g1); - auto v2 = g1.create_vertex(20); + auto v2 = g1.add_vertex(20); VKey k2 = vertex_id(v2, g1); - g1.create_edge(k1, k2, 100); + g1.add_edge(k1, k2, 100); REQUIRE(g1.vertices().size() == 2); REQUIRE(g1.num_edges() == 1); @@ -1035,14 +1035,14 @@ TEST_CASE("move constructor", "[undirected_adjacency_list][memory][move]") { TEST_CASE("move assignment", "[undirected_adjacency_list][memory][move]") { undirected_adjacency_list g1; - auto v1 = g1.create_vertex(10); + auto v1 = g1.add_vertex(10); VKey k1 = vertex_id(v1, g1); - auto v2 = g1.create_vertex(20); + auto v2 = g1.add_vertex(20); VKey k2 = vertex_id(v2, g1); - g1.create_edge(k1, k2, 100); + g1.add_edge(k1, k2, 100); undirected_adjacency_list g2; - g2.create_vertex(99); // Give it some data first + g2.add_vertex(99); // Give it some data first // Move assign g2 = std::move(g1); @@ -1063,16 +1063,16 @@ TEST_CASE("clear method", "[undirected_adjacency_list][memory][clear]") { undirected_adjacency_list g; // Add some data - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - auto v3 = g.create_vertex(30); + auto v3 = g.add_vertex(30); VKey k3 = vertex_id(v3, g); - g.create_edge(k1, k2, 100); - g.create_edge(k2, k3, 200); - g.create_edge(k1, k3, 300); + g.add_edge(k1, k2, 100); + g.add_edge(k2, k3, 200); + g.add_edge(k1, k3, 300); REQUIRE(g.vertices().size() == 3); REQUIRE(g.num_edges() == 3); @@ -1087,7 +1087,7 @@ TEST_CASE("clear method", "[undirected_adjacency_list][memory][clear]") { } SECTION("can add new data after clear") { - auto v = g.create_vertex(42); + auto v = g.add_vertex(42); REQUIRE(g.vertices().size() == 1); REQUIRE(vertex_id(v, g) == 0); } @@ -1099,12 +1099,12 @@ TEST_CASE("destructor cleanup", "[undirected_adjacency_list][memory][destructor] { undirected_adjacency_list g; for (int i = 0; i < 10; ++i) { - g.create_vertex(i); + g.add_vertex(i); } // Create edges for (size_t i = 0; i < 9; ++i) { - g.create_edge(static_cast(i), static_cast(i + 1), static_cast(i * 10)); + g.add_edge(static_cast(i), static_cast(i + 1), static_cast(i * 10)); } REQUIRE(g.vertices().size() == 10); @@ -1119,21 +1119,21 @@ TEST_CASE("destructor cleanup", "[undirected_adjacency_list][memory][destructor] TEST_CASE("swap operation", "[undirected_adjacency_list][memory][swap]") { undirected_adjacency_list g1; - auto v1a = g1.create_vertex(10); + auto v1a = g1.add_vertex(10); VKey k1a = vertex_id(v1a, g1); - auto v1b = g1.create_vertex(20); + auto v1b = g1.add_vertex(20); VKey k1b = vertex_id(v1b, g1); - g1.create_edge(k1a, k1b, 100); + g1.add_edge(k1a, k1b, 100); undirected_adjacency_list g2; - auto v2a = g2.create_vertex(30); + auto v2a = g2.add_vertex(30); VKey k2a = vertex_id(v2a, g2); - auto v2b = g2.create_vertex(40); + auto v2b = g2.add_vertex(40); VKey k2b = vertex_id(v2b, g2); - auto v2c = g2.create_vertex(50); + auto v2c = g2.add_vertex(50); VKey k2c = vertex_id(v2c, g2); - g2.create_edge(k2a, k2b, 200); - g2.create_edge(k2b, k2c, 300); + g2.add_edge(k2a, k2b, 200); + g2.add_edge(k2b, k2c, 300); // Swap using std::swap std::swap(g1, g2); @@ -1159,11 +1159,11 @@ TEST_CASE("graph with graph value", "[undirected_adjacency_list][memory][graph_v REQUIRE(g.graph_value() == 42); - auto v1 = g.create_vertex(10); + auto v1 = g.add_vertex(10); VKey k1 = vertex_id(v1, g); - auto v2 = g.create_vertex(20); + auto v2 = g.add_vertex(20); VKey k2 = vertex_id(v2, g); - g.create_edge(k1, k2, 100); + g.add_edge(k1, k2, 100); // Move construct preserves graph value undirected_adjacency_list g2(std::move(g)); @@ -1179,13 +1179,13 @@ TEST_CASE("large graph cleanup", "[undirected_adjacency_list][memory][stress]") // Create many vertices for (int i = 0; i < NUM_VERTICES; ++i) { - g.create_vertex(i); + g.add_vertex(i); } // Create edges (every vertex connects to next 5) for (int i = 0; i < NUM_VERTICES - 5; ++i) { for (int j = 1; j <= 5; ++j) { - g.create_edge(static_cast(i), static_cast(i + j), i * 1000 + j); + g.add_edge(static_cast(i), static_cast(i + j), i * 1000 + j); } } @@ -1207,9 +1207,9 @@ TEST_CASE("large graph cleanup", "[undirected_adjacency_list][memory][stress]") TEST_CASE("copy constructor", "[undirected_adjacency_list][memory][copy]") { undirected_adjacency_list g1; - g1.create_vertex(10); - g1.create_vertex(20); - g1.create_edge(0, 1, 100); // Add an edge + g1.add_vertex(10); + g1.add_vertex(20); + g1.add_edge(0, 1, 100); // Add an edge undirected_adjacency_list g2(g1); @@ -1230,7 +1230,7 @@ TEST_CASE("copy constructor", "[undirected_adjacency_list][memory][copy]") { SECTION("edges are independent") { // Create another edge in copy - g2.create_edge(0, 1, 200); + g2.add_edge(0, 1, 200); REQUIRE(g2.num_edges() > g1.num_edges()); } } @@ -1238,13 +1238,13 @@ TEST_CASE("copy constructor", "[undirected_adjacency_list][memory][copy]") { TEST_CASE("copy constructor with multiple edges", "[undirected_adjacency_list][memory][copy]") { undirected_adjacency_list g1; for (int i = 0; i < 5; ++i) { - g1.create_vertex(i * 10); + g1.add_vertex(i * 10); } - g1.create_edge(0, 1, 100); - g1.create_edge(1, 2, 200); - g1.create_edge(2, 3, 300); - g1.create_edge(3, 4, 400); - g1.create_edge(0, 4, 500); // Creates a cycle + g1.add_edge(0, 1, 100); + g1.add_edge(1, 2, 200); + g1.add_edge(2, 3, 300); + g1.add_edge(3, 4, 400); + g1.add_edge(0, 4, 500); // Creates a cycle undirected_adjacency_list g2(g1); @@ -1259,12 +1259,12 @@ TEST_CASE("copy constructor with multiple edges", "[undirected_adjacency_list][m TEST_CASE("copy assignment", "[undirected_adjacency_list][memory][copy]") { undirected_adjacency_list g1; - g1.create_vertex(10); - g1.create_vertex(20); - g1.create_edge(0, 1, 100); + g1.add_vertex(10); + g1.add_vertex(20); + g1.add_edge(0, 1, 100); undirected_adjacency_list g2; - g2.create_vertex(99); + g2.add_vertex(99); SECTION("assignment replaces content") { g2 = g1; @@ -1284,9 +1284,9 @@ TEST_CASE("copy assignment", "[undirected_adjacency_list][memory][copy]") { TEST_CASE("copy with graph value", "[undirected_adjacency_list][memory][copy]") { undirected_adjacency_list g1("original graph"); - g1.create_vertex(10); - g1.create_vertex(20); - g1.create_edge(0, 1, 100); + g1.add_vertex(10); + g1.add_vertex(20); + g1.add_edge(0, 1, 100); SECTION("copy constructor preserves graph value") { undirected_adjacency_list g2(g1); @@ -1396,13 +1396,13 @@ TEST_CASE("edge range constructor empty range", "[undirected_adjacency_list][con // Iterator Invalidation Tests // ============================================================================= -TEST_CASE("vertex iterator invalidation on create_vertex", "[undirected_adjacency_list][iterator_invalidation]") { +TEST_CASE("vertex iterator invalidation on add_vertex", "[undirected_adjacency_list][iterator_invalidation]") { undirected_adjacency_list g; // Reserve enough space to avoid reallocation // Note: This depends on implementation - vector may reallocate - g.create_vertex(10); - g.create_vertex(20); + g.add_vertex(10); + g.add_vertex(20); auto it = g.begin(); VKey original_key = vertex_id(it, g); @@ -1410,7 +1410,7 @@ TEST_CASE("vertex iterator invalidation on create_vertex", "[undirected_adjacenc // Add many more vertices - this may invalidate iterators for (int i = 0; i < 100; ++i) { - g.create_vertex(i * 10); + g.add_vertex(i * 10); } // Verify we can still access by key (keys are stable) @@ -1420,9 +1420,9 @@ TEST_CASE("vertex iterator invalidation on create_vertex", "[undirected_adjacenc TEST_CASE("edge iterator stable during vertex addition", "[undirected_adjacency_list][iterator_invalidation]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_edge(0, 1, 100); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1, 100); auto& v0 = g.vertices()[0]; auto edge_it = v0.edges(g, 0).begin(); @@ -1430,7 +1430,7 @@ TEST_CASE("edge iterator stable during vertex addition", "[undirected_adjacency_ // Add more vertices - edge iterators should remain valid for (int i = 0; i < 50; ++i) { - g.create_vertex(i * 10); + g.add_vertex(i * 10); } // Edge should still be accessible and valid @@ -1439,17 +1439,17 @@ TEST_CASE("edge iterator stable during vertex addition", "[undirected_adjacency_ TEST_CASE("edge reference stable across operations", "[undirected_adjacency_list][iterator_invalidation]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); - g.create_edge(0, 1, 100); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 100); auto& v0 = g.vertices()[0]; auto& edge = *v0.edges(g, 0).begin(); // Add more edges - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); // Original edge reference should still be valid REQUIRE(edge.value() == 100); @@ -1473,9 +1473,9 @@ TEST_CASE("edge reference stable across operations", "[undirected_adjacency_list TEST_CASE("try_find_vertex", "[undirected_adjacency_list][vertex][find]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); SECTION("find existing vertex") { auto it = g.try_find_vertex(1); @@ -1510,12 +1510,12 @@ TEST_CASE("try_find_vertex", "[undirected_adjacency_list][vertex][find]") { TEST_CASE("clear_edges per-vertex", "[undirected_adjacency_list][vertex][clear_edges]") { undirected_adjacency_list g; - g.create_vertex(0); - g.create_vertex(1); - g.create_vertex(2); - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(0); + g.add_vertex(1); + g.add_vertex(2); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); REQUIRE(g.num_edges() == 3); REQUIRE(g.vertices()[0].num_edges() == 2); @@ -1536,13 +1536,13 @@ TEST_CASE("clear_edges per-vertex", "[undirected_adjacency_list][vertex][clear_e TEST_CASE("erase_edge with iterator range", "[undirected_adjacency_list][edge][erase]") { undirected_adjacency_list g; - g.create_vertex(0); - g.create_vertex(1); - g.create_vertex(2); - g.create_vertex(3); - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(0, 3, 300); + g.add_vertex(0); + g.add_vertex(1); + g.add_vertex(2); + g.add_vertex(3); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(0, 3, 300); REQUIRE(g.vertices()[0].num_edges() == 3); @@ -1563,9 +1563,9 @@ TEST_CASE("erase_edge with iterator range", "[undirected_adjacency_list][edge][e /* TEST_CASE("graph with void vertex value", "[undirected_adjacency_list][void_types]") { undirected_adjacency_list g; - g.create_vertex(); - g.create_vertex(); - g.create_edge(0, 1, 100); + g.add_vertex(); + g.add_vertex(); + g.add_edge(0, 1, 100); REQUIRE(g.vertices().size() == 2); REQUIRE(g.num_edges() == 1); @@ -1578,9 +1578,9 @@ TEST_CASE("graph with void vertex value", "[undirected_adjacency_list][void_type TEST_CASE("graph with void edge value", "[undirected_adjacency_list][void_types]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_edge(0, 1); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1); REQUIRE(g.vertices().size() == 2); REQUIRE(g.vertices()[0].value() == 10); @@ -1590,9 +1590,9 @@ TEST_CASE("graph with void edge value", "[undirected_adjacency_list][void_types] TEST_CASE("graph with void graph value", "[undirected_adjacency_list][void_types]") { undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_edge(0, 1, 100); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1, 100); REQUIRE(g.vertices().size() == 2); REQUIRE(g.num_edges() == 1); @@ -1600,11 +1600,11 @@ TEST_CASE("graph with void graph value", "[undirected_adjacency_list][void_types TEST_CASE("graph with all void values", "[undirected_adjacency_list][void_types]") { undirected_adjacency_list g; - g.create_vertex(); - g.create_vertex(); - g.create_vertex(); - g.create_edge(0, 1); - g.create_edge(1, 2); + g.add_vertex(); + g.add_vertex(); + g.add_vertex(); + g.add_edge(0, 1); + g.add_edge(1, 2); REQUIRE(g.vertices().size() == 3); REQUIRE(g.num_edges() == 2); diff --git a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list_cpo.cpp b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list_cpo.cpp index ff7dfd0..a5c4526 100644 --- a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list_cpo.cpp +++ b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list_cpo.cpp @@ -37,9 +37,9 @@ using graph::adj_list::find_vertex_edge; TEST_CASE("vertices CPO basic", "[undirected_adjacency_list][cpo][vertices]") { IntGraph g(42); // graph value = 42 - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); // Test via ADL (friend functions) auto verts = vertices(g); @@ -54,9 +54,9 @@ TEST_CASE("vertices CPO basic", "[undirected_adjacency_list][cpo][vertices]") { TEST_CASE("vertex_id CPO", "[undirected_adjacency_list][cpo][vertex_id]") { IntGraph g(0); - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); SECTION("vertex_id returns correct id for each vertex") { auto verts = vertices(g); @@ -85,19 +85,19 @@ TEST_CASE("num_vertices CPO", "[undirected_adjacency_list][cpo][num_vertices]") IntGraph g(0); REQUIRE(num_vertices(g) == 0); - g.create_vertex(10); + g.add_vertex(10); REQUIRE(num_vertices(g) == 1); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(20); + g.add_vertex(30); REQUIRE(num_vertices(g) == 3); } TEST_CASE("find_vertex CPO", "[undirected_adjacency_list][cpo][find_vertex]") { IntGraph g(0); - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); SECTION("find existing vertex by id") { auto it = find_vertex(g, 0u); @@ -134,27 +134,27 @@ TEST_CASE("find_vertex CPO", "[undirected_adjacency_list][cpo][find_vertex]") { TEST_CASE("num_edges CPO", "[undirected_adjacency_list][cpo][num_edges]") { IntGraph g(0); - g.create_vertex(); - g.create_vertex(); - g.create_vertex(); + g.add_vertex(); + g.add_vertex(); + g.add_vertex(); REQUIRE(num_edges(g) == 0); - g.create_edge(0, 1, 100); + g.add_edge(0, 1, 100); REQUIRE(num_edges(g) == 1); - g.create_edge(1, 2, 200); + g.add_edge(1, 2, 200); REQUIRE(num_edges(g) == 2); } TEST_CASE("has_edges CPO", "[undirected_adjacency_list][cpo][has_edges]") { IntGraph g(0); - g.create_vertex(); - g.create_vertex(); + g.add_vertex(); + g.add_vertex(); REQUIRE_FALSE(has_edges(g)); - g.create_edge(0, 1, 100); + g.add_edge(0, 1, 100); REQUIRE(has_edges(g)); } @@ -176,9 +176,9 @@ TEST_CASE("graph_value CPO", "[undirected_adjacency_list][cpo][graph_value]") { TEST_CASE("vertex_value CPO", "[undirected_adjacency_list][cpo][vertex_value]") { IntGraph g(0); - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); auto verts = vertices(g); auto it = verts.begin(); @@ -197,12 +197,12 @@ TEST_CASE("vertex_value CPO", "[undirected_adjacency_list][cpo][vertex_value]") TEST_CASE("edges CPO", "[undirected_adjacency_list][cpo][edges]") { IntGraph g(0); - g.create_vertex(); - g.create_vertex(); - g.create_vertex(); - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(); + g.add_vertex(); + g.add_vertex(); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("edges from vertex 0") { auto verts = vertices(g); @@ -218,7 +218,7 @@ TEST_CASE("edges CPO", "[undirected_adjacency_list][cpo][edges]") { SECTION("edges from vertex with no edges") { IntGraph g2(0); - g2.create_vertex(); + g2.add_vertex(); auto verts = vertices(g2); auto v = *verts.begin(); auto edge_range = edges(g2, v); @@ -233,12 +233,12 @@ TEST_CASE("edges CPO", "[undirected_adjacency_list][cpo][edges]") { TEST_CASE("degree CPO", "[undirected_adjacency_list][cpo][degree]") { IntGraph g(0); - g.create_vertex(); - g.create_vertex(); - g.create_vertex(); - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(); + g.add_vertex(); + g.add_vertex(); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("degree of vertex with 2 edges") { auto verts = vertices(g); @@ -248,7 +248,7 @@ TEST_CASE("degree CPO", "[undirected_adjacency_list][cpo][degree]") { SECTION("degree of vertex with no edges") { IntGraph g2(0); - g2.create_vertex(); + g2.add_vertex(); auto verts = vertices(g2); auto v = *verts.begin(); REQUIRE(degree(g2, v) == 0); @@ -257,11 +257,11 @@ TEST_CASE("degree CPO", "[undirected_adjacency_list][cpo][degree]") { TEST_CASE("edge target_id CPO via ADL", "[undirected_adjacency_list][cpo][target_id]") { IntGraph g(0); - g.create_vertex(); - g.create_vertex(); - g.create_vertex(); - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); + g.add_vertex(); + g.add_vertex(); + g.add_vertex(); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); auto verts = vertices(g); auto v0 = *verts.begin(); @@ -281,9 +281,9 @@ TEST_CASE("edge target_id CPO via ADL", "[undirected_adjacency_list][cpo][target TEST_CASE("edge source_id CPO via ADL", "[undirected_adjacency_list][cpo][source_id]") { IntGraph g(0); - g.create_vertex(); - g.create_vertex(); - g.create_edge(0, 1, 100); + g.add_vertex(); + g.add_vertex(); + g.add_edge(0, 1, 100); auto verts = vertices(g); auto v0 = *verts.begin(); @@ -296,9 +296,9 @@ TEST_CASE("edge source_id CPO via ADL", "[undirected_adjacency_list][cpo][source TEST_CASE("edge_value CPO via ADL", "[undirected_adjacency_list][cpo][edge_value]") { IntGraph g(0); - g.create_vertex(); - g.create_vertex(); - g.create_edge(0, 1, 100); + g.add_vertex(); + g.add_vertex(); + g.add_edge(0, 1, 100); auto verts = vertices(g); auto v0 = *verts.begin(); @@ -320,12 +320,12 @@ TEST_CASE("edge_value CPO via ADL", "[undirected_adjacency_list][cpo][edge_value TEST_CASE("CPO integration - graph traversal", "[undirected_adjacency_list][cpo][integration]") { IntGraph g(0); // Create a simple triangle graph: 0 -- 1 -- 2 -- 0 - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); - g.create_edge(0, 1, 12); - g.create_edge(1, 2, 23); - g.create_edge(2, 0, 31); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 12); + g.add_edge(1, 2, 23); + g.add_edge(2, 0, 31); SECTION("traverse all vertices and edges using CPOs") { int total_vertex_value = 0; @@ -351,11 +351,11 @@ TEST_CASE("CPO integration - graph traversal", "[undirected_adjacency_list][cpo] TEST_CASE("target CPO", "[undirected_adjacency_list][cpo][target]") { IntGraph g(0); - g.create_vertex(10); // vertex 0 - g.create_vertex(20); // vertex 1 - g.create_vertex(30); // vertex 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); + g.add_vertex(10); // vertex 0 + g.add_vertex(20); // vertex 1 + g.add_vertex(30); // vertex 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); SECTION("target returns vertex descriptor") { auto verts = vertices(g); @@ -391,11 +391,11 @@ TEST_CASE("target CPO", "[undirected_adjacency_list][cpo][target]") { TEST_CASE("source CPO", "[undirected_adjacency_list][cpo][source]") { IntGraph g(0); - g.create_vertex(10); // vertex 0 - g.create_vertex(20); // vertex 1 - g.create_vertex(30); // vertex 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); + g.add_vertex(10); // vertex 0 + g.add_vertex(20); // vertex 1 + g.add_vertex(30); // vertex 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); SECTION("source returns vertex descriptor") { auto verts = vertices(g); @@ -424,12 +424,12 @@ TEST_CASE("source CPO", "[undirected_adjacency_list][cpo][source]") { TEST_CASE("find_vertex_edge CPO", "[undirected_adjacency_list][cpo][find_vertex_edge]") { IntGraph g(0); - g.create_vertex(10); // vertex 0 - g.create_vertex(20); // vertex 1 - g.create_vertex(30); // vertex 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // vertex 0 + g.add_vertex(20); // vertex 1 + g.add_vertex(30); // vertex 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("find_vertex_edge with descriptor and vid") { auto verts = vertices(g); @@ -474,11 +474,11 @@ TEST_CASE("find_vertex_edge CPO", "[undirected_adjacency_list][cpo][find_vertex_ TEST_CASE("contains_edge CPO", "[undirected_adjacency_list][cpo][contains_edge]") { IntGraph g(0); - g.create_vertex(10); // vertex 0 - g.create_vertex(20); // vertex 1 - g.create_vertex(30); // vertex 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); + g.add_vertex(10); // vertex 0 + g.add_vertex(20); // vertex 1 + g.add_vertex(30); // vertex 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); // No edge between 1 and 2 SECTION("contains_edge with two vertex ids - edge exists") { @@ -513,12 +513,12 @@ TEST_CASE("contains_edge CPO", "[undirected_adjacency_list][cpo][contains_edge]" TEST_CASE("edges(g) graph-level CPO", "[undirected_adjacency_list][cpo][edges]") { IntGraph g(0); - g.create_vertex(10); // vertex 0 - g.create_vertex(20); // vertex 1 - g.create_vertex(30); // vertex 2 - g.create_edge(0, 1, 100); - g.create_edge(1, 2, 200); - g.create_edge(0, 2, 300); + g.add_vertex(10); // vertex 0 + g.add_vertex(20); // vertex 1 + g.add_vertex(30); // vertex 2 + g.add_edge(0, 1, 100); + g.add_edge(1, 2, 200); + g.add_edge(0, 2, 300); SECTION("graph-level edges iteration visits all edges") { // Use the graph's edges() member which iterates all edges @@ -547,11 +547,11 @@ TEST_CASE("edges(g) graph-level CPO", "[undirected_adjacency_list][cpo][edges]") TEST_CASE("source_id with vertex descriptor edges", "[undirected_adjacency_list][cpo][source_id]") { IntGraph g(0); - g.create_vertex(10); // vertex 0 - g.create_vertex(20); // vertex 1 - g.create_vertex(30); // vertex 2 - g.create_edge(0, 1, 100); - g.create_edge(1, 2, 200); + g.add_vertex(10); // vertex 0 + g.add_vertex(20); // vertex 1 + g.add_vertex(30); // vertex 2 + g.add_edge(0, 1, 100); + g.add_edge(1, 2, 200); SECTION("source_id via CPO") { auto verts = vertices(g); @@ -591,9 +591,9 @@ TEST_CASE("CPO with empty graph", "[undirected_adjacency_list][cpo][empty]") { TEST_CASE("CPO const correctness", "[undirected_adjacency_list][cpo][const]") { IntGraph g(0); - g.create_vertex(10); - g.create_vertex(20); - g.create_edge(0, 1, 100); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1, 100); const IntGraph& cg = g; @@ -626,7 +626,7 @@ TEST_CASE("CPO const correctness", "[undirected_adjacency_list][cpo][const]") { TEST_CASE("CPO vertex_id consistency", "[undirected_adjacency_list][cpo][vertex_id]") { IntGraph g(0); for (int i = 0; i < 10; ++i) { - g.create_vertex(i * 10); + g.add_vertex(i * 10); } SECTION("vertex_id matches iteration order") { @@ -649,14 +649,14 @@ TEST_CASE("CPO vertex_id consistency", "[undirected_adjacency_list][cpo][vertex_ TEST_CASE("CPO edge traversal consistency", "[undirected_adjacency_list][cpo][edges]") { IntGraph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_vertex(40); // 3 + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_vertex(40); // 3 // Create a path: 0 -- 1 -- 2 -- 3 - g.create_edge(0, 1, 1); - g.create_edge(1, 2, 2); - g.create_edge(2, 3, 3); + g.add_edge(0, 1, 1); + g.add_edge(1, 2, 2); + g.add_edge(2, 3, 3); SECTION("edge target and source are consistent") { for (auto v : vertices(g)) { @@ -689,12 +689,12 @@ TEST_CASE("edges via vertex descriptor CPO", "[undirected_adjacency_list][cpo][e using namespace graph; using namespace graph::adj_list; undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("get edges via vertex descriptor from vertices()") { size_t v0_count = 0; @@ -728,13 +728,13 @@ TEST_CASE("degree via vertex descriptor CPO", "[undirected_adjacency_list][cpo][ using namespace graph; using namespace graph::adj_list; undirected_adjacency_list g; - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); - g.create_vertex(40); // isolated vertex - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_vertex(40); // isolated vertex + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("degree via vertex descriptor") { for (auto v : vertices(g)) { diff --git a/tests/container/undirected_adjacency_list/test_undirected_adjacency_list_mutation.cpp b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list_mutation.cpp new file mode 100644 index 0000000..910b804 --- /dev/null +++ b/tests/container/undirected_adjacency_list/test_undirected_adjacency_list_mutation.cpp @@ -0,0 +1,259 @@ +/** + * @file test_undirected_adjacency_list_mutation.cpp + * @brief Tests for the mutation API of undirected_adjacency_list. + * + * Covers the uniform mutation member functions: + * - add_vertex() (default / move / copy value) + * - add_edge() (by id and by iterator, with/without value) + * - remove_edge(pos) (by edge_iterator) + * - remove_edge(uid,vid)(by endpoint ids) + * - remove_vertex(uid) (with renumbering of higher ids) + */ + +#include +#include +#include +#include +#include +#include +#include + +using graph::container::undirected_adjacency_list; + +using graph::adj_list::num_edges; +using graph::adj_list::num_vertices; +using graph::adj_list::contains_edge; + +// int edge value, int vertex value, int graph value +using IntGraph = undirected_adjacency_list; +// double edge value (different edge value type) +using DblGraph = undirected_adjacency_list; +// std::string vertex value +using StrVGraph = undirected_adjacency_list; + +// Collect the neighbor ids of vertex uid into a multiset (handles parallel edges/self-loops). +template +static std::multiset neighbors_of(G& g, typename G::vertex_id_type uid) { + std::multiset result; + auto& v = g.vertices()[uid]; + for (auto& e : v.edges(g, uid)) { + auto owner = e.list_owner_id(); + auto target = e.list_target_id(); + result.insert(owner == uid ? target : owner); + } + return result; +} + +TEST_CASE("add_vertex variants", "[undirected_adjacency_list][mutation][add_vertex]") { + SECTION("default value vertices") { + IntGraph g(0); + g.add_vertex(); + g.add_vertex(); + REQUIRE(num_vertices(g) == 2); + } + + SECTION("copied value vertices") { + IntGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + REQUIRE(num_vertices(g) == 3); + REQUIRE(g.vertices()[0].value() == 10); + REQUIRE(g.vertices()[2].value() == 30); + } + + SECTION("moved value vertices") { + StrVGraph g(0); + std::string s = "alice"; + g.add_vertex(std::move(s)); + REQUIRE(num_vertices(g) == 1); + REQUIRE(g.vertices()[0].value() == "alice"); + } +} + +TEST_CASE("add_edge variants", "[undirected_adjacency_list][mutation][add_edge]") { + SECTION("by id with value") { + IntGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 100); + g.add_edge(1, 2, 200); + REQUIRE(num_edges(g) == 2); + REQUIRE(contains_edge(g, 0u, 1u)); + REQUIRE(contains_edge(g, 1u, 2u)); + REQUIRE_FALSE(contains_edge(g, 0u, 2u)); + } + + SECTION("undirected: edge visible from both endpoints") { + IntGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1, 100); + REQUIRE(g.vertices()[0].num_edges() == 1); + REQUIRE(g.vertices()[1].num_edges() == 1); + REQUIRE(neighbors_of(g, 0u) == std::multiset{1}); + REQUIRE(neighbors_of(g, 1u) == std::multiset{0}); + } + + SECTION("double edge value graph") { + DblGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1, 2.5); + REQUIRE(num_edges(g) == 1); + REQUIRE(contains_edge(g, 0u, 1u)); + } +} + +TEST_CASE("remove_edge by iterator", "[undirected_adjacency_list][mutation][remove_edge]") { + IntGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); + REQUIRE(num_edges(g) == 3); + + // Remove the first physical edge encountered in the graph-wide edge list. + auto pos = g.edges().begin(); + g.remove_edge(pos); + REQUIRE(num_edges(g) == 2); +} + +TEST_CASE("remove_edge by endpoint ids", "[undirected_adjacency_list][mutation][remove_edge]") { + SECTION("removes the matching undirected edge from both endpoints") { + IntGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); + REQUIRE(num_edges(g) == 3); + + auto removed = g.remove_edge(0, 1); + REQUIRE(removed == 1); + REQUIRE(num_edges(g) == 2); + REQUIRE_FALSE(contains_edge(g, 0u, 1u)); + REQUIRE(contains_edge(g, 0u, 2u)); + REQUIRE(contains_edge(g, 1u, 2u)); + REQUIRE(g.vertices()[0].num_edges() == 1); + REQUIRE(g.vertices()[1].num_edges() == 1); + } + + SECTION("symmetric: order of endpoints does not matter") { + IntGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1, 100); + auto removed = g.remove_edge(1, 0); + REQUIRE(removed == 1); + REQUIRE(num_edges(g) == 0); + } + + SECTION("removing a non-existent edge returns 0") { + IntGraph g(0); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 100); + auto removed = g.remove_edge(1, 2); + REQUIRE(removed == 0); + REQUIRE(num_edges(g) == 1); + } + + SECTION("out-of-range endpoint throws") { + IntGraph g(0); + g.add_vertex(10); + REQUIRE_THROWS_AS(g.remove_edge(0, 5), std::out_of_range); + } +} + +TEST_CASE("remove_vertex renumbers higher ids", "[undirected_adjacency_list][mutation][remove_vertex]") { + SECTION("isolated vertex in the middle") { + IntGraph g(0); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 (isolated, to be removed) + g.add_vertex(30); // 2 + g.add_edge(0, 2, 200); + REQUIRE(num_vertices(g) == 3); + REQUIRE(num_edges(g) == 1); + + g.remove_vertex(1); + + REQUIRE(num_vertices(g) == 2); + REQUIRE(num_edges(g) == 1); + // Old vertex 2 (value 30) is now vertex 1. + REQUIRE(g.vertices()[0].value() == 10); + REQUIRE(g.vertices()[1].value() == 30); + // Edge 0--2 is now 0--1. + REQUIRE(contains_edge(g, 0u, 1u)); + REQUIRE(neighbors_of(g, 0u) == std::multiset{1}); + REQUIRE(neighbors_of(g, 1u) == std::multiset{0}); + } + + SECTION("connected vertex: incident edges are removed") { + IntGraph g(0); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 (to be removed; has 2 edges) + g.add_vertex(30); // 2 + g.add_vertex(40); // 3 + g.add_edge(0, 1, 100); + g.add_edge(1, 2, 200); + g.add_edge(2, 3, 300); + REQUIRE(num_edges(g) == 3); + + g.remove_vertex(1); + + REQUIRE(num_vertices(g) == 3); + // Edges 0-1 and 1-2 removed; only the old 2-3 edge remains. + REQUIRE(num_edges(g) == 1); + // Old vertices 2,3 (values 30,40) become ids 1,2. + REQUIRE(g.vertices()[0].value() == 10); + REQUIRE(g.vertices()[1].value() == 30); + REQUIRE(g.vertices()[2].value() == 40); + REQUIRE(contains_edge(g, 1u, 2u)); + REQUIRE_FALSE(contains_edge(g, 0u, 1u)); + REQUIRE(g.vertices()[0].num_edges() == 0); + } + + SECTION("removing the last vertex needs no renumbering") { + IntGraph g(0); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(1, 2, 200); + + g.remove_vertex(2); + + REQUIRE(num_vertices(g) == 2); + REQUIRE(num_edges(g) == 1); + REQUIRE(contains_edge(g, 0u, 1u)); + REQUIRE(neighbors_of(g, 1u) == std::multiset{0}); + } + + SECTION("removing vertex 0 shifts everything down") { + IntGraph g(0); + g.add_vertex(10); // 0 (removed) + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(1, 2, 200); + + g.remove_vertex(0); + + REQUIRE(num_vertices(g) == 2); + REQUIRE(num_edges(g) == 1); + REQUIRE(g.vertices()[0].value() == 20); + REQUIRE(g.vertices()[1].value() == 30); + REQUIRE(contains_edge(g, 0u, 1u)); + } + + SECTION("out-of-range id throws") { + IntGraph g(0); + g.add_vertex(10); + REQUIRE_THROWS_AS(g.remove_vertex(5), std::out_of_range); + } +} diff --git a/tests/container/undirected_adjacency_list/test_undirected_bidirectional.cpp b/tests/container/undirected_adjacency_list/test_undirected_bidirectional.cpp index 4c2a232..12b6b9b 100644 --- a/tests/container/undirected_adjacency_list/test_undirected_bidirectional.cpp +++ b/tests/container/undirected_adjacency_list/test_undirected_bidirectional.cpp @@ -58,12 +58,12 @@ TEST_CASE("undirected_adjacency_list models bidirectional_adjacency_list", TEST_CASE("in_edges returns same edges as edges for undirected graph", "[undirected_adjacency_list][bidirectional][in_edges]") { IntGraph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("in_edges and edges produce identical target sets per vertex") { for (auto v : vertices(g)) { @@ -93,12 +93,12 @@ TEST_CASE("in_edges returns same edges as edges for undirected graph", TEST_CASE("in_edges by vertex id", "[undirected_adjacency_list][bidirectional][in_edges]") { IntGraph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); // in_edges(g, uid) should work via the CPO default tier size_t count = 0; @@ -111,9 +111,9 @@ TEST_CASE("in_edges by vertex id", TEST_CASE("in_edges on const graph", "[undirected_adjacency_list][bidirectional][in_edges]") { IntGraph g(0); - g.create_vertex(10); - g.create_vertex(20); - g.create_edge(0, 1, 100); + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(0, 1, 100); const IntGraph& cg = g; auto v = *vertices(cg).begin(); @@ -128,7 +128,7 @@ TEST_CASE("in_edges on const graph", TEST_CASE("in_edges on vertex with no edges", "[undirected_adjacency_list][bidirectional][in_edges]") { IntGraph g(0); - g.create_vertex(10); // isolated vertex + g.add_vertex(10); // isolated vertex auto v = *vertices(g).begin(); auto range = in_edges(g, v); @@ -147,13 +147,13 @@ TEST_CASE("in_edges on vertex with no edges", TEST_CASE("in_degree equals degree for undirected graph", "[undirected_adjacency_list][bidirectional][in_degree]") { IntGraph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_vertex(40); // 3 (isolated) - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_vertex(40); // 3 (isolated) + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("in_degree matches degree via vertex descriptor") { for (auto v : vertices(g)) { @@ -186,12 +186,12 @@ TEST_CASE("in_degree equals degree for undirected graph", TEST_CASE("find_in_edge works on undirected graph", "[undirected_adjacency_list][bidirectional][find_in_edge]") { IntGraph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); SECTION("find_in_edge with two vertex ids - edge exists") { // find_in_edge(g, uid, vid) default: find_vertex_edge(g, vid, uid) @@ -252,11 +252,11 @@ TEST_CASE("find_in_edge works on undirected graph", TEST_CASE("contains_in_edge works on undirected graph", "[undirected_adjacency_list][bidirectional][contains_in_edge]") { IntGraph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); // No edge between 1 and 2 SECTION("contains_in_edge with two vertex ids - edge exists") { @@ -310,12 +310,12 @@ TEST_CASE("undirected graph edge symmetry with in_edges", "[undirected_adjacency_list][bidirectional][integration]") { IntGraph g(0); // Triangle: 0--1, 1--2, 2--0 - g.create_vertex(10); - g.create_vertex(20); - g.create_vertex(30); - g.create_edge(0, 1, 12); - g.create_edge(1, 2, 23); - g.create_edge(2, 0, 31); + g.add_vertex(10); + g.add_vertex(20); + g.add_vertex(30); + g.add_edge(0, 1, 12); + g.add_edge(1, 2, 23); + g.add_edge(2, 0, 31); SECTION("total in_edges iteration matches total edges iteration") { size_t total_out = 0, total_in = 0; @@ -347,11 +347,11 @@ TEST_CASE("undirected graph star topology - in_edges correctness", IntGraph g(0); // Star graph: vertex 0 connected to 1..4 for (int i = 0; i < 5; ++i) - g.create_vertex(i * 10); - g.create_edge(0, 1, 1); - g.create_edge(0, 2, 2); - g.create_edge(0, 3, 3); - g.create_edge(0, 4, 4); + g.add_vertex(i * 10); + g.add_edge(0, 1, 1); + g.add_edge(0, 2, 2); + g.add_edge(0, 3, 3); + g.add_edge(0, 4, 4); SECTION("hub vertex has same in_degree and degree") { REQUIRE(in_degree(g, 0u) == 4); diff --git a/tests/views/test_adaptors.cpp b/tests/views/test_adaptors.cpp index e888320..f91904c 100644 --- a/tests/views/test_adaptors.cpp +++ b/tests/views/test_adaptors.cpp @@ -1188,12 +1188,12 @@ using BiGraph = undirected_adjacency_list; // Triangle graph: 0--1 (w=100), 0--2 (w=200), 1--2 (w=300) static BiGraph make_bi_graph() { BiGraph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); return g; } diff --git a/tests/views/test_basic_edgelist.cpp b/tests/views/test_basic_edgelist.cpp index c709ad9..22f9d3f 100644 --- a/tests/views/test_basic_edgelist.cpp +++ b/tests/views/test_basic_edgelist.cpp @@ -352,12 +352,12 @@ TEST_CASE("basic_edgelist - undirected_adjacency_list", "[basic_edgelist][undire using Graph = graph::container::undirected_adjacency_list; Graph g; - g.create_vertex(100); - g.create_vertex(200); - g.create_vertex(300); - g.create_edge(0, 1, 10); - g.create_edge(0, 2, 20); - g.create_edge(1, 2, 12); + g.add_vertex(100); + g.add_vertex(200); + g.add_vertex(300); + g.add_edge(0, 1, 10); + g.add_edge(0, 2, 20); + g.add_edge(1, 2, 12); SECTION("basic_edgelist(g) - basic iteration") { auto el = basic_edgelist(g); diff --git a/tests/views/test_basic_incidence.cpp b/tests/views/test_basic_incidence.cpp index 704cb44..e0466a6 100644 --- a/tests/views/test_basic_incidence.cpp +++ b/tests/views/test_basic_incidence.cpp @@ -292,12 +292,12 @@ TEST_CASE("basic_incidence - undirected_adjacency_list", "[basic_incidence][undi using Graph = graph::container::undirected_adjacency_list; Graph g; - g.create_vertex(100); - g.create_vertex(200); - g.create_vertex(300); - g.create_edge(0, 1, 10); - g.create_edge(0, 2, 20); - g.create_edge(1, 2, 12); + g.add_vertex(100); + g.add_vertex(200); + g.add_vertex(300); + g.add_edge(0, 1, 10); + g.add_edge(0, 2, 20); + g.add_edge(1, 2, 12); SECTION("basic_incidence(g, uid) - basic iteration") { auto inc = basic_incidence(g, 0u); diff --git a/tests/views/test_basic_neighbors.cpp b/tests/views/test_basic_neighbors.cpp index d83002a..e33d6fa 100644 --- a/tests/views/test_basic_neighbors.cpp +++ b/tests/views/test_basic_neighbors.cpp @@ -292,12 +292,12 @@ TEST_CASE("basic_neighbors - undirected_adjacency_list", "[basic_neighbors][undi using Graph = graph::container::undirected_adjacency_list; Graph g; - g.create_vertex(100); - g.create_vertex(200); - g.create_vertex(300); - g.create_edge(0, 1, 10); - g.create_edge(0, 2, 20); - g.create_edge(1, 2, 12); + g.add_vertex(100); + g.add_vertex(200); + g.add_vertex(300); + g.add_edge(0, 1, 10); + g.add_edge(0, 2, 20); + g.add_edge(1, 2, 12); SECTION("basic_neighbors(g, uid) - basic iteration") { auto nbrs = basic_neighbors(g, 0u); diff --git a/tests/views/test_in_incidence.cpp b/tests/views/test_in_incidence.cpp index f55a464..147f15f 100644 --- a/tests/views/test_in_incidence.cpp +++ b/tests/views/test_in_incidence.cpp @@ -39,12 +39,12 @@ namespace view = graph::views; // --------------------------------------------------------------------------- static Graph make_triangle() { Graph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); return g; } @@ -232,9 +232,9 @@ TEST_CASE("basic_in_incidence", "[in_incidence][basic_in]") { TEST_CASE("in_incidence - isolated vertex", "[in_incidence][empty]") { Graph g(0); - g.create_vertex(10); // 0 — no edges - g.create_vertex(20); // 1 - g.create_edge(1, 1, 99); // self-loop on 1 (just so the graph isn't trivial) + g.add_vertex(10); // 0 — no edges + g.add_vertex(20); // 1 + g.add_edge(1, 1, 99); // self-loop on 1 (just so the graph isn't trivial) auto v0 = *adj::find_vertex(g, 0u); auto iview = view::in_incidence(g, v0); diff --git a/tests/views/test_in_neighbors.cpp b/tests/views/test_in_neighbors.cpp index 197f8ca..9eb9c38 100644 --- a/tests/views/test_in_neighbors.cpp +++ b/tests/views/test_in_neighbors.cpp @@ -37,12 +37,12 @@ namespace view = graph::views; // --------------------------------------------------------------------------- static Graph make_triangle() { Graph g(0); - g.create_vertex(10); // 0 - g.create_vertex(20); // 1 - g.create_vertex(30); // 2 - g.create_edge(0, 1, 100); - g.create_edge(0, 2, 200); - g.create_edge(1, 2, 300); + g.add_vertex(10); // 0 + g.add_vertex(20); // 1 + g.add_vertex(30); // 2 + g.add_edge(0, 1, 100); + g.add_edge(0, 2, 200); + g.add_edge(1, 2, 300); return g; } @@ -227,9 +227,9 @@ TEST_CASE("basic_in_neighbors", "[in_neighbors][basic_in]") { TEST_CASE("in_neighbors - isolated vertex", "[in_neighbors][empty]") { Graph g(0); - g.create_vertex(10); - g.create_vertex(20); - g.create_edge(1, 1, 99); // self-loop on 1 + g.add_vertex(10); + g.add_vertex(20); + g.add_edge(1, 1, 99); // self-loop on 1 auto v0 = *adj::find_vertex(g, 0u); auto nview = view::in_neighbors(g, v0); diff --git a/tests/views/test_incidence.cpp b/tests/views/test_incidence.cpp index 1771271..29774f3 100644 --- a/tests/views/test_incidence.cpp +++ b/tests/views/test_incidence.cpp @@ -669,22 +669,22 @@ TEST_CASE("incidence - undirected_adjacency_list basic", "[incidence][undirected Graph g; // Create vertices: 0, 1, 2, 3, 4 - g.create_vertex(100); // vertex 0, value=100 - g.create_vertex(200); // vertex 1, value=200 - g.create_vertex(300); // vertex 2, value=300 - g.create_vertex(400); // vertex 3, value=400 - g.create_vertex(500); // vertex 4, value=500 + g.add_vertex(100); // vertex 0, value=100 + g.add_vertex(200); // vertex 1, value=200 + g.add_vertex(300); // vertex 2, value=300 + g.add_vertex(400); // vertex 3, value=400 + g.add_vertex(500); // vertex 4, value=500 // Create edges from vertex 0 to multiple targets (star topology from 0) // These are undirected edges - each creates one edge accessible from both ends - g.create_edge(0, 1, 10); // 0 -- 1, weight=10 - g.create_edge(0, 2, 20); // 0 -- 2, weight=20 - g.create_edge(0, 3, 30); // 0 -- 3, weight=30 - g.create_edge(0, 4, 40); // 0 -- 4, weight=40 + g.add_edge(0, 1, 10); // 0 -- 1, weight=10 + g.add_edge(0, 2, 20); // 0 -- 2, weight=20 + g.add_edge(0, 3, 30); // 0 -- 3, weight=30 + g.add_edge(0, 4, 40); // 0 -- 4, weight=40 // Additional edges to make vertex 2 a hub - g.create_edge(2, 3, 23); // 2 -- 3, weight=23 - g.create_edge(2, 4, 24); // 2 -- 4, weight=24 + g.add_edge(2, 3, 23); // 2 -- 3, weight=23 + g.add_edge(2, 4, 24); // 2 -- 4, weight=24 SECTION("vertex 0 has 4 incident edges") { auto verts = vertices(g); @@ -773,13 +773,13 @@ TEST_CASE("incidence - undirected_adjacency_list iteration order", "[incidence][ Graph g; // Create a simple triangle: 0 -- 1 -- 2 -- 0 - g.create_vertex(0); - g.create_vertex(1); - g.create_vertex(2); + g.add_vertex(0); + g.add_vertex(1); + g.add_vertex(2); - g.create_edge(0, 1, 1); - g.create_edge(1, 2, 2); - g.create_edge(2, 0, 3); + g.add_edge(0, 1, 1); + g.add_edge(1, 2, 2); + g.add_edge(2, 0, 3); SECTION("each vertex has exactly 2 incident edges") { for (auto [id, v] : vertexlist(g)) { @@ -817,14 +817,14 @@ TEST_CASE("incidence - undirected_adjacency_list range algorithms", "[incidence] // Create vertices for (int i = 0; i < 5; ++i) { - g.create_vertex(i * 100); + g.add_vertex(i * 100); } // Create a hub at vertex 0 with many edges - g.create_edge(0, 1, 10); - g.create_edge(0, 2, 20); - g.create_edge(0, 3, 30); - g.create_edge(0, 4, 40); + g.add_edge(0, 1, 10); + g.add_edge(0, 2, 20); + g.add_edge(0, 3, 30); + g.add_edge(0, 4, 40); auto v0_it = find_vertex(g, 0u); auto v0 = *v0_it; @@ -868,8 +868,8 @@ TEST_CASE("incidence - undirected_adjacency_list empty and single edge", "[incid SECTION("vertex with no edges") { Graph g; - g.create_vertex(0); - g.create_vertex(1); + g.add_vertex(0); + g.add_vertex(1); // No edges created auto v0_it = find_vertex(g, 0u); @@ -882,9 +882,9 @@ TEST_CASE("incidence - undirected_adjacency_list empty and single edge", "[incid SECTION("single edge - both endpoints see it") { Graph g; - g.create_vertex(0); - g.create_vertex(1); - g.create_edge(0, 1, 42); + g.add_vertex(0); + g.add_vertex(1); + g.add_edge(0, 1, 42); auto v0_it = find_vertex(g, 0u); auto v0 = *v0_it;