From f1829f32ab2418aa8e403520e21acf983b47873b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Sun, 26 Oct 2025 16:48:49 +0100 Subject: [PATCH 01/54] YT-CPPHGL-1: Prepare the repository for the HGL module implementation - Updated the license comments and the licence verification script - Created a new `include/hgl` directory with a dummy implementation file - Moved the current CPP-GL test files to a dedicated `tests/source/gl` directory - Created a new `tests/source/hgl` directory with a dummy test file for the HGL module - Aligned the testing CMake file to define executables for the GL and HGL modules separately - Removed the library namespace aliases in test files - Aligned the github workflows to properly execute both GL and HGL module tests --- .github/workflows/clang.yaml | 10 +- .github/workflows/gpp.yaml | 10 +- .gitignore | 1 + CMakeLists.txt | 5 +- include/gl/algorithm/breadth_first_search.hpp | 2 +- include/gl/algorithm/coloring.hpp | 2 +- include/gl/algorithm/constants.hpp | 2 +- include/gl/algorithm/deapth_first_search.hpp | 2 +- include/gl/algorithm/dijkstra.hpp | 2 +- include/gl/algorithm/impl/bfs.hpp | 2 +- include/gl/algorithm/impl/common.hpp | 2 +- include/gl/algorithm/impl/dfs.hpp | 2 +- include/gl/algorithm/impl/pfs.hpp | 2 +- include/gl/algorithm/mst.hpp | 2 +- include/gl/algorithm/topological_sort.hpp | 2 +- include/gl/algorithm/types.hpp | 2 +- include/gl/algorithms.hpp | 2 +- include/gl/attributes/force_inline.hpp | 2 +- include/gl/constants.hpp | 2 +- include/gl/decl/graph_traits.hpp | 2 +- include/gl/decl/impl_tags.hpp | 2 +- include/gl/edge_descriptor.hpp | 2 +- include/gl/edge_tags.hpp | 2 +- include/gl/graph.hpp | 2 +- include/gl/graph_file_io.hpp | 2 +- include/gl/graph_io.hpp | 2 +- include/gl/graph_traits.hpp | 2 +- include/gl/graph_utility.hpp | 2 +- include/gl/impl/adjacency_list.hpp | 2 +- include/gl/impl/adjacency_matrix.hpp | 2 +- include/gl/impl/impl_tags.hpp | 2 +- .../gl/impl/specialized/adjacency_list.hpp | 2 +- .../gl/impl/specialized/adjacency_matrix.hpp | 2 +- include/gl/io.hpp | 2 +- include/gl/io/format.hpp | 2 +- include/gl/io/stream_options_manipulator.hpp | 2 +- include/gl/topologies.hpp | 2 +- include/gl/topology/binary_tree.hpp | 2 +- include/gl/topology/bipartite.hpp | 2 +- include/gl/topology/clique.hpp | 2 +- include/gl/topology/cycle.hpp | 2 +- include/gl/topology/path.hpp | 2 +- include/gl/types/dereferencing_iterator.hpp | 2 +- include/gl/types/iterator_range.hpp | 2 +- include/gl/types/non_null_iterator.hpp | 2 +- include/gl/types/properties.hpp | 2 +- include/gl/types/traits/cache_mode.hpp | 2 +- include/gl/types/traits/concepts.hpp | 2 +- include/gl/types/traits/type_extractors.hpp | 2 +- include/gl/types/type_traits.hpp | 2 +- include/gl/types/types.hpp | 2 +- include/gl/util/enum.hpp | 2 +- include/gl/util/pow.hpp | 2 +- include/gl/vertex_descriptor.hpp | 2 +- include/hgl/hypergraph.hpp | 9 + pyrightconfig.json | 6 + scripts/check_licence.py | 29 +-- scripts/common.py | 7 +- scripts/format.py | 33 ++-- tests/CMakeLists.txt | 65 ++++--- .../bicoloring_bipartite_graph_coloring.txt | 0 .../bicoloring_directed_bipartite_graph.gsf | 0 ...icoloring_directed_not_bipartite_graph.gsf | 0 .../bicoloring_undirected_bipartite_graph.gsf | 0 ...oloring_undirected_not_bipartite_graph.gsf | 0 .../{ => gl}/dijkstra_directed_distances.txt | 0 .../data/{ => gl}/dijkstra_directed_graph.gsf | 0 .../dijkstra_directed_predecessors.txt | 0 .../dijkstra_undirected_distances.txt | 0 .../{ => gl}/dijkstra_undirected_graph.gsf | 0 .../dijkstra_undirected_predecessors.txt | 0 tests/data/{ => gl}/mst_edges.txt | 0 tests/data/{ => gl}/mst_graph.gsf | 0 tests/data/{ => gl}/mst_weight.txt | 0 ...opological_sort_directed_acyclic_graph.gsf | 0 ...opological_sort_directed_acyclic_order.txt | 0 ...ogical_sort_directed_not_acyclic_graph.gsf | 0 tests/include/constants.hpp | 48 ----- tests/include/namespaces.hpp | 21 --- tests/include/{ => testing/gl}/alg_common.hpp | 13 +- tests/include/testing/gl/constants.hpp | 47 +++++ tests/include/{ => testing/gl}/functional.hpp | 2 - tests/include/{ => testing/gl}/io_common.hpp | 8 +- tests/include/{ => testing/gl}/transforms.hpp | 7 +- tests/include/{ => testing/gl}/types.hpp | 0 tests/source/{ => gl}/test_adjacency_list.cpp | 69 ++++---- .../source/{ => gl}/test_adjacency_matrix.cpp | 73 ++++---- tests/source/{ => gl}/test_alg_bfs.cpp | 80 ++++----- tests/source/{ => gl}/test_alg_coloring.cpp | 94 +++++----- tests/source/{ => gl}/test_alg_dfs.cpp | 155 ++++++++-------- tests/source/{ => gl}/test_alg_dijkstra.cpp | 125 ++++++------- tests/source/{ => gl}/test_alg_mst.cpp | 114 ++++++------ .../{ => gl}/test_alg_topological_sort.cpp | 35 ++-- .../{ => gl}/test_dereferencing_iterator.cpp | 32 ++-- .../source/{ => gl}/test_edge_descriptor.cpp | 22 +-- tests/source/{ => gl}/test_edge_tags.cpp | 26 +-- tests/source/{ => gl}/test_graph.cpp | 111 ++++++------ tests/source/{ => gl}/test_graph_file_io.cpp | 38 ++-- .../source/{ => gl}/test_graph_incidence.cpp | 10 +- tests/source/{ => gl}/test_graph_io.cpp | 60 +++---- .../{ => gl}/test_graph_topology_builders.cpp | 101 ++++++----- tests/source/{ => gl}/test_iterator_range.cpp | 34 ++-- .../{ => gl}/test_non_null_iterator.cpp | 32 ++-- tests/source/{ => gl}/test_properties.cpp | 20 +-- .../gl/test_stream_options_manipulator.cpp | 167 ++++++++++++++++++ tests/source/{ => gl}/test_util.cpp | 16 +- .../{ => gl}/test_vertex_degree_getters.cpp | 64 +++---- .../{ => gl}/test_vertex_descriptor.cpp | 12 +- tests/source/hgl/test_hgl_init.cpp | 13 ++ tests/source/test_extarnal_libs_config.cpp | 52 ------ .../test_stream_options_manipulator.cpp | 167 ------------------ 111 files changed, 1070 insertions(+), 1073 deletions(-) create mode 100644 include/hgl/hypergraph.hpp create mode 100644 pyrightconfig.json rename tests/data/{ => gl}/bicoloring_bipartite_graph_coloring.txt (100%) rename tests/data/{ => gl}/bicoloring_directed_bipartite_graph.gsf (100%) rename tests/data/{ => gl}/bicoloring_directed_not_bipartite_graph.gsf (100%) rename tests/data/{ => gl}/bicoloring_undirected_bipartite_graph.gsf (100%) rename tests/data/{ => gl}/bicoloring_undirected_not_bipartite_graph.gsf (100%) rename tests/data/{ => gl}/dijkstra_directed_distances.txt (100%) rename tests/data/{ => gl}/dijkstra_directed_graph.gsf (100%) rename tests/data/{ => gl}/dijkstra_directed_predecessors.txt (100%) rename tests/data/{ => gl}/dijkstra_undirected_distances.txt (100%) rename tests/data/{ => gl}/dijkstra_undirected_graph.gsf (100%) rename tests/data/{ => gl}/dijkstra_undirected_predecessors.txt (100%) rename tests/data/{ => gl}/mst_edges.txt (100%) rename tests/data/{ => gl}/mst_graph.gsf (100%) rename tests/data/{ => gl}/mst_weight.txt (100%) rename tests/data/{ => gl}/topological_sort_directed_acyclic_graph.gsf (100%) rename tests/data/{ => gl}/topological_sort_directed_acyclic_order.txt (100%) rename tests/data/{ => gl}/topological_sort_directed_not_acyclic_graph.gsf (100%) delete mode 100644 tests/include/constants.hpp delete mode 100644 tests/include/namespaces.hpp rename tests/include/{ => testing/gl}/alg_common.hpp (76%) create mode 100644 tests/include/testing/gl/constants.hpp rename tests/include/{ => testing/gl}/functional.hpp (84%) rename tests/include/{ => testing/gl}/io_common.hpp (92%) rename tests/include/{ => testing/gl}/transforms.hpp (71%) rename tests/include/{ => testing/gl}/types.hpp (100%) rename tests/source/{ => gl}/test_adjacency_list.cpp (92%) rename tests/source/{ => gl}/test_adjacency_matrix.cpp (90%) rename tests/source/{ => gl}/test_alg_bfs.cpp (58%) rename tests/source/{ => gl}/test_alg_coloring.cpp (55%) rename tests/source/{ => gl}/test_alg_dfs.cpp (61%) rename tests/source/{ => gl}/test_alg_dijkstra.cpp (60%) rename tests/source/{ => gl}/test_alg_mst.cpp (70%) rename tests/source/{ => gl}/test_alg_topological_sort.cpp (65%) rename tests/source/{ => gl}/test_dereferencing_iterator.cpp (85%) rename tests/source/{ => gl}/test_edge_descriptor.cpp (86%) rename tests/source/{ => gl}/test_edge_tags.cpp (81%) rename tests/source/{ => gl}/test_graph.cpp (90%) rename tests/source/{ => gl}/test_graph_file_io.cpp (77%) rename tests/source/{ => gl}/test_graph_incidence.cpp (96%) rename tests/source/{ => gl}/test_graph_io.cpp (75%) rename tests/source/{ => gl}/test_graph_topology_builders.cpp (80%) rename tests/source/{ => gl}/test_iterator_range.cpp (82%) rename tests/source/{ => gl}/test_non_null_iterator.cpp (84%) rename tests/source/{ => gl}/test_properties.cpp (90%) create mode 100644 tests/source/gl/test_stream_options_manipulator.cpp rename tests/source/{ => gl}/test_util.cpp (81%) rename tests/source/{ => gl}/test_vertex_degree_getters.cpp (64%) rename tests/source/{ => gl}/test_vertex_descriptor.cpp (71%) create mode 100644 tests/source/hgl/test_hgl_init.cpp delete mode 100644 tests/source/test_extarnal_libs_config.cpp delete mode 100644 tests/source/test_stream_options_manipulator.cpp diff --git a/.github/workflows/clang.yaml b/.github/workflows/clang.yaml index adcf9cc6..712009ed 100644 --- a/.github/workflows/clang.yaml +++ b/.github/workflows/clang.yaml @@ -2,7 +2,7 @@ name: clang++ on: push: branches: - - '*' + - "*" paths: - .github/workflows/clang.yaml - include/** @@ -19,10 +19,10 @@ jobs: - name: Prepare env: - CC: clang-17 + CC: clang-17 CXX: clang++-17 run: | - cmake -B build -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC + cmake -B build -DBUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC continue-on-error: false - name: Build test executable @@ -32,5 +32,5 @@ jobs: - name: Run tests run: | - ./build/tests/run - continue-on-error: false + ./build/tests/gl + ./build/tests/hgl diff --git a/.github/workflows/gpp.yaml b/.github/workflows/gpp.yaml index 2333f769..9801959d 100644 --- a/.github/workflows/gpp.yaml +++ b/.github/workflows/gpp.yaml @@ -2,13 +2,12 @@ name: g++ on: push: branches: - - '*' + - "*" paths: - .github/workflows/gpp.yaml - include/** - tests/** - jobs: build: name: Build and run tests @@ -20,10 +19,10 @@ jobs: - name: Prepare env: - CC: gcc-13 + CC: gcc-13 CXX: g++-13 run: | - cmake -B build -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC + cmake -B build -DBUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC continue-on-error: false - name: Build test executable @@ -33,4 +32,5 @@ jobs: - name: Run tests run: | - ./build/tests/run + ./build/tests/gl + ./build/tests/hgl diff --git a/.gitignore b/.gitignore index b1c8eda5..d7bb8631 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # editor files .vscode/ .idea/ +.zed/ # build files *.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 223d89a9..d978900a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,13 +8,14 @@ else() endif() project(cpp-gl - VERSION 1.0.4 + VERSION 2.0.0 DESCRIPTION "General purpose header-only template graph library for C++20" HOMEPAGE_URL "https://github.com/SpectraL519/cpp-gl" LANGUAGES CXX ) -option(BUILD_TESTS "Build project tests" ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +option(BUILD_TESTS "Build project tests" OFF) # Set the proper cxx standard if(NOT DEFINED CMAKE_CXX_STANDARD) diff --git a/include/gl/algorithm/breadth_first_search.hpp b/include/gl/algorithm/breadth_first_search.hpp index c06143ed..edff05dc 100644 --- a/include/gl/algorithm/breadth_first_search.hpp +++ b/include/gl/algorithm/breadth_first_search.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/coloring.hpp b/include/gl/algorithm/coloring.hpp index aaf4e333..43d5067d 100644 --- a/include/gl/algorithm/coloring.hpp +++ b/include/gl/algorithm/coloring.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/constants.hpp b/include/gl/algorithm/constants.hpp index 2cc6ea67..fbe8c0e5 100644 --- a/include/gl/algorithm/constants.hpp +++ b/include/gl/algorithm/constants.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/deapth_first_search.hpp b/include/gl/algorithm/deapth_first_search.hpp index ca607eb0..61dfc428 100644 --- a/include/gl/algorithm/deapth_first_search.hpp +++ b/include/gl/algorithm/deapth_first_search.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/dijkstra.hpp b/include/gl/algorithm/dijkstra.hpp index 47f84c94..ccc90e95 100644 --- a/include/gl/algorithm/dijkstra.hpp +++ b/include/gl/algorithm/dijkstra.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/impl/bfs.hpp b/include/gl/algorithm/impl/bfs.hpp index cb98fd28..527daf02 100644 --- a/include/gl/algorithm/impl/bfs.hpp +++ b/include/gl/algorithm/impl/bfs.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/impl/common.hpp b/include/gl/algorithm/impl/common.hpp index 8b44ce94..b6bc4c70 100644 --- a/include/gl/algorithm/impl/common.hpp +++ b/include/gl/algorithm/impl/common.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/impl/dfs.hpp b/include/gl/algorithm/impl/dfs.hpp index e8ed2879..722f8f01 100644 --- a/include/gl/algorithm/impl/dfs.hpp +++ b/include/gl/algorithm/impl/dfs.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/impl/pfs.hpp b/include/gl/algorithm/impl/pfs.hpp index 1b42e515..fe40c88e 100644 --- a/include/gl/algorithm/impl/pfs.hpp +++ b/include/gl/algorithm/impl/pfs.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/mst.hpp b/include/gl/algorithm/mst.hpp index 454953f5..fb0401d6 100644 --- a/include/gl/algorithm/mst.hpp +++ b/include/gl/algorithm/mst.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/topological_sort.hpp b/include/gl/algorithm/topological_sort.hpp index 445a15c5..c31ac76e 100644 --- a/include/gl/algorithm/topological_sort.hpp +++ b/include/gl/algorithm/topological_sort.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithm/types.hpp b/include/gl/algorithm/types.hpp index 62124f5f..61095fde 100644 --- a/include/gl/algorithm/types.hpp +++ b/include/gl/algorithm/types.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/algorithms.hpp b/include/gl/algorithms.hpp index a25a40e9..31686d41 100644 --- a/include/gl/algorithms.hpp +++ b/include/gl/algorithms.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/attributes/force_inline.hpp b/include/gl/attributes/force_inline.hpp index 94bed0b2..9301b0f2 100644 --- a/include/gl/attributes/force_inline.hpp +++ b/include/gl/attributes/force_inline.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/constants.hpp b/include/gl/constants.hpp index 682e7fae..3ccf2fb8 100644 --- a/include/gl/constants.hpp +++ b/include/gl/constants.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/decl/graph_traits.hpp b/include/gl/decl/graph_traits.hpp index cda02d8b..74399fe1 100644 --- a/include/gl/decl/graph_traits.hpp +++ b/include/gl/decl/graph_traits.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/decl/impl_tags.hpp b/include/gl/decl/impl_tags.hpp index 8240764c..acbadb3c 100644 --- a/include/gl/decl/impl_tags.hpp +++ b/include/gl/decl/impl_tags.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index 82967c7a..1658af73 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/edge_tags.hpp b/include/gl/edge_tags.hpp index c1364599..438ab657 100644 --- a/include/gl/edge_tags.hpp +++ b/include/gl/edge_tags.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index f0cee676..90792bc8 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/graph_file_io.hpp b/include/gl/graph_file_io.hpp index c75e2c05..4ffe1b00 100644 --- a/include/gl/graph_file_io.hpp +++ b/include/gl/graph_file_io.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/graph_io.hpp b/include/gl/graph_io.hpp index 8957a792..d88861af 100644 --- a/include/gl/graph_io.hpp +++ b/include/gl/graph_io.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/graph_traits.hpp b/include/gl/graph_traits.hpp index 2f3429a2..729dd840 100644 --- a/include/gl/graph_traits.hpp +++ b/include/gl/graph_traits.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/graph_utility.hpp b/include/gl/graph_utility.hpp index da252666..5053ddb3 100644 --- a/include/gl/graph_utility.hpp +++ b/include/gl/graph_utility.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index baccf292..b4f74f28 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 4fc27b6c..a84a2643 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/impl/impl_tags.hpp b/include/gl/impl/impl_tags.hpp index de53a6d6..5b4245b3 100644 --- a/include/gl/impl/impl_tags.hpp +++ b/include/gl/impl/impl_tags.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index ac3d83a2..a953f173 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 1a1420c2..e7472d13 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/io.hpp b/include/gl/io.hpp index a699162f..1889c5f8 100644 --- a/include/gl/io.hpp +++ b/include/gl/io.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/io/format.hpp b/include/gl/io/format.hpp index 72fe8e9c..0de7e9ad 100644 --- a/include/gl/io/format.hpp +++ b/include/gl/io/format.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/io/stream_options_manipulator.hpp b/include/gl/io/stream_options_manipulator.hpp index 3b03a905..6eaaf916 100644 --- a/include/gl/io/stream_options_manipulator.hpp +++ b/include/gl/io/stream_options_manipulator.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/topologies.hpp b/include/gl/topologies.hpp index ef044c27..368fe4d8 100644 --- a/include/gl/topologies.hpp +++ b/include/gl/topologies.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/topology/binary_tree.hpp b/include/gl/topology/binary_tree.hpp index 40905221..0b71f12e 100644 --- a/include/gl/topology/binary_tree.hpp +++ b/include/gl/topology/binary_tree.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/topology/bipartite.hpp b/include/gl/topology/bipartite.hpp index 29ac6298..16480fbd 100644 --- a/include/gl/topology/bipartite.hpp +++ b/include/gl/topology/bipartite.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/topology/clique.hpp b/include/gl/topology/clique.hpp index bc22f4bf..57bd0747 100644 --- a/include/gl/topology/clique.hpp +++ b/include/gl/topology/clique.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/topology/cycle.hpp b/include/gl/topology/cycle.hpp index 4eb9e2b8..4e0033fb 100644 --- a/include/gl/topology/cycle.hpp +++ b/include/gl/topology/cycle.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/topology/path.hpp b/include/gl/topology/path.hpp index ca4e800b..46cea023 100644 --- a/include/gl/topology/path.hpp +++ b/include/gl/topology/path.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/dereferencing_iterator.hpp b/include/gl/types/dereferencing_iterator.hpp index f1c9ce35..e540e083 100644 --- a/include/gl/types/dereferencing_iterator.hpp +++ b/include/gl/types/dereferencing_iterator.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/iterator_range.hpp b/include/gl/types/iterator_range.hpp index e25bcc9e..272aa040 100644 --- a/include/gl/types/iterator_range.hpp +++ b/include/gl/types/iterator_range.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/non_null_iterator.hpp b/include/gl/types/non_null_iterator.hpp index 5347aef0..35d14c5c 100644 --- a/include/gl/types/non_null_iterator.hpp +++ b/include/gl/types/non_null_iterator.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index ea59c324..684f2df5 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/traits/cache_mode.hpp b/include/gl/types/traits/cache_mode.hpp index 8e417f04..ab50bd90 100644 --- a/include/gl/types/traits/cache_mode.hpp +++ b/include/gl/types/traits/cache_mode.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/traits/concepts.hpp b/include/gl/types/traits/concepts.hpp index 66001b87..d01503e5 100644 --- a/include/gl/types/traits/concepts.hpp +++ b/include/gl/types/traits/concepts.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/traits/type_extractors.hpp b/include/gl/types/traits/type_extractors.hpp index ae0b50aa..1fec2f88 100644 --- a/include/gl/types/traits/type_extractors.hpp +++ b/include/gl/types/traits/type_extractors.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/type_traits.hpp b/include/gl/types/type_traits.hpp index 9fafae90..4a536ca2 100644 --- a/include/gl/types/type_traits.hpp +++ b/include/gl/types/type_traits.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/types/types.hpp b/include/gl/types/types.hpp index 03775746..d03b2b41 100644 --- a/include/gl/types/types.hpp +++ b/include/gl/types/types.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/util/enum.hpp b/include/gl/util/enum.hpp index 811ebfde..68771e8a 100644 --- a/include/gl/util/enum.hpp +++ b/include/gl/util/enum.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/util/pow.hpp b/include/gl/util/pow.hpp index 7a2e93f8..7a139696 100644 --- a/include/gl/util/pow.hpp +++ b/include/gl/util/pow.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 6568d9b0..bc225be9 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Jakub Musiał +// Copyright (c) 2024-2026 Jakub Musiał // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp new file mode 100644 index 00000000..42d2ab72 --- /dev/null +++ b/include/hgl/hypergraph.hpp @@ -0,0 +1,9 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +namespace hgl { + +class hypergraph; + +} // namespace hgl diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..f8e98c84 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,6 @@ +{ + "include": ["scripts"], + "typeCheckingMode": "basic", + "reportImplicitRelativeImport": false, + "reportUnusedCallResult": false +} diff --git a/scripts/check_licence.py b/scripts/check_licence.py index 1304e82b..f33f4df0 100644 --- a/scripts/check_licence.py +++ b/scripts/check_licence.py @@ -7,7 +7,7 @@ LICENCE_INFO = [ - "// Copyright (c) 2024 Jakub Musiał", + "// Copyright (c) 2024-2026 Jakub Musiał", "// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl).", "// Licensed under the MIT License. See the LICENSE file in the project root for full license information.", ] @@ -23,28 +23,31 @@ class DefaultParameters: def parse_args(): parser = argparse.ArgumentParser() parser.add_argument( - "-p", "--search-paths", + "-p", + "--search-paths", type=str, default=DefaultParameters.search_paths, nargs="*", action="extend", - help="list of search directory paths" + help="list of search directory paths", ) parser.add_argument( - "-f", "--file-patterns", + "-f", + "--file-patterns", type=str, default=DefaultParameters.file_patterns, nargs="*", action="extend", - help="list of file patterns to include" + help="list of file patterns to include", ) parser.add_argument( - "-e", "--exclude-paths", + "-e", + "--exclude-paths", type=str, default=DefaultParameters.exclude_paths, nargs="*", action="extend", - help="list of directory paths to exclude" + help="list of directory paths to exclude", ) return vars(parser.parse_args()) @@ -62,6 +65,7 @@ def check_licence(files: set[Path]) -> int: print(f"Files to check: {n_files}") return_code = None + def _set_return_code(c: ReturnCode): nonlocal return_code return_code = c if not return_code else return_code @@ -78,7 +82,9 @@ def _check_file(file: Path): print(f"[Licence error] File `{file}` to short") return - matching_lines = [lines[i] == LICENCE_INFO[i] for i in range(n_licence_lines)] + matching_lines = [ + lines[i] == LICENCE_INFO[i] for i in range(n_licence_lines) + ] correct_licence = all(matching_lines) if not correct_licence: missing_info = any(matching_lines) @@ -89,7 +95,6 @@ def _check_file(file: Path): _set_return_code(ReturnCode.missing_licence) print(f"[Licence error] Missing licence info in file `{file}`") - for i, file in enumerate(files): print(f"[{i + 1}/{n_files}] {file}") _check_file(file) @@ -97,11 +102,7 @@ def _check_file(file: Path): return return_code -def main( - search_paths: list[str], - file_patterns: list[str], - exclude_paths: list[str] -): +def main(search_paths: list[str], file_patterns: list[str], exclude_paths: list[str]): files_to_check = find_files(search_paths, file_patterns, exclude_paths) sys.exit(check_licence(files_to_check)) diff --git a/scripts/common.py b/scripts/common.py index cc398699..0428b8bf 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -2,9 +2,7 @@ def find_files( - search_paths: list[str], - file_patterns: list[str], - exclude_paths: list[str] + search_paths: list[str], file_patterns: list[str], exclude_paths: list[str] ) -> set[Path]: matching_files = [] for search_path in search_paths: @@ -13,7 +11,8 @@ def find_files( matching_files.extend(path.rglob(pattern)) filtered_files = { - file for file in matching_files + file + for file in matching_files if not any(str(file.parent).startswith(path) for path in exclude_paths) } diff --git a/scripts/format.py b/scripts/format.py index e52c85c4..4c7daccf 100644 --- a/scripts/format.py +++ b/scripts/format.py @@ -17,42 +17,47 @@ class DefaultParameters: def parse_args(): parser = argparse.ArgumentParser() parser.add_argument( - "-m", "--modified-files", + "-m", + "--modified-files", type=bool, default=DefaultParameters.modified_files, action=argparse.BooleanOptionalAction, - help="run clang-format only on the files modified since last pushed commit" + help="run clang-format only on the files modified since last pushed commit", ) parser.add_argument( - "-p", "--search-paths", + "-p", + "--search-paths", type=str, default=DefaultParameters.search_paths, nargs="*", action="extend", - help="list of search directory paths" + help="list of search directory paths", ) parser.add_argument( - "-f", "--file-patterns", + "-f", + "--file-patterns", type=str, default=DefaultParameters.file_patterns, nargs="*", action="extend", - help="list of file patterns to include" + help="list of file patterns to include", ) parser.add_argument( - "-e", "--exclude-paths", + "-e", + "--exclude-paths", type=str, default=DefaultParameters.exclude_paths, nargs="*", action="extend", - help="list of directory paths to exclude" + help="list of directory paths to exclude", ) parser.add_argument( - "-c", "--check", + "-c", + "--check", type=bool, default=DefaultParameters.check, action=argparse.BooleanOptionalAction, - help="run format check" + help="run format check", ) return vars(parser.parse_args()) @@ -65,7 +70,7 @@ def get_modified_files(files: set[Path]) -> set[Path]: check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, ) modified_files = {Path(file) for file in result.stdout.splitlines() if file} @@ -95,7 +100,9 @@ def run_clang_format(files: set[Path], check: bool) -> int: result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: return_code = result.returncode - print(f"[Format error]\n[stdout]\n{result.stdout}\n[stderr]\n{result.stderr}") + print( + f"[Format error]\n[stdout]\n{result.stdout}\n[stderr]\n{result.stderr}" + ) print("Done!") return return_code @@ -106,7 +113,7 @@ def main( search_paths: list[str], file_patterns: list[str], exclude_paths: list[str], - check: bool + check: bool, ): files_to_format = find_files(search_paths, file_patterns, exclude_paths) if modified_files: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 39db8d8e..7c8d07e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,19 +1,6 @@ cmake_minimum_required(VERSION 3.12) project(cpp-gl-test) -# Structure -set(SOURCE_DIRS "source" "app") -set(INCLUDE_DIRS "include" "external") -set(EXECUTABLE_DIR "${CMAKE_CURRENT_BINARY_DIR}") -set(DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data") - -# Source files -file(GLOB_RECURSE SOURCES "") -foreach(SOURCE_DIR ${SOURCE_DIRS}) - file(GLOB_RECURSE CURRENT_SOURCES ${SOURCE_DIR}/*.cpp) - list(APPEND SOURCES ${CURRENT_SOURCES}) -endforeach() - # Set compile options if(NOT DEFINED CMAKE_CXX_FLAGS) set( @@ -23,15 +10,43 @@ if(NOT DEFINED CMAKE_CXX_FLAGS) ) endif() -# Executable -add_executable(run ${SOURCES}) -set_target_properties(run PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_DIR}" - CXX_STANDARD ${CMAKE_CXX_STANDARD} - CXX_STANDARD_REQUIRED YES - CXX_EXTENSIONS NO -) -target_include_directories(run PRIVATE ${INCLUDE_DIRS}) -target_compile_options(run PRIVATE ${CMAKE_CXX_FLAGS}) -target_compile_definitions(run PRIVATE GL_TESTING TEST_DATA_PATH="${DATA_DIR}") -target_link_libraries(run PRIVATE cpp-gl) +# Common structure +set(SOURCE_DIRS "app") +set(INCLUDE_DIRS "include" "external") +set(EXECUTABLE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + +# Test target generation +function(add_test_target TARGET_NAME SOURCE_DIRS DATA_DIR) + set(TARGET_SOURCES "") + foreach(SOURCE_DIR ${SOURCE_DIRS}) + file(GLOB_RECURSE CURRENT_SOURCES "${SOURCE_DIR}/*.cpp") + list(APPEND TARGET_SOURCES ${CURRENT_SOURCES}) + endforeach() + + add_executable(${TARGET_NAME} ${TARGET_SOURCES}) + + set_target_properties(${TARGET_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_DIR}" + CXX_STANDARD ${CMAKE_CXX_STANDARD} + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + + target_include_directories(${TARGET_NAME} PRIVATE ${INCLUDE_DIRS}) + target_compile_options(${TARGET_NAME} PRIVATE ${CMAKE_CXX_FLAGS}) + target_compile_definitions(${TARGET_NAME} PRIVATE + GL_TESTING + TEST_DATA_PATH="${DATA_DIR}" + ) + target_link_libraries(${TARGET_NAME} PRIVATE cpp-gl) +endfunction() + +# GL target +set(GL_SOURCE_DIRS "source/gl" ${SOURCE_DIRS}) +set(GL_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data/gl") +add_test_target(gl "${GL_SOURCE_DIRS}" "${GL_DATA_DIR}") + +# HGL target +set(HGL_SOURCE_DIRS "source/hgl" ${SOURCE_DIRS}) +set(HGL_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data/hgl") +add_test_target(hgl "${HGL_SOURCE_DIRS}" "${HGL_DATA_DIR}") diff --git a/tests/data/bicoloring_bipartite_graph_coloring.txt b/tests/data/gl/bicoloring_bipartite_graph_coloring.txt similarity index 100% rename from tests/data/bicoloring_bipartite_graph_coloring.txt rename to tests/data/gl/bicoloring_bipartite_graph_coloring.txt diff --git a/tests/data/bicoloring_directed_bipartite_graph.gsf b/tests/data/gl/bicoloring_directed_bipartite_graph.gsf similarity index 100% rename from tests/data/bicoloring_directed_bipartite_graph.gsf rename to tests/data/gl/bicoloring_directed_bipartite_graph.gsf diff --git a/tests/data/bicoloring_directed_not_bipartite_graph.gsf b/tests/data/gl/bicoloring_directed_not_bipartite_graph.gsf similarity index 100% rename from tests/data/bicoloring_directed_not_bipartite_graph.gsf rename to tests/data/gl/bicoloring_directed_not_bipartite_graph.gsf diff --git a/tests/data/bicoloring_undirected_bipartite_graph.gsf b/tests/data/gl/bicoloring_undirected_bipartite_graph.gsf similarity index 100% rename from tests/data/bicoloring_undirected_bipartite_graph.gsf rename to tests/data/gl/bicoloring_undirected_bipartite_graph.gsf diff --git a/tests/data/bicoloring_undirected_not_bipartite_graph.gsf b/tests/data/gl/bicoloring_undirected_not_bipartite_graph.gsf similarity index 100% rename from tests/data/bicoloring_undirected_not_bipartite_graph.gsf rename to tests/data/gl/bicoloring_undirected_not_bipartite_graph.gsf diff --git a/tests/data/dijkstra_directed_distances.txt b/tests/data/gl/dijkstra_directed_distances.txt similarity index 100% rename from tests/data/dijkstra_directed_distances.txt rename to tests/data/gl/dijkstra_directed_distances.txt diff --git a/tests/data/dijkstra_directed_graph.gsf b/tests/data/gl/dijkstra_directed_graph.gsf similarity index 100% rename from tests/data/dijkstra_directed_graph.gsf rename to tests/data/gl/dijkstra_directed_graph.gsf diff --git a/tests/data/dijkstra_directed_predecessors.txt b/tests/data/gl/dijkstra_directed_predecessors.txt similarity index 100% rename from tests/data/dijkstra_directed_predecessors.txt rename to tests/data/gl/dijkstra_directed_predecessors.txt diff --git a/tests/data/dijkstra_undirected_distances.txt b/tests/data/gl/dijkstra_undirected_distances.txt similarity index 100% rename from tests/data/dijkstra_undirected_distances.txt rename to tests/data/gl/dijkstra_undirected_distances.txt diff --git a/tests/data/dijkstra_undirected_graph.gsf b/tests/data/gl/dijkstra_undirected_graph.gsf similarity index 100% rename from tests/data/dijkstra_undirected_graph.gsf rename to tests/data/gl/dijkstra_undirected_graph.gsf diff --git a/tests/data/dijkstra_undirected_predecessors.txt b/tests/data/gl/dijkstra_undirected_predecessors.txt similarity index 100% rename from tests/data/dijkstra_undirected_predecessors.txt rename to tests/data/gl/dijkstra_undirected_predecessors.txt diff --git a/tests/data/mst_edges.txt b/tests/data/gl/mst_edges.txt similarity index 100% rename from tests/data/mst_edges.txt rename to tests/data/gl/mst_edges.txt diff --git a/tests/data/mst_graph.gsf b/tests/data/gl/mst_graph.gsf similarity index 100% rename from tests/data/mst_graph.gsf rename to tests/data/gl/mst_graph.gsf diff --git a/tests/data/mst_weight.txt b/tests/data/gl/mst_weight.txt similarity index 100% rename from tests/data/mst_weight.txt rename to tests/data/gl/mst_weight.txt diff --git a/tests/data/topological_sort_directed_acyclic_graph.gsf b/tests/data/gl/topological_sort_directed_acyclic_graph.gsf similarity index 100% rename from tests/data/topological_sort_directed_acyclic_graph.gsf rename to tests/data/gl/topological_sort_directed_acyclic_graph.gsf diff --git a/tests/data/topological_sort_directed_acyclic_order.txt b/tests/data/gl/topological_sort_directed_acyclic_order.txt similarity index 100% rename from tests/data/topological_sort_directed_acyclic_order.txt rename to tests/data/gl/topological_sort_directed_acyclic_order.txt diff --git a/tests/data/topological_sort_directed_not_acyclic_graph.gsf b/tests/data/gl/topological_sort_directed_not_acyclic_graph.gsf similarity index 100% rename from tests/data/topological_sort_directed_not_acyclic_graph.gsf rename to tests/data/gl/topological_sort_directed_not_acyclic_graph.gsf diff --git a/tests/include/constants.hpp b/tests/include/constants.hpp deleted file mode 100644 index ef1ac27d..00000000 --- a/tests/include/constants.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "namespaces.hpp" -#include "types.hpp" - -#include - -#include - -#define IC inline constexpr - -namespace gl_testing::constants { - -IC lib_t::size_type zero = 0ull; -IC lib_t::size_type one = 1ull; -IC lib_t::size_type two = 2ull; -IC lib_t::size_type three = 3ull; - -IC lib_t::size_type n_elements = three; -IC lib_t::size_type zero_elements = zero; -IC lib_t::size_type one_element = one; - -// n_elements for graph topology tests -IC lib_t::size_type n_elements_top = 10ull; - -// n_elements for graph algorithm tests -IC lib_t::size_type n_elements_alg = 10ull; -IC lib_t::size_type depth = 5ull; - -IC lib_t::size_type first_element_idx = zero; -IC lib_t::size_type last_element_idx = n_elements - one_element; -IC lib_t::size_type out_of_range_elemenet_idx = n_elements; - -IC lib_t::id_type vertex_id_1 = first_element_idx; -IC lib_t::id_type vertex_id_2 = vertex_id_1 + one_element; -IC lib_t::id_type vertex_id_3 = vertex_id_2 + one_element; - -IC auto vertex_id_view = std::views::iota(first_element_idx, n_elements); - -IC types::it_distance_type empty_distance = static_cast(zero); - -IC types::visited_property visited{true}; -IC types::visited_property not_visited{false}; - -IC types::used_property used{true}; -IC types::used_property not_used{false}; - -} // namespace gl_testing::constants diff --git a/tests/include/namespaces.hpp b/tests/include/namespaces.hpp deleted file mode 100644 index f1884786..00000000 --- a/tests/include/namespaces.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -// required for the type alias -namespace gl { - -namespace impl {} // namespace impl - -namespace types {} // namespace types - -namespace type_traits {} // namespace type_traits - -} // namespace gl - -namespace gl_testing { - -namespace lib = gl; -namespace lib_i = lib::impl; -namespace lib_t = lib::types; -namespace lib_tt = lib::type_traits; - -} // namespace gl_testing diff --git a/tests/include/alg_common.hpp b/tests/include/testing/gl/alg_common.hpp similarity index 76% rename from tests/include/alg_common.hpp rename to tests/include/testing/gl/alg_common.hpp index 023163a9..0402a983 100644 --- a/tests/include/alg_common.hpp +++ b/tests/include/testing/gl/alg_common.hpp @@ -1,7 +1,6 @@ #pragma once #include "constants.hpp" -#include "namespaces.hpp" #include "types.hpp" #include @@ -15,8 +14,8 @@ namespace gl_testing::alg_common { inline const fs::path data_path(TEST_DATA_PATH); template -requires(lib_tt::c_readable) -[[nodiscard]] std::vector load_list(const lib_t::size_type n, const fs::path& file_path) { +requires(gl::type_traits::c_readable) +[[nodiscard]] std::vector load_list(const gl::types::size_type n, const fs::path& file_path) { std::vector list(n); std::ifstream file(file_path); @@ -28,16 +27,16 @@ requires(lib_tt::c_readable) ); } - for (lib_t::size_type i = 0; i < n; ++i) + for (gl::types::size_type i = 0; i < n; ++i) file >> list[i]; return list; } [[nodiscard]] inline auto has_correct_bin_predecessor( - const lib::algorithm::predecessors_descriptor& pd + const gl::algorithm::predecessors_descriptor& pd ) { - return [&pd](const lib_t::id_type vertex_id) { + return [&pd](const gl::types::id_type vertex_id) { if (not pd.is_reachable(vertex_id)) return false; @@ -48,7 +47,7 @@ requires(lib_tt::c_readable) }; } -template VertexType> +template VertexType> requires(std::same_as) struct vertex_visited_projection { [[nodiscard]] bool operator()(const VertexType& vertex) const { diff --git a/tests/include/testing/gl/constants.hpp b/tests/include/testing/gl/constants.hpp new file mode 100644 index 00000000..0e3deb04 --- /dev/null +++ b/tests/include/testing/gl/constants.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "types.hpp" + +#include + +#include + +#define IC inline constexpr + +namespace gl_testing::constants { + +IC gl::types::size_type zero = 0ull; +IC gl::types::size_type one = 1ull; +IC gl::types::size_type two = 2ull; +IC gl::types::size_type three = 3ull; + +IC gl::types::size_type n_elements = three; +IC gl::types::size_type zero_elements = zero; +IC gl::types::size_type one_element = one; + +// n_elements for graph topology tests +IC gl::types::size_type n_elements_top = 10ull; + +// n_elements for graph algorithm tests +IC gl::types::size_type n_elements_alg = 10ull; +IC gl::types::size_type depth = 5ull; + +IC gl::types::size_type first_element_idx = zero; +IC gl::types::size_type last_element_idx = n_elements - one_element; +IC gl::types::size_type out_of_range_elemenet_idx = n_elements; + +IC gl::types::id_type vertex_id_1 = first_element_idx; +IC gl::types::id_type vertex_id_2 = vertex_id_1 + one_element; +IC gl::types::id_type vertex_id_3 = vertex_id_2 + one_element; + +IC auto vertex_id_view = std::views::iota(first_element_idx, n_elements); + +IC types::it_distance_type empty_distance = static_cast(zero); + +IC types::visited_property visited{true}; +IC types::visited_property not_visited{false}; + +IC types::used_property used{true}; +IC types::used_property not_used{false}; + +} // namespace gl_testing::constants diff --git a/tests/include/functional.hpp b/tests/include/testing/gl/functional.hpp similarity index 84% rename from tests/include/functional.hpp rename to tests/include/testing/gl/functional.hpp index 61be44fd..f306263c 100644 --- a/tests/include/functional.hpp +++ b/tests/include/testing/gl/functional.hpp @@ -1,7 +1,5 @@ #pragma once -#include "namespaces.hpp" - namespace gl_testing::func { template diff --git a/tests/include/io_common.hpp b/tests/include/testing/gl/io_common.hpp similarity index 92% rename from tests/include/io_common.hpp rename to tests/include/testing/gl/io_common.hpp index c89cdc2d..f58402ec 100644 --- a/tests/include/io_common.hpp +++ b/tests/include/testing/gl/io_common.hpp @@ -1,14 +1,12 @@ #pragma once -#include "namespaces.hpp" - #include #include namespace gl_testing::io_common { -template +template void verify_graph_structure(const GraphType& actual, const GraphType& expected) { REQUIRE_EQ(actual.n_vertices(), expected.n_vertices()); REQUIRE_EQ(actual.n_unique_edges(), expected.n_unique_edges()); @@ -21,7 +19,7 @@ void verify_graph_structure(const GraphType& actual, const GraphType& expected) })); } -template +template void verify_vertex_properties(const GraphType& actual, const GraphType& expected) { const auto properties_proj = [](const auto& vertex) { return vertex.properties; }; @@ -34,7 +32,7 @@ void verify_vertex_properties(const GraphType& actual, const GraphType& expected )); } -template +template void verify_edge_properties(const GraphType& actual, const GraphType& expected) { const auto properties_proj = [](const auto& vertex) { return vertex.properties; }; diff --git a/tests/include/transforms.hpp b/tests/include/testing/gl/transforms.hpp similarity index 71% rename from tests/include/transforms.hpp rename to tests/include/testing/gl/transforms.hpp index f85db54b..ca694135 100644 --- a/tests/include/transforms.hpp +++ b/tests/include/testing/gl/transforms.hpp @@ -1,15 +1,14 @@ #pragma once -#include "namespaces.hpp" - #include #include namespace gl_testing::transforms { -template VertexType = lib::vertex_descriptor<>> -inline lib_t::id_type extract_vertex_id(const VertexType& vertex) { +template < + gl::type_traits::c_instantiation_of VertexType = gl::vertex_descriptor<>> +inline gl::types::id_type extract_vertex_id(const VertexType& vertex) { return vertex.id(); } diff --git a/tests/include/types.hpp b/tests/include/testing/gl/types.hpp similarity index 100% rename from tests/include/types.hpp rename to tests/include/testing/gl/types.hpp diff --git a/tests/source/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp similarity index 92% rename from tests/source/test_adjacency_list.cpp rename to tests/source/gl/test_adjacency_list.cpp index 8dd4d04c..bd91baf6 100644 --- a/tests/source/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -1,6 +1,6 @@ -#include "constants.hpp" -#include "functional.hpp" -#include "transforms.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" +#include "testing/gl/transforms.hpp" #include #include @@ -29,16 +29,17 @@ TEST_CASE_TEMPLATE_DEFINE( REQUIRE_EQ(sut.n_vertices(), constants::n_elements); REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); - std::ranges::for_each(constants::vertex_id_view, [&sut](const lib_t::id_type vertex_id) { + std::ranges::for_each(constants::vertex_id_view, [&sut](const gl::types::id_type vertex_id) { CHECK_EQ(sut.adjacent_edges(vertex_id).distance(), constants::zero_elements); }); } SUBCASE("add_vertex should properly extend the current adjacency list") { SutType sut{}; - constexpr lib_t::size_type target_n_vertices = constants::n_elements; + constexpr gl::types::size_type target_n_vertices = constants::n_elements; - for (lib_t::size_type n_vertices = constants::one_element; n_vertices <= target_n_vertices; + for (gl::types::size_type n_vertices = constants::one_element; + n_vertices <= target_n_vertices; n_vertices++) { sut.add_vertex(); CHECK_EQ(sut.n_vertices(), n_vertices); @@ -59,35 +60,37 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( edge_directional_tag_sut_template, - lib_i::adjacency_list>, // directed adj list - lib_i::adjacency_list> // undirected adj list + gl::impl::adjacency_list>, // directed adj list + gl::impl::adjacency_list> // undirected adj list ); namespace { -constexpr lib_t::size_type n_incident_edges_for_fully_connected_vertex = +constexpr gl::types::size_type n_incident_edges_for_fully_connected_vertex = constants::n_elements - constants::one_element; } // namespace struct test_directed_adjacency_list { - using vertex_type = lib::vertex_descriptor<>; - using edge_type = lib::directed_edge; - using edge_ptr_type = lib::directed_t::edge_ptr_type; - using sut_type = lib_i::adjacency_list>; + using vertex_type = gl::vertex_descriptor<>; + using edge_type = gl::directed_edge; + using edge_ptr_type = gl::directed_t::edge_ptr_type; + using sut_type = gl::impl::adjacency_list>; test_directed_adjacency_list() { for (const auto id : constants::vertex_id_view) vertices.emplace_back(id); } - const edge_type& add_edge(const lib_t::id_type first_id, const lib_t::id_type second_id) { + const edge_type& add_edge( + const gl::types::id_type first_id, const gl::types::id_type second_id + ) { return sut.add_edge( - lib::detail::make_edge(vertices[first_id], vertices[second_id]) + gl::detail::make_edge(vertices[first_id], vertices[second_id]) ); } - void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { for (const auto second_id : constants::vertex_id_view) { if (second_id == first_id and no_loops) continue; @@ -109,7 +112,7 @@ struct test_directed_adjacency_list { sut_type sut{constants::n_elements}; std::vector vertices; - const lib_t::size_type n_unique_edges_in_full_graph = + const gl::types::size_type n_unique_edges_in_full_graph = n_incident_edges_for_fully_connected_vertex * constants::n_elements; }; @@ -229,7 +232,7 @@ TEST_CASE_FIXTURE( test_directed_adjacency_list, "get_edges(id, id) should return a valid edge view if the given vertices are connected" ) { - std::vector> expected_edges; + std::vector> expected_edges; for (auto _ = constants::first_element_idx; _ < constants::n_elements; _++) expected_edges.push_back(std::cref(add_edge(constants::vertex_id_1, constants::vertex_id_2)) ); @@ -306,7 +309,7 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - std::function deg_proj; + std::function deg_proj; SUBCASE("in_degree") { deg_proj = [this](const auto vertex_id) { return sut.in_degree(vertex_id); }; @@ -363,7 +366,7 @@ TEST_CASE_FIXTURE( init_complete_graph(false); const auto expected_deg = constants::n_elements; - std::vector degree_map; + std::vector degree_map; SUBCASE("in_degree") { degree_map = sut.in_degree_map(); @@ -387,7 +390,7 @@ TEST_CASE_FIXTURE( init_complete_graph(false); const auto expected_deg = constants::n_elements * constants::two; - std::vector degree_map = sut.degree_map(); + std::vector degree_map = sut.degree_map(); REQUIRE_EQ(degree_map.size(), constants::n_elements); CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); @@ -420,23 +423,25 @@ TEST_CASE_FIXTURE( } struct test_undirected_adjacency_list { - using vertex_type = lib::vertex_descriptor<>; - using edge_type = lib::undirected_edge; - using edge_ptr_type = lib::undirected_t::edge_ptr_type; - using sut_type = lib_i::adjacency_list>; + using vertex_type = gl::vertex_descriptor<>; + using edge_type = gl::undirected_edge; + using edge_ptr_type = gl::undirected_t::edge_ptr_type; + using sut_type = gl::impl::adjacency_list>; test_undirected_adjacency_list() { for (const auto id : constants::vertex_id_view) vertices.emplace_back(id); } - const edge_type& add_edge(const lib_t::id_type first_id, const lib_t::id_type second_id) { + const edge_type& add_edge( + const gl::types::id_type first_id, const gl::types::id_type second_id + ) { return sut.add_edge( - lib::detail::make_edge(vertices[first_id], vertices[second_id]) + gl::detail::make_edge(vertices[first_id], vertices[second_id]) ); } - void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { for (const auto second_id : constants::vertex_id_view) { if (second_id == first_id and no_loops) continue; @@ -461,7 +466,7 @@ struct test_undirected_adjacency_list { sut_type sut{constants::n_elements}; std::vector vertices; - const lib_t::size_type n_unique_edges_in_full_graph = + const gl::types::size_type n_unique_edges_in_full_graph = (n_incident_edges_for_fully_connected_vertex * constants::n_elements) / 2; }; @@ -605,7 +610,7 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_list, "get_edges(id, id) should return a valid edge view if the given vertices are connected" ) { - std::vector> expected_edges; + std::vector> expected_edges; for (auto _ = constants::first_element_idx; _ < constants::n_elements; _++) expected_edges.push_back(std::cref(add_edge(constants::vertex_id_1, constants::vertex_id_2)) ); @@ -702,7 +707,7 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - std::function deg_proj; + std::function deg_proj; SUBCASE("degree") { deg_proj = [this](const auto vertex_id) { return sut.degree(vertex_id); }; @@ -740,7 +745,7 @@ TEST_CASE_FIXTURE( init_complete_graph(false); const auto expected_deg = constants::n_elements + 1; - std::vector degree_map; + std::vector degree_map; SUBCASE("in_degree") { degree_map = sut.in_degree_map(); diff --git a/tests/source/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp similarity index 90% rename from tests/source/test_adjacency_matrix.cpp rename to tests/source/gl/test_adjacency_matrix.cpp index de7d15ff..72b62656 100644 --- a/tests/source/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -1,6 +1,6 @@ -#include "constants.hpp" -#include "functional.hpp" -#include "transforms.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" +#include "testing/gl/transforms.hpp" #include #include @@ -29,16 +29,17 @@ TEST_CASE_TEMPLATE_DEFINE( REQUIRE_EQ(sut.n_vertices(), constants::n_elements); REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); - std::ranges::for_each(constants::vertex_id_view, [&sut](const lib_t::id_type vertex_id) { + std::ranges::for_each(constants::vertex_id_view, [&sut](const gl::types::id_type vertex_id) { CHECK_EQ(sut.adjacent_edges(vertex_id).distance(), constants::zero_elements); }); } SUBCASE("add_vertex should properly extend the current adjacency matrix") { SutType sut{}; - constexpr lib_t::size_type target_n_vertices = constants::n_elements; + constexpr gl::types::size_type target_n_vertices = constants::n_elements; - for (lib_t::size_type n_vertices = constants::one_element; n_vertices <= target_n_vertices; + for (gl::types::size_type n_vertices = constants::one_element; + n_vertices <= target_n_vertices; n_vertices++) { sut.add_vertex(); CHECK_EQ(sut.n_vertices(), n_vertices); @@ -65,10 +66,10 @@ TEST_CASE_TEMPLATE_DEFINE( const vertex_type v1{constants::vertex_id_1}; const vertex_type v2{constants::vertex_id_2}; - sut.add_edge(lib::detail::make_edge(v1, v2)); + sut.add_edge(gl::detail::make_edge(v1, v2)); REQUIRE(sut.has_edge(constants::vertex_id_1, constants::vertex_id_2)); - CHECK_THROWS_AS(sut.add_edge(lib::detail::make_edge(v1, v2)), std::logic_error); + CHECK_THROWS_AS(sut.add_edge(gl::detail::make_edge(v1, v2)), std::logic_error); } SUBCASE("add_edges_from should throw an error if the vertices are already incident") { @@ -86,7 +87,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector new_edges; for (const auto& target : vertex_refs) - new_edges.push_back(lib::detail::make_edge(v1, target.get())); + new_edges.push_back(gl::detail::make_edge(v1, target.get())); sut.add_edges_from(constants::vertex_id_1, std::move(new_edges)); @@ -96,7 +97,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::ranges::for_each(vertex_refs, [&sut, &v1](const auto& target) { std::vector new_edges; - new_edges.push_back(lib::detail::make_edge(v1, target.get())); + new_edges.push_back(gl::detail::make_edge(v1, target.get())); CHECK_THROWS_AS(sut.add_edges_from(v1.id(), std::move(new_edges)), std::logic_error); }); @@ -105,35 +106,37 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( edge_directional_tag_sut_template, - lib_i::adjacency_matrix>, // directed adj list - lib_i::adjacency_matrix> // undirected adj list + gl::impl::adjacency_matrix>, // directed adj list + gl::impl::adjacency_matrix> // undirected adj list ); namespace { -constexpr lib_t::size_type n_incident_edges_for_fully_connected_vertex = +constexpr gl::types::size_type n_incident_edges_for_fully_connected_vertex = constants::n_elements - constants::one_element; } // namespace struct test_directed_adjacency_matrix { - using vertex_type = lib::vertex_descriptor<>; - using edge_type = lib::directed_edge; - using edge_ptr_type = lib::directed_t::edge_ptr_type; - using sut_type = lib_i::adjacency_matrix>; + using vertex_type = gl::vertex_descriptor<>; + using edge_type = gl::directed_edge; + using edge_ptr_type = gl::directed_t::edge_ptr_type; + using sut_type = gl::impl::adjacency_matrix>; test_directed_adjacency_matrix() { for (const auto id : constants::vertex_id_view) vertices.emplace_back(id); } - const edge_type& add_edge(const lib_t::id_type first_id, const lib_t::id_type second_id) { + const edge_type& add_edge( + const gl::types::id_type first_id, const gl::types::id_type second_id + ) { return sut.add_edge( - lib::detail::make_edge(vertices[first_id], vertices[second_id]) + gl::detail::make_edge(vertices[first_id], vertices[second_id]) ); } - void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { for (const auto second_id : constants::vertex_id_view) { if (second_id == first_id and no_loops) continue; @@ -155,7 +158,7 @@ struct test_directed_adjacency_matrix { sut_type sut{constants::n_elements}; std::vector vertices; - static constexpr lib_t::size_type n_unique_edges_in_full_graph = + static constexpr gl::types::size_type n_unique_edges_in_full_graph = n_incident_edges_for_fully_connected_vertex * constants::n_elements; }; @@ -302,7 +305,7 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - std::function deg_proj; + std::function deg_proj; SUBCASE("in_degree") { deg_proj = [this](const auto vertex_id) { return sut.in_degree(vertex_id); }; @@ -359,7 +362,7 @@ TEST_CASE_FIXTURE( init_complete_graph(false); const auto expected_deg = constants::n_elements; - std::vector degree_map; + std::vector degree_map; SUBCASE("in_degree") { degree_map = sut.in_degree_map(); @@ -383,7 +386,7 @@ TEST_CASE_FIXTURE( init_complete_graph(false); const auto expected_deg = constants::n_elements * constants::two; - std::vector degree_map = sut.degree_map(); + std::vector degree_map = sut.degree_map(); REQUIRE_EQ(degree_map.size(), constants::n_elements); CHECK_EQ(std::ranges::count(degree_map, expected_deg), constants::n_elements); @@ -416,23 +419,25 @@ TEST_CASE_FIXTURE( } struct test_undirected_adjacency_matrix { - using vertex_type = lib::vertex_descriptor<>; - using edge_type = lib::undirected_edge; - using edge_ptr_type = lib::undirected_t::edge_ptr_type; - using sut_type = lib_i::adjacency_matrix>; + using vertex_type = gl::vertex_descriptor<>; + using edge_type = gl::undirected_edge; + using edge_ptr_type = gl::undirected_t::edge_ptr_type; + using sut_type = gl::impl::adjacency_matrix>; test_undirected_adjacency_matrix() { for (const auto id : constants::vertex_id_view) vertices.emplace_back(id); } - const edge_type& add_edge(const lib_t::id_type first_id, const lib_t::id_type second_id) { + const edge_type& add_edge( + const gl::types::id_type first_id, const gl::types::id_type second_id + ) { return sut.add_edge( - lib::detail::make_edge(vertices[first_id], vertices[second_id]) + gl::detail::make_edge(vertices[first_id], vertices[second_id]) ); } - void fully_connect_vertex(const lib_t::id_type first_id, const bool no_loops = true) { + void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { for (const auto second_id : constants::vertex_id_view) { if (second_id == first_id and no_loops) continue; @@ -457,7 +462,7 @@ struct test_undirected_adjacency_matrix { sut_type sut{constants::n_elements}; std::vector vertices; - static constexpr lib_t::size_type n_unique_edges_in_full_graph = + static constexpr gl::types::size_type n_unique_edges_in_full_graph = (n_incident_edges_for_fully_connected_vertex * constants::n_elements) / 2; }; @@ -641,7 +646,7 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - std::function deg_proj; + std::function deg_proj; SUBCASE("degree") { deg_proj = [this](const auto vertex_id) { return sut.degree(vertex_id); }; @@ -679,7 +684,7 @@ TEST_CASE_FIXTURE( init_complete_graph(false); const auto expected_deg = constants::n_elements + 1; - std::vector degree_map; + std::vector degree_map; SUBCASE("in_degree") { degree_map = sut.in_degree_map(); diff --git a/tests/source/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp similarity index 58% rename from tests/source/test_alg_bfs.cpp rename to tests/source/gl/test_alg_bfs.cpp index 1afa49f4..90488566 100644 --- a/tests/source/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -1,5 +1,5 @@ -#include "alg_common.hpp" -#include "constants.hpp" +#include "testing/gl/alg_common.hpp" +#include "testing/gl/constants.hpp" #include #include @@ -19,27 +19,27 @@ TEST_CASE_TEMPLATE_DEFINE( using vertex_type = typename GraphType::vertex_type; graph_type graph; - std::vector expected_previsit_order; + std::vector expected_previsit_order; SUBCASE("empty graph") { - graph = lib::topology::clique(constants::zero_elements); + graph = gl::topology::clique(constants::zero_elements); expected_previsit_order = {}; } SUBCASE("single vertex graph") { - graph = lib::topology::clique(constants::one_element); + graph = gl::topology::clique(constants::one_element); expected_previsit_order = {0}; } SUBCASE("clique") { - graph = lib::topology::clique(constants::n_elements_alg); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) + graph = gl::topology::clique(constants::n_elements_alg); + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) expected_previsit_order.push_back(id); } SUBCASE("path graph") { - graph = lib::topology::bidirectional_path(constants::n_elements_alg); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) + graph = gl::topology::bidirectional_path(constants::n_elements_alg); + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) expected_previsit_order.push_back(id); } @@ -49,19 +49,19 @@ TEST_CASE_TEMPLATE_DEFINE( B = {3, 4} root = 0 -> connected to B -> connected to A (root already visited) */ - graph = lib::topology::biclique(constants::three, constants::two); + graph = gl::topology::biclique(constants::three, constants::two); expected_previsit_order = {0, 3, 4, 1, 2}; } CAPTURE(graph); CAPTURE(expected_previsit_order); - std::vector expected_postvisit_order = expected_previsit_order; + std::vector expected_postvisit_order = expected_previsit_order; - std::vector previsit_order, postvisit_order; - lib::algorithm::breadth_first_search( + std::vector previsit_order, postvisit_order; + gl::algorithm::breadth_first_search( graph, - lib::algorithm::no_root_vertex, + gl::algorithm::no_root_vertex, [&](const auto& vertex) { // previsit previsit_order.push_back(vertex.id()); }, @@ -80,16 +80,16 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( bfs_no_return_graph_template, - lib::graph< - lib::list_graph_traits>, // directed adjacency list - lib::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix ); @@ -101,20 +101,20 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; graph_type graph; - std::optional root_vertex_id; - std::deque expected_previsit_order; + std::optional root_vertex_id; + std::deque expected_previsit_order; SUBCASE("single vertex graph") { - graph = lib::topology::clique(constants::one_element); + graph = gl::topology::clique(constants::one_element); root_vertex_id.emplace(constants::vertex_id_1); expected_previsit_order = {0}; } SUBCASE("clique") { - graph = lib::topology::clique(constants::n_elements_alg); + graph = gl::topology::clique(constants::n_elements_alg); root_vertex_id.emplace(constants::vertex_id_3); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) { + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) { if (id != constants::vertex_id_3) expected_previsit_order.push_back(id); } @@ -125,10 +125,10 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(root_vertex_id); CAPTURE(expected_previsit_order); - std::deque expected_postvisit_order = expected_previsit_order; + std::deque expected_postvisit_order = expected_previsit_order; - std::vector previsit_order, postvisit_order; - lib::algorithm::breadth_first_search( + std::vector previsit_order, postvisit_order; + gl::algorithm::breadth_first_search( graph, root_vertex_id, [&](const auto& vertex) { // previsit @@ -145,10 +145,10 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( bfs_no_return_with_root_graph_template, - lib::graph>, // directed adjacency list - lib::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix + gl::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list + gl::graph>, // directed adjacency matrix + gl::graph> // undirected adjacency matrix ); TEST_CASE_TEMPLATE_DEFINE( @@ -159,9 +159,9 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; using vertex_type = typename graph_type::vertex_type; - const auto graph = lib::topology::regular_binary_tree(constants::three); + const auto graph = gl::topology::regular_binary_tree(constants::three); const auto pd = - lib::algorithm::breadth_first_search(graph); + gl::algorithm::breadth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pd.predecessors.size(), graph.n_vertices()); @@ -170,10 +170,10 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( bfs_return_graph_template, - lib::graph>, // directed adjacency list - lib::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix + gl::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list + gl::graph>, // directed adjacency matrix + gl::graph> // undirected adjacency matrix ); TEST_SUITE_END(); // test_alg_bfs diff --git a/tests/source/test_alg_coloring.cpp b/tests/source/gl/test_alg_coloring.cpp similarity index 55% rename from tests/source/test_alg_coloring.cpp rename to tests/source/gl/test_alg_coloring.cpp index 8cd317d1..a43e30c3 100644 --- a/tests/source/test_alg_coloring.cpp +++ b/tests/source/gl/test_alg_coloring.cpp @@ -1,5 +1,5 @@ -#include "alg_common.hpp" -#include "constants.hpp" +#include "testing/gl/alg_common.hpp" +#include "testing/gl/constants.hpp" #include #include @@ -12,38 +12,38 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_alg_coloring"); TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_template) { - using sut_type = lib::graph; + using sut_type = gl::graph; SUBCASE("apply_coloring should return false if the size of coloring is different then " "n_vertices") { sut_type sut{constants::n_elements_alg}; - std::vector empty_coloring; + std::vector empty_coloring; - CHECK_FALSE(lib::algorithm::apply_coloring(sut, empty_coloring)); + CHECK_FALSE(gl::algorithm::apply_coloring(sut, empty_coloring)); } SUBCASE("bipartite graph") { sut_type sut; - std::vector expected_coloring; + std::vector expected_coloring; SUBCASE("biclique") { const auto [n_vertices_a, n_vertices_b] = std::make_pair(4ull, 6ull); - sut = lib::topology::biclique(n_vertices_a, n_vertices_b); + sut = gl::topology::biclique(n_vertices_a, n_vertices_b); expected_coloring = - std::vector(n_vertices_a, lib::bin_color_value::black); - for (lib_t::size_type i = constants::first_element_idx; i < n_vertices_b; i++) - expected_coloring.emplace_back(lib::bin_color_value::white); + std::vector(n_vertices_a, gl::bin_color_value::black); + for (gl::types::size_type i = constants::first_element_idx; i < n_vertices_b; i++) + expected_coloring.emplace_back(gl::bin_color_value::white); } SUBCASE("regular binary tree") { - sut = lib::topology::regular_binary_tree(constants::depth); + sut = gl::topology::regular_binary_tree(constants::depth); - lib_t::size_type n_vertices = constants::one_element; - lib_t::binary_color c{lib::bin_color_value::black}; + gl::types::size_type n_vertices = constants::one_element; + gl::types::binary_color c{gl::bin_color_value::black}; - for (lib_t::size_type d = constants::zero; d < constants::depth; d++) { - for (lib_t::size_type i = constants::zero; i < n_vertices; i++) + for (gl::types::size_type d = constants::zero; d < constants::depth; d++) { + for (gl::types::size_type i = constants::zero; i < n_vertices; i++) expected_coloring.push_back(c); n_vertices *= constants::two; @@ -52,10 +52,10 @@ TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_te } SUBCASE("path graph") { - sut = lib::topology::path(constants::n_elements_alg); + sut = gl::topology::path(constants::n_elements_alg); - lib_t::binary_color c{lib::bin_color_value::black}; - for (lib_t::size_type i = constants::zero; i < constants::n_elements_alg; i++) { + gl::types::binary_color c{gl::bin_color_value::black}; + for (gl::types::size_type i = constants::zero; i < constants::n_elements_alg; i++) { expected_coloring.push_back(c); c = c.next(); } @@ -63,10 +63,10 @@ TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_te SUBCASE("even cycle graph") { const auto n_vertices = constants::two * constants::n_elements_alg; - sut = lib::topology::cycle(n_vertices); + sut = gl::topology::cycle(n_vertices); - lib_t::binary_color c{lib::bin_color_value::black}; - for (lib_t::size_type i = constants::zero; i < n_vertices; i++) { + gl::types::binary_color c{gl::bin_color_value::black}; + for (gl::types::size_type i = constants::zero; i < n_vertices; i++) { expected_coloring.push_back(c); c = c.next(); } @@ -75,11 +75,11 @@ TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_te SUBCASE("custom graph") { fs::path gsf_file_path = alg_common::data_path - / (lib_tt::is_directed_v + / (gl::type_traits::is_directed_v ? "bicoloring_directed_bipartite_graph.gsf" : "bicoloring_undirected_bipartite_graph.gsf"); - sut = lib::io::load(gsf_file_path); + sut = gl::io::load(gsf_file_path); fs::path coloring_file_path = alg_common::data_path / "bicoloring_bipartite_graph_coloring.txt"; @@ -90,23 +90,23 @@ TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_te coloring_values.begin(), coloring_values.end(), std::back_inserter(expected_coloring), - [](const std::uint16_t value) { return lib::bin_color_value{value}; } + [](const std::uint16_t value) { return gl::bin_color_value{value}; } ); } CAPTURE(sut); CAPTURE(expected_coloring); - const auto coloring_opt = lib::algorithm::bipartite_coloring(sut); + const auto coloring_opt = gl::algorithm::bipartite_coloring(sut); REQUIRE(coloring_opt.has_value()); CHECK(std::ranges::equal(coloring_opt.value(), expected_coloring)); // check the is_bipartite function - formality - CHECK(lib::algorithm::is_bipartite(sut)); + CHECK(gl::algorithm::is_bipartite(sut)); // --- apply_coloring test --- - REQUIRE(lib::algorithm::apply_coloring(sut, expected_coloring)); + REQUIRE(gl::algorithm::apply_coloring(sut, expected_coloring)); CHECK(std::ranges::equal( sut.vertices(), expected_coloring, @@ -119,60 +119,60 @@ TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_te sut_type sut; SUBCASE("clique") { - sut = lib::topology::clique(constants::n_elements_alg); + sut = gl::topology::clique(constants::n_elements_alg); } SUBCASE("odd cycle graph") { - sut = lib::topology::cycle( + sut = gl::topology::cycle( constants::two * constants::n_elements_alg + constants::one ); } SUBCASE("regular binary tree with an additional edge between siblings") { - sut = lib::topology::regular_binary_tree(constants::depth); + sut = gl::topology::regular_binary_tree(constants::depth); sut.add_edge(constants::vertex_id_2, constants::vertex_id_3); } SUBCASE("biclique with an additional edge between vertices from the same set") { const auto [n_vertices_a, n_vertices_b] = std::make_pair(4ull, 6ull); - sut = lib::topology::biclique(n_vertices_a, n_vertices_b); + sut = gl::topology::biclique(n_vertices_a, n_vertices_b); sut.add_edge(constants::vertex_id_1, constants::vertex_id_2); } SUBCASE("custom graph") { fs::path gsf_file_path = alg_common::data_path - / (lib_tt::is_directed_v + / (gl::type_traits::is_directed_v ? "bicoloring_directed_not_bipartite_graph.gsf" : "bicoloring_undirected_not_bipartite_graph.gsf"); - sut = lib::io::load(gsf_file_path); + sut = gl::io::load(gsf_file_path); } CAPTURE(sut); - const auto coloring_opt = lib::algorithm::bipartite_coloring(sut); + const auto coloring_opt = gl::algorithm::bipartite_coloring(sut); CHECK_FALSE(coloring_opt.has_value()); // check the is_bipartite function - formality - CHECK_FALSE(lib::algorithm::is_bipartite(sut)); + CHECK_FALSE(gl::algorithm::is_bipartite(sut)); } } TEST_CASE_TEMPLATE_INSTANTIATE( traits_type_template, - lib::list_graph_traits< - lib::directed_t, - lib_t::binary_color_property>, // directed adjacency list graph - lib::list_graph_traits< - lib::undirected_t, - lib_t::binary_color_property>, // undirected adjacency list graph - lib::matrix_graph_traits< - lib::directed_t, - lib_t::binary_color_property>, // directed adjacency matrix graph - lib::matrix_graph_traits< - lib::undirected_t, - lib_t::binary_color_property> // undirected adjacency matrix graph + gl::list_graph_traits< + gl::directed_t, + gl::types::binary_color_property>, // directed adjacency list graph + gl::list_graph_traits< + gl::undirected_t, + gl::types::binary_color_property>, // undirected adjacency list graph + gl::matrix_graph_traits< + gl::directed_t, + gl::types::binary_color_property>, // directed adjacency matrix graph + gl::matrix_graph_traits< + gl::undirected_t, + gl::types::binary_color_property> // undirected adjacency matrix graph ); TEST_SUITE_END(); // test_alg_coloring diff --git a/tests/source/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp similarity index 61% rename from tests/source/test_alg_dfs.cpp rename to tests/source/gl/test_alg_dfs.cpp index c3d93f49..8e625c4e 100644 --- a/tests/source/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -1,5 +1,5 @@ -#include "alg_common.hpp" -#include "constants.hpp" +#include "testing/gl/alg_common.hpp" +#include "testing/gl/constants.hpp" #include #include @@ -21,29 +21,29 @@ TEST_CASE_TEMPLATE_DEFINE( using vertex_type = typename GraphType::vertex_type; graph_type graph; - std::deque expected_previsit_order; + std::deque expected_previsit_order; SUBCASE("empty graph") { - graph = lib::topology::clique(constants::zero_elements); + graph = gl::topology::clique(constants::zero_elements); expected_previsit_order = {}; } SUBCASE("single vertex graph") { - graph = lib::topology::clique(constants::one_element); + graph = gl::topology::clique(constants::one_element); expected_previsit_order = {0}; } SUBCASE("clique") { - graph = lib::topology::clique(constants::n_elements_alg); - for (auto id = lib::constants::initial_id + constants::one; id < constants::n_elements_alg; + graph = gl::topology::clique(constants::n_elements_alg); + for (auto id = gl::constants::initial_id + constants::one; id < constants::n_elements_alg; id++) expected_previsit_order.push_front(id); - expected_previsit_order.push_front(lib::constants::initial_id); + expected_previsit_order.push_front(gl::constants::initial_id); } SUBCASE("path graph") { - graph = lib::topology::bidirectional_path(constants::n_elements_alg); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) + graph = gl::topology::bidirectional_path(constants::n_elements_alg); + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) expected_previsit_order.push_back(id); } @@ -57,19 +57,19 @@ TEST_CASE_TEMPLATE_DEFINE( -> 2 connected to B (4 visited) [s: 3 1 3] finally: 0 -> 4 -> 2 -> 1 -> 3 */ - graph = lib::topology::biclique(constants::three, constants::two); + graph = gl::topology::biclique(constants::three, constants::two); expected_previsit_order = {0, 4, 2, 3, 1}; } CAPTURE(graph); CAPTURE(expected_previsit_order); - std::deque expected_postvisit_order = expected_previsit_order; + std::deque expected_postvisit_order = expected_previsit_order; - std::vector previsit_order, postvisit_order; - lib::algorithm::depth_first_search( + std::vector previsit_order, postvisit_order; + gl::algorithm::depth_first_search( graph, - lib::algorithm::no_root_vertex, + gl::algorithm::no_root_vertex, [&](const auto& vertex) { // previsit previsit_order.push_back(vertex.id()); }, @@ -88,16 +88,16 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( dfs_no_return_graph_template, - lib::graph< - lib::list_graph_traits>, // directed adjacency list - lib::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix ); @@ -109,20 +109,20 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; graph_type graph; - std::optional root_vertex_id; - std::deque expected_previsit_order; + std::optional root_vertex_id; + std::deque expected_previsit_order; SUBCASE("single vertex graph") { - graph = lib::topology::clique(constants::one_element); + graph = gl::topology::clique(constants::one_element); root_vertex_id.emplace(constants::vertex_id_1); expected_previsit_order = {0}; } SUBCASE("clique") { - graph = lib::topology::clique(constants::n_elements_alg); + graph = gl::topology::clique(constants::n_elements_alg); root_vertex_id.emplace(constants::vertex_id_3); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) { + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) { if (id != constants::vertex_id_3) expected_previsit_order.push_front(id); } @@ -133,10 +133,10 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(root_vertex_id); CAPTURE(expected_previsit_order); - std::deque expected_postvisit_order = expected_previsit_order; + std::deque expected_postvisit_order = expected_previsit_order; - std::vector previsit_order, postvisit_order; - lib::algorithm::depth_first_search( + std::vector previsit_order, postvisit_order; + gl::algorithm::depth_first_search( graph, root_vertex_id, [&](const auto& vertex) { // previsit @@ -153,10 +153,10 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( dfs_no_return_with_root_graph_template, - lib::graph>, // directed adjacency list - lib::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix + gl::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list + gl::graph>, // directed adjacency matrix + gl::graph> // undirected adjacency matrix ); TEST_CASE_TEMPLATE_DEFINE( @@ -167,9 +167,9 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; using vertex_type = typename graph_type::vertex_type; - const auto graph = lib::topology::regular_binary_tree(constants::three); + const auto graph = gl::topology::regular_binary_tree(constants::three); const auto pd = - lib::algorithm::depth_first_search(graph); + gl::algorithm::depth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pd.predecessors.size(), graph.n_vertices()); @@ -178,10 +178,10 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( dfs_return_graph_template, - lib::graph>, // directed adjacency list - lib::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix + gl::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list + gl::graph>, // directed adjacency matrix + gl::graph> // undirected adjacency matrix ); // --- recursive dfs tests --- @@ -195,27 +195,27 @@ TEST_CASE_TEMPLATE_DEFINE( using vertex_type = typename GraphType::vertex_type; graph_type graph; - std::vector expected_previsit_order; + std::vector expected_previsit_order; SUBCASE("empty graph") { - graph = lib::topology::clique(constants::zero_elements); + graph = gl::topology::clique(constants::zero_elements); expected_previsit_order = {}; } SUBCASE("single vertex graph") { - graph = lib::topology::clique(constants::one_element); + graph = gl::topology::clique(constants::one_element); expected_previsit_order = {0}; } SUBCASE("clique") { - graph = lib::topology::clique(constants::n_elements_alg); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) + graph = gl::topology::clique(constants::n_elements_alg); + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) expected_previsit_order.push_back(id); } SUBCASE("path graph") { - graph = lib::topology::bidirectional_path(constants::n_elements_alg); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) + graph = gl::topology::bidirectional_path(constants::n_elements_alg); + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) expected_previsit_order.push_back(id); } @@ -230,7 +230,7 @@ TEST_CASE_TEMPLATE_DEFINE( -> 4 connected to A (2) finally: 0 -> 3 -> 1 -> 4 -> 2 */ - graph = lib::topology::biclique(constants::three, constants::two); + graph = gl::topology::biclique(constants::three, constants::two); expected_previsit_order = {0, 3, 1, 4, 2}; } @@ -244,10 +244,10 @@ TEST_CASE_TEMPLATE_DEFINE( */ const auto expected_postvisit_order = std::views::reverse(expected_previsit_order); - std::vector previsit_order, postvisit_order; - lib::algorithm::recursive_depth_first_search( + std::vector previsit_order, postvisit_order; + gl::algorithm::recursive_depth_first_search( graph, - lib::algorithm::no_root_vertex, + gl::algorithm::no_root_vertex, [&](const auto& vertex) { // previsit previsit_order.push_back(vertex.id()); }, @@ -266,16 +266,16 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( rdfs_no_return_graph_template, - lib::graph< - lib::list_graph_traits>, // directed adjacency list - lib::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix ); @@ -287,21 +287,21 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; graph_type graph; - std::optional root_vertex_id; - std::vector expected_previsit_order; + std::optional root_vertex_id; + std::vector expected_previsit_order; SUBCASE("single vertex graph") { - graph = lib::topology::clique(constants::one_element); + graph = gl::topology::clique(constants::one_element); root_vertex_id.emplace(constants::vertex_id_1); expected_previsit_order = {0}; } SUBCASE("clique") { - graph = lib::topology::clique(constants::n_elements_alg); + graph = gl::topology::clique(constants::n_elements_alg); root_vertex_id.emplace(constants::vertex_id_3); expected_previsit_order.push_back(constants::vertex_id_3); - for (auto id = lib::constants::initial_id; id < constants::n_elements_alg; id++) { + for (auto id = gl::constants::initial_id; id < constants::n_elements_alg; id++) { if (id != constants::vertex_id_3) expected_previsit_order.push_back(id); } @@ -318,8 +318,8 @@ TEST_CASE_TEMPLATE_DEFINE( */ const auto expected_postvisit_order = std::views::reverse(expected_previsit_order); - std::vector previsit_order, postvisit_order; - lib::algorithm::recursive_depth_first_search( + std::vector previsit_order, postvisit_order; + gl::algorithm::recursive_depth_first_search( graph, root_vertex_id, [&](const auto& vertex) { // previsit @@ -336,10 +336,10 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( rdfs_no_return_with_root_graph_template, - lib::graph>, // directed adjacency list - lib::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix + gl::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list + gl::graph>, // directed adjacency matrix + gl::graph> // undirected adjacency matrix ); TEST_CASE_TEMPLATE_DEFINE( @@ -350,10 +350,9 @@ TEST_CASE_TEMPLATE_DEFINE( using graph_type = GraphType; using vertex_type = typename graph_type::vertex_type; - const auto graph = lib::topology::regular_binary_tree(constants::three); + const auto graph = gl::topology::regular_binary_tree(constants::three); const auto pd = - lib::algorithm::recursive_depth_first_search( - graph + gl::algorithm::recursive_depth_first_search(graph ); // verify the predecessors of each vertex @@ -363,10 +362,10 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( rdfs_return_graph_template, - lib::graph>, // directed adjacency list - lib::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix + gl::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list + gl::graph>, // directed adjacency matrix + gl::graph> // undirected adjacency matrix ); TEST_SUITE_END(); // test_alg_dfs diff --git a/tests/source/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp similarity index 60% rename from tests/source/test_alg_dijkstra.cpp rename to tests/source/gl/test_alg_dijkstra.cpp index d83eee9e..9e689d58 100644 --- a/tests/source/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -1,6 +1,6 @@ -#include "alg_common.hpp" -#include "constants.hpp" -#include "functional.hpp" +#include "testing/gl/alg_common.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" #include #include @@ -19,22 +19,22 @@ TEST_CASE_TEMPLATE_DEFINE( TraitsType, wieghted_edge_traits_type_template ) { - using sut_type = lib::graph; + using sut_type = gl::graph; using weight_type = typename sut_type::edge_properties_type::weight_type; using distance_type = weight_type; - static_assert(lib_tt::c_weight_properties_type); + static_assert(gl::type_traits::c_weight_properties_type< + typename sut_type::edge_properties_type>); SUBCASE("should throw if there is an edge with a negative weight") { - const auto sut = lib::topology::clique(constants::n_elements_alg); + const auto sut = gl::topology::clique(constants::n_elements_alg); sut.get_edge(constants::vertex_id_1, constants::vertex_id_2) .value() .get() .properties.weight = -static_cast(constants::n_elements_alg); CHECK_THROWS_AS( - func::discard_result( - lib::algorithm::dijkstra_shortest_paths(sut, constants::vertex_id_1) + func::discard_result(gl::algorithm::dijkstra_shortest_paths(sut, constants::vertex_id_1) ), std::invalid_argument ); @@ -42,22 +42,23 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("should return a proper paths descriptor for a valid graph") { sut_type sut; - lib_t::id_type source_id; - std::vector expected_predecessors; + gl::types::id_type source_id; + std::vector expected_predecessors; std::vector expected_distances; const auto source_distance = static_cast(constants::zero); SUBCASE("clique") { - sut = lib::topology::clique(constants::n_elements_alg); + sut = gl::topology::clique(constants::n_elements_alg); source_id = constants::first_element_idx; expected_predecessors = - std::vector(constants::n_elements_alg, source_id); + std::vector(constants::n_elements_alg, source_id); expected_distances.push_back(source_distance); const auto edge_weight = static_cast(constants::n_elements_alg); - for (lib_t::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; id++) { + for (gl::types::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; + id++) { sut.get_edge(constants::vertex_id_1, id).value().get().properties.weight = edge_weight; expected_distances.push_back(edge_weight); @@ -65,7 +66,7 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("regular binary tree") { - sut = lib::topology::regular_binary_tree(constants::depth); + sut = gl::topology::regular_binary_tree(constants::depth); source_id = constants::first_element_idx; for (const auto id : sut.vertex_ids()) { @@ -76,25 +77,28 @@ TEST_CASE_TEMPLATE_DEFINE( expected_predecessors.push_back(parent_id); const auto vertex_depth = - constants::zero ? constants::zero - : static_cast(std::log2(id + constants::one)); + constants::zero + ? constants::zero + : static_cast(std::log2(id + constants::one)); expected_distances.push_back(vertex_depth); } } SUBCASE("custom graph") { const std::string file_name_prefix = - lib_tt::is_directed_v ? "dijkstra_directed_" : "dijkstra_undirected_"; + gl::type_traits::is_directed_v + ? "dijkstra_directed_" + : "dijkstra_undirected_"; const fs::path gsf_file_path = alg_common::data_path / (file_name_prefix + "graph.gsf"); - sut = lib::io::load(gsf_file_path); + sut = gl::io::load(gsf_file_path); source_id = constants::first_element_idx; const fs::path predecessors_file_path = alg_common::data_path / (file_name_prefix + "predecessors.txt"); expected_predecessors = - alg_common::load_list(sut.n_vertices(), predecessors_file_path); + alg_common::load_list(sut.n_vertices(), predecessors_file_path); const fs::path distances_file_path = alg_common::data_path / (file_name_prefix + "distances.txt"); @@ -107,7 +111,7 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(expected_predecessors); CAPTURE(expected_distances); - const auto paths = lib::algorithm::dijkstra_shortest_paths(sut, source_id); + const auto paths = gl::algorithm::dijkstra_shortest_paths(sut, source_id); REQUIRE(std::ranges::all_of(sut.vertex_ids(), [&paths](const auto vertex_id) { return paths.is_reachable(vertex_id); @@ -126,22 +130,22 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( wieghted_edge_traits_type_template, - lib::list_graph_traits< - lib::directed_t, - lib_t::empty_properties, - lib_t::weight_property<>>, // directed adjacency list graph - lib::list_graph_traits< - lib::undirected_t, - lib_t::empty_properties, - lib_t::weight_property<>>, // undirected adjacency list graph - lib::matrix_graph_traits< - lib::directed_t, - lib_t::empty_properties, - lib_t::weight_property<>>, // directed adjacency matrix graph - lib::matrix_graph_traits< - lib::undirected_t, - lib_t::empty_properties, - lib_t::weight_property<>> // undirected adjacency matrix graph + gl::list_graph_traits< + gl::directed_t, + gl::types::empty_properties, + gl::types::weight_property<>>, // directed adjacency list graph + gl::list_graph_traits< + gl::undirected_t, + gl::types::empty_properties, + gl::types::weight_property<>>, // undirected adjacency list graph + gl::matrix_graph_traits< + gl::directed_t, + gl::types::empty_properties, + gl::types::weight_property<>>, // directed adjacency matrix graph + gl::matrix_graph_traits< + gl::undirected_t, + gl::types::empty_properties, + gl::types::weight_property<>> // undirected adjacency matrix graph ); TEST_CASE_TEMPLATE_DEFINE( @@ -149,33 +153,35 @@ TEST_CASE_TEMPLATE_DEFINE( TraitsType, unwieghted_edge_traits_type_template ) { - using sut_type = lib::graph; - using distance_type = lib_t::default_vertex_distance_type; + using sut_type = gl::graph; + using distance_type = gl::types::default_vertex_distance_type; - static_assert(not lib_tt::c_weight_properties_type); + static_assert(not gl::type_traits::c_weight_properties_type< + typename sut_type::edge_properties_type>); SUBCASE("should return a proper paths descriptor for a valid graph") { sut_type sut; - lib_t::id_type source_id; - std::vector expected_predecessors; + gl::types::id_type source_id; + std::vector expected_predecessors; std::vector expected_distances; const auto source_distance = static_cast(constants::zero); SUBCASE("clique") { - sut = lib::topology::clique(constants::n_elements_alg); + sut = gl::topology::clique(constants::n_elements_alg); source_id = constants::first_element_idx; expected_predecessors = - std::vector(constants::n_elements_alg, source_id); + std::vector(constants::n_elements_alg, source_id); expected_distances.push_back(source_distance); - for (lib_t::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; id++) + for (gl::types::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; + id++) expected_distances.push_back(constants::one); } SUBCASE("regular binary tree") { - sut = lib::topology::regular_binary_tree(constants::depth); + sut = gl::topology::regular_binary_tree(constants::depth); source_id = constants::first_element_idx; for (const auto id : sut.vertex_ids()) { @@ -186,8 +192,9 @@ TEST_CASE_TEMPLATE_DEFINE( expected_predecessors.push_back(parent_id); const auto vertex_depth = - constants::zero ? constants::zero - : static_cast(std::log2(id + constants::one)); + constants::zero + ? constants::zero + : static_cast(std::log2(id + constants::one)); expected_distances.push_back(vertex_depth); } } @@ -197,7 +204,7 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(expected_predecessors); CAPTURE(expected_distances); - const auto paths = lib::algorithm::dijkstra_shortest_paths(sut, source_id); + const auto paths = gl::algorithm::dijkstra_shortest_paths(sut, source_id); REQUIRE(std::ranges::all_of(sut.vertex_ids(), [&paths](const auto vertex_id) { return paths.is_reachable(vertex_id); @@ -216,27 +223,27 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( unwieghted_edge_traits_type_template, - lib::list_graph_traits, // directed adjacency list graph - lib::list_graph_traits, // undirected adjacency list graph - lib::matrix_graph_traits, // directed adjacency matrix graph - lib::matrix_graph_traits // undirected adjacency matrix graph + gl::list_graph_traits, // directed adjacency list graph + gl::list_graph_traits, // undirected adjacency list graph + gl::matrix_graph_traits, // directed adjacency matrix graph + gl::matrix_graph_traits // undirected adjacency matrix graph ); TEST_CASE("reconstruct_path should thow if the vertex is not reachable") { - const std::vector> predecessor_map = {0, 3, 1, std::nullopt}; - lib_t::id_type vertex_id = predecessor_map.size() - constants::one; + const std::vector> predecessor_map = {0, 3, 1, std::nullopt}; + gl::types::id_type vertex_id = predecessor_map.size() - constants::one; CHECK_THROWS_AS( - func::discard_result(lib::algorithm::reconstruct_path(predecessor_map, vertex_id)), + func::discard_result(gl::algorithm::reconstruct_path(predecessor_map, vertex_id)), std::invalid_argument ); } TEST_CASE("reconstruct_path should properly reconstruct the search path to the specified vertex") { - const std::vector> predecessor_map = {0, 3, 1, 0}; + const std::vector> predecessor_map = {0, 3, 1, 0}; - lib_t::id_type vertex_id; - std::deque expected_path; + gl::types::id_type vertex_id; + std::deque expected_path; SUBCASE("starting vertex = 0") { vertex_id = 0; @@ -262,7 +269,7 @@ TEST_CASE("reconstruct_path should properly reconstruct the search path to the s CAPTURE(expected_path); CHECK(std::ranges::equal( - lib::algorithm::reconstruct_path(predecessor_map, vertex_id), expected_path + gl::algorithm::reconstruct_path(predecessor_map, vertex_id), expected_path )); } diff --git a/tests/source/test_alg_mst.cpp b/tests/source/gl/test_alg_mst.cpp similarity index 70% rename from tests/source/test_alg_mst.cpp rename to tests/source/gl/test_alg_mst.cpp index 8e71f3ba..7bff8f27 100644 --- a/tests/source/test_alg_mst.cpp +++ b/tests/source/gl/test_alg_mst.cpp @@ -1,6 +1,6 @@ -#include "alg_common.hpp" -#include "constants.hpp" -#include "functional.hpp" +#include "testing/gl/alg_common.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" #include #include @@ -19,22 +19,23 @@ TEST_CASE_TEMPLATE_DEFINE( TraitsType, edge_heap_prim_wieghted_edge_traits_type_template ) { - using sut_type = lib::graph; + using sut_type = gl::graph; using weight_type = typename sut_type::edge_properties_type::weight_type; using distance_type = weight_type; - static_assert(lib_tt::c_weight_properties_type); + static_assert(gl::type_traits::c_weight_properties_type< + typename sut_type::edge_properties_type>); SUBCASE("should return a proper mst descriptor for a valid graph") { - using vertex_id_pair = std::pair; + using vertex_id_pair = std::pair; sut_type sut; - lib_t::id_type source_id; + gl::types::id_type source_id; std::vector expected_edges; distance_type expected_weight; SUBCASE("regular binary tree") { - sut = lib::topology::regular_binary_tree(constants::depth); + sut = gl::topology::regular_binary_tree(constants::depth); source_id = constants::first_element_idx; const weight_type edge_weight = constants::three; @@ -51,14 +52,14 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("custom graph") { const fs::path gsf_file_path = alg_common::data_path / "mst_graph.gsf"; - sut = lib::io::load(gsf_file_path); + sut = gl::io::load(gsf_file_path); source_id = constants::first_element_idx; const fs::path edges_file_path = alg_common::data_path / "mst_edges.txt"; const auto n_vertex_ids = (sut.n_vertices() - constants::one) * constants::two; const auto vertex_id_list = - alg_common::load_list(n_vertex_ids, edges_file_path); - for (lib_t::size_type i = 0; i < n_vertex_ids; i += constants::two) + alg_common::load_list(n_vertex_ids, edges_file_path); + for (gl::types::size_type i = 0; i < n_vertex_ids; i += constants::two) expected_edges.emplace_back(vertex_id_list[i], vertex_id_list[i + 1]); const fs::path weight_file_path = alg_common::data_path / "mst_weight.txt"; @@ -71,7 +72,7 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(expected_edges); CAPTURE(expected_weight); - const auto mst = lib::algorithm::edge_heap_prim_mst(sut, source_id); + const auto mst = gl::algorithm::edge_heap_prim_mst(sut, source_id); REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); @@ -94,14 +95,14 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( edge_heap_prim_wieghted_edge_traits_type_template, - lib::undirected_graph_traits< - lib_t::empty_properties, - lib_t::weight_property<>, - lib_i::list_t>, // undirected adjacency list graph - lib::undirected_graph_traits< - lib_t::empty_properties, - lib_t::weight_property<>, - lib_i::matrix_t> // undirected adjacency matrix graph + gl::undirected_graph_traits< + gl::types::empty_properties, + gl::types::weight_property<>, + gl::impl::list_t>, // undirected adjacency list graph + gl::undirected_graph_traits< + gl::types::empty_properties, + gl::types::weight_property<>, + gl::impl::matrix_t> // undirected adjacency matrix graph ); TEST_CASE_TEMPLATE_DEFINE( @@ -109,15 +110,16 @@ TEST_CASE_TEMPLATE_DEFINE( TraitsType, edge_heap_prim_unwieghted_edge_traits_type_template ) { - using sut_type = lib::graph; - using distance_type = lib_t::default_vertex_distance_type; + using sut_type = gl::graph; + using distance_type = gl::types::default_vertex_distance_type; using weight_type = distance_type; - using vertex_id_pair = std::pair; + using vertex_id_pair = std::pair; - static_assert(not lib_tt::c_weight_properties_type); + static_assert(not gl::type_traits::c_weight_properties_type< + typename sut_type::edge_properties_type>); - const auto sut = lib::topology::regular_binary_tree(constants::depth); - const lib_t::id_type source_id = constants::first_element_idx; + const auto sut = gl::topology::regular_binary_tree(constants::depth); + const gl::types::id_type source_id = constants::first_element_idx; std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) @@ -126,7 +128,7 @@ TEST_CASE_TEMPLATE_DEFINE( const weight_type expected_weight = sut.n_vertices() - constants::one; - const auto mst = lib::algorithm::edge_heap_prim_mst(sut, source_id); + const auto mst = gl::algorithm::edge_heap_prim_mst(sut, source_id); REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); @@ -148,8 +150,8 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( edge_heap_prim_unwieghted_edge_traits_type_template, - lib::list_graph_traits, // undirected adjacency list graph - lib::matrix_graph_traits // undirected adjacency matrix graph + gl::list_graph_traits, // undirected adjacency list graph + gl::matrix_graph_traits // undirected adjacency matrix graph ); TEST_CASE_TEMPLATE_DEFINE( @@ -157,22 +159,23 @@ TEST_CASE_TEMPLATE_DEFINE( TraitsType, vertex_heap_prim_wieghted_edge_traits_type_template ) { - using sut_type = lib::graph; + using sut_type = gl::graph; using weight_type = typename sut_type::edge_properties_type::weight_type; using distance_type = weight_type; - static_assert(lib_tt::c_weight_properties_type); + static_assert(gl::type_traits::c_weight_properties_type< + typename sut_type::edge_properties_type>); SUBCASE("should return a proper mst descriptor for a valid graph") { - using vertex_id_pair = std::pair; + using vertex_id_pair = std::pair; sut_type sut; - lib_t::id_type source_id; + gl::types::id_type source_id; std::vector expected_edges; distance_type expected_weight; SUBCASE("regular binary tree") { - sut = lib::topology::regular_binary_tree(constants::depth); + sut = gl::topology::regular_binary_tree(constants::depth); source_id = constants::first_element_idx; const weight_type edge_weight = constants::three; @@ -189,14 +192,14 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("custom graph") { const fs::path gsf_file_path = alg_common::data_path / "mst_graph.gsf"; - sut = lib::io::load(gsf_file_path); + sut = gl::io::load(gsf_file_path); source_id = constants::first_element_idx; const fs::path edges_file_path = alg_common::data_path / "mst_edges.txt"; const auto n_vertex_ids = (sut.n_vertices() - constants::one) * constants::two; const auto vertex_id_list = - alg_common::load_list(n_vertex_ids, edges_file_path); - for (lib_t::size_type i = 0; i < n_vertex_ids; i += constants::two) + alg_common::load_list(n_vertex_ids, edges_file_path); + for (gl::types::size_type i = 0; i < n_vertex_ids; i += constants::two) expected_edges.emplace_back(vertex_id_list[i], vertex_id_list[i + 1]); const fs::path weight_file_path = alg_common::data_path / "mst_weight.txt"; @@ -209,7 +212,7 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(expected_edges); CAPTURE(expected_weight); - const auto mst = lib::algorithm::vertex_heap_prim_mst(sut, source_id); + const auto mst = gl::algorithm::vertex_heap_prim_mst(sut, source_id); REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); @@ -232,14 +235,14 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( vertex_heap_prim_wieghted_edge_traits_type_template, - lib::undirected_graph_traits< - lib_t::empty_properties, - lib_t::weight_property<>, - lib_i::list_t>, // undirected adjacency list graph - lib::undirected_graph_traits< - lib_t::empty_properties, - lib_t::weight_property<>, - lib_i::matrix_t> // undirected adjacency matrix graph + gl::undirected_graph_traits< + gl::types::empty_properties, + gl::types::weight_property<>, + gl::impl::list_t>, // undirected adjacency list graph + gl::undirected_graph_traits< + gl::types::empty_properties, + gl::types::weight_property<>, + gl::impl::matrix_t> // undirected adjacency matrix graph ); TEST_CASE_TEMPLATE_DEFINE( @@ -247,15 +250,16 @@ TEST_CASE_TEMPLATE_DEFINE( TraitsType, vertex_heap_prim_unwieghted_edge_traits_type_template ) { - using sut_type = lib::graph; - using distance_type = lib_t::default_vertex_distance_type; + using sut_type = gl::graph; + using distance_type = gl::types::default_vertex_distance_type; using weight_type = distance_type; - using vertex_id_pair = std::pair; + using vertex_id_pair = std::pair; - static_assert(not lib_tt::c_weight_properties_type); + static_assert(not gl::type_traits::c_weight_properties_type< + typename sut_type::edge_properties_type>); - const auto sut = lib::topology::regular_binary_tree(constants::depth); - const lib_t::id_type source_id = constants::first_element_idx; + const auto sut = gl::topology::regular_binary_tree(constants::depth); + const gl::types::id_type source_id = constants::first_element_idx; std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) @@ -264,7 +268,7 @@ TEST_CASE_TEMPLATE_DEFINE( const weight_type expected_weight = sut.n_vertices() - constants::one; - const auto mst = lib::algorithm::vertex_heap_prim_mst(sut, source_id); + const auto mst = gl::algorithm::vertex_heap_prim_mst(sut, source_id); REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); @@ -286,8 +290,8 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( vertex_heap_prim_unwieghted_edge_traits_type_template, - lib::list_graph_traits, // undirected adjacency list graph - lib::matrix_graph_traits // undirected adjacency matrix graph + gl::list_graph_traits, // undirected adjacency list graph + gl::matrix_graph_traits // undirected adjacency matrix graph ); TEST_SUITE_END(); // test_alg_mst diff --git a/tests/source/test_alg_topological_sort.cpp b/tests/source/gl/test_alg_topological_sort.cpp similarity index 65% rename from tests/source/test_alg_topological_sort.cpp rename to tests/source/gl/test_alg_topological_sort.cpp index a6fef3df..a0f46aeb 100644 --- a/tests/source/test_alg_topological_sort.cpp +++ b/tests/source/gl/test_alg_topological_sort.cpp @@ -1,5 +1,5 @@ -#include "alg_common.hpp" -#include "constants.hpp" +#include "testing/gl/alg_common.hpp" +#include "testing/gl/constants.hpp" #include #include @@ -16,33 +16,34 @@ TEST_SUITE_BEGIN("test_alg_topological_sort"); TEST_CASE_TEMPLATE_DEFINE( "topological sort tests for directed graphs", TraitsType, directed_traits_type_template ) { - using sut_type = lib::graph; + using sut_type = gl::graph; SUBCASE("acyclic graph") { sut_type sut; - std::vector expected_topological_order; + std::vector expected_topological_order; SUBCASE("path graph") { - sut = lib::topology::path(constants::n_elements_alg); + sut = gl::topology::path(constants::n_elements_alg); for (const auto id : sut.vertex_ids()) expected_topological_order.push_back(id); } SUBCASE("path graph with an additional source vertex") { - sut = lib::topology::path(constants::n_elements_alg); + sut = gl::topology::path(constants::n_elements_alg); const auto& additional_vertex = sut.add_vertex(); sut.add_edge(additional_vertex.id(), constants::vertex_id_2); expected_topological_order.push_back(constants::vertex_id_1); expected_topological_order.push_back(additional_vertex.id()); - for (lib_t::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; id++) + for (gl::types::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; + id++) expected_topological_order.push_back(id); } SUBCASE("regular binary tree") { - sut = lib::topology::regular_binary_tree(constants::depth); + sut = gl::topology::regular_binary_tree(constants::depth); for (const auto id : sut.vertex_ids()) expected_topological_order.push_back(id); @@ -51,18 +52,18 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("custom graph") { const fs::path gsf_file_path = alg_common::data_path / "topological_sort_directed_acyclic_graph.gsf"; - sut = lib::io::load(gsf_file_path); + sut = gl::io::load(gsf_file_path); const fs::path order_file_path = alg_common::data_path / "topological_sort_directed_acyclic_order.txt"; expected_topological_order = - alg_common::load_list(sut.n_vertices(), order_file_path); + alg_common::load_list(sut.n_vertices(), order_file_path); } CAPTURE(sut); CAPTURE(expected_topological_order); - const auto topological_order_opt = lib::algorithm::topological_sort(sut); + const auto topological_order_opt = gl::algorithm::topological_sort(sut); REQUIRE(topological_order_opt.has_value()); CHECK(std::ranges::equal(topological_order_opt.value(), expected_topological_order)); @@ -72,30 +73,30 @@ TEST_CASE_TEMPLATE_DEFINE( sut_type sut; SUBCASE("cycle graph") { - sut = lib::topology::cycle(constants::n_elements_alg); + sut = gl::topology::cycle(constants::n_elements_alg); } SUBCASE("clique") { - sut = lib::topology::clique(constants::n_elements_alg); + sut = gl::topology::clique(constants::n_elements_alg); } SUBCASE("custom graph") { const fs::path gsf_file_path = alg_common::data_path / "topological_sort_directed_not_acyclic_graph.gsf"; - sut = lib::io::load(gsf_file_path); + sut = gl::io::load(gsf_file_path); } CAPTURE(sut); - const auto topological_order_opt = lib::algorithm::topological_sort(sut); + const auto topological_order_opt = gl::algorithm::topological_sort(sut); CHECK_FALSE(topological_order_opt.has_value()); } } TEST_CASE_TEMPLATE_INSTANTIATE( directed_traits_type_template, - lib::list_graph_traits, // adjacency list graph - lib::matrix_graph_traits // adjacency matrix graph + gl::list_graph_traits, // adjacency list graph + gl::matrix_graph_traits // adjacency matrix graph ); TEST_SUITE_END(); // test_alg_topological_sort diff --git a/tests/source/test_dereferencing_iterator.cpp b/tests/source/gl/test_dereferencing_iterator.cpp similarity index 85% rename from tests/source/test_dereferencing_iterator.cpp rename to tests/source/gl/test_dereferencing_iterator.cpp index e52d1cb4..87d3fdba 100644 --- a/tests/source/test_dereferencing_iterator.cpp +++ b/tests/source/gl/test_dereferencing_iterator.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "functional.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" #include #include @@ -19,7 +19,7 @@ TEST_SUITE_BEGIN("test_dereferencing_iterator"); // TODO: add specific tests covering the individual methods struct data { - lib_t::id_type id; + gl::types::id_type id; std::string str; friend bool operator==(const data&, const data&) = default; @@ -33,7 +33,7 @@ template struct test_dereferencing_iterator { using container_type = Container; using data_ptr_type = typename container_type::value_type; - using sut_type = lib_t::dereferencing_iterator; + using sut_type = gl::types::dereferencing_iterator; struct address_projection { auto operator()(data& data) const { @@ -41,13 +41,13 @@ struct test_dereferencing_iterator { } auto operator()(const data_ptr_type& data_ptr) const - requires(lib_tt::c_strong_smart_ptr) + requires(gl::type_traits::c_strong_smart_ptr) { return data_ptr.get(); } auto operator()(data_ptr_type data_ptr) const - requires(not lib_tt::c_strong_smart_ptr) + requires(not gl::type_traits::c_strong_smart_ptr) { return data_ptr; } @@ -80,10 +80,10 @@ TEST_CASE_TEMPLATE_DEFINE( fixture_type fixture; SUBCASE("normal iterator") { - const auto sut_range = lib::make_iterator_range( - lib::deref_begin(fixture.container), lib::deref_end(fixture.container) + const auto sut_range = gl::make_iterator_range( + gl::deref_begin(fixture.container), gl::deref_end(fixture.container) ); - const auto ptr_range = lib::make_iterator_range(fixture.container); + const auto ptr_range = gl::make_iterator_range(fixture.container); CHECK(std::ranges::equal( sut_range, ptr_range, std::ranges::equal_to{}, address_projection{}, address_projection{} @@ -91,10 +91,10 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("const iterator") { - const auto sut_crange = lib::make_iterator_range( - lib::deref_cbegin(fixture.container), lib::deref_cend(fixture.container) + const auto sut_crange = gl::make_iterator_range( + gl::deref_cbegin(fixture.container), gl::deref_cend(fixture.container) ); - const auto ptr_crange = lib::make_const_iterator_range(fixture.container); + const auto ptr_crange = gl::make_const_iterator_range(fixture.container); CHECK(std::ranges::equal( sut_crange, @@ -136,8 +136,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("normal iterator") { // Create dereferencing iterator range - auto sut_it = lib::deref_end(fixture.container); - auto sut_begin = lib::deref_begin(fixture.container); + auto sut_it = gl::deref_end(fixture.container); + auto sut_begin = gl::deref_begin(fixture.container); auto ptr_it = std::ranges::end(fixture.container); auto ptr_begin = std::ranges::begin(fixture.container); @@ -165,8 +165,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("const iterator") { // Create dereferencing iterator range - auto sut_it = lib::deref_cend(fixture.container); - auto sut_begin = lib::deref_cbegin(fixture.container); + auto sut_it = gl::deref_cend(fixture.container); + auto sut_begin = gl::deref_cbegin(fixture.container); auto ptr_it = std::ranges::cend(fixture.container); auto ptr_begin = std::ranges::cbegin(fixture.container); diff --git a/tests/source/test_edge_descriptor.cpp b/tests/source/gl/test_edge_descriptor.cpp similarity index 86% rename from tests/source/test_edge_descriptor.cpp rename to tests/source/gl/test_edge_descriptor.cpp index 402af266..6c8d6bd7 100644 --- a/tests/source/test_edge_descriptor.cpp +++ b/tests/source/gl/test_edge_descriptor.cpp @@ -1,6 +1,6 @@ -#include "constants.hpp" -#include "functional.hpp" -#include "types.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" +#include "testing/gl/types.hpp" #include @@ -11,7 +11,7 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_edge_descriptor"); struct test_edge_descriptor { - using vertex_type = lib::vertex_descriptor<>; + using vertex_type = gl::vertex_descriptor<>; vertex_type vd_1{constants::vertex_id_1}; vertex_type vd_2{constants::vertex_id_2}; @@ -23,10 +23,10 @@ struct test_edge_descriptor { TEST_CASE_FIXTURE( test_edge_descriptor, "is_directed() should return true only for edges with directed edge tag" ) { - lib::directed_edge> directed_edge{vd_1, vd_2}; + gl::directed_edge> directed_edge{vd_1, vd_2}; CHECK(directed_edge.is_directed()); - lib::undirected_edge> undirected_edge{vd_1, vd_2}; + gl::undirected_edge> undirected_edge{vd_1, vd_2}; CHECK_FALSE(undirected_edge.is_directed()); } @@ -34,10 +34,10 @@ TEST_CASE_FIXTURE( test_edge_descriptor, "is_undirected() should return true only for edges with bidirectional edge tag" ) { - lib::undirected_edge> undirected_edge{vd_1, vd_2}; + gl::undirected_edge> undirected_edge{vd_1, vd_2}; CHECK(undirected_edge.is_undirected()); - lib::directed_edge> directed_edge{vd_1, vd_2}; + gl::directed_edge> directed_edge{vd_1, vd_2}; CHECK_FALSE(directed_edge.is_undirected()); } @@ -53,7 +53,7 @@ TEST_CASE_TEMPLATE_DEFINE( } // TODO: fix .clang-format to split such lines -TEST_CASE_TEMPLATE_INSTANTIATE(properties_edge_directional_tag_template, lib::directed_edge, types::used_property>, lib::undirected_edge, types::used_property>); +TEST_CASE_TEMPLATE_INSTANTIATE(properties_edge_directional_tag_template, gl::directed_edge, types::used_property>, gl::undirected_edge, types::used_property>); TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent tests", EdgeType, edge_directional_tag_template @@ -136,8 +136,8 @@ TEST_CASE_TEMPLATE_DEFINE( // TODO: fix .clang-format to split such lines TEST_CASE_TEMPLATE_INSTANTIATE( edge_directional_tag_template, - lib::directed_edge>, // default directed edge - lib::undirected_edge> // default undirected edge + gl::directed_edge>, // default directed edge + gl::undirected_edge> // default undirected edge ); TEST_SUITE_END(); // test_edge_descriptor diff --git a/tests/source/test_edge_tags.cpp b/tests/source/gl/test_edge_tags.cpp similarity index 81% rename from tests/source/test_edge_tags.cpp rename to tests/source/gl/test_edge_tags.cpp index f729e320..000d18db 100644 --- a/tests/source/test_edge_tags.cpp +++ b/tests/source/gl/test_edge_tags.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "types.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/types.hpp" #include @@ -10,7 +10,7 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_edge_tags"); struct test_edge_tags { - using vertex_type = lib::vertex_descriptor<>; + using vertex_type = gl::vertex_descriptor<>; vertex_type vd_1{constants::vertex_id_1}; vertex_type vd_2{constants::vertex_id_2}; @@ -19,10 +19,10 @@ struct test_edge_tags { }; struct test_directed_edge_tag : test_edge_tags { - using sut_type = lib::directed_t; - using edge_type = lib::directed_edge; + using sut_type = gl::directed_t; + using edge_type = gl::directed_edge; - const std::unique_ptr edge = lib::detail::make_edge(vd_1, vd_2); + const std::unique_ptr edge = gl::detail::make_edge(vd_1, vd_2); }; TEST_CASE_FIXTURE( @@ -41,10 +41,10 @@ TEST_CASE_FIXTURE( test_directed_edge_tag, "make_edge should return a unique ptr to a directed edge with the given properties" ) { - using property_edge_type = lib::directed_edge; + using property_edge_type = gl::directed_edge; const types::used_property used{true}; - const auto property_edge = lib::detail::make_edge(vd_1, vd_2, used); + const auto property_edge = gl::detail::make_edge(vd_1, vd_2, used); static_assert(std::is_same_v< std::remove_cvref_t, @@ -75,10 +75,10 @@ TEST_CASE_FIXTURE( } struct test_undirected_edge_tag : test_edge_tags { - using sut_type = lib::undirected_t; - using edge_type = lib::undirected_edge; + using sut_type = gl::undirected_t; + using edge_type = gl::undirected_edge; - const std::shared_ptr edge = lib::detail::make_edge(vd_1, vd_2); + const std::shared_ptr edge = gl::detail::make_edge(vd_1, vd_2); }; TEST_CASE_FIXTURE( @@ -97,10 +97,10 @@ TEST_CASE_FIXTURE( test_directed_edge_tag, "make_edge should return a shared ptr to a directed edge with the given properties" ) { - using property_edge_type = lib::undirected_edge; + using property_edge_type = gl::undirected_edge; const types::used_property used{true}; - const auto property_edge = lib::detail::make_edge(vd_1, vd_2, used); + const auto property_edge = gl::detail::make_edge(vd_1, vd_2, used); static_assert(std::is_same_v< std::remove_cvref_t, diff --git a/tests/source/test_graph.cpp b/tests/source/gl/test_graph.cpp similarity index 90% rename from tests/source/test_graph.cpp rename to tests/source/gl/test_graph.cpp index 784a09ec..1a9ec1c1 100644 --- a/tests/source/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -1,7 +1,7 @@ -#include "constants.hpp" -#include "functional.hpp" -#include "transforms.hpp" -#include "types.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" +#include "testing/gl/transforms.hpp" +#include "testing/gl/types.hpp" #include @@ -16,11 +16,11 @@ TEST_SUITE_BEGIN("test_graph"); template struct test_graph { using traits_type = TraitsType; - using sut_type = lib::graph; + using sut_type = gl::graph; using vertex_type = typename sut_type::vertex_type; - template GraphType> - requires(lib_tt::is_directed_v) + template GraphType> + requires(gl::type_traits::is_directed_v) void init_complete_graph(GraphType& graph) { const auto vertices = graph.vertices(); for (const auto& first : vertices) @@ -28,15 +28,15 @@ struct test_graph { if (first != second) graph.add_edge(first, second); - const lib_t::size_type n_unique_edges_in_full_graph = + const gl::types::size_type n_unique_edges_in_full_graph = n_incident_edges_for_fully_connected_vertex(graph) * graph.n_vertices(); REQUIRE_EQ(graph.n_unique_edges(), n_unique_edges_in_full_graph); validate_full_graph_edges(graph); } - template GraphType> - requires(lib_tt::is_undirected_v) + template GraphType> + requires(gl::type_traits::is_undirected_v) void init_complete_graph(GraphType& graph) { const auto vertices = graph.vertices(); for (const auto& first : vertices) @@ -44,25 +44,25 @@ struct test_graph { if (first < second) graph.add_edge(first, second); - const lib_t::size_type n_unique_edges_in_full_graph = + const gl::types::size_type n_unique_edges_in_full_graph = (n_incident_edges_for_fully_connected_vertex(graph) * graph.n_vertices()) / 2; REQUIRE_EQ(graph.n_unique_edges(), n_unique_edges_in_full_graph); validate_full_graph_edges(graph); } - template GraphType> + template GraphType> void validate_full_graph_edges(const GraphType& graph) { REQUIRE(std::ranges::all_of( graph.vertex_ids(), [this, &graph, expected_n_edges = n_incident_edges_for_fully_connected_vertex(graph)]( - const lib_t::id_type vertex_id + const gl::types::id_type vertex_id ) { return graph.adjacent_edges(vertex_id).distance() == expected_n_edges; } )); } - template GraphType> - lib_t::size_type n_incident_edges_for_fully_connected_vertex(const GraphType& graph) { + template GraphType> + gl::types::size_type n_incident_edges_for_fully_connected_vertex(const GraphType& graph) { return graph.n_vertices() - constants::one_element; } @@ -71,27 +71,27 @@ struct test_graph { }; template < - lib_tt::c_instantiation_of TraitsType, - lib_tt::c_properties VertexProperties> -using add_vertex_property = lib::graph_traits< + gl::type_traits::c_instantiation_of TraitsType, + gl::type_traits::c_properties VertexProperties> +using add_vertex_property = gl::graph_traits< typename TraitsType::edge_directional_tag, VertexProperties, typename TraitsType::edge_properties_type, typename TraitsType::implementation_tag>; template < - lib_tt::c_instantiation_of TraitsType, - lib_tt::c_properties EdgeProperties> -using add_edge_property = lib::graph_traits< + gl::type_traits::c_instantiation_of TraitsType, + gl::type_traits::c_properties EdgeProperties> +using add_edge_property = gl::graph_traits< typename TraitsType::edge_directional_tag, typename TraitsType::vertex_properties_type, EdgeProperties, typename TraitsType::implementation_tag>; -using vertex_id_list = std::vector; +using vertex_id_list = std::vector; -template VertexType> -using vertex_ref_list = std::vector>; +template VertexType> +using vertex_ref_list = std::vector>; TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_template) { using fixture_type = test_graph; @@ -127,9 +127,12 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp std::out_of_range ); - CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const lib_t::id_type vertex_id) { - return not static_cast(sut.adjacent_edges(vertex_id).distance()); - })); + CHECK(std::ranges::all_of( + constants::vertex_id_view, + [&sut](const gl::types::id_type vertex_id) { + return not static_cast(sut.adjacent_edges(vertex_id).distance()); + } + )); } // --- vertex method tests --- @@ -137,8 +140,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_vertex should return a vertex_descriptor with an incremented id and no edges") { sut_type sut; - constexpr lib_t::size_type target_n_vertices = constants::n_elements; - for (lib_t::id_type v_id = constants::zero_elements; v_id < target_n_vertices; v_id++) { + constexpr gl::types::size_type target_n_vertices = constants::n_elements; + for (gl::types::id_type v_id = constants::zero_elements; v_id < target_n_vertices; v_id++) { const auto& vertex = sut.add_vertex(); CHECK_EQ(vertex.id(), v_id); CHECK_EQ(sut.n_vertices(), v_id + constants::one_element); @@ -150,7 +153,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_vertex should initialize a new vertex with the input properties structure") { using properties_traits_type = add_vertex_property; - lib::graph sut; + gl::graph sut; const auto& vertex = sut.add_vertex(constants::visited); REQUIRE_EQ(sut.n_vertices(), constants::one_element); @@ -170,7 +173,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_vertices_with should properly extend the current adjacency list with the given " "properties") { using properties_traits_type = add_vertex_property; - lib::graph sut; + gl::graph sut; const std::vector properties_list{ constants::visited, constants::not_visited, constants::visited @@ -261,7 +264,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut.remove_vertex(constants::vertex_id_1); - constexpr lib_t::size_type n_vertices_after_remove = + constexpr gl::types::size_type n_vertices_after_remove = constants::n_elements - constants::one_element; const auto expected_n_incident_edges = fixture.n_incident_edges_for_fully_connected_vertex(sut); @@ -273,7 +276,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp )); REQUIRE(std::ranges::all_of( vertex_id_view, - [&sut, expected_n_incident_edges](const lib_t::id_type vertex_id) { + [&sut, expected_n_incident_edges](const gl::types::id_type vertex_id) { return sut.adjacent_edges(vertex_id).distance() == expected_n_incident_edges; } )); @@ -295,7 +298,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut.remove_vertex(constants::vertex_id_1); - constexpr lib_t::size_type n_vertices_after_remove = + constexpr gl::types::size_type n_vertices_after_remove = constants::n_elements - constants::one_element; const auto expected_n_incident_edges = fixture.n_incident_edges_for_fully_connected_vertex(sut); @@ -307,7 +310,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp )); REQUIRE(std::ranges::all_of( vertex_id_view, - [&sut, expected_n_incident_edges](const lib_t::id_type vertex_id) { + [&sut, expected_n_incident_edges](const gl::types::id_type vertex_id) { return sut.adjacent_edges(vertex_id).distance() == expected_n_incident_edges; } )); @@ -398,7 +401,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_EQ(&new_edge_extracted_1, &new_edge); const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - if constexpr (lib_tt::is_undirected_v) { + if constexpr (gl::type_traits::is_undirected_v) { CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; CHECK_EQ(&new_edge_extracted_2, &new_edge); @@ -429,7 +432,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_EQ(&new_edge_extracted_1, &new_edge); const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - if constexpr (lib_tt::is_undirected_v) { + if constexpr (gl::type_traits::is_undirected_v) { CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; CHECK_EQ(&new_edge_extracted_2, &new_edge); @@ -462,7 +465,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); constexpr auto source_id = constants::vertex_id_1; - const std::vector target_id_list{ + const std::vector target_id_list{ constants::vertex_id_1, constants::vertex_id_2, constants::vertex_id_3 }; @@ -530,7 +533,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); REQUIRE_EQ(adjacent_edges_1.distance(), constants::one_element); - if constexpr (lib_tt::is_undirected_v) + if constexpr (gl::type_traits::is_undirected_v) REQUIRE_EQ(adjacent_edges_2.distance(), constants::one_element); sut.remove_edge(added_edge); @@ -556,7 +559,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements + constants::one_element); - std::vector> edges_to_remove{edge_1, edge_2, edge_3}; + std::vector> edges_to_remove{ + edge_1, edge_2, edge_3 + }; const auto has_edge = [&sut](const auto& edge_ref) { return sut.has_edge(edge_ref.get()); @@ -578,7 +583,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("edge method tests for non-default properties type") { using properties_traits_type = add_edge_property; using property_edge_type = typename properties_traits_type::edge_type; - lib::graph sut{constants::n_elements}; + gl::graph sut{constants::n_elements}; const auto vertices = sut.vertices(); const auto& vertex_1 = vertices[constants::vertex_id_1]; @@ -615,7 +620,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_EQ(&new_edge_extracted_1, &new_edge); const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - if constexpr (lib_tt::is_undirected_v) { + if constexpr (gl::type_traits::is_undirected_v) { CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; CHECK_EQ(&new_edge_extracted_2, &new_edge); @@ -659,7 +664,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_EQ(&new_edge_extracted_1, &new_edge); const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - if constexpr (lib_tt::is_undirected_v) { + if constexpr (gl::type_traits::is_undirected_v) { CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; CHECK_EQ(&new_edge_extracted_2, &new_edge); @@ -678,7 +683,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); REQUIRE_EQ(adjacent_edges_1.distance(), constants::one_element); - if constexpr (lib_tt::is_undirected_v) + if constexpr (gl::type_traits::is_undirected_v) REQUIRE_EQ(adjacent_edges_2.distance(), constants::one_element); sut.remove_edge(added_edge); @@ -704,7 +709,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements + constants::one_element); - const std::vector> edges_to_remove{ + const std::vector> edges_to_remove{ edge_1, edge_2, edge_3 }; @@ -796,7 +801,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE(edge_opt_1.has_value()); CHECK_EQ(&edge_opt_1->get(), &edge); - if constexpr (lib_tt::is_undirected_v) { + if constexpr (gl::type_traits::is_undirected_v) { const auto edge_opt_2 = sut.get_edge(vd_2, vd_1); REQUIRE(edge_opt_2.has_value()); CHECK_EQ(&edge_opt_2->get(), &edge); @@ -830,9 +835,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("get_edges(id, id) should return a valid edge reference vector if the given vertices " "are incident") { sut_type sut{constants::n_elements}; - std::vector> expected_edges; + std::vector> expected_edges; - if constexpr (std::same_as) { + if constexpr (std::same_as) { for (auto _ = constants::first_element_idx; _ < constants::n_elements; _++) expected_edges.push_back( std::cref(sut.add_edge(constants::vertex_id_1, constants::vertex_id_2)) @@ -854,7 +859,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp address_projection )); - if constexpr (lib_tt::is_directed_v) { + if constexpr (gl::type_traits::is_directed_v) { CHECK(sut.get_edges(constants::vertex_id_2, constants::vertex_id_2).empty()); } else { @@ -933,10 +938,10 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp TEST_CASE_TEMPLATE_INSTANTIATE( graph_traits_template, - lib::list_graph_traits, // directed adjacency list - lib::list_graph_traits, // undirected adjacency list - lib::matrix_graph_traits, // directed adjacency matrix - lib::matrix_graph_traits // undirected adjacency matrix + gl::list_graph_traits, // directed adjacency list + gl::list_graph_traits, // undirected adjacency list + gl::matrix_graph_traits, // directed adjacency matrix + gl::matrix_graph_traits // undirected adjacency matrix ); TEST_SUITE_END(); // test_graph diff --git a/tests/source/test_graph_file_io.cpp b/tests/source/gl/test_graph_file_io.cpp similarity index 77% rename from tests/source/test_graph_file_io.cpp rename to tests/source/gl/test_graph_file_io.cpp index 4cba7652..71235bee 100644 --- a/tests/source/test_graph_file_io.cpp +++ b/tests/source/gl/test_graph_file_io.cpp @@ -1,6 +1,6 @@ -#include "constants.hpp" -#include "functional.hpp" -#include "io_common.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" +#include "testing/gl/io_common.hpp" #include #include @@ -35,11 +35,11 @@ TEST_SUITE_BEGIN("test_graph_file_io"); struct test_graph_file_io { using traits_type = - lib::graph_traits; - using sut_type = lib::graph; + gl::graph_traits; + using sut_type = gl::graph; test_graph_file_io() { - sut_out = lib::topology::clique(n_vertices); + sut_out = gl::topology::clique(n_vertices); // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; @@ -54,7 +54,7 @@ struct test_graph_file_io { fs::remove(path); } - const lib_t::size_type n_vertices = 5ull; + const gl::types::size_type n_vertices = 5ull; sut_type sut_out; fs::path path{"test_directed_graph_file_io.gsf"}; @@ -72,13 +72,13 @@ TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_te FAIL("Could not initialize an empty file"); CUSTOM_REQUIRE_THROWS_FS_ERROR( - lib::io::save(fixture.sut_out, fixture.path), std::errc::file_exists + gl::io::save(fixture.sut_out, fixture.path), std::errc::file_exists ); } SUBCASE("load shoul throw if a file does not exist") { CUSTOM_REQUIRE_THROWS_FS_ERROR( - func::discard_result(lib::io::load(fixture.path)), + func::discard_result(gl::io::load(fixture.path)), std::errc::no_such_file_or_directory ); } @@ -88,24 +88,24 @@ TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_te // * could not open file tests SUBCASE("file io should properly save and load a graph in a gsf format") { - lib::io::save(fixture.sut_out, fixture.path); - const auto sut_in = lib::io::load(fixture.path); + gl::io::save(fixture.sut_out, fixture.path); + const auto sut_in = gl::io::load(fixture.path); io_common::verify_graph_structure(sut_in, fixture.sut_out); } SUBCASE("file io should properly save and load a graph in a gsf format with vertex properties" ) { - lib::io::save(fixture.sut_out, fixture.path, {lib::io::with_vertex_properties}); - const auto sut_in = lib::io::load(fixture.path); + gl::io::save(fixture.sut_out, fixture.path, {gl::io::with_vertex_properties}); + const auto sut_in = gl::io::load(fixture.path); io_common::verify_graph_structure(sut_in, fixture.sut_out); io_common::verify_vertex_properties(sut_in, fixture.sut_out); } SUBCASE("file io should properly save and load a graph in a gsf format with edge properties") { - lib::io::save(fixture.sut_out, fixture.path, {lib::io::with_edge_properties}); - const auto sut_in = lib::io::load(fixture.path); + gl::io::save(fixture.sut_out, fixture.path, {gl::io::with_edge_properties}); + const auto sut_in = gl::io::load(fixture.path); io_common::verify_graph_structure(sut_in, fixture.sut_out); io_common::verify_edge_properties(sut_in, fixture.sut_out); @@ -113,8 +113,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_te SUBCASE("file io should properly save and load a graph in a gsf format with vertex and edge " "properties") { - lib::io::save(fixture.sut_out, fixture.path, {lib::io::with_properties}); - const auto sut_in = lib::io::load(fixture.path); + gl::io::save(fixture.sut_out, fixture.path, {gl::io::with_properties}); + const auto sut_in = gl::io::load(fixture.path); io_common::verify_graph_structure(sut_in, fixture.sut_out); io_common::verify_vertex_properties(sut_in, fixture.sut_out); @@ -124,8 +124,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_te TEST_CASE_TEMPLATE_INSTANTIATE( directional_tag_sut_template, - lib::graph>, // directed adj list - lib::graph> // undirected adj list + gl::graph>, // directed adj list + gl::graph> // undirected adj list ); TEST_SUITE_END(); // test_graph_file_io diff --git a/tests/source/test_graph_incidence.cpp b/tests/source/gl/test_graph_incidence.cpp similarity index 96% rename from tests/source/test_graph_incidence.cpp rename to tests/source/gl/test_graph_incidence.cpp index 1edd15e4..5d461693 100644 --- a/tests/source/test_graph_incidence.cpp +++ b/tests/source/gl/test_graph_incidence.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "functional.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" #include @@ -10,7 +10,7 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_graph_incidence"); struct test_graph_incidence { - using vertex_type = lib::vertex_descriptor<>; + using vertex_type = gl::vertex_descriptor<>; vertex_type invalid_vertex{constants::vertex_id_1}; vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; @@ -183,8 +183,8 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ TEST_CASE_TEMPLATE_INSTANTIATE( graph_type_template, - lib::graph>, // directed graph - lib::graph> // undirected graph + gl::graph>, // directed graph + gl::graph> // undirected graph ); TEST_SUITE_END(); // test_graph_incidence diff --git a/tests/source/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp similarity index 75% rename from tests/source/test_graph_io.cpp rename to tests/source/gl/test_graph_io.cpp index 4249dd94..f8333108 100644 --- a/tests/source/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "io_common.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/io_common.hpp" #include #include @@ -17,13 +17,13 @@ TEST_SUITE_BEGIN("test_graph_io"); struct test_directed_graph_io { using traits_type = - lib::graph_traits; - using sut_type = lib::graph; + gl::graph_traits; + using sut_type = gl::graph; test_directed_graph_io() { - ss << lib::io::enable_gsf; + ss << gl::io::enable_gsf; - sut_out = lib::topology::clique(n_vertices); + sut_out = gl::topology::clique(n_vertices); // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; @@ -34,7 +34,7 @@ struct test_directed_graph_io { } } - const lib_t::size_type n_vertices = 5ull; + const gl::types::size_type n_vertices = 5ull; sut_type sut_out; sut_type sut_in; @@ -44,9 +44,9 @@ struct test_directed_graph_io { TEST_CASE_FIXTURE( test_directed_graph_io, "io read should throw if the directional tag doesn't match" ) { - using undir_traits = lib::graph_traits; + using undir_traits = gl::graph_traits; - lib::graph invalid_sut_out{n_vertices}; + gl::graph invalid_sut_out{n_vertices}; ss << invalid_sut_out; CHECK_THROWS_AS(ss >> sut_in, std::ios_base::failure); @@ -60,8 +60,8 @@ TEST_CASE_FIXTURE( ss << gl::io::with_vertex_properties << sut_out; using not_readable_vp_traits = - lib::graph_traits; - lib::graph invalid_sut_in; + gl::graph_traits; + gl::graph invalid_sut_in; CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); } @@ -73,8 +73,8 @@ TEST_CASE_FIXTURE( ss << gl::io::with_edge_properties << sut_out; using not_readable_vp_traits = - lib::graph_traits; - lib::graph invalid_sut_in; + gl::graph_traits; + gl::graph invalid_sut_in; CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); } @@ -94,7 +94,7 @@ TEST_CASE_FIXTURE( test_directed_graph_io, "io operators should properly write and read the graph from a stream with vertex properties" ) { - ss << lib::io::with_vertex_properties << sut_out; + ss << gl::io::with_vertex_properties << sut_out; ss >> sut_in; io_common::verify_graph_structure(sut_in, sut_out); @@ -105,7 +105,7 @@ TEST_CASE_FIXTURE( test_directed_graph_io, "io operators should properly write and read the graph from a stream with edge properties" ) { - ss << lib::io::with_edge_properties << sut_out; + ss << gl::io::with_edge_properties << sut_out; ss >> sut_in; io_common::verify_graph_structure(sut_in, sut_out); @@ -117,7 +117,7 @@ TEST_CASE_FIXTURE( "io operators should properly write and read the graph from a stream with both vertex and edge " "properties" ) { - ss << lib::io::with_vertex_properties << lib::io::with_edge_properties << sut_out; + ss << gl::io::with_vertex_properties << gl::io::with_edge_properties << sut_out; ss >> sut_in; io_common::verify_graph_structure(sut_in, sut_out); @@ -127,13 +127,13 @@ TEST_CASE_FIXTURE( struct test_undirected_graph_io { using traits_type = - lib::graph_traits; - using sut_type = lib::graph; + gl::graph_traits; + using sut_type = gl::graph; test_undirected_graph_io() { - ss << lib::io::enable_gsf; + ss << gl::io::enable_gsf; - sut_out = lib::topology::clique(n_vertices); + sut_out = gl::topology::clique(n_vertices); // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; @@ -145,7 +145,7 @@ struct test_undirected_graph_io { } } - const lib_t::size_type n_vertices = 5ull; + const gl::types::size_type n_vertices = 5ull; sut_type sut_out; sut_type sut_in; @@ -155,9 +155,9 @@ struct test_undirected_graph_io { TEST_CASE_FIXTURE( test_undirected_graph_io, "io read should throw if the directional tag doesn't match" ) { - using dir_traits = lib::graph_traits; + using dir_traits = gl::graph_traits; - lib::graph invalid_sut_out{n_vertices}; + gl::graph invalid_sut_out{n_vertices}; ss << invalid_sut_out; CHECK_THROWS_AS(ss >> sut_in, std::ios_base::failure); @@ -171,8 +171,8 @@ TEST_CASE_FIXTURE( ss << gl::io::with_vertex_properties << sut_out; using not_readable_vp_traits = - lib::graph_traits; - lib::graph invalid_sut_in; + gl::graph_traits; + gl::graph invalid_sut_in; CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); } @@ -184,8 +184,8 @@ TEST_CASE_FIXTURE( ss << gl::io::with_edge_properties << sut_out; using not_readable_vp_traits = - lib::graph_traits; - lib::graph invalid_sut_in; + gl::graph_traits; + gl::graph invalid_sut_in; CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); } @@ -205,7 +205,7 @@ TEST_CASE_FIXTURE( test_undirected_graph_io, "io operators should properly write and read the graph from a stream with vertex properties" ) { - ss << lib::io::with_vertex_properties << sut_out; + ss << gl::io::with_vertex_properties << sut_out; ss >> sut_in; io_common::verify_graph_structure(sut_in, sut_out); @@ -216,7 +216,7 @@ TEST_CASE_FIXTURE( test_undirected_graph_io, "io operators should properly write and read the graph from a stream with edge properties" ) { - ss << lib::io::with_edge_properties << sut_out; + ss << gl::io::with_edge_properties << sut_out; ss >> sut_in; io_common::verify_graph_structure(sut_in, sut_out); @@ -228,7 +228,7 @@ TEST_CASE_FIXTURE( "io operators should properly write and read the graph from a stream with both vertex and edge " "properties" ) { - ss << lib::io::with_vertex_properties << lib::io::with_edge_properties << sut_out; + ss << gl::io::with_vertex_properties << gl::io::with_edge_properties << sut_out; ss >> sut_in; io_common::verify_graph_structure(sut_in, sut_out); diff --git a/tests/source/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp similarity index 80% rename from tests/source/test_graph_topology_builders.cpp rename to tests/source/gl/test_graph_topology_builders.cpp index 77340e88..7f6510db 100644 --- a/tests/source/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -1,4 +1,4 @@ -#include "constants.hpp" +#include "testing/gl/constants.hpp" #include @@ -10,31 +10,31 @@ TEST_SUITE_BEGIN("test_graph_topology_builders"); namespace { -template -[[nodiscard]] lib_t::size_type n_unique_edges_for_bidir_topology( - const lib_t::size_type n_connections +template +[[nodiscard]] gl::types::size_type n_unique_edges_for_bidir_topology( + const gl::types::size_type n_connections ) { - if constexpr (lib_tt::is_directed_v) + if constexpr (gl::type_traits::is_directed_v) return n_connections; else return n_connections / constants::two; } -template +template void verify_graph_size( const GraphType& graph, - const lib_t::size_type expected_n_vertices, - const lib_t::size_type expected_n_connections + const gl::types::size_type expected_n_vertices, + const gl::types::size_type expected_n_connections ) { REQUIRE_EQ(graph.n_vertices(), expected_n_vertices); REQUIRE_EQ(graph.n_unique_edges(), expected_n_connections); } -template +template void verify_bidir_graph_size( const GraphType& graph, - const lib_t::size_type expected_n_vertices, - const lib_t::size_type expected_n_connections + const gl::types::size_type expected_n_vertices, + const gl::types::size_type expected_n_connections ) { REQUIRE_EQ(graph.n_vertices(), expected_n_vertices); REQUIRE_EQ( @@ -46,7 +46,7 @@ void verify_bidir_graph_size( namespace predicate { -template +template [[nodiscard]] auto is_vertex_fully_connected(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { @@ -56,7 +56,7 @@ template }; } -template +template [[nodiscard]] auto is_vertex_not_connected(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { @@ -66,7 +66,7 @@ template }; } -template +template [[nodiscard]] auto is_vertex_not_connected_to_any_from( const GraphType& graph, const auto& vertex_it_range ) { @@ -79,7 +79,7 @@ template }; } -template +template [[nodiscard]] auto is_vertex_connected_to_next_only(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { @@ -92,7 +92,7 @@ template }; } -template +template [[nodiscard]] auto is_vertex_connected_to_prev_only(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { @@ -106,7 +106,7 @@ template }; } -template +template [[nodiscard]] auto is_vertex_connected_to_id_adjacent(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { @@ -124,11 +124,11 @@ template }; } -template +template [[nodiscard]] auto is_connected_to_binary_chlidren(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { - const auto target_ids = lib::topology::detail::get_binary_target_ids(source.id()); + const auto target_ids = gl::topology::detail::get_binary_target_ids(source.id()); if (target_ids.first >= graph.n_vertices()) // no need to check second as second = first + 1 @@ -145,12 +145,12 @@ template }; } -template +template [[nodiscard]] auto is_biconnected_to_binary_chlidren(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { - const auto target_ids = lib::topology::detail::get_binary_target_ids(source.id()); - const lib_t::id_type parent_id = + const auto target_ids = gl::topology::detail::get_binary_target_ids(source.id()); + const gl::types::id_type parent_id = source.id() == constants::zero ? constants::zero : (source.id() - constants::one) / constants::two; @@ -190,7 +190,7 @@ TEST_CASE_TEMPLATE_DEFINE( using edge_type = typename graph_type::edge_type; SUBCASE("clique(n_vertices) should build a fully connected graph of size n_vertices") { - const auto clique = lib::topology::clique(constants::n_elements_top); + const auto clique = gl::topology::clique(constants::n_elements_top); const auto expected_n_connections = constants::n_elements_top * (constants::n_elements_top - constants::one_element); @@ -202,7 +202,7 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("biclique(n_vertices_a, n_vertices_b) should build a biclique with " "vertex sets of sizes n_vertices_a and n_vertices_b respectively") { const auto biclique = - lib::topology::biclique(constants::n_elements_top, constants::n_elements); + gl::topology::biclique(constants::n_elements_top, constants::n_elements); const auto expected_n_vertices = constants::n_elements_top + constants::n_elements; // `2x` is required to account for adding edges both ways @@ -213,7 +213,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto vertices = biclique.vertices(); const auto vertices_a = vertices | std::views::take(constants::n_elements_top); - const auto vertices_b = lib::make_iterator_range( + const auto vertices_b = gl::make_iterator_range( std::ranges::next(vertices.begin(), constants::n_elements_top), std::ranges::next(vertices.begin(), expected_n_vertices) ); @@ -241,14 +241,14 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("regular_binary_tree(depth) should return a regular binay tree with the given depth") { SUBCASE("depth = 0 : empty graph") { const auto complete_bin_tree = - lib::topology::regular_binary_tree(constants::zero); + gl::topology::regular_binary_tree(constants::zero); REQUIRE_EQ(complete_bin_tree.n_vertices(), constants::zero_elements); REQUIRE_EQ(complete_bin_tree.n_unique_edges(), constants::zero_elements); } SUBCASE("depth = 1 : graph with one vertex and no edges") { const auto complete_bin_tree = - lib::topology::regular_binary_tree(constants::one); + gl::topology::regular_binary_tree(constants::one); REQUIRE_EQ(complete_bin_tree.n_vertices(), constants::one_element); REQUIRE_EQ(complete_bin_tree.n_unique_edges(), constants::zero_elements); } @@ -257,10 +257,10 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( graph_type_template, - lib::graph>, // directed adjacency list - lib::graph>, // undirected adjacency list - lib::graph>, // directed adjacency matrix - lib::graph> // undirected adjacency matrix + gl::graph>, // directed adjacency list + gl::graph>, // undirected adjacency list + gl::graph>, // directed adjacency matrix + gl::graph> // undirected adjacency matrix ); TEST_CASE_TEMPLATE_DEFINE( @@ -271,7 +271,7 @@ TEST_CASE_TEMPLATE_DEFINE( using edge_type = typename graph_type::edge_type; SUBCASE("cycle(n_vertices) should build a one-way cycle graph of size n_vertices") { - const auto cycle = lib::topology::cycle(constants::n_elements_top); + const auto cycle = gl::topology::cycle(constants::n_elements_top); verify_graph_size(cycle, constants::n_elements_top, constants::n_elements_top); CHECK(std::ranges::all_of( @@ -281,8 +281,7 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("bidirectional_cycle(n_vertices) should build a two-way cycle graph of size n_vertices" ) { - const auto cycle = - lib::topology::bidirectional_cycle(constants::n_elements_top); + const auto cycle = gl::topology::bidirectional_cycle(constants::n_elements_top); verify_graph_size( cycle, constants::n_elements_top, constants::two * constants::n_elements_top ); @@ -293,7 +292,7 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("path(n_vertices) should build a one-way path graph of size n_vertices") { - const auto path = lib::topology::path(constants::n_elements_top); + const auto path = gl::topology::path(constants::n_elements_top); const auto n_source_vertices = path.n_vertices() - constants::one_element; verify_graph_size(path, constants::n_elements_top, n_source_vertices); @@ -306,7 +305,7 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("bidirectional_path(n_vertices) should build a two-way path graph of size n_vertices") { - const auto path = lib::topology::bidirectional_path(constants::n_elements_top); + const auto path = gl::topology::bidirectional_path(constants::n_elements_top); const auto n_source_vertices = path.n_vertices() - constants::one_element; verify_graph_size(path, constants::n_elements_top, constants::two * n_source_vertices); @@ -325,10 +324,10 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("regular_binary_tree(depth) should return a one-way regular binay tree with the " "given depth") { - const auto bin_tree = lib::topology::regular_binary_tree(constants::depth); + const auto bin_tree = gl::topology::regular_binary_tree(constants::depth); const auto expected_n_vertices = - lib::util::upow_sum(constants::two, constants::zero, constants::depth - constants::one); + gl::util::upow_sum(constants::two, constants::zero, constants::depth - constants::one); const auto expected_n_connections = expected_n_vertices - constants::one; verify_graph_size(bin_tree, expected_n_vertices, expected_n_connections); @@ -340,10 +339,10 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("bidirectional_regular_binary_tree(depth) should return a two-way regular binay tree " "with the given depth") { const auto bin_tree = - lib::topology::bidirectional_regular_binary_tree(constants::depth); + gl::topology::bidirectional_regular_binary_tree(constants::depth); const auto expected_n_vertices = - lib::util::upow_sum(constants::two, constants::zero, constants::depth - constants::one); + gl::util::upow_sum(constants::two, constants::zero, constants::depth - constants::one); const auto expected_n_connections = (expected_n_vertices - constants::one) * constants::two; verify_graph_size(bin_tree, expected_n_vertices, expected_n_connections); @@ -355,8 +354,8 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( directed_graph_type_template, - lib::graph>, // adjacency list - lib::graph> // adjacency matrix + gl::graph>, // adjacency list + gl::graph> // adjacency matrix ); TEST_CASE_TEMPLATE_DEFINE( @@ -370,11 +369,11 @@ TEST_CASE_TEMPLATE_DEFINE( graph_type cycle; SUBCASE("cycle builder") { - cycle = lib::topology::cycle(constants::n_elements_top); + cycle = gl::topology::cycle(constants::n_elements_top); } SUBCASE("bidirectional_cycle builder") { - cycle = lib::topology::bidirectional_cycle(constants::n_elements_top); + cycle = gl::topology::bidirectional_cycle(constants::n_elements_top); } CAPTURE(cycle); @@ -390,11 +389,11 @@ TEST_CASE_TEMPLATE_DEFINE( graph_type path; SUBCASE("path builder") { - path = lib::topology::path(constants::n_elements_top); + path = gl::topology::path(constants::n_elements_top); } SUBCASE("bidirectional_path builder") { - path = lib::topology::bidirectional_path(constants::n_elements_top); + path = gl::topology::bidirectional_path(constants::n_elements_top); } CAPTURE(path); @@ -418,18 +417,18 @@ TEST_CASE_TEMPLATE_DEFINE( graph_type bin_tree; SUBCASE("regular_binary_tree builder") { - bin_tree = lib::topology::regular_binary_tree(constants::depth); + bin_tree = gl::topology::regular_binary_tree(constants::depth); } SUBCASE("bidirectional_regular_binary_tree builder") { bin_tree = - lib::topology::bidirectional_regular_binary_tree(constants::depth); + gl::topology::bidirectional_regular_binary_tree(constants::depth); } CAPTURE(bin_tree); const auto expected_n_vertices = - lib::util::upow_sum(constants::two, constants::zero, constants::depth - constants::one); + gl::util::upow_sum(constants::two, constants::zero, constants::depth - constants::one); const auto expected_n_connections = expected_n_vertices - constants::one; verify_graph_size(bin_tree, expected_n_vertices, expected_n_connections); @@ -441,8 +440,8 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( undirected_graph_type_template, - lib::graph>, // adjacency list - lib::graph> // adjacency matrix + gl::graph>, // adjacency list + gl::graph> // adjacency matrix ); TEST_SUITE_END(); // test_graph_topology_builders diff --git a/tests/source/test_iterator_range.cpp b/tests/source/gl/test_iterator_range.cpp similarity index 82% rename from tests/source/test_iterator_range.cpp rename to tests/source/gl/test_iterator_range.cpp index a95292b6..f672d515 100644 --- a/tests/source/test_iterator_range.cpp +++ b/tests/source/gl/test_iterator_range.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "functional.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" #include @@ -15,7 +15,7 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_iterator_range"); -template +template struct test_iterator_range_type_params { using container_type = Container; using cache_mode = CacheMode; @@ -25,7 +25,7 @@ template struct test_iterator_range { using container_type = typename TypeParams::container_type; using iterator_type = typename container_type::iterator; - using sut_type = lib_t::iterator_range; + using sut_type = gl::types::iterator_range; test_iterator_range() : container(constants::n_elements), sut(container.begin(), container.end()) { @@ -61,7 +61,7 @@ TEST_CASE_TEMPLATE_DEFINE("iterator_range tests", TypeParams, type_params_templa SUBCASE("should properly initialize the begin and end iterator for a range constructor") { sut_type range_constructed_sut = - lib::make_iterator_range(container); + gl::make_iterator_range(container); CHECK_EQ(range_constructed_sut.begin(), std::ranges::begin(container)); CHECK_EQ(range_constructed_sut.end(), std::ranges::end(container)); @@ -130,19 +130,19 @@ TEST_CASE_TEMPLATE_DEFINE("iterator_range tests", TypeParams, type_params_templa SUBCASE("make_iterator_range should return a properly initialized iterator_range") { const auto range = - lib::make_iterator_range(container.begin(), container.end()); + gl::make_iterator_range(container.begin(), container.end()); CHECK(std::ranges::equal(range, container)); } SUBCASE("make_iterator_range should return an iterator_range properly initialized with the " "container") { - const auto range = lib::make_iterator_range(container); + const auto range = gl::make_iterator_range(container); CHECK(std::ranges::equal(range, container)); } SUBCASE("make_const_iterator_range should return an iterator_range properly initialized with " "the container") { - const auto range = lib::make_const_iterator_range(container); + const auto range = gl::make_const_iterator_range(container); CHECK(std::ranges::equal(range, container)); } } @@ -152,33 +152,33 @@ TEST_CASE_TEMPLATE_INSTANTIATE( // cache_mode_value::none test_iterator_range_type_params< std::vector, - lib_tt::no_cache>, // random access iterator + gl::type_traits::no_cache>, // random access iterator test_iterator_range_type_params< std::list, - lib_tt::no_cache>, // bidirectional iterator + gl::type_traits::no_cache>, // bidirectional iterator test_iterator_range_type_params< std::forward_list, - lib_tt::no_cache>, // forward iterator + gl::type_traits::no_cache>, // forward iterator // cache_mode_value::lazy test_iterator_range_type_params< std::vector, - lib_tt::lazy_cache>, // random access iterator + gl::type_traits::lazy_cache>, // random access iterator test_iterator_range_type_params< std::list, - lib_tt::lazy_cache>, // bidirectional iterator + gl::type_traits::lazy_cache>, // bidirectional iterator test_iterator_range_type_params< std::forward_list, - lib_tt::lazy_cache>, // forward iterator + gl::type_traits::lazy_cache>, // forward iterator // cache_mode_value::eager test_iterator_range_type_params< std::vector, - lib_tt::eager_cache>, // random access iterator + gl::type_traits::eager_cache>, // random access iterator test_iterator_range_type_params< std::list, - lib_tt::eager_cache>, // bidirectional iterator + gl::type_traits::eager_cache>, // bidirectional iterator test_iterator_range_type_params< std::forward_list, - lib_tt::eager_cache> // forward iterator + gl::type_traits::eager_cache> // forward iterator ); TEST_SUITE_END(); // test_iterator_range diff --git a/tests/source/test_non_null_iterator.cpp b/tests/source/gl/test_non_null_iterator.cpp similarity index 84% rename from tests/source/test_non_null_iterator.cpp rename to tests/source/gl/test_non_null_iterator.cpp index 6100e2cd..b0df5370 100644 --- a/tests/source/test_non_null_iterator.cpp +++ b/tests/source/gl/test_non_null_iterator.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "functional.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" #include #include @@ -19,7 +19,7 @@ TEST_SUITE_BEGIN("test_non_null_iterator"); // TODO: add specific tests covering the individual methods struct data { - lib_t::id_type id; + gl::types::id_type id; std::string str; friend bool operator==(const data&, const data&) = default; @@ -33,17 +33,17 @@ template struct test_non_null_iterator { using container_type = Container; using data_ptr_type = typename container_type::value_type; - using sut_type = lib_t::non_null_iterator; + using sut_type = gl::types::non_null_iterator; struct reference_projection { auto& operator()(const data_ptr_type& data_ptr) const - requires(lib_tt::c_strong_smart_ptr) + requires(gl::type_traits::c_strong_smart_ptr) { return *data_ptr; } auto& operator()(data_ptr_type data_ptr) const - requires(not lib_tt::c_strong_smart_ptr) + requires(not gl::type_traits::c_strong_smart_ptr) { return *data_ptr; } @@ -82,10 +82,10 @@ TEST_CASE_TEMPLATE_DEFINE( fixture_type fixture; SUBCASE("normal iterator") { - const auto sut_range = lib::make_iterator_range( - lib::non_null_begin(fixture.container), lib::non_null_end(fixture.container) + const auto sut_range = gl::make_iterator_range( + gl::non_null_begin(fixture.container), gl::non_null_end(fixture.container) ); - const auto non_null_range = lib::make_iterator_range(fixture.non_null_container); + const auto non_null_range = gl::make_iterator_range(fixture.non_null_container); CHECK(std::ranges::equal( sut_range, @@ -97,10 +97,10 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("cosnt iterator") { - const auto sut_range = lib::make_iterator_range( - lib::non_null_cbegin(fixture.container), lib::non_null_cend(fixture.container) + const auto sut_range = gl::make_iterator_range( + gl::non_null_cbegin(fixture.container), gl::non_null_cend(fixture.container) ); - const auto non_null_range = lib::make_const_iterator_range(fixture.non_null_container); + const auto non_null_range = gl::make_const_iterator_range(fixture.non_null_container); CHECK(std::ranges::equal( sut_range, @@ -142,8 +142,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("normal iterator") { // Create non_null iterator range - auto sut_it = lib::non_null_end(fixture.container); - auto sut_begin = lib::non_null_begin(fixture.container); + auto sut_it = gl::non_null_end(fixture.container); + auto sut_begin = gl::non_null_begin(fixture.container); auto ptr_it = std::ranges::end(fixture.non_null_container); auto ptr_begin = std::ranges::begin(fixture.non_null_container); @@ -168,8 +168,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("const iterator") { // Create non_null iterator range - auto sut_it = lib::non_null_cend(fixture.container); - auto sut_begin = lib::non_null_cbegin(fixture.container); + auto sut_it = gl::non_null_cend(fixture.container); + auto sut_begin = gl::non_null_cbegin(fixture.container); auto ptr_it = std::ranges::cend(fixture.non_null_container); auto ptr_begin = std::ranges::cbegin(fixture.non_null_container); diff --git a/tests/source/test_properties.cpp b/tests/source/gl/test_properties.cpp similarity index 90% rename from tests/source/test_properties.cpp rename to tests/source/gl/test_properties.cpp index 14a9b12e..e611bb44 100644 --- a/tests/source/test_properties.cpp +++ b/tests/source/gl/test_properties.cpp @@ -1,4 +1,4 @@ -#include "functional.hpp" +#include "testing/gl/functional.hpp" #include @@ -13,10 +13,10 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_properties"); struct test_name_property { - using sut_type = lib_t::name_property; + using sut_type = gl::types::name_property; using value_type = typename sut_type::value_type; - static_assert(lib_tt::c_properties); + static_assert(gl::type_traits::c_properties); const value_type value = "element name"; sut_type sut{value}; @@ -64,10 +64,10 @@ TEST_CASE_FIXTURE( } struct test_dynamic_properties { - using sut_type = lib_t::dynamic_properties; + using sut_type = gl::types::dynamic_properties; using key_type = typename sut_type::key_type; - static_assert(lib_tt::c_properties); + static_assert(gl::type_traits::c_properties); struct compound_value { int x; @@ -164,11 +164,11 @@ TEST_CASE_FIXTURE(test_dynamic_properties, "remove should properly erase the key } struct test_binary_color { - using sut_type = lib_t::binary_color; + using sut_type = gl::types::binary_color; using color = sut_type::value; static constexpr color out_of_bounds_color = - static_cast(lib::util::to_underlying(color::unset) + 1); + static_cast(gl::util::to_underlying(color::unset) + 1); }; TEST_CASE_FIXTURE(test_binary_color, "should be unset by default") { @@ -180,7 +180,7 @@ TEST_CASE_FIXTURE(test_binary_color, "out of bounds values should be restricted sut_type sut{out_of_bounds_color}; REQUIRE_EQ(sut, color::unset); - CHECK_EQ(sut.to_underlying(), lib::util::to_underlying(color::unset)); + CHECK_EQ(sut.to_underlying(), gl::util::to_underlying(color::unset)); } TEST_CASE_FIXTURE( @@ -212,8 +212,8 @@ TEST_CASE_FIXTURE( } // assertions for not tested property types -static_assert(lib_tt::c_properties); -static_assert(lib_tt::c_properties>); +static_assert(gl::type_traits::c_properties); +static_assert(gl::type_traits::c_properties>); TEST_SUITE_END(); // test_properties diff --git a/tests/source/gl/test_stream_options_manipulator.cpp b/tests/source/gl/test_stream_options_manipulator.cpp new file mode 100644 index 00000000..ce1fc6cd --- /dev/null +++ b/tests/source/gl/test_stream_options_manipulator.cpp @@ -0,0 +1,167 @@ +#include "testing/gl/constants.hpp" + +#include + +#include + +#include + +namespace gl_testing { + +TEST_SUITE_BEGIN("test_stream_options_manipulator"); + +struct test_stream_options_manipulator { + test_stream_options_manipulator() { + REQUIRE_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + REQUIRE_FALSE(gl::io::is_option_set(ss2, bit_position_1)); + } + + std::stringstream ss1; + std::stringstream ss2; + + static constexpr gl::io::bit_position_type bit_position_1 = constants::first_element_idx; + static constexpr gl::io::bit_position_type bit_position_2 = bit_position_1 + constants::one; + static constexpr gl::io::iword_type options_bitmask = + (gl::io::iword_bit << bit_position_1) | (gl::io::iword_bit << bit_position_2); +}; + +TEST_CASE_FIXTURE( + test_stream_options_manipulator, "should properly handle istream option operations per stream" +) { + ss1 >> gl::io::set_option(bit_position_1); + CHECK(gl::io::is_option_set(ss1, bit_position_1)); + CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); + + ss2 >> gl::io::set_option(bit_position_1); + CHECK(gl::io::is_option_set(ss1, bit_position_1)); + CHECK(gl::io::is_option_set(ss2, bit_position_1)); + + ss1 >> gl::io::unset_option(bit_position_1); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + CHECK(gl::io::is_option_set(ss2, bit_position_1)); + + ss2 >> gl::io::unset_option(bit_position_1); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); +} + +TEST_CASE_FIXTURE( + test_stream_options_manipulator, "should properly handle ostream option operations per stream" +) { + ss1 << gl::io::set_option(bit_position_1); + CHECK(gl::io::is_option_set(ss1, bit_position_1)); + CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); + + ss2 << gl::io::set_option(bit_position_1); + CHECK(gl::io::is_option_set(ss1, bit_position_1)); + CHECK(gl::io::is_option_set(ss2, bit_position_1)); + + ss1 << gl::io::unset_option(bit_position_1); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + CHECK(gl::io::is_option_set(ss2, bit_position_1)); + + ss2 << gl::io::unset_option(bit_position_1); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); +} + +enum class BitPositionEnum : gl::io::bit_position_type { + bit_position_1 = test_stream_options_manipulator::bit_position_1, + bit_position_2 = test_stream_options_manipulator::bit_position_2 +}; + +TEST_CASE_FIXTURE( + test_stream_options_manipulator, + "should properly handle istream option operations for bit position lists" +) { + const auto bit_positions = {bit_position_1, bit_position_2}; + + ss1 >> gl::io::set_options(bit_positions); + CHECK(gl::io::are_options_set(ss1, bit_positions)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 >> gl::io::unset_options(bit_positions); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 >> gl::io::set_options({bit_position_1}); + CHECK(gl::io::is_option_set(ss1, bit_position_1)); + CHECK(gl::io::are_options_set(ss1, {bit_position_1})); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_2)); + CHECK_FALSE(gl::io::are_options_set(ss1, {bit_position_2})); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); +} + +TEST_CASE_FIXTURE( + test_stream_options_manipulator, + "should properly handle osream option operations for bit position lists" +) { + const auto bit_positions = {bit_position_1, bit_position_2}; + + ss1 << gl::io::set_options(bit_positions); + CHECK(gl::io::are_options_set(ss1, bit_positions)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 << gl::io::unset_options(bit_positions); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 << gl::io::set_options({bit_position_1}); + CHECK(gl::io::is_option_set(ss1, bit_position_1)); + CHECK(gl::io::are_options_set(ss1, {bit_position_1})); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_2)); + CHECK_FALSE(gl::io::are_options_set(ss1, {bit_position_2})); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); +} + +TEST_CASE_FIXTURE( + test_stream_options_manipulator, + "should properly handle istream option operations for enum bit position lists" +) { + const auto bit_positions = {BitPositionEnum::bit_position_1, BitPositionEnum::bit_position_2}; + + ss1 >> gl::io::set_options(bit_positions); + CHECK(gl::io::are_options_set(ss1, bit_positions)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 >> gl::io::unset_options(bit_positions); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 >> gl::io::set_options({BitPositionEnum::bit_position_1}); + CHECK(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_1)); + CHECK(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_1})); + CHECK_FALSE(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_2)); + CHECK_FALSE(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_2})); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); +} + +TEST_CASE_FIXTURE( + test_stream_options_manipulator, + "should properly handle ostream option operations for enum bit position lists" +) { + const auto bit_positions = {BitPositionEnum::bit_position_1, BitPositionEnum::bit_position_2}; + + ss1 << gl::io::set_options(bit_positions); + CHECK(gl::io::are_options_set(ss1, bit_positions)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 << gl::io::unset_options(bit_positions); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 << gl::io::set_options({BitPositionEnum::bit_position_1}); + CHECK(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_1)); + CHECK(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_1})); + CHECK_FALSE(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_2)); + CHECK_FALSE(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_2})); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); +} + +TEST_SUITE_END(); // test_stream_options_manipulator + +} // namespace gl_testing diff --git a/tests/source/test_util.cpp b/tests/source/gl/test_util.cpp similarity index 81% rename from tests/source/test_util.cpp rename to tests/source/gl/test_util.cpp index db968993..30f058b6 100644 --- a/tests/source/test_util.cpp +++ b/tests/source/gl/test_util.cpp @@ -1,5 +1,3 @@ -#include "namespaces.hpp" - #include #include @@ -10,7 +8,7 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_util"); TEST_CASE("upow should return the `base ^ exponent` operation result") { - lib_t::size_type base, exponent, expected_result; + gl::types::size_type base, exponent, expected_result; // clang-format off @@ -27,11 +25,11 @@ TEST_CASE("upow should return the `base ^ exponent` operation result") { CAPTURE(exponent); CAPTURE(expected_result); - CHECK_EQ(lib::util::upow(base, exponent), expected_result); + CHECK_EQ(gl::util::upow(base, exponent), expected_result); } TEST_CASE("upow_sum function test") { - lib_t::size_type base, i_begin, i_end, expected_result; + gl::types::size_type base, i_begin, i_end, expected_result; // clang-format off @@ -51,7 +49,7 @@ TEST_CASE("upow_sum function test") { CAPTURE(i_end); CAPTURE(expected_result); - CHECK_EQ(lib::util::upow_sum(base, i_begin, i_end), expected_result); + CHECK_EQ(gl::util::upow_sum(base, i_begin, i_end), expected_result); } TEST_CASE("to_underlying should return the underlying value of an enum class") { @@ -62,9 +60,9 @@ TEST_CASE("to_underlying should return the underlying value of an enum class") { enum class Enum : underlying_type { value_1 = value_1, value_2 = value_2 }; - CHECK_EQ(lib::util::to_underlying(Enum::value_1), value_1); - CHECK_EQ(lib::util::to_underlying(Enum::value_2), value_2); - CHECK_EQ(lib::util::to_underlying(static_cast(out_of_bounds_value)), out_of_bounds_value); + CHECK_EQ(gl::util::to_underlying(Enum::value_1), value_1); + CHECK_EQ(gl::util::to_underlying(Enum::value_2), value_2); + CHECK_EQ(gl::util::to_underlying(static_cast(out_of_bounds_value)), out_of_bounds_value); } TEST_SUITE_END(); // test_util diff --git a/tests/source/test_vertex_degree_getters.cpp b/tests/source/gl/test_vertex_degree_getters.cpp similarity index 64% rename from tests/source/test_vertex_degree_getters.cpp rename to tests/source/gl/test_vertex_degree_getters.cpp index 74e849ac..079d4982 100644 --- a/tests/source/test_vertex_degree_getters.cpp +++ b/tests/source/gl/test_vertex_degree_getters.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "transforms.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/transforms.hpp" #include #include @@ -15,47 +15,47 @@ TEST_SUITE_BEGIN("test_vertex_degree_getters"); TEST_CASE_TEMPLATE_DEFINE( "vertex degree getter tests for directed graphs", TraitsType, directed_graph_traits_template ) { - using sut_type = lib::graph; + using sut_type = gl::graph; using vertex_type = typename sut_type::vertex_type; const auto n_vertices = constants::n_elements_top; sut_type sut; - std::deque expected_in_deg_list, expected_out_deg_list; + std::deque expected_in_deg_list, expected_out_deg_list; SUBCASE("clique") { - sut = lib::topology::clique(n_vertices); + sut = gl::topology::clique(n_vertices); expected_in_deg_list = - std::deque(n_vertices, n_vertices - constants::one); + std::deque(n_vertices, n_vertices - constants::one); expected_out_deg_list = expected_in_deg_list; } SUBCASE("clique with an additional loop") { - sut = lib::topology::clique(n_vertices); + sut = gl::topology::clique(n_vertices); sut.add_edge(constants::first_element_idx, constants::first_element_idx); expected_in_deg_list = - std::deque(n_vertices, n_vertices - constants::one); + std::deque(n_vertices, n_vertices - constants::one); expected_in_deg_list.front()++; expected_out_deg_list = expected_in_deg_list; } SUBCASE("cycle") { - sut = lib::topology::cycle(n_vertices); - expected_in_deg_list = std::deque(n_vertices, constants::one); + sut = gl::topology::cycle(n_vertices); + expected_in_deg_list = std::deque(n_vertices, constants::one); expected_out_deg_list = expected_in_deg_list; } SUBCASE("path") { - sut = lib::topology::path(n_vertices); + sut = gl::topology::path(n_vertices); expected_in_deg_list = - std::deque(n_vertices - constants::one, constants::one); + std::deque(n_vertices - constants::one, constants::one); expected_in_deg_list.push_front(constants::zero); expected_out_deg_list = - std::deque(n_vertices - constants::one, constants::one); + std::deque(n_vertices - constants::one, constants::one); expected_out_deg_list.push_back(constants::zero); } @@ -63,15 +63,15 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(expected_in_deg_list); CAPTURE(expected_out_deg_list); - std::deque expected_deg_list(n_vertices); + std::deque expected_deg_list(n_vertices); std::ranges::transform( expected_in_deg_list, expected_out_deg_list, expected_deg_list.begin(), - std::plus{} + std::plus{} ); - lib_t::size_type i = constants::zero; + gl::types::size_type i = constants::zero; CHECK(std::ranges::all_of(sut.vertices(), [&](const auto& vertex) { const bool result = sut.in_degree(vertex) == expected_in_deg_list[i] @@ -97,44 +97,46 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( directed_graph_traits_template, - lib::list_graph_traits, // directed adjacency list graph - lib::matrix_graph_traits // directed adjacency matrix graph + gl::list_graph_traits, // directed adjacency list graph + gl::matrix_graph_traits // directed adjacency matrix graph ); TEST_CASE_TEMPLATE_DEFINE( "vertex degree getter tests for undirected graphs", TraitsType, undirected_graph_traits_template ) { - using sut_type = lib::graph; + using sut_type = gl::graph; using vertex_type = typename sut_type::vertex_type; const auto n_vertices = constants::n_elements_top; sut_type sut; - std::deque expected_deg_list; + std::deque expected_deg_list; SUBCASE("clique") { - sut = lib::topology::clique(n_vertices); - expected_deg_list = std::deque(n_vertices, n_vertices - constants::one); + sut = gl::topology::clique(n_vertices); + expected_deg_list = + std::deque(n_vertices, n_vertices - constants::one); } SUBCASE("clique with an additional loop") { - sut = lib::topology::clique(n_vertices); + sut = gl::topology::clique(n_vertices); sut.add_edge(constants::first_element_idx, constants::first_element_idx); - expected_deg_list = std::deque(n_vertices, n_vertices - constants::one); + expected_deg_list = + std::deque(n_vertices, n_vertices - constants::one); expected_deg_list.front() += constants::two; // loops counted twice } SUBCASE("cycle") { - sut = lib::topology::cycle(n_vertices); - expected_deg_list = std::deque(n_vertices, constants::two); + sut = gl::topology::cycle(n_vertices); + expected_deg_list = std::deque(n_vertices, constants::two); } SUBCASE("path") { - sut = lib::topology::path(n_vertices); + sut = gl::topology::path(n_vertices); expected_deg_list = - std::deque(n_vertices - constants::two, constants::two); + std::deque(n_vertices - constants::two, constants::two); expected_deg_list.push_front(constants::one); expected_deg_list.push_back(constants::one); } @@ -142,7 +144,7 @@ TEST_CASE_TEMPLATE_DEFINE( CAPTURE(sut); CAPTURE(expected_deg_list); - lib_t::size_type i = constants::zero; + gl::types::size_type i = constants::zero; CHECK(std::ranges::all_of(sut.vertices(), [&](const auto& vertex) { const auto expected_deg = expected_deg_list[i]; const bool result = @@ -169,8 +171,8 @@ TEST_CASE_TEMPLATE_DEFINE( TEST_CASE_TEMPLATE_INSTANTIATE( undirected_graph_traits_template, - lib::list_graph_traits, // undirected adjacency list graph - lib::matrix_graph_traits // undirected adjacency matrix graph + gl::list_graph_traits, // undirected adjacency list graph + gl::matrix_graph_traits // undirected adjacency matrix graph ); TEST_SUITE_END(); // test_vertex_degree_getters diff --git a/tests/source/test_vertex_descriptor.cpp b/tests/source/gl/test_vertex_descriptor.cpp similarity index 71% rename from tests/source/test_vertex_descriptor.cpp rename to tests/source/gl/test_vertex_descriptor.cpp index e2125f90..bcdb283c 100644 --- a/tests/source/test_vertex_descriptor.cpp +++ b/tests/source/gl/test_vertex_descriptor.cpp @@ -1,5 +1,5 @@ -#include "constants.hpp" -#include "types.hpp" +#include "testing/gl/constants.hpp" +#include "testing/gl/types.hpp" #include @@ -10,20 +10,20 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_vertex_descriptor"); TEST_CASE("id() should return the correct vertex id") { - const lib::vertex_descriptor sut{constants::vertex_id_1}; + const gl::vertex_descriptor sut{constants::vertex_id_1}; CHECK_EQ(sut.id(), constants::vertex_id_1); } TEST_CASE("properties should be properly initialized") { - const lib::vertex_descriptor sut{ + const gl::vertex_descriptor sut{ constants::vertex_id_1, constants::visited }; CHECK_EQ(sut.properties, constants::visited); } TEST_CASE("vertex_descriptor objects should be compared by id") { - const lib::vertex_descriptor vd_1{constants::vertex_id_1}; - const lib::vertex_descriptor vd_2{constants::vertex_id_2}; + const gl::vertex_descriptor vd_1{constants::vertex_id_1}; + const gl::vertex_descriptor vd_2{constants::vertex_id_2}; REQUIRE_NE(vd_1, vd_2); CHECK_EQ(vd_1, vd_1); diff --git a/tests/source/hgl/test_hgl_init.cpp b/tests/source/hgl/test_hgl_init.cpp new file mode 100644 index 00000000..6faa9680 --- /dev/null +++ b/tests/source/hgl/test_hgl_init.cpp @@ -0,0 +1,13 @@ +#include + +namespace hgl_testing { + +TEST_SUITE_BEGIN("test_hgl_init"); + +TEST_CASE("dummy") { + CHECK(true); +} + +TEST_SUITE_END(); // test_hgl_init + +} // namespace hgl_testing diff --git a/tests/source/test_extarnal_libs_config.cpp b/tests/source/test_extarnal_libs_config.cpp deleted file mode 100644 index 4b824088..00000000 --- a/tests/source/test_extarnal_libs_config.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include - -/* -This test file was created to determine whether the doctest and fakeit libraries -and testing CMakeLists.txt file have been set up correctly :) -*/ - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_doctest_config"); - -namespace { - -int add(int a, int b) { - return a + b; -} - -float multiply(float a, float b) { - return a * b; -} - -int divide(const int numerator, const int denominator) { - if (not denominator) - throw std::logic_error("Division by zero"); - return numerator / denominator; -} - -} // namespace - -TEST_CASE("addition of integers") { - int a = 5; - int b = 10; - CHECK(add(a, b) == 15); -} - -TEST_CASE("multiplication of floats") { - constexpr float x = 2.5f; - constexpr float y = 3.0f; - CHECK(multiply(x, y) == doctest::Approx(7.5).epsilon(0.01)); -} - -TEST_CASE("division by zero") { - int numerator = 10; - int denominator = 0; - CHECK_THROWS_AS(divide(numerator, denominator), std::logic_error); -} - -TEST_SUITE_END(); // test_doctest_config - -} // namespace gl_testing diff --git a/tests/source/test_stream_options_manipulator.cpp b/tests/source/test_stream_options_manipulator.cpp deleted file mode 100644 index acaca580..00000000 --- a/tests/source/test_stream_options_manipulator.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "constants.hpp" - -#include - -#include - -#include - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_stream_options_manipulator"); - -struct test_stream_options_manipulator { - test_stream_options_manipulator() { - REQUIRE_FALSE(lib::io::is_option_set(ss1, bit_position_1)); - REQUIRE_FALSE(lib::io::is_option_set(ss2, bit_position_1)); - } - - std::stringstream ss1; - std::stringstream ss2; - - static constexpr lib::io::bit_position_type bit_position_1 = constants::first_element_idx; - static constexpr lib::io::bit_position_type bit_position_2 = bit_position_1 + constants::one; - static constexpr lib::io::iword_type options_bitmask = - (lib::io::iword_bit << bit_position_1) | (lib::io::iword_bit << bit_position_2); -}; - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, "should properly handle istream option operations per stream" -) { - ss1 >> lib::io::set_option(bit_position_1); - CHECK(lib::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(lib::io::is_option_set(ss2, bit_position_1)); - - ss2 >> lib::io::set_option(bit_position_1); - CHECK(lib::io::is_option_set(ss1, bit_position_1)); - CHECK(lib::io::is_option_set(ss2, bit_position_1)); - - ss1 >> lib::io::unset_option(bit_position_1); - CHECK_FALSE(lib::io::is_option_set(ss1, bit_position_1)); - CHECK(lib::io::is_option_set(ss2, bit_position_1)); - - ss2 >> lib::io::unset_option(bit_position_1); - CHECK_FALSE(lib::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(lib::io::is_option_set(ss2, bit_position_1)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, "should properly handle ostream option operations per stream" -) { - ss1 << lib::io::set_option(bit_position_1); - CHECK(lib::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(lib::io::is_option_set(ss2, bit_position_1)); - - ss2 << lib::io::set_option(bit_position_1); - CHECK(lib::io::is_option_set(ss1, bit_position_1)); - CHECK(lib::io::is_option_set(ss2, bit_position_1)); - - ss1 << lib::io::unset_option(bit_position_1); - CHECK_FALSE(lib::io::is_option_set(ss1, bit_position_1)); - CHECK(lib::io::is_option_set(ss2, bit_position_1)); - - ss2 << lib::io::unset_option(bit_position_1); - CHECK_FALSE(lib::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(lib::io::is_option_set(ss2, bit_position_1)); -} - -enum class BitPositionEnum : lib::io::bit_position_type { - bit_position_1 = test_stream_options_manipulator::bit_position_1, - bit_position_2 = test_stream_options_manipulator::bit_position_2 -}; - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle istream option operations for bit position lists" -) { - const auto bit_positions = {bit_position_1, bit_position_2}; - - ss1 >> lib::io::set_options(bit_positions); - CHECK(lib::io::are_options_set(ss1, bit_positions)); - CHECK(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 >> lib::io::unset_options(bit_positions); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 >> lib::io::set_options({bit_position_1}); - CHECK(lib::io::is_option_set(ss1, bit_position_1)); - CHECK(lib::io::are_options_set(ss1, {bit_position_1})); - CHECK_FALSE(lib::io::is_option_set(ss1, bit_position_2)); - CHECK_FALSE(lib::io::are_options_set(ss1, {bit_position_2})); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle osream option operations for bit position lists" -) { - const auto bit_positions = {bit_position_1, bit_position_2}; - - ss1 << lib::io::set_options(bit_positions); - CHECK(lib::io::are_options_set(ss1, bit_positions)); - CHECK(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 << lib::io::unset_options(bit_positions); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 << lib::io::set_options({bit_position_1}); - CHECK(lib::io::is_option_set(ss1, bit_position_1)); - CHECK(lib::io::are_options_set(ss1, {bit_position_1})); - CHECK_FALSE(lib::io::is_option_set(ss1, bit_position_2)); - CHECK_FALSE(lib::io::are_options_set(ss1, {bit_position_2})); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle istream option operations for enum bit position lists" -) { - const auto bit_positions = {BitPositionEnum::bit_position_1, BitPositionEnum::bit_position_2}; - - ss1 >> lib::io::set_options(bit_positions); - CHECK(lib::io::are_options_set(ss1, bit_positions)); - CHECK(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 >> lib::io::unset_options(bit_positions); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 >> lib::io::set_options({BitPositionEnum::bit_position_1}); - CHECK(lib::io::is_option_set(ss1, BitPositionEnum::bit_position_1)); - CHECK(lib::io::are_options_set(ss1, {BitPositionEnum::bit_position_1})); - CHECK_FALSE(lib::io::is_option_set(ss1, BitPositionEnum::bit_position_2)); - CHECK_FALSE(lib::io::are_options_set(ss1, {BitPositionEnum::bit_position_2})); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle ostream option operations for enum bit position lists" -) { - const auto bit_positions = {BitPositionEnum::bit_position_1, BitPositionEnum::bit_position_2}; - - ss1 << lib::io::set_options(bit_positions); - CHECK(lib::io::are_options_set(ss1, bit_positions)); - CHECK(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 << lib::io::unset_options(bit_positions); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); - - ss1 << lib::io::set_options({BitPositionEnum::bit_position_1}); - CHECK(lib::io::is_option_set(ss1, BitPositionEnum::bit_position_1)); - CHECK(lib::io::are_options_set(ss1, {BitPositionEnum::bit_position_1})); - CHECK_FALSE(lib::io::is_option_set(ss1, BitPositionEnum::bit_position_2)); - CHECK_FALSE(lib::io::are_options_set(ss1, {BitPositionEnum::bit_position_2})); - CHECK_FALSE(lib::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(lib::io::are_options_set(ss1, options_bitmask)); -} - -TEST_SUITE_END(); // test_stream_options_manipulator - -} // namespace gl_testing From e220490c769d669e39fae6a4b1b8d9aed482119f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:09:49 +0100 Subject: [PATCH 02/54] YT-CPPHGL-2: Cleanup of the CPP-GL module implementation and documentation A small cleanup required for the development of HGL - Changed the minimum required C++ standard to C++23 - Changed the minimum required GCC version to 14 and CLang version to 18 - Replaced the usage of `std::optional` as algorithm predicate result type with a new ternary `predicate_result` type - Removed the `no_return` and `default_return` algorithm-specific types and related concepts; Introduced a `result_discriminator` type in their place - Aligned the documentation --- .github/workflows/clang.yaml | 12 ++-- .github/workflows/gpp.yaml | 12 ++-- CMakeLists.txt | 9 +-- LICENSE | 2 +- README.md | 11 ++-- docs/algoithms.md | 60 +++++++++++-------- docs/graph_elements.md | 10 +++- include/gl/{algorithms.hpp => algorithm.hpp} | 2 +- include/gl/algorithm/breadth_first_search.hpp | 12 ++-- include/gl/algorithm/coloring.hpp | 4 +- ...irst_search.hpp => depth_first_search.hpp} | 24 ++++---- include/gl/algorithm/dijkstra.hpp | 4 +- include/gl/algorithm/impl/bfs.hpp | 10 ++-- include/gl/algorithm/impl/common.hpp | 22 +++---- include/gl/algorithm/impl/dfs.hpp | 10 ++-- include/gl/algorithm/impl/pfs.hpp | 10 ++-- include/gl/algorithm/topological_sort.hpp | 2 +- include/gl/algorithm/types.hpp | 49 +++++++++------ include/gl/impl/adjacency_list.hpp | 6 +- include/gl/impl/adjacency_matrix.hpp | 2 +- .../gl/impl/specialized/adjacency_list.hpp | 26 ++++---- .../gl/impl/specialized/adjacency_matrix.hpp | 21 ++++--- include/gl/io/stream_options_manipulator.hpp | 9 ++- include/gl/types/iterator_range.hpp | 2 - include/gl/types/properties.hpp | 3 +- include/gl/util/enum.hpp | 20 ------- tests/include/testing/gl/alg_common.hpp | 2 +- tests/source/gl/test_alg_bfs.cpp | 9 ++- tests/source/gl/test_alg_coloring.cpp | 2 +- tests/source/gl/test_alg_dfs.cpp | 16 +++-- tests/source/gl/test_alg_dijkstra.cpp | 2 +- tests/source/gl/test_alg_mst.cpp | 2 +- tests/source/gl/test_alg_topological_sort.cpp | 2 +- tests/source/gl/test_alg_types.cpp | 31 ++++++++++ tests/source/gl/test_properties.cpp | 10 ++-- tests/source/gl/test_util.cpp | 14 ----- 36 files changed, 230 insertions(+), 214 deletions(-) rename include/gl/{algorithms.hpp => algorithm.hpp} (90%) rename include/gl/algorithm/{deapth_first_search.hpp => depth_first_search.hpp} (77%) delete mode 100644 include/gl/util/enum.hpp create mode 100644 tests/source/gl/test_alg_types.cpp diff --git a/.github/workflows/clang.yaml b/.github/workflows/clang.yaml index 712009ed..237d1ec3 100644 --- a/.github/workflows/clang.yaml +++ b/.github/workflows/clang.yaml @@ -19,18 +19,18 @@ jobs: - name: Prepare env: - CC: clang-17 - CXX: clang++-17 + CC: clang-18 + CXX: clang++-18 run: | - cmake -B build -DBUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC + cmake -B build_clang -DBUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC continue-on-error: false - name: Build test executable run: | - cd build && make -j 4 + cmake --build build_clang/ -j5 continue-on-error: false - name: Run tests run: | - ./build/tests/gl - ./build/tests/hgl + ./build_clang/tests/gl + ./build_clang/tests/hgl diff --git a/.github/workflows/gpp.yaml b/.github/workflows/gpp.yaml index 9801959d..5e7279c2 100644 --- a/.github/workflows/gpp.yaml +++ b/.github/workflows/gpp.yaml @@ -19,18 +19,18 @@ jobs: - name: Prepare env: - CC: gcc-13 - CXX: g++-13 + CC: gcc-14 + CXX: g++-14 run: | - cmake -B build -DBUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC + cmake -B build_gcc -DBUILD_TESTS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC continue-on-error: false - name: Build test executable run: | - cd build && make -j 4 + cmake --build build_gcc/ -j5 continue-on-error: false - name: Run tests run: | - ./build/tests/gl - ./build/tests/hgl + ./build_gcc/tests/gl + ./build_gcc/tests/hgl diff --git a/CMakeLists.txt b/CMakeLists.txt index d978900a..0964138c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,20 +9,21 @@ endif() project(cpp-gl VERSION 2.0.0 - DESCRIPTION "General purpose header-only template graph library for C++20" + DESCRIPTION "General purpose header-only template graph library for C++23" HOMEPAGE_URL "https://github.com/SpectraL519/cpp-gl" LANGUAGES CXX ) +set(MIN_CXX_STANDARD 23) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(BUILD_TESTS "Build project tests" OFF) # Set the proper cxx standard if(NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) -elseif(CMAKE_CXX_STANDARD LESS 20) + set(CMAKE_CXX_STANDARD ${MIN_CXX_STANDARD}) +elseif(CMAKE_CXX_STANDARD LESS ${MIN_CXX_STANDARD}) message(WARNING "The defined C++ standard (${CMAKE_CXX_STANDARD}) is too low - overriding!") - set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD ${MIN_CXX_STANDARD}) endif() message(STATUS "The C++ standard is set to ${CMAKE_CXX_STANDARD}") diff --git a/LICENSE b/LICENSE index 3ed41280..f44ee2a6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ MIT License Copyright (c) 2024 Jakub Musiał -Project: "CPP-GL: General purpose header-only template graph library for C++20 and newer standards." +Project: "CPP-GL: General purpose header-only template graph library for C++23 and newer standards." https://github.com/SpectraL519/cpp-gl Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index bf2886b9..b115a5b2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CPP-GL -General purpose header-only template graph library for C++20 and newer standards. +General purpose header-only template graph library for C++23 and newer standards. [![g++](https://github.com/SpectraL519/cpp-gl/actions/workflows/gpp.yaml/badge.svg)](https://github.com/SpectraL519/cpp-gl/actions/workflows/g++) [![clang++](https://github.com/SpectraL519/cpp-gl/actions/workflows/clang.yaml/badge.svg)](https://github.com/SpectraL519/cpp-gl/actions/workflows/clang++) @@ -65,7 +65,7 @@ FetchContent_MakeAvailable(cpp-gl) add_executable(my_project main.cpp) set_target_properties(my_project PROPERTIES - CXX_STANDARD 20 # or newer + CXX_STANDARD 23 # or newer CXX_STANDARD_REQUIRED YES ) @@ -128,13 +128,14 @@ The instructions and requirements of working on the `CPP-GL` project can be foun ## Compiler support + | Compiler | Min version | | :-: | :-: | -| GNU G++ | 13 | -| Clang | 17 | +| GNU G++ | 14 | +| Clang | 18 | > [!NOTE] -> Although currently the project has been properly verified using only the G++ and Clang compilers it should work fine with other compilers with C++20 support like MSVC. +> Although currently the project has been properly verified using only the G++ and Clang compilers it should work fine with other compilers with C++23 support like MSVC.
diff --git a/docs/algoithms.md b/docs/algoithms.md index 10d08989..e9c9899f 100644 --- a/docs/algoithms.md +++ b/docs/algoithms.md @@ -1,6 +1,6 @@ # Algorithms -The `CPP-GL` library provides a set of customizable graph algorithms, which are defined in the [gl/algorithms.hpp](/include/gl/algorithms.hpp) header file or in the specific header files in the [gl/algorithm/](/include/gl/algorithm/) directory. +The `CPP-GL` library provides a set of customizable graph algorithms, which are defined in the [gl/algorithm.hpp](/include/gl/algorithm.hpp) header file or in the specific header files in the [gl/algorithm/](/include/gl/algorithm/) directory.
@@ -31,7 +31,12 @@ This section covers the specific types and type traits used for the algorithm im > [!NOTE] > All types listed below are defined in the `gl::algorithm` namespace -- `no_return` - A placeholder type to represent algorithms that do not return a value. It is primarily used in type traits to conditionally handle return types. +- `result_discriminator` - An enumeration type used to discriminate whether the algorithm should return a result value or not. + - **Members:** + - `ret` - Indicates that the algorithm returns a value. + - `noret` - Indicates that the algorithm does not return a value. + - The `gl::algorithm` namespace [uses](https://en.cppreference.com/w/cpp/language/enum.html#using_enum_declaration) the `result_discriminator` enum, which allows for the usage of it's members directly from the `gl::algorithm` namespace, e.g. `gl::algorithm::noret`. + - `empty_callback` - Represents an empty callback, used as a default value where no callback functionality is needed. - `vertex_callback` @@ -68,6 +73,25 @@ This section covers the specific types and type traits used for the algorithm im - `edge: types::const_ref_wrap` - a constant reference wrapper for the edge. - `source_id: types::id_type` - the ID of the source vertex of the held edge. +- `predicate_result` + - *Description*: Represents the result of a predicate evaluation. + - *Type definitions*: + - `eval` - an enumeration type representing the result value. + - `ok` - equivalent to `true`. + - `not_ok` - equivalent to `false`. + - `unknown` - represents an unknown result. + - *Constructors*: + - `predicate_result(const eval value)` - initializes the object with the given evaluation result. + - `predicate_result(const bool value)` - initializes the object with the given boolean value. + - If the value is `true`, initializes the object with `eval::ok`. + - If the value is `false`, initializes the object with `eval::not_ok`. + - *Member variables*: + - `value: eval` - the evaluation result. + - *Operators*: + - `operator bool()` - returns `true` if the result is `eval::ok`, `false` otherwise. + - `operator==(const eval& value) const` - returns `true` if the result's value is equal to the given value. + - `operator=(const bool value)` - initializes the object with the given boolean value similarly to the boolean constructor. + - `predecessors_descriptor` - *Description*: A structure that holds a collection of predecessors for a set of vertices. - *Type definitions*: @@ -93,18 +117,6 @@ This section covers the specific types and type traits used for the algorithm im > [!NOTE] > All concepts listed below are defined in the `gl::type_traits` namespace -- `c_alg_no_return_type` - - *Description*: Checks if the type `T` is `algorithm::no_return`. Used to identify algorithms that do not return a value. - - *Template parameters*: - - `T` - the type to check. - - *Equivalent to*: `std::same_as` - -- `c_alg_return_graph_type` - - *Description*: Checks if the type `T` is a valid return type for graph algorithms, i.e., either a graph type or `no_return`. - - *Template parameters*: - - `T` - the type to check. - - *Equivalent to*: `c_graph or c_alg_no_return_type` - - `c_empty_callback` - *Description*: Checks if a callback type is `algorithm::empty_callback`. Used to determine when no callback is needed for an algorithm. - *Template parameters*: @@ -169,7 +181,7 @@ This section covers the specific types and type traits used for the algorithm im - *Description*: Performs an iterative depth-first search (DFS) on the specified graph and conditionally returns a `predecessors_descriptor` instance. - *Template parameters*: - - `AlgReturnType: type_traits::c_alg_return_type` (default = `algorithm::default_return`) - Specifies whether the algorrithm should return the predecessors descriptor or not (can be eigher `algorithm::default_return` or `algorithm::no_return`). + - `ResultDiscriminator: result_discriminator` (default = `algorithm::ret`) - Specifies whether the algorrithm should return the predecessors descriptor or not (can be eigher `algorithm::ret` or `algorithm::no_return`). - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. @@ -181,16 +193,16 @@ This section covers the specific types and type traits used for the algorithm im - `post_visit: const PostVisitCallback&` (default = `{}`) - The callback function to be called after visiting a vertex. - *Return type*: - - `impl::alg_return_type` - If `AlgReturnType` is `algorithm::no_return` - nothing will be returned (`void`). Otherwise the algorithm will return an instance of `predecessors_descriptor`. + - `impl::alg_return_type` - If `ResultDiscriminator` is `algorithm::noret` - nothing will be returned (`void`). Otherwise the algorithm will return an instance of `predecessors_descriptor`. - - *Defined in*: [gl/algorithm/depth_first_search.hpp](/include/gl/algorithm/deapth_first_search.hpp) + - *Defined in*: [gl/algorithm/depth_first_search.hpp](/include/gl/algorithm/depth_first_search.hpp) - `recursive_depth_first_search(graph, root_vertex_id_opt, pre_visit, post_visit)` - *Description*: Performs a recursive depth-first search (DFS) on the specified graph and conditionally returns a `predecessors_descriptor` instance. **NOTE:** This algoithm has the same template parameters, parameters and return type as the iterative version (`depth_first_search`) - - *Defined in*: [gl/algorithm/depth_first_search.hpp](/include/gl/algorithm/deapth_first_search.hpp) + - *Defined in*: [gl/algorithm/depth_first_search.hpp](/include/gl/algorithm/depth_first_search.hpp) ### Breadth-first search @@ -198,7 +210,7 @@ This section covers the specific types and type traits used for the algorithm im - *Description*: Performs an breadth-first search (BFS) on the specified graph and conditionally returns a `predecessors_descriptor` instance. - *Template parameters*: - - `AlgReturnType: type_traits::c_alg_return_type` (default = `algorithm::default_return`) - Specifies whether the algorrithm should return the predecessors descriptor or not (can be eigher `algorithm::default_return` or `algorithm::no_return`). + - `ResultDiscriminator: result_discriminator` (default = `algorithm::ret`) - Specifies whether the algorrithm should return the predecessors descriptor or not (can be eigher `algorithm::ret` or `algorithm::no_return`). - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. @@ -210,7 +222,7 @@ This section covers the specific types and type traits used for the algorithm im - `post_visit: const PostVisitCallback&` (default = `{}`) - The callback function to be called after visiting a vertex. - *Return type*: - - `impl::alg_return_type` - If `AlgReturnType` is `algorithm::no_return` - nothing will be returned (`void`). Otherwise the algorithm will return an instance of `predecessors_descriptor`. + - `impl::alg_return_type` - If `ResultDiscriminator` is `algorithm::noret` - nothing will be returned (`void`). Otherwise the algorithm will return an instance of `predecessors_descriptor`. - *Defined in*: [gl/algorithm/breadth_first_search.hpp](/include/gl/algorithm/breadth_first_search.hpp) @@ -401,7 +413,7 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback, const typename GraphType::edge_type&>` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) + - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. @@ -423,7 +435,7 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback, const typename GraphType::edge_type&>` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) + - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. @@ -452,7 +464,7 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - `InitQueueRangeType: type_traits::c_sized_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback, const typename GraphType::edge_type&>` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) + - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. @@ -483,7 +495,7 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - `InitQueueRangeType: type_traits::c_sized_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback, const typename GraphType::edge_type&>` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) + - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. diff --git a/docs/graph_elements.md b/docs/graph_elements.md index 22ed2792..a145f086 100644 --- a/docs/graph_elements.md +++ b/docs/graph_elements.md @@ -164,14 +164,20 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - **`incident_vertex(const vertex_type& vertex) const`**: - *Description*: Returns the vertex on the other end of the edge relative to the provided vertex. Throws an error if the provided vertex is not incident with the edge. - - *Returned value*: $\begin{cases} v & \text{: vertex} = u \\ u & \text{: vertex} = v \\ \text{error} & \text{: otherwise} \end{cases}$ + - *Returned value*: + - $v$ if $\text{vertex} = u$ + - $u$ if $\text{vertex} = v$ + - error otherwise - *Parameters*: - `vertex: const vertex_type&` – the vertex for which the opposite vertex is requested. - *Return type*: `const vertex_type&` - **`incident_vertex_id(const types::id_type vertex_id) const`**: - *Description*: Returns the ID of the vertex on the other end of the edge relative to the provided vertex ID. Throws an error if the provided vertex ID is invalid. - - *Returned value*: $\begin{cases} v_{id} & \text{: vertex-id} = u_{id} \\ u_{id} & \text{: vertex-id} = v_{id} \\ \text{error} & \text{: otherwise} \end{cases}$ + - *Returned value*: + - $v_{id}$ if $\text{vertex-id} = u_{id}$ + - $u_{id}$ if $\text{vertex-id} = v_{id}$ + - error otherwise - *Parameters*: - `vertex_id: const types::id_type` – the vertex ID for which the opposite vertex ID is requested. - *Return type*: `types::id_type` diff --git a/include/gl/algorithms.hpp b/include/gl/algorithm.hpp similarity index 90% rename from include/gl/algorithms.hpp rename to include/gl/algorithm.hpp index 31686d41..cc741c91 100644 --- a/include/gl/algorithms.hpp +++ b/include/gl/algorithm.hpp @@ -6,7 +6,7 @@ #include "algorithm/breadth_first_search.hpp" #include "algorithm/coloring.hpp" -#include "algorithm/deapth_first_search.hpp" +#include "algorithm/depth_first_search.hpp" #include "algorithm/dijkstra.hpp" #include "algorithm/mst.hpp" #include "algorithm/topological_sort.hpp" diff --git a/include/gl/algorithm/breadth_first_search.hpp b/include/gl/algorithm/breadth_first_search.hpp index edff05dc..2be03ded 100644 --- a/include/gl/algorithm/breadth_first_search.hpp +++ b/include/gl/algorithm/breadth_first_search.hpp @@ -11,13 +11,13 @@ namespace gl::algorithm { template < - type_traits::c_alg_return_type AlgReturnType = algorithm::default_return, + result_discriminator ResultDiscriminator = algorithm::ret, type_traits::c_graph GraphType, type_traits::c_optional_vertex_callback PreVisitCallback = algorithm::empty_callback, type_traits::c_optional_vertex_callback PostVisitCallback = algorithm::empty_callback> -impl::alg_return_type breadth_first_search( +impl::alg_return_type breadth_first_search( const GraphType& graph, const std::optional& root_vertex_id_opt = no_root_vertex, const PreVisitCallback& pre_visit = {}, @@ -29,14 +29,14 @@ impl::alg_return_type breadth_first_sear std::vector visited(graph.n_vertices(), false); std::vector sources(graph.n_vertices()); - auto pd = impl::init_return_value(graph); + auto pd = impl::init_return_value(graph); if (root_vertex_id_opt) { impl::bfs( graph, impl::init_range(root_vertex_id_opt.value()), impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit @@ -48,14 +48,14 @@ impl::alg_return_type breadth_first_sear graph, impl::init_range(root_id), impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit ); } - if constexpr (not type_traits::c_alg_no_return_type) + if constexpr (ResultDiscriminator == algorithm::ret) return pd; } diff --git a/include/gl/algorithm/coloring.hpp b/include/gl/algorithm/coloring.hpp index 43d5067d..d32387ec 100644 --- a/include/gl/algorithm/coloring.hpp +++ b/include/gl/algorithm/coloring.hpp @@ -42,7 +42,7 @@ template < algorithm::empty_callback{}, // visit predicate algorithm::empty_callback{}, // visit callback [&coloring](const vertex_type& vertex, const edge_type& in_edge) - -> std::optional { // enqueue predicate + -> predicate_result { // enqueue predicate if (in_edge.is_loop()) return false; @@ -50,7 +50,7 @@ template < const auto source_id = in_edge.incident_vertex(vertex).id(); if (coloring[vertex_id] == coloring[source_id]) - return std::nullopt; // graph is not bipartite + return predicate_result::unknown; // graph is not bipartite if (not coloring[vertex.id()].is_set()) { coloring[vertex.id()] = coloring[source_id].next(); diff --git a/include/gl/algorithm/deapth_first_search.hpp b/include/gl/algorithm/depth_first_search.hpp similarity index 77% rename from include/gl/algorithm/deapth_first_search.hpp rename to include/gl/algorithm/depth_first_search.hpp index 61dfc428..97c3f0eb 100644 --- a/include/gl/algorithm/deapth_first_search.hpp +++ b/include/gl/algorithm/depth_first_search.hpp @@ -10,13 +10,13 @@ namespace gl::algorithm { template < - type_traits::c_alg_return_type AlgReturnType = algorithm::default_return, + result_discriminator ResultDiscriminator = algorithm::ret, type_traits::c_graph GraphType, type_traits::c_optional_vertex_callback PreVisitCallback = algorithm::empty_callback, type_traits::c_optional_vertex_callback PostVisitCallback = algorithm::empty_callback> -impl::alg_return_type depth_first_search( +impl::alg_return_type depth_first_search( const GraphType& graph, const std::optional& root_vertex_id_opt = no_root_vertex, const PreVisitCallback& pre_visit = {}, @@ -28,14 +28,14 @@ impl::alg_return_type depth_first_search std::vector visited(graph.n_vertices(), false); std::vector sources(graph.n_vertices()); - auto pd = impl::init_return_value(graph); + auto pd = impl::init_return_value(graph); if (root_vertex_id_opt) { impl::dfs( graph, graph.get_vertex(root_vertex_id_opt.value()), impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit @@ -47,25 +47,25 @@ impl::alg_return_type depth_first_search graph, root_vertex, impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit ); } - if constexpr (not type_traits::c_alg_no_return_type) + if constexpr (ResultDiscriminator == algorithm::ret) return pd; } template < - type_traits::c_alg_return_type AlgReturnType = algorithm::default_return, + result_discriminator ResultDiscriminator = algorithm::ret, type_traits::c_graph GraphType, type_traits::c_optional_vertex_callback PreVisitCallback = algorithm::empty_callback, type_traits::c_optional_vertex_callback PostVisitCallback = algorithm::empty_callback> -impl::alg_return_type recursive_depth_first_search( +impl::alg_return_type recursive_depth_first_search( const GraphType& graph, const std::optional& root_vertex_id_opt = no_root_vertex, const PreVisitCallback& pre_visit = {}, @@ -77,7 +77,7 @@ impl::alg_return_type recursive_depth_fi std::vector visited(graph.n_vertices(), false); std::vector sources(graph.n_vertices()); - auto pd = impl::init_return_value(graph); + auto pd = impl::init_return_value(graph); if (root_vertex_id_opt) { const auto root_id = root_vertex_id_opt.value(); @@ -86,7 +86,7 @@ impl::alg_return_type recursive_depth_fi graph.get_vertex(root_id), root_id, impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit @@ -99,14 +99,14 @@ impl::alg_return_type recursive_depth_fi root_vertex, root_vertex.id(), impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit ); } - if constexpr (not type_traits::c_alg_no_return_type) + if constexpr (ResultDiscriminator == algorithm::ret) return pd; } diff --git a/include/gl/algorithm/dijkstra.hpp b/include/gl/algorithm/dijkstra.hpp index ccc90e95..0a870e8e 100644 --- a/include/gl/algorithm/dijkstra.hpp +++ b/include/gl/algorithm/dijkstra.hpp @@ -95,14 +95,14 @@ template < algorithm::empty_callback{}, // visit predicate algorithm::empty_callback{}, // visit callback [&paths, &negative_edge](const vertex_type& vertex, const edge_type& in_edge) - -> std::optional { // enqueue predicate + -> predicate_result { // enqueue predicate const auto vertex_id = vertex.id(); const auto source_id = in_edge.incident_vertex(vertex).id(); const auto edge_weight = get_weight(in_edge); if (edge_weight < constants::zero) { negative_edge = std::cref(in_edge); - return std::nullopt; + return predicate_result::unknown; } const auto new_distance = paths.distances[source_id] + edge_weight; diff --git a/include/gl/algorithm/impl/bfs.hpp b/include/gl/algorithm/impl/bfs.hpp index 527daf02..abc8dd4f 100644 --- a/include/gl/algorithm/impl/bfs.hpp +++ b/include/gl/algorithm/impl/bfs.hpp @@ -16,9 +16,8 @@ template < std::vector, type_traits::c_optional_vertex_callback VisitVertexPredicate, type_traits::c_optional_vertex_callback VisitCallback, - type_traits:: - c_vertex_callback, const typename GraphType::edge_type&> - EnqueueVertexPred, + type_traits::c_vertex_callback + EnqueueVertexPred, type_traits::c_optional_vertex_callback PreVisitCallback = algorithm::empty_callback, type_traits::c_optional_vertex_callback PostVisitCallback = @@ -39,7 +38,6 @@ bool bfs( using vertex_queue_type = std::queue; vertex_queue_type vertex_queue; - // TODO [C++23]: replace with push_range for (const auto& vinfo : initial_queue_content) vertex_queue.push(vinfo); @@ -64,10 +62,10 @@ bool bfs( const auto& incident_vertex = edge.incident_vertex(vertex); const auto enqueue = enqueue_vertex_pred(incident_vertex, edge); - if (not enqueue.has_value()) + if (enqueue == predicate_result::unknown) return false; - if (enqueue.value()) + if (enqueue) vertex_queue.emplace(incident_vertex.id(), vinfo.id); } diff --git a/include/gl/algorithm/impl/common.hpp b/include/gl/algorithm/impl/common.hpp index b6bc4c70..e688ad7f 100644 --- a/include/gl/algorithm/impl/common.hpp +++ b/include/gl/algorithm/impl/common.hpp @@ -11,15 +11,16 @@ namespace gl::algorithm::impl { // --- common functions --- template < - type_traits::c_alg_return_type AlgReturnType, + result_discriminator ResultDiscriminator, typename ReturnType, type_traits::c_graph GraphType> -[[nodiscard]] gl_attr_force_inline alg_return_type_non_void +[[nodiscard]] gl_attr_force_inline alg_return_type_non_void init_return_value(const GraphType& graph) { - if constexpr (type_traits::c_alg_no_return_type) - return AlgReturnType{}; + using return_type = alg_return_type_non_void; + if constexpr (ResultDiscriminator == algorithm::ret) + return return_type(graph.n_vertices()); else - return ReturnType(graph.n_vertices()); + return return_type(); } template < @@ -36,23 +37,24 @@ template }; } -template +template [[nodiscard]] gl_attr_force_inline auto default_visit_callback( - std::vector& visited, alg_return_type_non_void& pd + std::vector& visited, + alg_return_type_non_void& pd ) { return [&](const typename GraphType::vertex_type& vertex, const types::id_type source_id) { const auto vertex_id = vertex.id(); visited[vertex_id] = true; - if constexpr (type_traits::c_alg_default_return_type) + if constexpr (ResultDiscriminator == algorithm::ret) pd[vertex_id].emplace(source_id); return true; }; } -template +template [[nodiscard]] gl_attr_force_inline auto default_enqueue_vertex_predicate(std::vector& visited ) { - using return_type = std::conditional_t, bool>; + using return_type = std::conditional_t; return [&](const typename GraphType::vertex_type& vertex, const typename GraphType::edge_type& in_edge) -> return_type { diff --git a/include/gl/algorithm/impl/dfs.hpp b/include/gl/algorithm/impl/dfs.hpp index 722f8f01..1e0c4fb4 100644 --- a/include/gl/algorithm/impl/dfs.hpp +++ b/include/gl/algorithm/impl/dfs.hpp @@ -14,9 +14,8 @@ template < type_traits::c_graph GraphType, type_traits::c_optional_vertex_callback VisitVertexPredicate, type_traits::c_vertex_callback VisitCallback, - type_traits:: - c_vertex_callback, const typename GraphType::edge_type&> - EnqueueVertexPred, + type_traits::c_vertex_callback + EnqueueVertexPred, type_traits::c_optional_vertex_callback PreVisitCallback = algorithm::empty_callback, type_traits::c_optional_vertex_callback PostVisitCallback = @@ -70,9 +69,8 @@ template < type_traits::c_graph GraphType, type_traits::c_vertex_callback VisitVertexPredicate, type_traits::c_vertex_callback VisitCallback, - type_traits:: - c_vertex_callback, const typename GraphType::edge_type&> - EnqueueVertexPred, + type_traits::c_vertex_callback + EnqueueVertexPred, type_traits::c_optional_vertex_callback PreVisitCallback = algorithm::empty_callback, type_traits::c_optional_vertex_callback PostVisitCallback = diff --git a/include/gl/algorithm/impl/pfs.hpp b/include/gl/algorithm/impl/pfs.hpp index fe40c88e..08036514 100644 --- a/include/gl/algorithm/impl/pfs.hpp +++ b/include/gl/algorithm/impl/pfs.hpp @@ -17,9 +17,8 @@ template < std::vector, type_traits::c_optional_vertex_callback VisitVertexPredicate, type_traits::c_optional_callback VisitCallback, - type_traits:: - c_vertex_callback, const typename GraphType::edge_type&> - EnqueueVertexPred, + type_traits::c_vertex_callback + EnqueueVertexPred, type_traits::c_optional_vertex_callback PreVisitCallback = algorithm::empty_callback, type_traits::c_optional_vertex_callback PostVisitCallback = @@ -42,7 +41,6 @@ bool pfs( std::priority_queue, PQCompare>; vertex_queue_type vertex_queue(pq_compare); - // TODO [C++23]: replace with push_range for (const auto& vinfo : initial_queue_content) vertex_queue.push(vinfo); @@ -67,10 +65,10 @@ bool pfs( const auto& incident_vertex = edge.incident_vertex(vertex); const auto enqueue = enqueue_vertex_pred(incident_vertex, edge); - if (not enqueue.has_value()) + if (enqueue == predicate_result::unknown) return false; - if (enqueue.value()) + if (enqueue) vertex_queue.emplace(incident_vertex.id(), vinfo.id); } if constexpr (not type_traits::c_empty_callback) diff --git a/include/gl/algorithm/topological_sort.hpp b/include/gl/algorithm/topological_sort.hpp index c31ac76e..ee4d20ac 100644 --- a/include/gl/algorithm/topological_sort.hpp +++ b/include/gl/algorithm/topological_sort.hpp @@ -50,7 +50,7 @@ template < return true; }, [&in_degree_map](const vertex_type& vertex, const edge_type& in_edge) - -> std::optional { // enqueue predicate + -> predicate_result { // enqueue predicate if (in_edge.is_loop()) return false; return --in_degree_map[vertex.id()] == constants::default_size; diff --git a/include/gl/algorithm/types.hpp b/include/gl/algorithm/types.hpp index 61095fde..79c823b7 100644 --- a/include/gl/algorithm/types.hpp +++ b/include/gl/algorithm/types.hpp @@ -12,9 +12,8 @@ namespace gl { namespace algorithm { -struct default_return {}; - -struct no_return {}; +enum class result_discriminator : bool { ret = true, noret = false }; +using enum result_discriminator; struct empty_callback {}; @@ -36,6 +35,30 @@ struct edge_info { types::id_type source_id; }; +struct predicate_result { + enum class eval : std::uint8_t { ok, not_ok, unknown }; + using enum eval; + + constexpr predicate_result(const eval value) : value(value) {} + + constexpr predicate_result(const bool value) : value(value ? eval::ok : eval::not_ok) {} + + constexpr predicate_result& operator=(const bool value) { + this->value = value ? eval::ok : eval::not_ok; + return *this; + } + + [[nodiscard]] constexpr operator bool() const { + return this->value == eval::ok; + } + + [[nodiscard]] constexpr bool operator==(const eval value) const { + return this->value == value; + } + + eval value; +}; + struct predecessors_descriptor { using predecessor_type = std::optional; @@ -72,15 +95,6 @@ struct predecessors_descriptor { namespace type_traits { -template -concept c_alg_default_return_type = std::same_as; - -template -concept c_alg_no_return_type = std::same_as; - -template -concept c_alg_return_type = c_alg_default_return_type or c_alg_no_return_type; - template concept c_empty_callback = std::same_as; @@ -107,13 +121,12 @@ concept c_optional_edge_callback = namespace algorithm::impl { -template -using alg_return_type = - std::conditional_t, void, DefaultReturnType>; +template +using alg_return_type = std::conditional_t; -template -using alg_return_type_non_void = std:: - conditional_t, no_return, DefaultReturnType>; +template +using alg_return_type_non_void = + std::conditional_t; } // namespace algorithm::impl diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index b4f74f28..3da4ad9d 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -54,13 +54,13 @@ class adjacency_list final { // --- vertex methods --- gl_attr_force_inline void add_vertex() { - this->_list.push_back(edge_list_type{}); + this->_list.emplace_back(edge_list_type{}); } inline void add_vertices(const types::size_type n) { this->_list.reserve(this->n_vertices() + n); for (types::size_type _ = constants::begin_idx; _ < n; ++_) - this->_list.push_back(edge_list_type{}); + this->_list.emplace_back(edge_list_type{}); } [[nodiscard]] gl_attr_force_inline types::size_type in_degree(const types::id_type vertex_id @@ -172,7 +172,7 @@ class adjacency_list final { for (const auto& edge : adjacent_edges) if (specialized_impl::is_edge_incident_to(edge, second_id, first_id)) - matching_edges.push_back(std::cref(*edge)); + matching_edges.emplace_back(*edge); matching_edges.shrink_to_fit(); return matching_edges; diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index a84a2643..caa9ec78 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -62,7 +62,7 @@ class adjacency_matrix final { void add_vertex() { for (auto& row : this->_matrix) - row.push_back(_make_null_edge()); + row.emplace_back(_make_null_edge()); auto& new_row = this->_matrix.emplace_back(); new_row.reserve(this->n_vertices()); std::generate_n(std::back_inserter(new_row), this->n_vertices(), _make_null_edge); diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index a953f173..454a53b9 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -67,15 +67,10 @@ struct directed_adjacency_list { const impl_type& self, const types::id_type vertex_id ) { types::size_type in_deg = constants::default_size; - for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { - const auto& adj_edges = self._list[id]; - if (adj_edges.empty()) - continue; - - in_deg += std::ranges::count(adj_edges, vertex_id, [](const auto& edge) { + for (const auto& adjacent_edges : self._list) + in_deg += std::ranges::count(adjacent_edges, vertex_id, [](const auto& edge) { return edge->second_id(); }); - } return in_deg; } @@ -107,10 +102,9 @@ struct directed_adjacency_list { [[nodiscard]] gl_attr_force_inline static std::vector out_degree_map( const impl_type& self ) { - const auto out_degree_view = - self._list - | std::views::transform([](const auto& adj_edges) { return adj_edges.size(); }); - return std::vector(out_degree_view.begin(), out_degree_view.end()); + return self._list + | std::views::transform([](const auto& adj_edges) { return adj_edges.size(); }) + | std::ranges::to>(); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { @@ -151,9 +145,9 @@ struct directed_adjacency_list { static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { auto& adjacent_edges_first = self._list[edge->first_id()]; - adjacent_edges_first.push_back(std::move(edge)); + auto& new_edge = adjacent_edges_first.emplace_back(std::move(edge)); ++self._n_unique_edges; - return *adjacent_edges_first.back(); + return *new_edge; } static void add_edges_from( @@ -163,7 +157,7 @@ struct directed_adjacency_list { adjacent_edges_source.reserve(adjacent_edges_source.size() + new_edges.size()); for (auto& edge : new_edges) - adjacent_edges_source.push_back(std::move(edge)); + adjacent_edges_source.emplace_back(std::move(edge)); self._n_unique_edges += new_edges.size(); } @@ -282,7 +276,7 @@ struct undirected_adjacency_list { if (not edge->is_loop()) self._list[edge->second_id()].push_back(edge); - adjacent_edges_first.push_back(std::move(edge)); + adjacent_edges_first.emplace_back(std::move(edge)); ++self._n_unique_edges; return *adjacent_edges_first.back(); @@ -297,7 +291,7 @@ struct undirected_adjacency_list { for (auto& edge : new_edges) { if (not edge->is_loop()) self._list[edge->second_id()].push_back(edge); - adjacent_edges_source.push_back(std::move(edge)); + adjacent_edges_source.emplace_back(std::move(edge)); } self._n_unique_edges += new_edges.size(); diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index e7472d13..92f91918 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -78,16 +78,20 @@ struct directed_adjacency_matrix { [[nodiscard]] gl_attr_force_inline static types::size_type degree( const impl_type& self, const types::id_type vertex_id ) { - return in_degree(self, vertex_id) + out_degree(self, vertex_id); + types::size_type deg = constants::zero; + for (types::id_type i = constants::initial_id; i < self._matrix.size(); ++i) + deg += static_cast(self._matrix[vertex_id][i] != nullptr) + + static_cast(self._matrix[i][vertex_id] != nullptr); + + return deg; } [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { std::vector in_degree_map(self._matrix.size(), constants::zero); for (const auto& row : self._matrix) - for (types::id_type id = constants::initial_id; id < self._matrix.size(); ++id) - if (row[id] != nullptr) - ++in_degree_map[id]; + for (auto [id, edge_ptr] : std::views::enumerate(row)) + in_degree_map[id] += static_cast(edge_ptr != nullptr); return in_degree_map; } @@ -182,11 +186,10 @@ struct undirected_adjacency_matrix { [[nodiscard]] gl_attr_force_inline static types::size_type degree( const impl_type& self, const types::id_type vertex_id ) { - types::size_type degree = constants::default_size; - for (const auto& edge : self._matrix[vertex_id]) - if (edge) - degree += constants::one + static_cast(edge->is_loop()); - return degree; + return std::ranges::count_if( + self._matrix[vertex_id], [](const auto& edge) { return edge != nullptr; } + ) + + static_cast(self._matrix[vertex_id][vertex_id] != nullptr); } [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( diff --git a/include/gl/io/stream_options_manipulator.hpp b/include/gl/io/stream_options_manipulator.hpp index 6eaaf916..be6c69ad 100644 --- a/include/gl/io/stream_options_manipulator.hpp +++ b/include/gl/io/stream_options_manipulator.hpp @@ -5,7 +5,6 @@ #pragma once #include "gl/attributes/force_inline.hpp" -#include "gl/util/enum.hpp" #include @@ -102,7 +101,7 @@ template iword_type options_bitmask = 0ul; for (const auto bit_position : bit_positions) options_bitmask |= - iword_bit << static_cast(util::to_underlying(bit_position)); + iword_bit << static_cast(std::to_underlying(bit_position)); return options_bitmask; } @@ -137,7 +136,7 @@ set_option(bit_position_type bit_position) { template [[nodiscard]] gl_attr_force_inline stream_options_manipulator set_option(BitPosition bit_position) { return stream_options_manipulator::from_bit_position( - static_cast(util::to_underlying(bit_position)), + static_cast(std::to_underlying(bit_position)), stream_options_manipulator::set ); } @@ -172,7 +171,7 @@ template [[nodiscard]] gl_attr_force_inline stream_options_manipulator unset_option(BitPosition bit_position ) { return stream_options_manipulator::from_bit_position( - static_cast(util::to_underlying(bit_position)), + static_cast(std::to_underlying(bit_position)), stream_options_manipulator::unset ); } @@ -209,7 +208,7 @@ template std::ios_base& stream, BitPosition bit_position ) { return stream_options_manipulator::is_option_set( - stream, static_cast(util::to_underlying(bit_position)) + stream, static_cast(std::to_underlying(bit_position)) ); } diff --git a/include/gl/types/iterator_range.hpp b/include/gl/types/iterator_range.hpp index 272aa040..471fe096 100644 --- a/include/gl/types/iterator_range.hpp +++ b/include/gl/types/iterator_range.hpp @@ -98,7 +98,6 @@ class iterator_range return this->_range.second; } -#if __cplusplus >= 202302L [[nodiscard]] gl_attr_force_inline auto cbegin() const { return std::make_const_iterator(this->_range.first); } @@ -106,7 +105,6 @@ class iterator_range [[nodiscard]] gl_attr_force_inline auto cend() const { return std::make_const_iterator(this->_range.second); } -#endif [[nodiscard]] gl_attr_force_inline distance_type distance() const requires(cache_mode::value == type_traits::cache_mode_value::eager) diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index 684f2df5..62fd1cd8 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -5,7 +5,6 @@ #pragma once #include "gl/attributes/force_inline.hpp" -#include "gl/util/enum.hpp" #include "traits/concepts.hpp" #include @@ -186,7 +185,7 @@ class binary_color } [[nodiscard]] gl_attr_force_inline std::underlying_type_t to_underlying() const { - return util::to_underlying(this->_value); + return std::to_underlying(this->_value); } [[nodiscard]] gl_attr_force_inline binary_color next() const { diff --git a/include/gl/util/enum.hpp b/include/gl/util/enum.hpp deleted file mode 100644 index 68771e8a..00000000 --- a/include/gl/util/enum.hpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "gl/attributes/force_inline.hpp" - -#include - -namespace gl::util { - -// TODO [C++23]: replace with std::to_underlying -template -requires(std::is_enum_v) -[[nodiscard]] gl_attr_force_inline constexpr std::underlying_type_t to_underlying(Enum e) { - return static_cast>(e); -} - -} // namespace gl::util diff --git a/tests/include/testing/gl/alg_common.hpp b/tests/include/testing/gl/alg_common.hpp index 0402a983..8bde5308 100644 --- a/tests/include/testing/gl/alg_common.hpp +++ b/tests/include/testing/gl/alg_common.hpp @@ -3,7 +3,7 @@ #include "constants.hpp" #include "types.hpp" -#include +#include #include #include diff --git a/tests/source/gl/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp index 90488566..2350d609 100644 --- a/tests/source/gl/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -1,7 +1,7 @@ #include "testing/gl/alg_common.hpp" #include "testing/gl/constants.hpp" -#include +#include #include #include @@ -59,7 +59,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_postvisit_order = expected_previsit_order; std::vector previsit_order, postvisit_order; - gl::algorithm::breadth_first_search( + gl::algorithm::breadth_first_search( graph, gl::algorithm::no_root_vertex, [&](const auto& vertex) { // previsit @@ -128,7 +128,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::deque expected_postvisit_order = expected_previsit_order; std::vector previsit_order, postvisit_order; - gl::algorithm::breadth_first_search( + gl::algorithm::breadth_first_search( graph, root_vertex_id, [&](const auto& vertex) { // previsit @@ -160,8 +160,7 @@ TEST_CASE_TEMPLATE_DEFINE( using vertex_type = typename graph_type::vertex_type; const auto graph = gl::topology::regular_binary_tree(constants::three); - const auto pd = - gl::algorithm::breadth_first_search(graph); + const auto pd = gl::algorithm::breadth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pd.predecessors.size(), graph.n_vertices()); diff --git a/tests/source/gl/test_alg_coloring.cpp b/tests/source/gl/test_alg_coloring.cpp index a43e30c3..fcd6a720 100644 --- a/tests/source/gl/test_alg_coloring.cpp +++ b/tests/source/gl/test_alg_coloring.cpp @@ -1,7 +1,7 @@ #include "testing/gl/alg_common.hpp" #include "testing/gl/constants.hpp" -#include +#include #include #include diff --git a/tests/source/gl/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp index 8e625c4e..dce59e9d 100644 --- a/tests/source/gl/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -1,7 +1,7 @@ #include "testing/gl/alg_common.hpp" #include "testing/gl/constants.hpp" -#include +#include #include #include @@ -67,7 +67,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::deque expected_postvisit_order = expected_previsit_order; std::vector previsit_order, postvisit_order; - gl::algorithm::depth_first_search( + gl::algorithm::depth_first_search( graph, gl::algorithm::no_root_vertex, [&](const auto& vertex) { // previsit @@ -136,7 +136,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::deque expected_postvisit_order = expected_previsit_order; std::vector previsit_order, postvisit_order; - gl::algorithm::depth_first_search( + gl::algorithm::depth_first_search( graph, root_vertex_id, [&](const auto& vertex) { // previsit @@ -168,8 +168,7 @@ TEST_CASE_TEMPLATE_DEFINE( using vertex_type = typename graph_type::vertex_type; const auto graph = gl::topology::regular_binary_tree(constants::three); - const auto pd = - gl::algorithm::depth_first_search(graph); + const auto pd = gl::algorithm::depth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pd.predecessors.size(), graph.n_vertices()); @@ -245,7 +244,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto expected_postvisit_order = std::views::reverse(expected_previsit_order); std::vector previsit_order, postvisit_order; - gl::algorithm::recursive_depth_first_search( + gl::algorithm::recursive_depth_first_search( graph, gl::algorithm::no_root_vertex, [&](const auto& vertex) { // previsit @@ -319,7 +318,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto expected_postvisit_order = std::views::reverse(expected_previsit_order); std::vector previsit_order, postvisit_order; - gl::algorithm::recursive_depth_first_search( + gl::algorithm::recursive_depth_first_search( graph, root_vertex_id, [&](const auto& vertex) { // previsit @@ -352,8 +351,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto graph = gl::topology::regular_binary_tree(constants::three); const auto pd = - gl::algorithm::recursive_depth_first_search(graph - ); + gl::algorithm::recursive_depth_first_search(graph); // verify the predecessors of each vertex REQUIRE_EQ(pd.predecessors.size(), graph.n_vertices()); diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index 9e689d58..17ca5535 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -2,7 +2,7 @@ #include "testing/gl/constants.hpp" #include "testing/gl/functional.hpp" -#include +#include #include #include diff --git a/tests/source/gl/test_alg_mst.cpp b/tests/source/gl/test_alg_mst.cpp index 7bff8f27..38cdf491 100644 --- a/tests/source/gl/test_alg_mst.cpp +++ b/tests/source/gl/test_alg_mst.cpp @@ -2,7 +2,7 @@ #include "testing/gl/constants.hpp" #include "testing/gl/functional.hpp" -#include +#include #include #include diff --git a/tests/source/gl/test_alg_topological_sort.cpp b/tests/source/gl/test_alg_topological_sort.cpp index a0f46aeb..ce037783 100644 --- a/tests/source/gl/test_alg_topological_sort.cpp +++ b/tests/source/gl/test_alg_topological_sort.cpp @@ -1,7 +1,7 @@ #include "testing/gl/alg_common.hpp" #include "testing/gl/constants.hpp" -#include +#include #include #include diff --git a/tests/source/gl/test_alg_types.cpp b/tests/source/gl/test_alg_types.cpp new file mode 100644 index 00000000..d1f73911 --- /dev/null +++ b/tests/source/gl/test_alg_types.cpp @@ -0,0 +1,31 @@ +#include "testing/gl/alg_common.hpp" +#include "testing/gl/constants.hpp" + +#include +#include + +#include + +using gl::algorithm::predicate_result; + +namespace gl_testing { + +TEST_SUITE_BEGIN("test_alg_types"); + +TEST_CASE("predicate_result(bool) should properly initialize the value") { + predicate_result result(true); + CHECK_EQ(result, predicate_result::ok); + + predicate_result result2(false); + CHECK_EQ(result2, predicate_result::not_ok); +} + +TEST_CASE("predicate_result bool conversion should return true only for ok eval") { + CHECK(predicate_result{predicate_result::ok}); + CHECK_FALSE(predicate_result{predicate_result::not_ok}); + CHECK_FALSE(predicate_result{predicate_result::unknown}); +} + +TEST_SUITE_END(); // test_alg_types + +} // namespace gl_testing diff --git a/tests/source/gl/test_properties.cpp b/tests/source/gl/test_properties.cpp index e611bb44..c86ffc65 100644 --- a/tests/source/gl/test_properties.cpp +++ b/tests/source/gl/test_properties.cpp @@ -93,7 +93,7 @@ TEST_CASE_FIXTURE(test_dynamic_properties, "is_present should return false for a } TEST_CASE_FIXTURE(test_dynamic_properties, "is_present should return true for a present key") { - sut.underlying()[key] = value; + sut.underlying()[key] = std::any{value}; CHECK(sut.is_present(key)); } @@ -102,7 +102,7 @@ TEST_CASE_FIXTURE(test_dynamic_properties, "get should throw for a not present k } TEST_CASE_FIXTURE(test_dynamic_properties, "get should throw for an invalid value type") { - sut.underlying()[key] = value; + sut.underlying()[key] = std::any{value}; CHECK_THROWS_AS(func::discard_result(sut.get(key)), std::bad_any_cast); } @@ -110,7 +110,7 @@ TEST_CASE_FIXTURE( test_dynamic_properties, "get should return a reference to the underlying object for a valid key and type" ) { - sut.underlying()[key] = value; + sut.underlying()[key] = std::any{value}; compound_value& value_ref = sut.get(key); REQUIRE_EQ(value_ref, value); @@ -168,7 +168,7 @@ struct test_binary_color { using color = sut_type::value; static constexpr color out_of_bounds_color = - static_cast(gl::util::to_underlying(color::unset) + 1); + static_cast(std::to_underlying(color::unset) + 1); }; TEST_CASE_FIXTURE(test_binary_color, "should be unset by default") { @@ -180,7 +180,7 @@ TEST_CASE_FIXTURE(test_binary_color, "out of bounds values should be restricted sut_type sut{out_of_bounds_color}; REQUIRE_EQ(sut, color::unset); - CHECK_EQ(sut.to_underlying(), gl::util::to_underlying(color::unset)); + CHECK_EQ(sut.to_underlying(), std::to_underlying(color::unset)); } TEST_CASE_FIXTURE( diff --git a/tests/source/gl/test_util.cpp b/tests/source/gl/test_util.cpp index 30f058b6..2ec38008 100644 --- a/tests/source/gl/test_util.cpp +++ b/tests/source/gl/test_util.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -52,19 +51,6 @@ TEST_CASE("upow_sum function test") { CHECK_EQ(gl::util::upow_sum(base, i_begin, i_end), expected_result); } -TEST_CASE("to_underlying should return the underlying value of an enum class") { - using underlying_type = int; - constexpr underlying_type value_1 = 1; - constexpr underlying_type value_2 = 2; - constexpr underlying_type out_of_bounds_value = 2; - - enum class Enum : underlying_type { value_1 = value_1, value_2 = value_2 }; - - CHECK_EQ(gl::util::to_underlying(Enum::value_1), value_1); - CHECK_EQ(gl::util::to_underlying(Enum::value_2), value_2); - CHECK_EQ(gl::util::to_underlying(static_cast(out_of_bounds_value)), out_of_bounds_value); -} - TEST_SUITE_END(); // test_util } // namespace gl_testing From 54efe67f09dfcc0bd6d5dc88ae6e36f8a1be3fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:41:22 +0100 Subject: [PATCH 03/54] YT-CPPGL-56: Replace the usage of pointers for vertex and edge storage to simple composite types [Part 1] - The `graph` class does not store a list of vertices directly, only the number of vertices and optionally a vertex properties map - Added a vertex property map getter to the `graph` class - Vertices are compared only using IDs within the graph representation models - Removed vertex validation within implementation types - only the `graph` class validates vertices - The properties members of the vertex and edge classes are now private with added getter methods --- docs/graph.md | 10 +- docs/graph_elements.md | 85 ++++---- include/gl/algorithm/coloring.hpp | 12 +- include/gl/algorithm/dijkstra.hpp | 4 +- include/gl/algorithm/mst.hpp | 6 +- include/gl/edge_descriptor.hpp | 80 ++++---- include/gl/edge_tags.hpp | 16 +- include/gl/graph.hpp | 185 +++++++++++------- include/gl/graph_utility.hpp | 2 +- include/gl/impl/adjacency_list.hpp | 41 +--- include/gl/impl/adjacency_matrix.hpp | 8 +- .../gl/impl/specialized/adjacency_list.hpp | 64 +++--- .../gl/impl/specialized/adjacency_matrix.hpp | 22 +-- include/gl/types/iterator_range.hpp | 2 - include/gl/types/properties.hpp | 16 +- include/gl/vertex_descriptor.hpp | 30 ++- include/gl/views.hpp | 11 ++ tests/include/testing/gl/alg_common.hpp | 2 +- tests/include/testing/gl/constants.hpp | 4 +- tests/include/testing/gl/io_common.hpp | 13 +- tests/source/gl/test_adjacency_list.cpp | 112 +---------- tests/source/gl/test_adjacency_matrix.cpp | 69 +------ tests/source/gl/test_alg_bfs.cpp | 2 +- tests/source/gl/test_alg_coloring.cpp | 3 +- tests/source/gl/test_alg_dfs.cpp | 4 +- tests/source/gl/test_alg_dijkstra.cpp | 5 +- tests/source/gl/test_alg_mst.cpp | 12 +- tests/source/gl/test_edge_descriptor.cpp | 34 +--- tests/source/gl/test_edge_tags.cpp | 34 ++-- tests/source/gl/test_graph.cpp | 149 ++++++-------- tests/source/gl/test_graph_file_io.cpp | 6 +- tests/source/gl/test_graph_incidence.cpp | 36 ++-- tests/source/gl/test_graph_io.cpp | 10 +- tests/source/gl/test_vertex_descriptor.cpp | 8 +- 34 files changed, 480 insertions(+), 617 deletions(-) create mode 100644 include/gl/views.hpp diff --git a/docs/graph.md b/docs/graph.md index 0bbd1510..731b099b 100644 --- a/docs/graph.md +++ b/docs/graph.md @@ -138,14 +138,14 @@ Based on the specified traits, the `graph` class defines the following types: ### Vertex Operations - **`graph.vertices() const`**: - - *Description*: Returns an iterator range over all vertices in the graph. + - *Description*: Returns an view over all vertices in the graph. - *Returned value*: $V$ - - *Return type*: `types::iterator_range` + - *Return type*: A random access *view* with values of type `vertex_type`. - **`graph.vertex_ids() const`**: - *Description*: Returns a range of vertex IDs, starting from the initial vertex ID to the number of vertices in the grap. - *Returned value*: $(v_{id} : v \in V)$ - - *Return type*: `std::ranges::iota_view` + - *Return type*: A random access *view* with values of type `types::id_type` : `std::ranges::iota_view`. - **`graph.get_vertex(vertex_id) const`**: - *Description*: Retrieves the vertex object associated with the given vertex ID. @@ -296,6 +296,10 @@ Based on the specified traits, the `graph` class defines the following types: - *Description*: Returns a vector containing the degrees of the corresponding vertices (degree at index `i` corresponds to the vertex with an ID equal `i`). - *Return type*: `std::vector` +- **`graph.vertex_properties_map() const`**: + - *Description*: Returns a *map-like view* the properties of the corresponding vertices (property at index `i` corresponds to the vertex with an ID equal `i`). + - *Return type*: A random access view with values of type `vertex_properties_type&` +
### Edge Operations diff --git a/docs/graph_elements.md b/docs/graph_elements.md index a145f086..9446aaed 100644 --- a/docs/graph_elements.md +++ b/docs/graph_elements.md @@ -14,17 +14,17 @@ This section provides an overview of the `vertex_descriptor` and `edge_descripto ## The vertex class -The `vertex_descriptor` class represents the identity of a vertex in the graph, optionally carrying additional properties as specified by the user. This class is designed for use within the `Graph` class and serves as a lightweight identifier for vertices, providing an interface for accessing vertex properties, comparing vertex descriptors, and outputting vertex information. +The `vertex_descriptor` class represents the vertex in the graph, optionally carrying additional properties as specified by the user. This class is designed for use within the `graph` class and serves as a lightweight identifier for vertices, providing an interface for accessing vertex properties, comparing vertex descriptors, and outputting vertex information. By default, the `vertex_descriptor` class does not carry any properties. However, properties can be associated with each vertex by passing a custom type as the template parameter `Properties`, making it highly flexible and customizable for various graph use cases. -### Template parameters +### Template Parameters - **`Properties`**: A type that defines the properties associated with each vertex. - *Default value*: `types::empty_properties` - *Constraints*: must satisfy the **`type_traits::c_properties`** concept -### Member types +### Member Types - **`type`**: Alias for the `vertex_descriptor` itself. - **`properties_type`**: Type of the vertex properties as defined by the `Properties` template parameter. @@ -37,10 +37,10 @@ By default, the `vertex_descriptor` class does not carry any properties. However - Constructs a `vertex_descriptor` with a unique ID and specified properties. - *Constraints*: the `properties_type` must be non-default. - **Move constructor and assignment operator**: *default* +- **Copy constructor and assignment operator**: *default* - **Deleted**: - **Default constructor**: prevent creating multiple vertices with the default ID within a graph. - - **Copy constructor and assignment operator**: prevent copying of `vertex_descriptor` instances, as they are meant to be identificators. ### Desctructor @@ -52,6 +52,10 @@ The destructor is *defaulted*, allowing proper cleanup of the `vertex_descriptor - *Description*: Returns the unique identifier of the vertex. - *Return type*: `types::id_type` +- **`properties()`**: + - *Description*: Returns a mutable reference to the properties associated with the vertex. + - *Return type*: `properties_type&` + - **`operator==(const vertex_descriptor& other) const`**: - *Description*: Compares two `vertex_descriptor` objects for equality based on their vertex IDs. - *Parameters*: @@ -64,12 +68,6 @@ The destructor is *defaulted*, allowing proper cleanup of the `vertex_descriptor - `other: const vertex_descriptor&` – the vertex descriptor to compare with. - *Return type*: `std::strong_ordering` -### Member variables - -- **`properties`**: - - *Description*: A mutable member that stores the properties associated with the vertex. - - *Type*: Defined by the `Properties` template parameter. - ### Additional utility - **`vertex`**: A convenient alias for `vertex_descriptor` with customizable properties. @@ -137,6 +135,11 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - *Returned value*: $(u, v)$ - *Return type*: `types::homogeneous_pair` +- **`incident_vertex_ids() const`**: + - *Description*: Returns the unique IDs of the vertices connected by the edge. + - *Returned value*: $(u_{id}, v_{id})$ + - *Return type*: `types::homogeneous_pair` + - **`first() const`**: - *Description*: Returns a reference to the first vertex of the edge. - *Returned value*: $u$ @@ -147,22 +150,7 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - *Returned value*: $v$ - *Return type*: `const vertex_type&` -- **`incident_vertex_ids() const`**: - - *Description*: Returns the unique IDs of the vertices connected by the edge. - - *Returned value*: $(u_{id}, v_{id})$ - - *Return type*: `types::homogeneous_pair` - -- **`first_id() const`**: - - *Description*: Returns the ID of the first vertex. - - *Returned value*: $u_{id}$ - - *Return type*: `types::id_type` - -- **`second_id() const`**: - - *Description*: Returns the ID of the second vertex. - - *Returned value*: $v_{id}$ - - *Return type*: `types::id_type` - -- **`incident_vertex(const vertex_type& vertex) const`**: +- **`incident_vertex(vertex) const`**: - *Description*: Returns the vertex on the other end of the edge relative to the provided vertex. Throws an error if the provided vertex is not incident with the edge. - *Returned value*: - $v$ if $\text{vertex} = u$ @@ -172,24 +160,31 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - `vertex: const vertex_type&` – the vertex for which the opposite vertex is requested. - *Return type*: `const vertex_type&` -- **`incident_vertex_id(const types::id_type vertex_id) const`**: - - *Description*: Returns the ID of the vertex on the other end of the edge relative to the provided vertex ID. Throws an error if the provided vertex ID is invalid. +- **`incident_vertex(vertex_id) const`**: + - *Description*: Returns the vertex on the other end of the edge relative to the provided vertex ID. Throws an error if the provided vertex ID is invalid. - *Returned value*: - - $v_{id}$ if $\text{vertex-id} = u_{id}$ - - $u_{id}$ if $\text{vertex-id} = v_{id}$ + - $v$ if $\text{vertex-id} = u_{id}$ + - $u$ if $\text{vertex-id} = v_{id}$ - error otherwise - *Parameters*: - `vertex_id: const types::id_type` – the vertex ID for which the opposite vertex ID is requested. - *Return type*: `types::id_type` -- **`is_incident_with(const vertex_type& vertex) const`**: +- **`is_incident_with(vertex) const`**: - *Description*: Returns `true` if the provided vertex is connected to the edge. - *Returned value*: $\text{vertex} \in {u, v}$ - *Parameters*: - `vertex: const vertex_type&` – the vertex to check for incidence with the edge. - *Return type*: `bool` -- **`is_incident_from(const vertex_type& vertex) const`**: +- **`is_incident_with(vertex_id) const`**: + - *Description*: Returns `true` if a vertex with the given ID is connected to the edge. + - *Returned value*: $\text{vertex-id} \in {u_{id}, v_{id}}$ + - *Parameters*: + - `vertex_id: const types::id_type` – the vertex ID to check for incidence with the edge. + - *Return type*: `bool` + +- **`is_incident_from(vertex) const`**: - *Description*: Returns `true` if the provided vertex is the source of the edge (for directed edges). - *Returned value*: - For directed edges: $\text{vertex} = u$ @@ -198,7 +193,16 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - `vertex: const vertex_type&` – the vertex to check if it is the source. - *Return type*: `bool` -- **`is_incident_to(const vertex_type& vertex) const`**: +- **`is_incident_from(vertex_id) const`**: + - *Description*: Returns `true` if a vertex with the given ID is the source of the edge. + - *Returned value*: + - For directed edges: $\text{vertex-id} = u_{id}$ + - For undirected edges: `is_incident_with(vertex)` + - *Parameters*: + - `vertex_id: const types::id_type` – the vertex ID to check if it is the source. + - *Return type*: `bool` + +- **`is_incident_to(vertex) const`**: - *Description*: Returns `true` if the provided vertex is the target vertex of the edge (for directed edges). - *Returned value*: - For directed edges: $\text{vertex} = v$ @@ -207,17 +211,20 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - `vertex: const vertex_type&` – the vertex to check if it is the target. - *Return type*: `bool` +- **`is_incident_to(vertex_id) const`**: + - *Description*: Returns `true` if a vertex with the given ID is the target of the edge. + - *Returned value*: + - For directed edges: $\text{vertex-id} = v_{id}$ + - For undirected edges: `is_incident_with(vertex)` + - *Parameters*: + - `vertex_id: const types::id_type` – the vertex ID to check if it is the target. + - *Return type*: `bool` + - **`is_loop() const`**: - *Description*: Returns `true` if the edge is a loop (i.e., both vertices are the same). - *Returned value*: $u = v$ - *Return type*: `bool` -### Member variables - -- **`properties`**: - - *Description*: A mutable member that stores the properties associated with the edge. - - *Type*: Defined by the `Properties` template parameter. - ### Additional utility - **`edge`**: A convenient alias for `edge_descriptor`, with customizable properties and directionality. diff --git a/include/gl/algorithm/coloring.hpp b/include/gl/algorithm/coloring.hpp index d32387ec..ce3f5b7b 100644 --- a/include/gl/algorithm/coloring.hpp +++ b/include/gl/algorithm/coloring.hpp @@ -28,7 +28,7 @@ template < coloring_opt.emplace(graph.n_vertices(), bin_color_value::unset); auto& coloring = coloring_opt.value(); - for (const auto& root_vertex : graph.vertices()) { + for (const auto root_vertex : graph.vertices()) { const auto root_id = root_vertex.id(); if (coloring[root_id].is_set()) continue; @@ -80,14 +80,14 @@ template < type_traits::c_sized_range_of ColorRange> requires(type_traits::c_binary_color_properties_type) bool apply_coloring(GraphType& graph, const ColorRange& color_range) { + using color_type = typename GraphType::vertex_properties_type::color_type; + if (color_range.size() != graph.n_vertices()) return false; - using color_type = typename GraphType::vertex_properties_type::color_type; - auto color_it = std::ranges::begin(color_range); - - for (const auto& vertex : graph.vertices()) - vertex.properties.color = color_type{*color_it++}; + auto vertices = graph.vertices(); // store the view to extend its lifetime + for (const auto& [vertex, color] : std::views::zip(vertices, color_range)) + vertex.properties().color = color; return true; } diff --git a/include/gl/algorithm/dijkstra.hpp b/include/gl/algorithm/dijkstra.hpp index 0a870e8e..345ff12a 100644 --- a/include/gl/algorithm/dijkstra.hpp +++ b/include/gl/algorithm/dijkstra.hpp @@ -121,8 +121,8 @@ template < const auto& edge = negative_edge.value().get(); throw std::invalid_argument(std::format( "[alg::dijkstra_shortest_paths] Found an edge with a negative weight: [{}, {} | w={}]", - edge.first_id(), - edge.second_id(), + edge.first().id(), + edge.second().id(), get_weight(edge) )); } diff --git a/include/gl/algorithm/mst.hpp b/include/gl/algorithm/mst.hpp index fb0401d6..611fc49e 100644 --- a/include/gl/algorithm/mst.hpp +++ b/include/gl/algorithm/mst.hpp @@ -80,7 +80,7 @@ template < const auto& min_edge = min_edge_info.edge.get(); const auto min_weight = get_weight(min_edge); - const auto& target_id = min_edge.incident_vertex_id(min_edge_info.source_id); + const auto& target_id = min_edge.incident_vertex(min_edge_info.source_id).id(); if (visited[target_id]) continue; @@ -93,7 +93,7 @@ template < // enqueue all edges adjacent to the `target` vertex if they lead to unvisited verties for (const auto& edge : graph.adjacent_edges(target_id)) - if (not visited[edge.incident_vertex_id(target_id)]) + if (not visited[edge.incident_vertex(target_id).id()]) edge_queue.emplace(edge, target_id); } @@ -157,7 +157,7 @@ requires type_traits::c_has_numeric_limits_max(edge); - const auto incident_vertex_id = edge.incident_vertex_id(vertex_id); + const auto incident_vertex_id = edge.incident_vertex(vertex_id).id(); if (not in_mst[incident_vertex_id] && edge_weight < min_cost[incident_vertex_id]) { min_cost[incident_vertex_id] = edge_weight; diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index 1658af73..dcca5a8d 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -20,6 +20,10 @@ class edge_descriptor final { using vertex_type = VertexType; using directional_tag = DirectionalTag; using properties_type = Properties; + using properties_ref_type = std::conditional_t< + type_traits::is_default_properties_type_v, + types::empty_properties, + properties_type&>; friend directional_tag; @@ -30,14 +34,14 @@ class edge_descriptor final { edge_descriptor(const edge_descriptor&) = delete; edge_descriptor& operator=(const edge_descriptor&) = delete; - explicit edge_descriptor(const vertex_type& first, const vertex_type& second) - : _vertices(first, second) {} + explicit edge_descriptor(const vertex_type first, const vertex_type& second) + : _vertices(std::move(first), std::move(second)) {} explicit edge_descriptor( - const vertex_type& first, const vertex_type& second, const properties_type& properties + const vertex_type first, const vertex_type second, properties_type properties ) requires(not type_traits::is_default_properties_type_v) - : _vertices(first, second), properties(properties) {} + : _vertices(std::move(first), std::move(second)), _properties(properties) {} edge_descriptor(edge_descriptor&&) = default; edge_descriptor& operator=(edge_descriptor&&) = default; @@ -56,10 +60,14 @@ class edge_descriptor final { // gl_attr_force_inline misplacement [[nodiscard]] gl_attr_force_inline - const types::homogeneous_pair& incident_vertices() const { + const types::homogeneous_pair& incident_vertices() const { return this->_vertices; } + [[nodiscard]] gl_attr_force_inline const types::homogeneous_pair incident_vertex_ids() const { + return std::make_pair(this->_vertices.first.id(), this->_vertices.second.id()); + } + [[nodiscard]] gl_attr_force_inline const vertex_type& first() const { return this->_vertices.first; } @@ -70,61 +78,56 @@ class edge_descriptor final { // clang-format on - [[nodiscard]] gl_attr_force_inline types::homogeneous_pair incident_vertex_ids( - ) const { - return std::make_pair(this->_vertices.first.id(), this->_vertices.second.id()); - } - - [[nodiscard]] gl_attr_force_inline types::id_type first_id() const { - return this->first().id(); - } - - [[nodiscard]] gl_attr_force_inline types::id_type second_id() const { - return this->second().id(); - } - // returns the `other` vertex or throws error if the given vertex is not incident with the edge - [[nodiscard]] const vertex_type& incident_vertex(const vertex_type& vertex) const { - if (&vertex == &this->_vertices.first) + [[nodiscard]] const vertex_type incident_vertex(const types::id_type vertex_id) const { + if (vertex_id == this->_vertices.first.id()) return this->_vertices.second; - if (&vertex == &this->_vertices.second) + if (vertex_id == this->_vertices.second.id()) return this->_vertices.first; - throw std::invalid_argument(std::format( - "Got invalid vertex [id = {} | addr = {}]", vertex.id(), io::format(&vertex) - )); + throw std::invalid_argument(std::format("Got invalid vertex id: {}", vertex_id)); } - [[nodiscard]] types::id_type incident_vertex_id(const types::id_type vertex_id) const { - if (vertex_id == this->first_id()) - return this->second_id(); - - if (vertex_id == this->second_id()) - return this->first_id(); + [[nodiscard]] const vertex_type incident_vertex(const vertex_type& vertex) const { + return this->incident_vertex(vertex.id()); + } - throw std::invalid_argument(std::format("Got invalid vertex id: {}", vertex_id)); + [[nodiscard]] gl_attr_force_inline bool is_incident_with(const types::id_type vertex_id) const { + return vertex_id == this->_vertices.first.id() or vertex_id == this->_vertices.second.id(); } [[nodiscard]] gl_attr_force_inline bool is_incident_with(const vertex_type& vertex) const { - return &vertex == &this->_vertices.first or &vertex == &this->_vertices.second; + return this->is_incident_with(vertex.id()); + } + + // true if the given vertex is the `source` of the edge + [[nodiscard]] gl_attr_force_inline bool is_incident_from(const types::id_type vertex_id) const { + return directional_tag::is_incident_from(*this, vertex_id); } // true if the given vertex is the `source` of the edge [[nodiscard]] gl_attr_force_inline bool is_incident_from(const vertex_type& vertex) const { - return directional_tag::is_incident_from(*this, vertex); + return this->is_incident_from(vertex.id()); + } + + // true if the given vertex is the `target` vertex of the edge + [[nodiscard]] gl_attr_force_inline bool is_incident_to(const types::id_type vertex_id) const { + return directional_tag::is_incident_to(*this, vertex_id); } // true if the given vertex is the `target` vertex of the edge [[nodiscard]] gl_attr_force_inline bool is_incident_to(const vertex_type& vertex) const { - return directional_tag::is_incident_to(*this, vertex); + return this->is_incident_to(vertex.id()); } [[nodiscard]] gl_attr_force_inline bool is_loop() const { return this->_vertices.first == this->_vertices.second; } - [[no_unique_address]] mutable properties_type properties{}; + [[nodiscard]] gl_attr_force_inline properties_type& properties() const { + return this->_properties; + } friend inline std::ostream& operator<<(std::ostream& os, const edge_descriptor& edge) { edge._write(os); @@ -164,11 +167,11 @@ class edge_descriptor final { if (io::is_option_set(os, io::graph_option::verbose)) { os << "[first: " << vertex_writer(this->first(), within_context) << ", second: " << vertex_writer(this->second(), within_context) - << " | properties: " << this->properties << "]"; + << " | properties: " << this->_properties << "]"; } else { os << "[" << vertex_writer(this->first(), within_context) << ", " - << vertex_writer(this->second(), within_context) << " | " << this->properties + << vertex_writer(this->second(), within_context) << " | " << this->_properties << "]"; } } @@ -183,7 +186,8 @@ class edge_descriptor final { << vertex_writer(this->second(), within_context) << "]"; } - types::homogeneous_pair _vertices; + types::homogeneous_pair _vertices; + [[no_unique_address]] mutable properties_type _properties{}; }; template < diff --git a/include/gl/edge_tags.hpp b/include/gl/edge_tags.hpp index 438ab657..cbb722f7 100644 --- a/include/gl/edge_tags.hpp +++ b/include/gl/edge_tags.hpp @@ -76,17 +76,17 @@ struct directed_t { template EdgeType> requires(type_traits::is_directed_v) [[nodiscard]] gl_attr_force_inline static bool is_incident_from( - const EdgeType& edge, const typename EdgeType::vertex_type& vertex + const EdgeType& edge, const types::id_type vertex_id ) { - return &vertex == &edge._vertices.first; + return vertex_id == edge._vertices.first.id(); } template EdgeType> requires(type_traits::is_directed_v) [[nodiscard]] gl_attr_force_inline static bool is_incident_to( - const EdgeType& edge, const typename EdgeType::vertex_type& vertex + const EdgeType& edge, const types::id_type vertex_id ) { - return &vertex == &edge._vertices.second; + return vertex_id == edge._vertices.second.id(); } }; @@ -118,17 +118,17 @@ struct undirected_t { template EdgeType> requires(type_traits::is_undirected_v) [[nodiscard]] gl_attr_force_inline static bool is_incident_from( - const EdgeType& edge, const typename EdgeType::vertex_type& vertex + const EdgeType& edge, const types::id_type vertex_id ) { - return edge.is_incident_with(vertex); + return edge.is_incident_with(vertex_id); } template EdgeType> requires(type_traits::is_undirected_v) [[nodiscard]] gl_attr_force_inline static bool is_incident_to( - const EdgeType& edge, const typename EdgeType::vertex_type& vertex + const EdgeType& edge, const types::id_type vertex_id ) { - return edge.is_incident_with(vertex); + return edge.is_incident_with(vertex_id); } }; diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 90792bc8..e54cffa8 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -9,6 +9,7 @@ #include "impl/impl_tags.hpp" #include "io/stream_options_manipulator.hpp" #include "types/iterator_range.hpp" +#include "views.hpp" #include @@ -23,12 +24,11 @@ class graph final { using implementation_type = typename implementation_tag::template type; using vertex_type = typename traits_type::vertex_type; - using vertex_ptr_type = typename traits_type::vertex_ptr_type; using vertex_properties_type = typename traits_type::vertex_properties_type; - - using vetex_list_type = std::vector; - using vertex_iterator_type = - types::dereferencing_iterator; + using vertex_properties_map_type = std::conditional_t< + type_traits::is_default_properties_type_v, + types::empty_properties_map, + std::vector>>; // TODO: reverese iterators should be available for bidirectional ranges @@ -45,10 +45,16 @@ class graph final { graph() = default; - graph(const types::size_type n_vertices) : _impl(n_vertices) { - this->_vertices.reserve(n_vertices); - for (auto vertex_id = constants::initial_id; vertex_id < n_vertices; ++vertex_id) - this->_vertices.push_back(detail::make_vertex(vertex_id)); + graph(const types::size_type n_vertices) + requires(type_traits::is_default_properties_type_v) + : _n_vertices(n_vertices), _impl(n_vertices) {} + + graph(const types::size_type n_vertices) + requires(not type_traits::is_default_properties_type_v) + : _n_vertices(n_vertices), _impl(n_vertices) { + this->_vertex_properties.reserve(n_vertices); + for (auto id : this->vertex_ids()) + this->_vertex_properties.push_back(std::make_unique()); } graph(graph&&) = default; @@ -59,7 +65,7 @@ class graph final { // --- general methods --- [[nodiscard]] gl_attr_force_inline types::size_type n_vertices() const { - return this->_vertices.size(); + return this->_n_vertices; } [[nodiscard]] gl_attr_force_inline types::size_type n_unique_edges() const { @@ -68,78 +74,109 @@ class graph final { // --- vertex methods --- - [[nodiscard]] gl_attr_force_inline types::iterator_range vertices( - ) const { - return make_iterator_range(deref_cbegin(this->_vertices), deref_cend(this->_vertices)); + [[nodiscard]] gl_attr_force_inline auto vertices() const + requires(type_traits::is_default_properties_type_v) + { + return this->vertex_ids() + | std::views::transform([](const types::id_type id) { return vertex_descriptor{id}; }); + } + + [[nodiscard]] gl_attr_force_inline auto vertices() const + requires(not type_traits::is_default_properties_type_v) + { + return this->_vertex_properties | std::views::enumerate + | std::views::transform([](const auto& x) { + const auto& [id, ptr] = x; + return vertex_descriptor{static_cast(id), *ptr}; + }); } [[nodiscard]] gl_attr_force_inline std::ranges::iota_view vertex_ids() const { - return std::views::iota(constants::initial_id, this->n_vertices()); + return std::views::iota(constants::initial_id, this->_n_vertices); } // clang-format off // gl_attr_force_inline misplacement - [[nodiscard]] gl_attr_force_inline const vertex_type& get_vertex( - const types::id_type vertex_id - ) const { + [[nodiscard]] gl_attr_force_inline vertex_type get_vertex(const types::id_type vertex_id) const { this->_verify_vertex_id(vertex_id); - return *this->_vertices[vertex_id]; + if constexpr (type_traits::is_default_properties_type_v) + return vertex_descriptor{vertex_id}; + else + return vertex_descriptor{vertex_id, *this->_vertex_properties[vertex_id]}; } // clang-format on [[nodiscard]] gl_attr_force_inline bool has_vertex(const types::id_type vertex_id) const { - return vertex_id < this->n_vertices(); + return vertex_id < this->_n_vertices; } [[nodiscard]] gl_attr_force_inline bool has_vertex(const vertex_type& vertex) const { - return this->has_vertex(vertex.id()) and &vertex == this->_vertices[vertex.id()].get(); + return this->has_vertex(vertex.id()); } - const vertex_type& add_vertex() { + vertex_type add_vertex() { this->_impl.add_vertex(); - this->_vertices.push_back(detail::make_vertex(this->n_vertices())); - return *this->_vertices.back(); + const auto new_vertex_id = this->_n_vertices++; + + if constexpr (type_traits::is_default_properties_type_v) + return vertex_descriptor{new_vertex_id}; + else { + this->_vertex_properties.push_back(std::make_unique()); + return vertex_descriptor{new_vertex_id, *this->_vertex_properties.back()}; + } } - const vertex_type& add_vertex(const vertex_properties_type& properties) + vertex_type add_vertex(vertex_properties_type properties) requires(not type_traits::is_default_properties_type_v) { this->_impl.add_vertex(); - this->_vertices.push_back(detail::make_vertex(this->n_vertices(), properties)); - return *this->_vertices.back(); + this->_vertex_properties.push_back( + std::make_unique(std::move(properties)) + ); + return vertex_descriptor{this->_n_vertices++, *this->_vertex_properties.back()}; } void add_vertices(const types::size_type n) { this->_impl.add_vertices(n); - this->_vertices.reserve(this->n_vertices() + n); + this->_n_vertices += n; - for (types::size_type _ = constants::begin_idx; _ < n; ++_) - this->_vertices.push_back(detail::make_vertex(this->n_vertices())); + if constexpr (not type_traits::is_default_properties_type_v) { + const auto old_size = this->_vertex_properties.size(); + this->_vertex_properties.reserve(this->_n_vertices); + for (types::size_type i = old_size; i < this->_n_vertices; ++i) + this->_vertex_properties.push_back(std::make_unique()); + } } template VertexPropertiesRange> - void add_vertices_with(const VertexPropertiesRange& properties_range) { + void add_vertices_with(const VertexPropertiesRange& properties_range) + requires(not type_traits::is_default_properties_type_v) + { const auto n = std::ranges::size(properties_range); this->_impl.add_vertices(n); - this->_vertices.reserve(this->n_vertices() + n); + this->_n_vertices += n; - for (const auto& properties : properties_range) - this->_vertices.push_back( - detail::make_vertex(this->n_vertices(), properties) - ); + if constexpr (not type_traits::is_default_properties_type_v) { + for (auto& properties : properties_range) { + this->_vertex_properties.emplace_back( + std::make_unique(properties) + ); + } + } } gl_attr_force_inline void remove_vertex(const types::size_type vertex_id) { - this->_remove_vertex_impl(this->get_vertex(vertex_id)); + this->_verify_vertex_id(vertex_id); + this->_remove_vertex_impl(vertex_id); } inline void remove_vertex(const vertex_type& vertex) { this->_verify_vertex(vertex); - this->_remove_vertex_impl(vertex); + this->_remove_vertex_impl(vertex.id()); } template IdRange> @@ -151,7 +188,7 @@ class graph final { // TODO: optimize for (const auto vertex_id : vertex_id_set) - this->_remove_vertex_impl(this->get_vertex(vertex_id)); + this->_remove_vertex_impl(vertex_id); } template > VertexRefRange> @@ -173,7 +210,7 @@ class graph final { // TODO: optimize for (const auto& vertex_ref : vertex_ref_set) - this->_remove_vertex_impl(vertex_ref.get()); + this->_remove_vertex_impl(vertex_ref.get().id()); } [[nodiscard]] gl_attr_force_inline types::size_type in_degree(const vertex_type& vertex) const { @@ -222,6 +259,12 @@ class graph final { return this->_impl.degree_map(); } + [[nodiscard]] gl_attr_force_inline auto vertex_properties_map() const noexcept + requires(not type_traits::is_default_properties_type_v) + { + return views::deref(this->_vertex_properties); + } + // --- edge methods --- // clang-format off @@ -334,6 +377,9 @@ class graph final { ) const { using edge_ref_set = std::vector>; + this->_verify_vertex_id(first_id); + this->_verify_vertex_id(second_id); + if constexpr (std::same_as) { return this->_impl.get_edges(first_id, second_id); } @@ -346,8 +392,6 @@ class graph final { [[nodiscard]] std::vector> get_edges( const vertex_type& first, const vertex_type& second ) const { - this->_verify_vertex(first); - this->_verify_vertex(second); return this->get_edges(first.id(), second.id()); } @@ -455,42 +499,38 @@ class graph final { throw std::out_of_range(std::format("Got invalid vertex id [{}]", vertex_id)); } - void _verify_vertex(const vertex_type& vertex) const { - const auto vertex_id = vertex.id(); - const auto& self_vertex = this->get_vertex(vertex_id); - - if (&vertex != &self_vertex) - throw std::invalid_argument(std::format( - "Got invalid vertex [id = {} | expected addr = {} | actual addr = {}]", - vertex_id, - io::format(&self_vertex), - io::format(&vertex) - )); + gl_attr_force_inline void _verify_vertex(const vertex_type& vertex) const { + this->_verify_vertex_id(vertex.id()); } void _verify_edge(const edge_type& edge) const { if (not this->has_edge(edge)) throw std::invalid_argument(std::format( "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge.first_id(), - edge.second_id(), + edge.first().id(), + edge.second().id(), io::format(&edge) )); } // --- vertex methods --- - void _remove_vertex_impl(const vertex_type& vertex) { - const auto vertex_id = vertex.id(); - this->_impl.remove_vertex(vertex); - this->_vertices.erase(std::next(std::begin(this->_vertices), vertex_id)); + void _remove_vertex_impl(const types::id_type vertex_id) { + this->_impl.remove_vertex(vertex_id); + this->_n_vertices--; - // align ids of remainig vertices - std::for_each( - std::next(std::begin(this->_vertices), vertex_id), - this->_vertices.end(), - [](auto& v) { --v->_id; } - ); + // update vertex ids in edges + for (auto id : this->vertex_ids()) { + for (auto& edge : this->_impl.adjacent_edges(id)) { + edge._vertices.first._id -= (edge._vertices.first._id > vertex_id); + edge._vertices.second._id -= (edge._vertices.second._id > vertex_id); + } + } + + if constexpr (not type_traits::is_default_properties_type_v) + this->_vertex_properties.erase( + std::next(std::begin(this->_vertex_properties), vertex_id) + ); } // --- io methods --- @@ -546,16 +586,16 @@ class graph final { if constexpr (type_traits::c_writable) if (with_vertex_properties) for (const auto& vertex : this->vertices()) - os << vertex.properties << '\n'; + os << vertex._properties << '\n'; if constexpr (type_traits::c_writable) { if (with_edge_properties) { const auto print_incident_edges = [this, &os](const types::id_type vertex_id) { for (const auto& edge : this->_impl.adjacent_edges(vertex_id)) { - if (edge.first_id() != vertex_id) + if (edge.first().id() != vertex_id) continue; // vertex is not the source - os << edge.first_id() << ' ' << edge.second_id() << ' ' << edge.properties - << '\n'; + os << edge.first().id() << ' ' << edge.second().id() << ' ' + << edge._properties << '\n'; } }; @@ -568,9 +608,9 @@ class graph final { const auto print_incident_edges = [this, &os](const types::id_type vertex_id) { for (const auto& edge : this->_impl.adjacent_edges(vertex_id)) { - if (edge.first_id() != vertex_id) + if (edge.first().id() != vertex_id) continue; // vertex is not the source - os << edge.first_id() << ' ' << edge.second_id() << '\n'; + os << edge.first().id() << ' ' << edge.second().id() << '\n'; } }; @@ -644,7 +684,10 @@ class graph final { } } - vetex_list_type _vertices{}; + types::size_type _n_vertices = 0uz; + [[no_unique_address]] vertex_properties_map_type _vertex_properties{ + }; // add conditional getter and tests + implementation_type _impl{}; }; diff --git a/include/gl/graph_utility.hpp b/include/gl/graph_utility.hpp index 5053ddb3..337002d6 100644 --- a/include/gl/graph_utility.hpp +++ b/include/gl/graph_utility.hpp @@ -56,7 +56,7 @@ template const typename GraphType::edge_type& edge ) { if constexpr (type_traits::c_weight_properties_type) - return edge.properties.weight; + return edge.properties().weight; else return static_cast(1ll); } diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index 3da4ad9d..529619bc 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -90,8 +90,8 @@ class adjacency_list final { return specialized_impl::degree_map(*this); } - gl_attr_force_inline void remove_vertex(const vertex_type& vertex) { - specialized_impl::remove_vertex(*this, vertex); + gl_attr_force_inline void remove_vertex(const types::id_type& vertex_id) { + specialized_impl::remove_vertex(*this, vertex_id); } // --- edge methods --- @@ -113,44 +113,30 @@ class adjacency_list final { [[nodiscard]] bool has_edge(const types::id_type first_id, const types::id_type second_id) const { - if (not (this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(second_id))) - return false; - const auto& adjacent_edges = this->_list[first_id]; return std::ranges::find_if( adjacent_edges, [first_id, second_id](const auto& edge) { - return specialized_impl::is_edge_incident_to(edge, second_id, first_id); + return specialized_impl::is_edge_incident_with(edge, second_id, first_id); } ) != adjacent_edges.end(); } - [[nodiscard]] bool has_edge(const edge_type& edge) const { - const auto first_id = edge.first_id(); - if (not ( - this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(edge.second_id()) - )) - return false; - + [[nodiscard]] gl_attr_force_inline bool has_edge(const edge_type& edge) const { // find the edge by address - const auto& adjacent_edges = this->_list[first_id]; - return std::ranges::find( - adjacent_edges, &edge, typename specialized_impl::address_projection{} - ) - != adjacent_edges.end(); + return std::ranges::contains( + this->_list[edge.first().id()], &edge, typename specialized_impl::address_projection{} + ); } [[nodiscard]] types::optional_ref get_edge( const types::id_type first_id, const types::id_type second_id ) const { - if (not (this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(second_id))) - return std::nullopt; - const auto& adjacent_edges = this->_list[first_id]; const auto it = std::ranges::find_if(adjacent_edges, [first_id, second_id](const auto& edge) { - return specialized_impl::is_edge_incident_to(edge, second_id, first_id); + return specialized_impl::is_edge_incident_with(edge, second_id, first_id); }); if (it == adjacent_edges.cend()) @@ -161,17 +147,13 @@ class adjacency_list final { [[nodiscard]] auto get_edges(const types::id_type first_id, const types::id_type second_id) const { using edge_ref_set = std::vector>; - - if (not (this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(second_id))) - return edge_ref_set{}; - const auto& adjacent_edges = this->_list[first_id]; edge_ref_set matching_edges{}; matching_edges.reserve(adjacent_edges.size()); for (const auto& edge : adjacent_edges) - if (specialized_impl::is_edge_incident_to(edge, second_id, first_id)) + if (specialized_impl::is_edge_incident_with(edge, second_id, first_id)) matching_edges.emplace_back(*edge); matching_edges.shrink_to_fit(); @@ -193,11 +175,6 @@ class adjacency_list final { using specialized_impl = typename specialized::list_impl_traits::type; friend specialized_impl; - [[nodiscard]] gl_attr_force_inline bool _is_valid_vertex_id(const types::id_type vertex_id - ) const { - return vertex_id < this->_list.size(); - } - list_type _list{}; types::size_type _n_unique_edges{constants::default_size}; }; diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index caa9ec78..e771ce5e 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -110,8 +110,8 @@ class adjacency_matrix final { return specialized_impl::degree_map(*this); } - gl_attr_force_inline void remove_vertex(const vertex_type& vertex) { - specialized_impl::remove_vertex(*this, vertex.id()); + gl_attr_force_inline void remove_vertex(const types::id_type vertex_id) { + specialized_impl::remove_vertex(*this, vertex_id); } // --- edge methods --- @@ -138,8 +138,8 @@ class adjacency_matrix final { } [[nodiscard]] bool has_edge(const edge_type& edge) const { - const auto first_id = edge.first_id(); - const auto second_id = edge.second_id(); + const auto first_id = edge.first().id(); + const auto second_id = edge.second().id(); if (not (this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(second_id))) return false; diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 454a53b9..0945b191 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -33,8 +34,8 @@ requires std::is_invocable_r_v< if (it == edge_set.end()) throw std::invalid_argument(std::format( "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge->first_id(), - edge->second_id(), + edge->first().id(), + edge->second().id(), io::format(edge) )); @@ -69,7 +70,7 @@ struct directed_adjacency_list { types::size_type in_deg = constants::default_size; for (const auto& adjacent_edges : self._list) in_deg += std::ranges::count(adjacent_edges, vertex_id, [](const auto& edge) { - return edge->second_id(); + return edge->second().id(); }); return in_deg; @@ -92,7 +93,7 @@ struct directed_adjacency_list { for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { std::ranges::for_each(self._list[id], [&in_degree_map](const auto& edge) { - ++in_degree_map[edge->second_id()]; + ++in_degree_map[edge->second().id()]; }); } @@ -115,25 +116,24 @@ struct directed_adjacency_list { // update in degrees std::ranges::for_each(self._list[id], [°ree_map](const auto& edge) { - ++degree_map[edge->second_id()]; + ++degree_map[edge->second().id()]; }); } return degree_map; } - static void remove_vertex(impl_type& self, const vertex_type& vertex) { - const auto vertex_id = vertex.id(); - + static void remove_vertex(impl_type& self, const types::id_type vertex_id) { // remove all edges incident to the vertex for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { auto& adj_edges = self._list[id]; if (id == vertex_id or adj_edges.empty()) continue; - const auto rem_subrange = std::ranges::remove_if( - adj_edges, [&vertex](const auto& edge) { return edge->is_incident_with(vertex); } - ); + const auto rem_subrange = + std::ranges::remove_if(adj_edges, [vertex_id](const auto& edge) { + return edge->is_incident_with(vertex_id); + }); self._n_unique_edges -= std::ranges::distance(rem_subrange.begin(), rem_subrange.end()); adj_edges.erase(rem_subrange.begin(), rem_subrange.end()); } @@ -144,7 +144,7 @@ struct directed_adjacency_list { } static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - auto& adjacent_edges_first = self._list[edge->first_id()]; + auto& adjacent_edges_first = self._list[edge->first().id()]; auto& new_edge = adjacent_edges_first.emplace_back(std::move(edge)); ++self._n_unique_edges; return *new_edge; @@ -162,7 +162,7 @@ struct directed_adjacency_list { self._n_unique_edges += new_edges.size(); } - [[nodiscard]] gl_attr_force_inline static bool is_edge_incident_to( + [[nodiscard]] gl_attr_force_inline static bool is_edge_incident_with( const edge_ptr_type& edge, const types::id_type vertex_id, [[maybe_unused]] const types::id_type source_id @@ -173,11 +173,11 @@ struct directed_adjacency_list { list at which the edge is located, but the parameter is necessary to match the function signature for undirected adjacency list */ - return edge->second_id() == vertex_id; + return edge->second().id() == vertex_id; } static void remove_edge(impl_type& self, const edge_type& edge) { - auto& adj_edges = self._list.at(edge.first_id()); + auto& adj_edges = self._list.at(edge.first().id()); adj_edges.erase(detail::strict_find(adj_edges, &edge)); --self._n_unique_edges; } @@ -246,23 +246,23 @@ struct undirected_adjacency_list { return degree_map; } - static void remove_vertex(impl_type& self, const vertex_type& vertex) { - const auto vertex_id = vertex.id(); + static void remove_vertex(impl_type& self, const types::id_type vertex_id) { std::unordered_set incident_vertex_id_set; // select the vertices incident with the removed vertex for (const auto& edge : self._list[vertex_id]) { if (edge->is_loop()) continue; // will be removed with the vertex's list - incident_vertex_id_set.insert(edge->incident_vertex(vertex).id()); + incident_vertex_id_set.insert(edge->incident_vertex(vertex_id).id()); } // remove all edges incident with the vertex (scan only the selected vertices) for (const auto& incident_vertex_id : incident_vertex_id_set) { auto& adj_edges = self._list[incident_vertex_id]; - const auto rem_subrange = std::ranges::remove_if( - adj_edges, [&vertex](const auto& edge) { return edge->is_incident_with(vertex); } - ); + const auto rem_subrange = + std::ranges::remove_if(adj_edges, [vertex_id](const auto& edge) { + return edge->is_incident_with(vertex_id); + }); adj_edges.erase(rem_subrange.begin(), rem_subrange.end()); } @@ -272,10 +272,10 @@ struct undirected_adjacency_list { } static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - auto& adjacent_edges_first = self._list[edge->first_id()]; + auto& adjacent_edges_first = self._list[edge->first().id()]; if (not edge->is_loop()) - self._list[edge->second_id()].push_back(edge); + self._list[edge->second().id()].push_back(edge); adjacent_edges_first.emplace_back(std::move(edge)); ++self._n_unique_edges; @@ -290,33 +290,33 @@ struct undirected_adjacency_list { for (auto& edge : new_edges) { if (not edge->is_loop()) - self._list[edge->second_id()].push_back(edge); + self._list[edge->second().id()].push_back(edge); adjacent_edges_source.emplace_back(std::move(edge)); } self._n_unique_edges += new_edges.size(); } - [[nodiscard]] inline static bool is_edge_incident_to( + [[nodiscard]] inline static bool is_edge_incident_with( const edge_ptr_type& edge, const types::id_type vertex_id, const types::id_type source_id ) { - if (edge->first_id() == source_id) - return edge->second_id() == vertex_id; - else if (edge->second_id() == source_id) - return edge->first_id() == vertex_id; + if (edge->first().id() == source_id) + return edge->second().id() == vertex_id; + else if (edge->second().id() == source_id) + return edge->first().id() == vertex_id; return false; } static void remove_edge(impl_type& self, const edge_type& edge) { if (edge.is_loop()) { - auto& adj_edges_first = self._list.at(edge.first_id()); + auto& adj_edges_first = self._list.at(edge.first().id()); adj_edges_first.erase( detail::strict_find(adj_edges_first, &edge) ); } else { - auto& adj_edges_first = self._list.at(edge.first_id()); - auto& adj_edges_second = self._list.at(edge.second_id()); + auto& adj_edges_first = self._list.at(edge.first().id()); + auto& adj_edges_second = self._list.at(edge.second().id()); adj_edges_first.erase( detail::strict_find(adj_edges_first, &edge) diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 92f91918..2a77443f 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -23,12 +23,12 @@ template AdjacencyMatrix> typename AdjacencyMatrix::matrix_type& matrix, const typename AdjacencyMatrix::edge_type* edge ) { // get the edge and validate the address - auto& matrix_element = matrix.at(edge->first_id()).at(edge->second_id()); + auto& matrix_element = matrix.at(edge->first().id()).at(edge->second().id()); if (edge != matrix_element.get()) throw std::invalid_argument(std::format( "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge->first_id(), - edge->second_id(), + edge->first().id(), + edge->second().id(), io::format(edge) )); @@ -136,7 +136,7 @@ struct directed_adjacency_matrix { static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { detail::check_edge_override(self, edge); - auto& matrix_element = self._matrix[edge->first_id()][edge->second_id()]; + auto& matrix_element = self._matrix[edge->first().id()][edge->second().id()]; matrix_element = std::move(edge); ++self._n_unique_edges; @@ -152,7 +152,7 @@ struct directed_adjacency_matrix { auto& matrix_row_source = self._matrix[source_id]; for (auto& edge : new_edges) - matrix_row_source[edge->second_id()] = std::move(edge); + matrix_row_source[edge->second().id()] = std::move(edge); self._n_unique_edges += new_edges.size(); } @@ -231,8 +231,8 @@ struct undirected_adjacency_matrix { static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { detail::check_edge_override(self, edge); - const auto first_id = edge->first_id(); - const auto second_id = edge->second_id(); + const auto first_id = edge->first().id(); + const auto second_id = edge->second().id(); if (not edge->is_loop()) self._matrix[second_id][first_id] = edge; @@ -253,8 +253,8 @@ struct undirected_adjacency_matrix { auto& matrix_row_source = self._matrix[source_id]; for (auto& edge : new_edges) { if (not edge->is_loop()) - self._matrix[edge->second_id()][source_id] = edge; - matrix_row_source[edge->second_id()] = std::move(edge); + self._matrix[edge->second().id()][source_id] = edge; + matrix_row_source[edge->second().id()] = std::move(edge); } self._n_unique_edges += new_edges.size(); @@ -265,8 +265,8 @@ struct undirected_adjacency_matrix { detail::strict_get(self._matrix, &edge) = nullptr; } else { - const auto first_id = edge.first_id(); - const auto second_id = edge.second_id(); + const auto first_id = edge.first().id(); + const auto second_id = edge.second().id(); detail::strict_get(self._matrix, &edge) = nullptr; // if the edge was found in the first matrix cell, diff --git a/include/gl/types/iterator_range.hpp b/include/gl/types/iterator_range.hpp index 471fe096..a2fc1110 100644 --- a/include/gl/types/iterator_range.hpp +++ b/include/gl/types/iterator_range.hpp @@ -46,9 +46,7 @@ class iterator_range { public: using iterator = Iterator; -#if __cplusplus >= 202302L using const_iterator = std::const_iterator; -#endif using distance_type = std::ptrdiff_t; using value_type = std::remove_reference_t; diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index 62fd1cd8..c60d1653 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -25,6 +25,7 @@ namespace types { // --- common properties --- using empty_properties = std::monostate; +using empty_properties_map = std::monostate; class name_property #ifndef _GL_PROPERTY_TYPES_NOT_FINAL @@ -36,7 +37,7 @@ class name_property name_property() = default; - name_property(std::string_view name) : _name(name) {} + name_property(const std::string_view name) : _name(name) {} name_property(const name_property&) = default; name_property(name_property&&) = default; @@ -50,6 +51,11 @@ class name_property virtual ~name_property() = default; #endif + name_property& operator=(const std::string_view name) { + this->_name = name; + return *this; + } + // clang-format off // gl_attr_force_inline misplacement @@ -62,6 +68,14 @@ class name_property [[nodiscard]] bool operator==(const name_property&) const = default; [[nodiscard]] auto operator<=>(const name_property&) const = default; + [[nodiscard]] bool operator==(const std::string_view name) const { + return this->_name == name; + } + + [[nodiscard]] auto operator<=>(const std::string_view name) const { + return this->_name <=> name; + } + friend std::ostream& operator<<(std::ostream& os, const name_property& property) { os << std::quoted(property._name); return os; diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index bc225be9..34da2b33 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -20,19 +20,28 @@ class vertex_descriptor final { public: using type = std::type_identity_t>; using properties_type = Properties; + using properties_ref_type = std::conditional_t< + type_traits::is_default_properties_type_v, + types::empty_properties, + properties_type&>; template GraphTraits> friend class graph; vertex_descriptor() = delete; - vertex_descriptor(const vertex_descriptor&) = delete; - vertex_descriptor& operator=(const vertex_descriptor&) = delete; - explicit vertex_descriptor(const types::id_type id) : _id(id) {} + // TODO: private + explicit vertex_descriptor(const types::id_type id) + requires(type_traits::is_default_properties_type_v) + : _id(id) {} - explicit vertex_descriptor(const types::id_type id, const properties_type& properties) + // TODO: private + explicit vertex_descriptor(const types::id_type id, properties_type& properties) requires(not type_traits::is_default_properties_type_v) - : _id(id), properties(properties) {} + : _id(id), _properties(properties) {} + + vertex_descriptor(const vertex_descriptor&) = default; + vertex_descriptor& operator=(const vertex_descriptor&) = default; vertex_descriptor(vertex_descriptor&&) = default; vertex_descriptor& operator=(vertex_descriptor&&) = default; @@ -51,7 +60,9 @@ class vertex_descriptor final { return this->_id; } - [[no_unique_address]] mutable properties_type properties{}; + [[nodiscard]] gl_attr_force_inline properties_ref_type properties() const { + return this->_properties; + } friend inline std::ostream& operator<<(std::ostream& os, const vertex_descriptor& vertex) { vertex._write(os); @@ -71,10 +82,10 @@ class vertex_descriptor final { } if (io::is_option_set(os, io::graph_option::verbose)) { - os << "[id: " << this->_id << " | properties: " << this->properties << "]"; + os << "[id: " << this->_id << " | properties: " << this->_properties << "]"; } else { - os << "[" << this->_id << " | " << this->properties << "]"; + os << "[" << this->_id << " | " << this->_properties << "]"; } } } @@ -88,7 +99,8 @@ class vertex_descriptor final { } } - types::id_type _id; + mutable types::id_type _id; + [[no_unique_address]] properties_ref_type _properties; }; template diff --git a/include/gl/views.hpp b/include/gl/views.hpp new file mode 100644 index 00000000..d8a6e977 --- /dev/null +++ b/include/gl/views.hpp @@ -0,0 +1,11 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +namespace gl::views { + +inline constexpr auto deref = std::views::transform([](auto&& p) -> decltype(auto) { return *p; }); + +} // namespace gl::views diff --git a/tests/include/testing/gl/alg_common.hpp b/tests/include/testing/gl/alg_common.hpp index 8bde5308..9dbcc37e 100644 --- a/tests/include/testing/gl/alg_common.hpp +++ b/tests/include/testing/gl/alg_common.hpp @@ -51,7 +51,7 @@ template VertexType> requires(std::same_as) struct vertex_visited_projection { [[nodiscard]] bool operator()(const VertexType& vertex) const { - return vertex.properties.visited; + return vertex.properties().visited; } }; diff --git a/tests/include/testing/gl/constants.hpp b/tests/include/testing/gl/constants.hpp index 0e3deb04..bab05d53 100644 --- a/tests/include/testing/gl/constants.hpp +++ b/tests/include/testing/gl/constants.hpp @@ -4,6 +4,7 @@ #include +#include #include #define IC inline constexpr @@ -28,11 +29,12 @@ IC gl::types::size_type depth = 5ull; IC gl::types::size_type first_element_idx = zero; IC gl::types::size_type last_element_idx = n_elements - one_element; -IC gl::types::size_type out_of_range_elemenet_idx = n_elements; +IC gl::types::size_type out_of_range_element_idx = n_elements; IC gl::types::id_type vertex_id_1 = first_element_idx; IC gl::types::id_type vertex_id_2 = vertex_id_1 + one_element; IC gl::types::id_type vertex_id_3 = vertex_id_2 + one_element; +IC gl::types::id_type invalid_id = std::numeric_limits::max(); IC auto vertex_id_view = std::views::iota(first_element_idx, n_elements); diff --git a/tests/include/testing/gl/io_common.hpp b/tests/include/testing/gl/io_common.hpp index f58402ec..eefebbaf 100644 --- a/tests/include/testing/gl/io_common.hpp +++ b/tests/include/testing/gl/io_common.hpp @@ -14,14 +14,14 @@ void verify_graph_structure(const GraphType& actual, const GraphType& expected) // verify that the edges of the in graph are equivalent to the edges of the out graph CHECK(std::ranges::all_of(actual.vertices(), [&](const auto& v_actual) { return std::ranges::all_of(actual.adjacent_edges(v_actual), [&](const auto& edge) { - return expected.has_edge(edge.first_id(), edge.second_id()); + return expected.has_edge(edge.first().id(), edge.second().id()); }); })); } template void verify_vertex_properties(const GraphType& actual, const GraphType& expected) { - const auto properties_proj = [](const auto& vertex) { return vertex.properties; }; + const auto properties_proj = [](const auto& item) { return item.properties(); }; CHECK(std::ranges::equal( actual.vertices(), @@ -34,13 +34,14 @@ void verify_vertex_properties(const GraphType& actual, const GraphType& expected template void verify_edge_properties(const GraphType& actual, const GraphType& expected) { - const auto properties_proj = [](const auto& vertex) { return vertex.properties; }; - CHECK(std::ranges::all_of(actual.vertices(), [&](const auto& v_actual) { return std::ranges::all_of(actual.adjacent_edges(v_actual), [&](const auto& edge) { // get edge returns optional - return edge.properties - == expected.get_edge(edge.first_id(), edge.second_id()).value().get().properties; + return edge.properties() + == expected.get_edge(edge.first().id(), edge.second().id()) + .value() + .get() + .properties(); }); })); } diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index bd91baf6..20d5c521 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -133,16 +133,6 @@ TEST_CASE_FIXTURE( CHECK_EQ(&new_edge_extracted, &new_edge); } -TEST_CASE_FIXTURE( - test_directed_adjacency_list, "has_edge(id, id) should return false if either id is invalid" -) { - CHECK_FALSE(sut.has_edge(constants::out_of_range_elemenet_idx, constants::vertex_id_2)); - CHECK_FALSE(sut.has_edge(constants::vertex_id_1, constants::out_of_range_elemenet_idx)); - CHECK_FALSE( - sut.has_edge(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - ); -} - TEST_CASE_FIXTURE( test_directed_adjacency_list, "has_edge(id, id) should return true if there is an edge in the graph which connects vertices " @@ -158,7 +148,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_directed_adjacency_list, - "has_edge(edge_ptr) should return true if the given edge is present in the graph" + "has_edge(edge) should return true if the given edge is present in the graph" ) { const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); @@ -173,20 +163,6 @@ TEST_CASE_FIXTURE( vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] }; CHECK_FALSE(sut.has_edge(not_present_edge)); - - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - CHECK_FALSE(sut.has_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]})); - CHECK_FALSE(sut.has_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex})); -} - -TEST_CASE_FIXTURE( - test_directed_adjacency_list, "get_edge(id, id) should return nullopt if either id is invalid" -) { - CHECK_FALSE(sut.get_edge(constants::out_of_range_elemenet_idx, constants::vertex_id_2)); - CHECK_FALSE(sut.get_edge(constants::vertex_id_1, constants::out_of_range_elemenet_idx)); - CHECK_FALSE( - sut.get_edge(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - ); } TEST_CASE_FIXTURE( @@ -211,16 +187,6 @@ TEST_CASE_FIXTURE( CHECK_FALSE(sut.get_edge(constants::vertex_id_2, constants::vertex_id_2)); } -TEST_CASE_FIXTURE( - test_directed_adjacency_list, - "get_edges(id, id) should return an empty view if either id is invalid" -) { - CHECK(sut.get_edges(constants::out_of_range_elemenet_idx, constants::vertex_id_2).empty()); - CHECK(sut.get_edges(constants::vertex_id_1, constants::out_of_range_elemenet_idx).empty()); - CHECK(sut.get_edges(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - .empty()); -} - TEST_CASE_FIXTURE( test_directed_adjacency_list, "get_edges(id, id) should return an empty if there is no edge connecting the given vertices" @@ -251,19 +217,6 @@ TEST_CASE_FIXTURE( } TEST_CASE_FIXTURE(test_directed_adjacency_list, "remove_edge should throw when an edge is invalid") { - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - - CHECK_THROWS_AS( - sut.remove_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]}), - std::out_of_range - ); - - // the edge with an invalid vertex will not be found in the list - CHECK_THROWS_AS( - sut.remove_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex}), - std::invalid_argument - ); - // not existing edge between valid vertices CHECK_THROWS_AS( sut.remove_edge( @@ -402,8 +355,8 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - const auto& removed_vertex = vertices[constants::first_element_idx]; - sut.remove_vertex(removed_vertex); + const auto removed_vertex_id = constants::first_element_idx; + sut.remove_vertex(removed_vertex_id); constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; constexpr auto n_incident_edges_after_remove = @@ -416,8 +369,8 @@ TEST_CASE_FIXTURE( constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { const auto adjacent_edges = sut.adjacent_edges(vertex_id); REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [&removed_vertex](const auto& edge) { - return edge.is_incident_with(removed_vertex); + CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { + return edge.is_incident_with(removed_vertex_id); })); } } @@ -508,16 +461,6 @@ TEST_CASE_FIXTURE( CHECK_EQ(&new_edge_extracted_1, &new_edge); } -TEST_CASE_FIXTURE( - test_directed_adjacency_list, "has_edge(id, id) should return false if either id is invalid" -) { - CHECK_FALSE(sut.has_edge(constants::out_of_range_elemenet_idx, constants::vertex_id_2)); - CHECK_FALSE(sut.has_edge(constants::vertex_id_1, constants::out_of_range_elemenet_idx)); - CHECK_FALSE( - sut.has_edge(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - ); -} - TEST_CASE_FIXTURE( test_undirected_adjacency_list, "has_edge(id, id) should return true if there is an edge in the graph which connects vertices " @@ -548,20 +491,6 @@ TEST_CASE_FIXTURE( vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] }; CHECK_FALSE(sut.has_edge(not_present_edge)); - - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - CHECK_FALSE(sut.has_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]})); - CHECK_FALSE(sut.has_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex})); -} - -TEST_CASE_FIXTURE( - test_undirected_adjacency_list, "get_edge(id, id) should return nullopt if either id is invalid" -) { - CHECK_FALSE(sut.get_edge(constants::out_of_range_elemenet_idx, constants::vertex_id_2)); - CHECK_FALSE(sut.get_edge(constants::vertex_id_1, constants::out_of_range_elemenet_idx)); - CHECK_FALSE( - sut.get_edge(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - ); } TEST_CASE_FIXTURE( @@ -589,16 +518,6 @@ TEST_CASE_FIXTURE( CHECK_NE(&edge_opt_2->get(), &edge_2); } -TEST_CASE_FIXTURE( - test_undirected_adjacency_list, - "get_edges(id, id) should return an empty view if either id is invalid" -) { - CHECK(sut.get_edges(constants::out_of_range_elemenet_idx, constants::vertex_id_2).empty()); - CHECK(sut.get_edges(constants::vertex_id_1, constants::out_of_range_elemenet_idx).empty()); - CHECK(sut.get_edges(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - .empty()); -} - TEST_CASE_FIXTURE( test_undirected_adjacency_list, "get_edges(id, id) should return an empty if there is no edge connecting the given vertices" @@ -637,17 +556,6 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_undirected_adjacency_list, "remove_edge should throw when an edge is invalid" ) { - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - - CHECK_THROWS_AS( - sut.remove_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]}), - std::out_of_range - ); - CHECK_THROWS_AS( - sut.remove_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex}), - std::out_of_range - ); - // not existing edge between valid vertices CHECK_THROWS_AS( sut.remove_edge( @@ -669,7 +577,7 @@ TEST_CASE_FIXTURE( const auto& edge_to_remove = adjacent_edges_first[constants::first_element_idx]; - const auto second_id = edge_to_remove.second_id(); + const auto second_id = edge_to_remove.second().id(); REQUIRE_EQ(sut.adjacent_edges(second_id).distance(), constants::one_element); sut.remove_edge(edge_to_remove); @@ -771,8 +679,8 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - const auto& removed_vertex = vertices[constants::first_element_idx]; - sut.remove_vertex(removed_vertex); + const auto removed_vertex_id = constants::first_element_idx; + sut.remove_vertex(removed_vertex_id); constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; constexpr auto n_incident_edges_after_remove = @@ -785,8 +693,8 @@ TEST_CASE_FIXTURE( constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { const auto adjacent_edges = sut.adjacent_edges(vertex_id); REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [&removed_vertex](const auto& edge) { - return edge.is_incident_with(removed_vertex); + CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { + return edge.is_incident_with(removed_vertex_id); })); } } diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index 72b62656..296d3eea 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -209,20 +209,6 @@ TEST_CASE_FIXTURE( vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] }; CHECK_FALSE(sut.has_edge(not_present_edge)); - - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - CHECK_FALSE(sut.has_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]})); - CHECK_FALSE(sut.has_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex})); -} - -TEST_CASE_FIXTURE( - test_directed_adjacency_matrix, "get_edge(id, id) should return nullopt if either id is invalid" -) { - CHECK_FALSE(sut.get_edge(constants::out_of_range_elemenet_idx, constants::vertex_id_2)); - CHECK_FALSE(sut.get_edge(constants::vertex_id_1, constants::out_of_range_elemenet_idx)); - CHECK_FALSE( - sut.get_edge(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - ); } TEST_CASE_FIXTURE( @@ -248,17 +234,6 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "remove_edge should throw when an edge is invalid" ) { - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - - CHECK_THROWS_AS( - sut.remove_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]}), - std::out_of_range - ); - CHECK_THROWS_AS( - sut.remove_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex}), - std::out_of_range - ); - // not existing edge between valid vertices CHECK_THROWS_AS( sut.remove_edge( @@ -398,8 +373,8 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - const auto& removed_vertex = vertices[constants::first_element_idx]; - sut.remove_vertex(removed_vertex); + const auto removed_vertex_id = constants::first_element_idx; + sut.remove_vertex(removed_vertex_id); constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; constexpr auto n_incident_edges_after_remove = @@ -412,8 +387,8 @@ TEST_CASE_FIXTURE( constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { const auto adjacent_edges = sut.adjacent_edges(vertex_id); REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [&removed_vertex](const auto& edge) { - return edge.is_incident_with(removed_vertex); + CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { + return edge.is_incident_with(removed_vertex_id); })); } } @@ -534,21 +509,6 @@ TEST_CASE_FIXTURE( vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] }; CHECK_FALSE(sut.has_edge(not_present_edge)); - - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - CHECK_FALSE(sut.has_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]})); - CHECK_FALSE(sut.has_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex})); -} - -TEST_CASE_FIXTURE( - test_undirected_adjacency_matrix, - "get_edge(id, id) should return nullopt if either id is invalid" -) { - CHECK_FALSE(sut.get_edge(constants::out_of_range_elemenet_idx, constants::vertex_id_2)); - CHECK_FALSE(sut.get_edge(constants::vertex_id_1, constants::out_of_range_elemenet_idx)); - CHECK_FALSE( - sut.get_edge(constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx) - ); } TEST_CASE_FIXTURE( @@ -576,17 +536,6 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "remove_edge should throw when an edge is invalid" ) { - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - - CHECK_THROWS_AS( - sut.remove_edge(edge_type{out_of_range_vertex, vertices[constants::vertex_id_2]}), - std::out_of_range - ); - CHECK_THROWS_AS( - sut.remove_edge(edge_type{vertices[constants::vertex_id_1], out_of_range_vertex}), - std::out_of_range - ); - // not existing edge between valid vertices CHECK_THROWS_AS( sut.remove_edge( @@ -608,7 +557,7 @@ TEST_CASE_FIXTURE( const auto& edge_to_remove = adjacent_edges_first[constants::first_element_idx]; - const auto second_id = edge_to_remove.second_id(); + const auto second_id = edge_to_remove.second().id(); REQUIRE_EQ(sut.adjacent_edges(second_id).distance(), constants::one_element); sut.remove_edge(edge_to_remove); @@ -710,8 +659,8 @@ TEST_CASE_FIXTURE( ) { init_complete_graph(); - const auto& removed_vertex = vertices[constants::first_element_idx]; - sut.remove_vertex(removed_vertex); + const auto removed_vertex_id = constants::first_element_idx; + sut.remove_vertex(removed_vertex_id); constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; constexpr auto n_incident_edges_after_remove = @@ -724,8 +673,8 @@ TEST_CASE_FIXTURE( constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { const auto adjacent_edges = sut.adjacent_edges(vertex_id); REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [&removed_vertex](const auto& edge) { - return edge.is_incident_with(removed_vertex); + CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { + return edge.is_incident_with(removed_vertex_id); })); } } diff --git a/tests/source/gl/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp index 2350d609..dee57627 100644 --- a/tests/source/gl/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -67,7 +67,7 @@ TEST_CASE_TEMPLATE_DEFINE( }, [&](const auto& vertex) { // postvisit postvisit_order.push_back(vertex.id()); - vertex.properties.visited = true; + vertex.properties().visited = true; } ); diff --git a/tests/source/gl/test_alg_coloring.cpp b/tests/source/gl/test_alg_coloring.cpp index fcd6a720..1903ad8b 100644 --- a/tests/source/gl/test_alg_coloring.cpp +++ b/tests/source/gl/test_alg_coloring.cpp @@ -83,6 +83,7 @@ TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_te fs::path coloring_file_path = alg_common::data_path / "bicoloring_bipartite_graph_coloring.txt"; + const auto coloring_values = alg_common::load_list(sut.n_vertices(), coloring_file_path); @@ -111,7 +112,7 @@ TEST_CASE_TEMPLATE_DEFINE("bipartite coloring tests", TraitsType, traits_type_te sut.vertices(), expected_coloring, std::ranges::equal_to{}, - [](const auto& vertex) { return vertex.properties.color; } + [](const auto& vertex) { return vertex.properties().color; } )); } diff --git a/tests/source/gl/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp index dce59e9d..96b2e03b 100644 --- a/tests/source/gl/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -75,7 +75,7 @@ TEST_CASE_TEMPLATE_DEFINE( }, [&](const auto& vertex) { // postvisit postvisit_order.push_back(vertex.id()); - vertex.properties.visited = true; + vertex.properties().visited = true; } ); @@ -252,7 +252,7 @@ TEST_CASE_TEMPLATE_DEFINE( }, [&](const auto& vertex) { // postvisit postvisit_order.push_back(vertex.id()); - vertex.properties.visited = true; + vertex.properties().visited = true; } ); diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index 17ca5535..a903f565 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -31,7 +31,8 @@ TEST_CASE_TEMPLATE_DEFINE( sut.get_edge(constants::vertex_id_1, constants::vertex_id_2) .value() .get() - .properties.weight = -static_cast(constants::n_elements_alg); + .properties() + .weight = -static_cast(constants::n_elements_alg); CHECK_THROWS_AS( func::discard_result(gl::algorithm::dijkstra_shortest_paths(sut, constants::vertex_id_1) @@ -59,7 +60,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto edge_weight = static_cast(constants::n_elements_alg); for (gl::types::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; id++) { - sut.get_edge(constants::vertex_id_1, id).value().get().properties.weight = + sut.get_edge(constants::vertex_id_1, id).value().get().properties().weight = edge_weight; expected_distances.push_back(edge_weight); } diff --git a/tests/source/gl/test_alg_mst.cpp b/tests/source/gl/test_alg_mst.cpp index 38cdf491..fb579fe9 100644 --- a/tests/source/gl/test_alg_mst.cpp +++ b/tests/source/gl/test_alg_mst.cpp @@ -41,8 +41,8 @@ TEST_CASE_TEMPLATE_DEFINE( const weight_type edge_weight = constants::three; for (const auto vertex_id : sut.vertex_ids()) { for (const auto& edge : sut.adjacent_edges(vertex_id)) { - edge.properties.weight = edge_weight; - expected_edges.emplace_back(edge.first_id(), edge.second_id()); + edge.properties().weight = edge_weight; + expected_edges.emplace_back(edge.first().id(), edge.second().id()); } } @@ -124,7 +124,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) for (const auto& edge : sut.adjacent_edges(vertex_id)) - expected_edges.emplace_back(edge.first_id(), edge.second_id()); + expected_edges.emplace_back(edge.first().id(), edge.second().id()); const weight_type expected_weight = sut.n_vertices() - constants::one; @@ -181,8 +181,8 @@ TEST_CASE_TEMPLATE_DEFINE( const weight_type edge_weight = constants::three; for (const auto vertex_id : sut.vertex_ids()) { for (const auto& edge : sut.adjacent_edges(vertex_id)) { - edge.properties.weight = edge_weight; - expected_edges.emplace_back(edge.first_id(), edge.second_id()); + edge.properties().weight = edge_weight; + expected_edges.emplace_back(edge.first().id(), edge.second().id()); } } @@ -264,7 +264,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) for (const auto& edge : sut.adjacent_edges(vertex_id)) - expected_edges.emplace_back(edge.first_id(), edge.second_id()); + expected_edges.emplace_back(edge.first().id(), edge.second().id()); const weight_type expected_weight = sut.n_vertices() - constants::one; diff --git a/tests/source/gl/test_edge_descriptor.cpp b/tests/source/gl/test_edge_descriptor.cpp index 6c8d6bd7..e4210da1 100644 --- a/tests/source/gl/test_edge_descriptor.cpp +++ b/tests/source/gl/test_edge_descriptor.cpp @@ -17,7 +17,7 @@ struct test_edge_descriptor { vertex_type vd_2{constants::vertex_id_2}; vertex_type vd_3{constants::vertex_id_3}; - vertex_type invalid_vd{constants::vertex_id_1}; + vertex_type invalid_vd{constants::invalid_id}; }; TEST_CASE_FIXTURE( @@ -49,7 +49,7 @@ TEST_CASE_TEMPLATE_DEFINE( const types::used_property used{true}; const EdgeType sut{fixture.vd_1, fixture.vd_2, used}; - CHECK_EQ(sut.properties, used); + CHECK_EQ(sut.properties(), used); } // TODO: fix .clang-format to split such lines @@ -68,14 +68,6 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(vertices.second, fixture.vd_2); } - SUBCASE("first should return the first vertex descriptor the edge was initialized with") { - CHECK_EQ(sut.first(), fixture.vd_1); - } - - SUBCASE("second should return the second vertex descriptor the edge was initialized with") { - CHECK_EQ(sut.second(), fixture.vd_2); - } - SUBCASE("incident_vertex_ids should return the pair of ids of the vertices the edge was " "initialized with") { const auto& vertex_ids = sut.incident_vertex_ids(); @@ -83,14 +75,12 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(vertex_ids.second, fixture.vd_2.id()); } - SUBCASE("first_id should return the id of the first vertex descriptor the edge was initialized " - "with") { - CHECK_EQ(sut.first_id(), fixture.vd_1.id()); + SUBCASE("first should return the first vertex descriptor the edge was initialized with") { + CHECK_EQ(sut.first(), fixture.vd_1); } - SUBCASE("second should return the id of the second vertex descriptor the edge was initialized " - "with") { - CHECK_EQ(sut.second_id(), fixture.vd_2.id()); + SUBCASE("second should return the second vertex descriptor the edge was initialized with") { + CHECK_EQ(sut.second(), fixture.vd_2); } SUBCASE("incident_vertex should throw if input vertex is not incident with the edge") { @@ -104,18 +94,6 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(sut.incident_vertex(fixture.vd_2), fixture.vd_1); } - SUBCASE("incident_vertex_id should throw if input vertex id is not valid") { - CHECK_THROWS_AS( - func::discard_result(sut.incident_vertex_id(fixture.vd_3.id())), std::invalid_argument - ); - } - - SUBCASE("incident_vertex_id should return the id of the vertex incident with the input vertex" - ) { - CHECK_EQ(sut.incident_vertex_id(fixture.vd_1.id()), fixture.vd_2.id()); - CHECK_EQ(sut.incident_vertex_id(fixture.vd_2.id()), fixture.vd_1.id()); - } - SUBCASE("is_incident_with should return true when the given vertex is one of the connected " "vertices") { CHECK(sut.is_incident_with(fixture.vd_1)); diff --git a/tests/source/gl/test_edge_tags.cpp b/tests/source/gl/test_edge_tags.cpp index 000d18db..1af2bab3 100644 --- a/tests/source/gl/test_edge_tags.cpp +++ b/tests/source/gl/test_edge_tags.cpp @@ -15,7 +15,7 @@ struct test_edge_tags { vertex_type vd_1{constants::vertex_id_1}; vertex_type vd_2{constants::vertex_id_2}; - vertex_type invalid_vd{constants::vertex_id_1}; + vertex_type invalid_vd{constants::invalid_id}; }; struct test_directed_edge_tag : test_edge_tags { @@ -55,23 +55,23 @@ TEST_CASE_FIXTURE( CHECK_EQ(property_edge->first(), vd_1); CHECK_EQ(property_edge->second(), vd_2); - CHECK_EQ(property_edge->properties, used); + CHECK_EQ(property_edge->properties(), used); } TEST_CASE_FIXTURE( test_directed_edge_tag, "is_incident_from should return true only for the first vertex" ) { - CHECK(sut_type::is_incident_from(*edge, vd_1)); - CHECK_FALSE(sut_type::is_incident_from(*edge, vd_2)); - CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd)); + CHECK(sut_type::is_incident_from(*edge, vd_1.id())); + CHECK_FALSE(sut_type::is_incident_from(*edge, vd_2.id())); + CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd.id())); } TEST_CASE_FIXTURE( test_directed_edge_tag, "is_incident_to should return true only for the second vertex" ) { - CHECK(sut_type::is_incident_to(*edge, vd_2)); - CHECK_FALSE(sut_type::is_incident_to(*edge, vd_1)); - CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd)); + CHECK(sut_type::is_incident_to(*edge, vd_2.id())); + CHECK_FALSE(sut_type::is_incident_to(*edge, vd_1.id())); + CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd.id())); } struct test_undirected_edge_tag : test_edge_tags { @@ -111,25 +111,25 @@ TEST_CASE_FIXTURE( CHECK_EQ(property_edge->first(), vd_1); CHECK_EQ(property_edge->second(), vd_2); - CHECK_EQ(property_edge->properties, used); + CHECK_EQ(property_edge->properties(), used); } TEST_CASE_FIXTURE( test_undirected_edge_tag, "is_incident_from should return true for both vertices" ) { - CHECK(sut_type::is_incident_from(*edge, vd_1)); - CHECK(sut_type::is_incident_from(*edge, vd_2)); + CHECK(sut_type::is_incident_from(*edge, vd_1.id())); + CHECK(sut_type::is_incident_from(*edge, vd_2.id())); - CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd)); - CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd)); + CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd.id())); + CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd.id())); } TEST_CASE_FIXTURE(test_undirected_edge_tag, "is_incident_to should return true for both vertices") { - CHECK(sut_type::is_incident_to(*edge, vd_1)); - CHECK(sut_type::is_incident_to(*edge, vd_2)); + CHECK(sut_type::is_incident_to(*edge, vd_1.id())); + CHECK(sut_type::is_incident_to(*edge, vd_2.id())); - CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd)); - CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd)); + CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd.id())); + CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd.id())); } TEST_SUITE_END(); // untest_edge_tags diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index 1a9ec1c1..aa6b3f1f 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -66,8 +66,8 @@ struct test_graph { return graph.n_vertices() - constants::one_element; } - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; - const vertex_type invalid_vertex{constants::vertex_id_1}; + const vertex_type out_of_range_vertex{constants::out_of_range_element_idx}; + const vertex_type invalid_vertex{constants::invalid_id}; // remove? }; template < @@ -123,7 +123,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE(std::ranges::equal(sut.vertex_ids(), constants::vertex_id_view)); CHECK_THROWS_AS( - static_cast(sut.get_vertex(constants::out_of_range_elemenet_idx)), + static_cast(sut.get_vertex(constants::out_of_range_element_idx)), std::out_of_range ); @@ -159,7 +159,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE_EQ(sut.n_vertices(), constants::one_element); CHECK_EQ(vertex.id(), constants::vertex_id_1); - CHECK_EQ(vertex.properties, constants::visited); + CHECK_EQ(vertex.properties(), constants::visited); } SUBCASE("add_vertices(n) should properly extend the current adjacency list") { @@ -189,7 +189,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut.vertices(), properties_list, std::ranges::equal_to{}, - [](const auto& vertex) { return vertex.properties; } + [](const auto& vertex) { return vertex.properties(); } )); } @@ -200,20 +200,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { return sut.has_vertex(vertex_id); })); - CHECK_FALSE(sut.has_vertex(constants::out_of_range_elemenet_idx)); - } - - SUBCASE("has_vertex(vertex) should return true when a vertex with the given id is present in " - "the graph") { - sut_type sut{constants::n_elements}; - - CHECK(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto vertex_id) { - return sut.has_vertex(sut.get_vertex(vertex_id)); - })); - CHECK(std::ranges::none_of(constants::vertex_id_view, [&sut](const auto vertex_id) { - return sut.has_vertex(vertex_type{vertex_id}); - })); - CHECK_FALSE(sut.has_vertex(fixture.out_of_range_vertex)); + CHECK_FALSE(sut.has_vertex(constants::out_of_range_element_idx)); } SUBCASE("get_vertex should throw if the given id is invalid") { @@ -223,8 +210,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("get_vertex should return a vertex with the given id") { sut_type sut; - const auto& added_vertex = sut.add_vertex(); - CHECK_EQ(&sut.get_vertex(added_vertex.id()), &added_vertex); + const auto added_vertex = sut.add_vertex(); + CHECK_EQ(sut.get_vertex(added_vertex.id()), added_vertex); } SUBCASE("vertices should return the correct vertex list iterator range") { @@ -252,11 +239,6 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_THROWS_AS(sut.remove_vertex(fixture.out_of_range_vertex), std::out_of_range); } - SUBCASE("remove_vertex(vertex) should throw if the id is valid but the address is not") { - sut_type sut{constants::n_elements}; - CHECK_THROWS_AS(sut.remove_vertex(fixture.invalid_vertex), std::invalid_argument); - } - SUBCASE("remove_vertex(vertex) should remove the given vertex and align ids of remaining " "vertices") { sut_type sut{constants::n_elements}; @@ -288,7 +270,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("remove_vertex(id) should throw if the given id is invalid") { sut_type sut{constants::n_elements}; - CHECK_THROWS_AS(sut.remove_vertex(constants::out_of_range_elemenet_idx), std::out_of_range); + CHECK_THROWS_AS(sut.remove_vertex(constants::out_of_range_element_idx), std::out_of_range); } SUBCASE("remove_vertex(id) should remove the given vertex and align ids of remaining vertices" @@ -379,11 +361,11 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edge(ids) should throw if either vertex id is invalid") { CHECK_THROWS_AS( - sut.add_edge(constants::out_of_range_elemenet_idx, constants::vertex_id_2), + sut.add_edge(constants::out_of_range_element_idx, constants::vertex_id_2), std::out_of_range ); CHECK_THROWS_AS( - sut.add_edge(constants::vertex_id_1, constants::out_of_range_elemenet_idx), + sut.add_edge(constants::vertex_id_1, constants::out_of_range_element_idx), std::out_of_range ); } @@ -414,9 +396,6 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edge(vertices) should throw if either vertex is invalid") { CHECK_THROWS_AS(sut.add_edge(fixture.out_of_range_vertex, vertex_2), std::out_of_range); CHECK_THROWS_AS(sut.add_edge(vertex_1, fixture.out_of_range_vertex), std::out_of_range); - - CHECK_THROWS_AS(sut.add_edge(fixture.invalid_vertex, vertex_2), std::invalid_argument); - CHECK_THROWS_AS(sut.add_edge(vertex_1, fixture.invalid_vertex), std::invalid_argument); } SUBCASE("add_edge(vertices) should properly add the new edge") { @@ -446,7 +425,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); CHECK_THROWS_AS( - sut.add_edges_from(constants::out_of_range_elemenet_idx, vertex_id_list{}), + sut.add_edges_from(constants::out_of_range_element_idx, vertex_id_list{}), std::out_of_range ); CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); @@ -454,7 +433,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_THROWS_AS( sut.add_edges_from( constants::vertex_id_1, - vertex_id_list{constants::vertex_id_2, constants::out_of_range_elemenet_idx} + vertex_id_list{constants::vertex_id_2, constants::out_of_range_element_idx} ), std::out_of_range ); @@ -487,12 +466,6 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp ); CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); - CHECK_THROWS_AS( - sut.add_edges_from(fixture.invalid_vertex, vertex_ref_list{}), - std::invalid_argument - ); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); - CHECK_THROWS_AS( sut.add_edges_from( vertex_1, vertex_ref_list{vertex_2, fixture.out_of_range_vertex} @@ -500,14 +473,6 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp std::out_of_range ); CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); - - CHECK_THROWS_AS( - sut.add_edges_from( - vertex_1, vertex_ref_list{vertex_2, fixture.invalid_vertex} - ), - std::invalid_argument - ); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); } SUBCASE("add_edges_from(vertices) should properly extend the graph if all ids are valid") { @@ -593,13 +558,13 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edge(ids) should throw if either vertex id is invalid") { CHECK_THROWS_AS( sut.add_edge( - constants::out_of_range_elemenet_idx, constants::vertex_id_2, constants::used + constants::out_of_range_element_idx, constants::vertex_id_2, constants::used ), std::out_of_range ); CHECK_THROWS_AS( sut.add_edge( - constants::vertex_id_1, constants::out_of_range_elemenet_idx, constants::used + constants::vertex_id_1, constants::out_of_range_element_idx, constants::used ), std::out_of_range ); @@ -610,7 +575,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut.add_edge(constants::vertex_id_1, constants::vertex_id_2, constants::used); REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); REQUIRE(new_edge.is_incident_to(vertices[constants::vertex_id_2])); - REQUIRE_EQ(new_edge.properties, constants::used); + REQUIRE_EQ(new_edge.properties(), constants::used); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); @@ -639,22 +604,13 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut.add_edge(vertex_1, fixture.out_of_range_vertex, constants::used), std::out_of_range ); - - CHECK_THROWS_AS( - sut.add_edge(fixture.invalid_vertex, vertex_2, constants::used), - std::invalid_argument - ); - CHECK_THROWS_AS( - sut.add_edge(vertex_1, fixture.invalid_vertex, constants::used), - std::invalid_argument - ); } SUBCASE("add_edge(vertices) should properly add the new edge") { const auto& new_edge = sut.add_edge(vertex_1, vertex_2, constants::used); REQUIRE(new_edge.is_incident_from(vertex_1)); REQUIRE(new_edge.is_incident_to(vertex_2)); - REQUIRE_EQ(new_edge.properties, constants::used); + REQUIRE_EQ(new_edge.properties(), constants::used); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); @@ -742,13 +698,6 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_THROWS_AS( func::discard_result(sut.has_edge(vd_1, fixture.out_of_range_vertex)), std::out_of_range ); - - CHECK_THROWS_AS( - func::discard_result(sut.has_edge(fixture.invalid_vertex, vd_2)), std::invalid_argument - ); - CHECK_THROWS_AS( - func::discard_result(sut.has_edge(vd_1, fixture.invalid_vertex)), std::invalid_argument - ); } SUBCASE("has_edge(vertex, vertex) should return true if there is an edge connecting the given " @@ -770,7 +719,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut_type sut{constants::n_elements}; const auto& valid_vertex = sut.get_vertex(constants::vertex_id_1); - const vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; + const vertex_type out_of_range_vertex{constants::out_of_range_element_idx}; CHECK_FALSE(sut.get_edge(valid_vertex, out_of_range_vertex)); CHECK_FALSE(sut.get_edge(out_of_range_vertex, valid_vertex)); CHECK_FALSE(sut.get_edge(out_of_range_vertex, out_of_range_vertex)); @@ -811,19 +760,21 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } } - SUBCASE("get_edges(id, id) should return an empty vector if either id is invalid") { + SUBCASE("get_edges(id, id) should throw if either id is invalid") { sut_type sut{constants::n_elements}; - CHECK(sut.get_edges(constants::out_of_range_elemenet_idx, constants::vertex_id_2).empty()); - CHECK(sut.get_edges(constants::vertex_id_1, constants::out_of_range_elemenet_idx).empty()); - - // clang-format off - - CHECK(sut.get_edges( - constants::out_of_range_elemenet_idx, constants::out_of_range_elemenet_idx - ).empty()); - - // clang-format on + CHECK_THROWS_AS( + func::discard_result( + sut.get_edges(constants::out_of_range_element_idx, constants::vertex_id_2) + ), + std::out_of_range + ); + CHECK_THROWS_AS( + func::discard_result( + sut.get_edges(constants::vertex_id_1, constants::out_of_range_element_idx) + ), + std::out_of_range + ); } SUBCASE("get_edges(id, id) should return an empty vector if the given vertices are not incident" @@ -887,19 +838,12 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp func::discard_result(sut.get_edges(vd_1, fixture.out_of_range_vertex)), std::out_of_range ); - - CHECK_THROWS_AS( - func::discard_result(sut.get_edges(fixture.invalid_vertex, vd_2)), std::invalid_argument - ); - CHECK_THROWS_AS( - func::discard_result(sut.get_edges(vd_1, fixture.invalid_vertex)), std::invalid_argument - ); } SUBCASE("adjacent_edges(id) should throw if the vertex_id is invalid") { sut_type sut{constants::n_elements}; CHECK_THROWS_AS( - func::discard_result(sut.adjacent_edges(constants::out_of_range_elemenet_idx)), + func::discard_result(sut.adjacent_edges(constants::out_of_range_element_idx)), std::out_of_range ); } @@ -921,9 +865,6 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_THROWS_AS( func::discard_result(sut.adjacent_edges(fixture.out_of_range_vertex)), std::out_of_range ); - CHECK_THROWS_AS( - func::discard_result(sut.adjacent_edges(fixture.invalid_vertex)), std::invalid_argument - ); } SUBCASE("adjacent_edges(vertex) should return a proper iterator range for a valid vertex") { @@ -944,6 +885,30 @@ TEST_CASE_TEMPLATE_INSTANTIATE( gl::matrix_graph_traits // undirected adjacency matrix ); -TEST_SUITE_END(); // test_graph +TEST_CASE_TEMPLATE_DEFINE( + "vertex_properties_map() should return a correct map", TraitsType, vp_graph_traits_template +) { + using sut_type = gl::graph; + + sut_type sut{constants::n_elements}; + + for (auto vertex : sut.vertices()) + vertex.properties() = std::format("vertex_{}", vertex.id()); + + auto map = sut.vertex_properties_map(); + CHECK(map.size() == constants::n_elements); + for (auto [id, property] : std::views::zip(sut.vertex_ids(), map)) { + CHECK_EQ(property, std::format("vertex_{}", id)); + CHECK_EQ(map[id], std::format("vertex_{}", id)); + } +} + +TEST_CASE_TEMPLATE_INSTANTIATE( + vp_graph_traits_template, + gl::list_graph_traits, // directed adjacency list + gl::list_graph_traits, // undirected adjacency list + gl::matrix_graph_traits, // directed adjacency matrix + gl::matrix_graph_traits // undirected adjacency matrix +); } // namespace gl_testing diff --git a/tests/source/gl/test_graph_file_io.cpp b/tests/source/gl/test_graph_file_io.cpp index 71235bee..829e0cfd 100644 --- a/tests/source/gl/test_graph_file_io.cpp +++ b/tests/source/gl/test_graph_file_io.cpp @@ -8,6 +8,8 @@ #include +#include + namespace fs = std::filesystem; namespace gl_testing { @@ -44,9 +46,9 @@ struct test_graph_file_io { // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : sut_out.vertices()) { - vertex.properties = {std::format("vertex_{}", v_idx++)}; + vertex.properties() = std::format("vertex_{}", v_idx++); for (const auto& edge : sut_out.adjacent_edges(vertex)) - edge.properties = {std::format("edge_{}", e_idx++)}; + edge.properties() = std::format("edge_{}", e_idx++); } } diff --git a/tests/source/gl/test_graph_incidence.cpp b/tests/source/gl/test_graph_incidence.cpp index 5d461693..3494451b 100644 --- a/tests/source/gl/test_graph_incidence.cpp +++ b/tests/source/gl/test_graph_incidence.cpp @@ -12,8 +12,7 @@ TEST_SUITE_BEGIN("test_graph_incidence"); struct test_graph_incidence { using vertex_type = gl::vertex_descriptor<>; - vertex_type invalid_vertex{constants::vertex_id_1}; - vertex_type out_of_range_vertex{constants::out_of_range_elemenet_idx}; + vertex_type out_of_range_vertex{constants::out_of_range_element_idx}; }; TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_template) { @@ -27,13 +26,13 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ SUBCASE("are_incident(vertex_id, vertex_id) should throw for out of range vertex ids") { CHECK_THROWS_AS( func::discard_result( - sut.are_incident(constants::out_of_range_elemenet_idx, constants::vertex_id_2) + sut.are_incident(constants::out_of_range_element_idx, constants::vertex_id_2) ), std::out_of_range ); CHECK_THROWS_AS( func::discard_result( - sut.are_incident(constants::vertex_id_1, constants::out_of_range_elemenet_idx) + sut.are_incident(constants::vertex_id_1, constants::out_of_range_element_idx) ), std::out_of_range ); @@ -73,19 +72,6 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ func::discard_result(sut.are_incident(vd_1, fixture.out_of_range_vertex)), std::out_of_range ); - - CHECK_THROWS_AS( - func::discard_result(sut.are_incident(fixture.invalid_vertex, fixture.invalid_vertex)), - std::invalid_argument - ); - CHECK_THROWS_AS( - func::discard_result(sut.are_incident(fixture.invalid_vertex, vd_2)), - std::invalid_argument - ); - CHECK_THROWS_AS( - func::discard_result(sut.are_incident(vd_1, fixture.invalid_vertex)), - std::invalid_argument - ); } SUBCASE("are_incident(vertex, vertex) should return true if there is an edge connecting the " @@ -109,21 +95,21 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ const auto& edge = sut.add_edge(vd_1, vd_2); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(fixture.invalid_vertex, edge)), - std::invalid_argument + func::discard_result(sut.are_incident(fixture.out_of_range_vertex, edge)), + std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(fixture.invalid_vertex, edge)), - std::invalid_argument + func::discard_result(sut.are_incident(fixture.out_of_range_vertex, edge)), + std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(edge, fixture.invalid_vertex)), - std::invalid_argument + func::discard_result(sut.are_incident(edge, fixture.out_of_range_vertex)), + std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(edge, fixture.invalid_vertex)), - std::invalid_argument + func::discard_result(sut.are_incident(edge, fixture.out_of_range_vertex)), + std::out_of_range ); } diff --git a/tests/source/gl/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp index f8333108..970eebd6 100644 --- a/tests/source/gl/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -28,9 +28,9 @@ struct test_directed_graph_io { // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : sut_out.vertices()) { - vertex.properties = {std::format("vertex_{}", v_idx++)}; + vertex.properties() = std::format("vertex_{}", v_idx++); for (const auto& edge : sut_out.adjacent_edges(vertex)) - edge.properties = {std::format("edge_{}", e_idx++)}; + edge.properties() = std::format("edge_{}", e_idx++); } } @@ -138,10 +138,10 @@ struct test_undirected_graph_io { // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : sut_out.vertices()) { - vertex.properties = {std::format("vertex_{}", v_idx++)}; + vertex.properties() = std::format("vertex_{}", v_idx++); for (const auto& edge : sut_out.adjacent_edges(vertex)) - if (edge.first_id() == vertex.id()) - edge.properties = {std::format("edge_{}", e_idx++)}; + if (edge.first().id() == vertex.id()) + edge.properties() = std::format("edge_{}", e_idx++); } } diff --git a/tests/source/gl/test_vertex_descriptor.cpp b/tests/source/gl/test_vertex_descriptor.cpp index bcdb283c..e3d4e109 100644 --- a/tests/source/gl/test_vertex_descriptor.cpp +++ b/tests/source/gl/test_vertex_descriptor.cpp @@ -15,10 +15,10 @@ TEST_CASE("id() should return the correct vertex id") { } TEST_CASE("properties should be properly initialized") { - const gl::vertex_descriptor sut{ - constants::vertex_id_1, constants::visited - }; - CHECK_EQ(sut.properties, constants::visited); + types::visited_property property{constants::visited}; + + const gl::vertex_descriptor sut{constants::vertex_id_1, property}; + CHECK_EQ(&sut.properties(), &property); } TEST_CASE("vertex_descriptor objects should be compared by id") { From eff9569e21f491557bea438c33f4e424ea7bc602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:28:38 +0100 Subject: [PATCH 04/54] YT-CPPGL-56: Replace the usage of pointers for vertex and edge storage to simple composite types [Part 2] - Modified the `edge_descriptor` class to store only the IDs of its incident vertices - Aligned graph implementation to use vertex IDs instead of descriptor objects for internal operations - Aligned algorithms to use vertex-ID-based callbacks --- README.md | 2 +- docs/{algoithms.md => algorithms.md} | 98 ++++++++-------- docs/graph.md | 2 +- docs/graph_elements.md | 86 +++----------- include/gl/algorithm/breadth_first_search.hpp | 14 +-- include/gl/algorithm/coloring.hpp | 20 ++-- include/gl/algorithm/depth_first_search.hpp | 42 +++---- include/gl/algorithm/dijkstra.hpp | 19 ++- include/gl/algorithm/impl/bfs.hpp | 27 ++--- include/gl/algorithm/impl/common.hpp | 18 +-- include/gl/algorithm/impl/dfs.hpp | 64 +++++----- include/gl/algorithm/impl/pfs.hpp | 27 ++--- include/gl/algorithm/mst.hpp | 30 ++--- include/gl/algorithm/topological_sort.hpp | 18 ++- include/gl/algorithm/types.hpp | 20 ++-- include/gl/edge_descriptor.hpp | 111 +++++------------- include/gl/edge_tags.hpp | 30 ++--- include/gl/graph.hpp | 53 ++++----- include/gl/graph_traits.hpp | 2 +- include/gl/impl/adjacency_list.hpp | 2 +- include/gl/impl/adjacency_matrix.hpp | 4 +- .../gl/impl/specialized/adjacency_list.hpp | 38 +++--- .../gl/impl/specialized/adjacency_matrix.hpp | 25 ++-- include/gl/vertex_descriptor.hpp | 18 --- tests/include/testing/gl/io_common.hpp | 7 +- tests/source/gl/test_adjacency_list.cpp | 62 +++------- tests/source/gl/test_adjacency_matrix.cpp | 100 ++++++---------- tests/source/gl/test_alg_bfs.cpp | 19 +-- tests/source/gl/test_alg_dfs.cpp | 38 +++--- tests/source/gl/test_alg_mst.cpp | 16 +-- tests/source/gl/test_edge_descriptor.cpp | 60 ++++------ tests/source/gl/test_edge_tags.cpp | 68 +++++------ tests/source/gl/test_graph.cpp | 16 +-- tests/source/gl/test_graph_incidence.cpp | 36 ++---- tests/source/gl/test_graph_io.cpp | 2 +- .../gl/test_graph_topology_builders.cpp | 35 +++--- .../source/gl/test_vertex_degree_getters.cpp | 4 +- 37 files changed, 481 insertions(+), 752 deletions(-) rename docs/{algoithms.md => algorithms.md} (80%) diff --git a/README.md b/README.md index b115a5b2..d0f9f074 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ While the `gl::graph` class is the key element of the library, it's not the only - [The vertex and edge classes - representation of the graph's elements](/docs/graph_elements.md) - [I/O operations](/docs/io.md) - [Graph topology generators](/docs/topologies.md) -- [Algorithms](/docs/algoithms.md) +- [Algorithms](/docs/algorithms.md) - [Core utility types](/docs/core_util_types.md) - [Additional functionality](/docs/additional_functionality.md) diff --git a/docs/algoithms.md b/docs/algorithms.md similarity index 80% rename from docs/algoithms.md rename to docs/algorithms.md index e9c9899f..56908859 100644 --- a/docs/algoithms.md +++ b/docs/algorithms.md @@ -131,32 +131,30 @@ This section covers the specific types and type traits used for the algorithm im - `Args...` - variadic template representing arguments of the callback type. - *Equivalent to*: `c_empty_callback or std::is_invocable_r_v` -- `c_vertex_callback` - - *Description*: Checks if a function is a valid vertex callback. +- `c_id_callback` + - *Description*: Checks if a function is a valid ID callback. - *Template parameters*: - `F` - the function type to check. - - `GraphType` - the graph type associated with the callback. - `ReturnType` - the return type of the callback. - `Args...` - variadic template representing additional arguments of the callback type. - - *Equivalent to*: `std::is_invocable_r_v` + - *Equivalent to*: `std::is_invocable_r_v` -- `c_edge_callback` - - *Description*: Checks if a function is a valid edge callback. +- `c_optional_id_callback` + - *Description*: Checks if a function is a valid (optional) ID callback. - *Template parameters*: - `F` - the function type to check. - - `GraphType` - the graph type associated with the callback. - `ReturnType` - the return type of the callback. - `Args...` - variadic template representing additional arguments of the callback type. - - *Equivalent to*: `std::is_invocable_r_v` + - *Equivalent to*: `c_optional_callback` -- `c_optional_vertex_callback` - - *Description*: Checks if a function is a valid (optional) vertex callback. +- `c_edge_callback` + - *Description*: Checks if a function is a valid edge callback. - *Template parameters*: - `F` - the function type to check. - `GraphType` - the graph type associated with the callback. - `ReturnType` - the return type of the callback. - `Args...` - variadic template representing additional arguments of the callback type. - - *Equivalent to*: `c_optional_callback` + - *Equivalent to*: `std::is_invocable_r_v` - `c_optional_edge_callback` - *Description*: Checks if a function is a valid (optional) edge callback. @@ -183,8 +181,8 @@ This section covers the specific types and type traits used for the algorithm im - *Template parameters*: - `ResultDiscriminator: result_discriminator` (default = `algorithm::ret`) - Specifies whether the algorrithm should return the predecessors descriptor or not (can be eigher `algorithm::ret` or `algorithm::no_return`). - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform DFS on. @@ -212,8 +210,8 @@ This section covers the specific types and type traits used for the algorithm im - *Template parameters*: - `ResultDiscriminator: result_discriminator` (default = `algorithm::ret`) - Specifies whether the algorrithm should return the predecessors descriptor or not (can be eigher `algorithm::ret` or `algorithm::no_return`). - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform BFS on. @@ -236,8 +234,8 @@ This section covers the specific types and type traits used for the algorithm im - *Template parameters*: - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform the coloring on. @@ -279,8 +277,8 @@ This section covers the specific types and type traits used for the algorithm im - *Template parameters*: - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform the search on. @@ -332,8 +330,8 @@ This section covers the specific types and type traits used for the algorithm im - *Template parameters*: - `GraphType: type_traits::c_directed_graph` - The type of the graph on which the sorting is performed (must be directed). - - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform the coloring on. @@ -347,28 +345,24 @@ This section covers the specific types and type traits used for the algorithm im ### MST finding -- `edge_heap_prim_mst(graph, pre_visit, post_visit)` +- `edge_heap_prim_mst(graph)` - *Description*: - Returns an `mst_descriptor` object containing the [Minimum Spanning Tree](https://en.wikipedia.org/wiki/Minimum_spanning_tree) edges and its total weight. - Performs the Prim's algorithm using a minimum binary heap of edges. - *Template parameters*: - `GraphType: type_traits::c_undirected_graph` - The type of the graph on which the search is performed (must be undirected). - - `PreVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_optional_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform the search on. - `root_id_opt: const std::optional` - An optional root vertex ID. If set, the search will begin with edges adjacent to the vertex $v$ such that $v_{id} = \text{root-id-opt.value()}$. Otherwise the first vertex of the graph will be used as root. - - `pre_visit: const PreVisitCallback&` (default = `{}`) - The callback function to be called before visiting a vertex. - - `post_visit: const PostVisitCallback&` (default = `{}`) - The callback function to be called after visiting a vertex. - *Return type*: - `algorithm::mst_descriptor` - An [MST](https://en.wikipedia.org/wiki/Minimum_spanning_tree) desciptor object (detailed information can be found in the note below). - *Defined in*: [gl/algorithm/mst.hpp](/include/gl/algorithm/mst.hpp) -- `vertex_heap_prim_mst(graph, pre_visit, post_visit)` +- `vertex_heap_prim_mst(graph)` - *Description*: - Returns an `mst_descriptor` object containing the [Minimum Spanning Tree](https://en.wikipedia.org/wiki/Minimum_spanning_tree) edges and its total weight. - Performs the Prim's algorithm using a minimum binary heap of vertices. @@ -406,20 +400,20 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates > [!NOTE] > The DFS algorithm templates are defined in the [gl/algorithm/impl/dfs.hpp](/include/gl/algorithm/impl/dfs.hpp) file. -- `dfs(graph, root_vertex, visit_vertex_pred, visit, enque_vertex_pred, pre_visit, post_visit)` +- `dfs(graph, root_id, visit_vertex_pred, visit, enque_vertex_pred, pre_visit, post_visit)` - *Desciption*: An iterative DFS algoithm template. - *Template parameters*: - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `VisitVertexPredicate: type_traits::c_optional_id_callback` - The vertex visiting unary predicate type. + - `VisitCallback: type_traits::c_optional_id_callback` - The vertex visting callback type (arguments: `vertex_id, pred_id`). + - `EnqueueVertexPred: type_traits::c_id_callback` - The vertex enqueue predicate type (arguments: `vertex_id, in_edge`) + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform DFS on. - - `root_vertex: const typename GraphType::vertex_type&` - The vertex from which the search will be started. + - `root_id: const types::id_type` - The ID of a vertex from which the search will be started. - `visit_vertex_pred: const VisitVertexPredicate&` - A predicate used to determine whether a vertex should be visited based on the vertex itself and its source/parent vertex's ID. - `visit: const VisitCallback&` - The vertex visiting function. - `enque_vertex_pred: const EnqueueVertexPred&` - A predicate used to determine whether a vertex should be pushed to the search stack based on the vertex itself and its source edge. @@ -428,21 +422,21 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - *Return type*: `void` -- `r_dfs(graph, vertex, source_id, visit_vertex_pred, visit, enque_vertex_pred, pre_visit, post_visit)` +- `r_dfs(graph, vertex_id, pred_id, visit_vertex_pred, visit, enque_vertex_pred, pre_visit, post_visit)` - *Description*: A recursive DFS algorithm template. - *Template parameters*: - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `VisitVertexPredicate: type_traits::c_optional_id_callback` - The vertex visiting unary predicate type. + - `VisitCallback: type_traits::c_optional_id_callback` - The vertex visting callback type (arguments: `vertex_id, pred_id`). + - `EnqueueVertexPred: type_traits::c_id_callback` - The vertex enqueue predicate type (arguments: `vertex_id, in_edge`) + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform DFS on. - - `vertex: const typename GraphType::vertex_type&` - The vertex to search in the current recursive call. - - `source_id: const types::id_type` - The ID of the parent/source vertex of the currently searched vertex. + - `vertex_id: const types::id_type&` - The ID of a vertex to search in the current recursive call. + - `pred_id: const types::id_type` - The ID of the predecessor vertex vertex of the currently searched vertex. - `visit_vertex_pred: const VisitVertexPredicate&` - A predicate used to determine whether a vertex should be visited based on the vertex itself and its source/parent vertex's ID. - `visit: const VisitCallback&` - The vertex visiting function. - `enque_vertex_pred: const EnqueueVertexPred&` - A predicate used to determine whether a vertex should be pushed to the search stack based on the vertex itself and its source edge. @@ -462,11 +456,11 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - *Template parameters*: - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - `InitQueueRangeType: type_traits::c_sized_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. - - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `VisitVertexPredicate: type_traits::c_optional_id_callback` - The vertex visiting unary predicate type. + - `VisitCallback: type_traits::c_optional_id_callback` - The vertex visting callback type (arguments: `vertex_id, pred_id`). + - `EnqueueVertexPred: type_traits::c_id_callback` - The vertex enqueue predicate type (arguments: `vertex_id, in_edge`) + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform DFS on. @@ -493,11 +487,11 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - `PQCompare: std::predicate` - The type of the vertex priority queue comparator. - `InitQueueRangeType: type_traits::c_sized_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. - - `VisitVertexPredicate: type_traits::c_optional_vertex_callback` - The vertex visiting unary predicate type. - - `VisitCallback: type_traits::c_vertex_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - - `EnqueueVertexPred: type_traits::c_vertex_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) - - `PreVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. - - `PostVisitCallback: type_traits::c_vertex_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. + - `VisitVertexPredicate: type_traits::c_optional_id_callback` - The vertex visiting unary predicate type. + - `VisitCallback: type_traits::c_optional_id_callback` - The vertex visting callback type (arguments: `vertex, source_id`). + - `EnqueueVertexPred: type_traits::c_id_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) + - `PreVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called before visiting a vertex. + - `PostVisitCallback: type_traits::c_optional_id_callback` (default = `algorithm::empty_callback`) - The type of the callback function called after visiting a vertex. - *Parameters*: - `graph: const GraphType&` - The graph to perform DFS on. diff --git a/docs/graph.md b/docs/graph.md index 731b099b..ce56297f 100644 --- a/docs/graph.md +++ b/docs/graph.md @@ -551,5 +551,5 @@ This section provides links to additional resources and documentation that can h - [The vertex and edge classes - representation of the graph's elements](/docs/graph_elements.md) - [I/O operations](/docs/io.md) - [Graph topology generators](/docs/topologies.md) -- [Algorithms](/docs/algoithms.md) +- [Algorithms](/docs/algorithms.md) - [Core utility types](/docs/core_util_types.md) diff --git a/docs/graph_elements.md b/docs/graph_elements.md index 9446aaed..8ccd3be7 100644 --- a/docs/graph_elements.md +++ b/docs/graph_elements.md @@ -87,8 +87,6 @@ By default, the `edge_descriptor` class does not carry any properties and assume ### Template parameters -- **`VertexType`**: The type of the vertex descriptors connected by this edge. - - *Constraints*: must satisfy the **`type_traits::c_instantiation_of`** concept - **`DirectionalTag`**: A tag type indicating whether the edge is directed or undirected. - *Default value*: `directed_t` - *Constraints*: must satisfy the **`type_traits::c_edge_directional_tag`** concept (either `directed_t` or `undirected_t`) @@ -99,15 +97,14 @@ By default, the `edge_descriptor` class does not carry any properties and assume ### Member types - **`type`**: Alias for the `edge_descriptor` itself. -- **`vertex_type`**: Type of the vertex descriptors connected by this edge. - **`directional_tag`**: The tag indicating whether the edge is directed or undirected. - **`properties_type`**: Type of the edge properties as defined by the `Properties` template parameter. ### Constructors -- **`edge_descriptor(const vertex_type& first, const vertex_type& second)`**: +- **`edge_descriptor(const types::id_type first, const types::id_type second)`**: - Constructs an edge between two vertices. -- **`edge_descriptor(const vertex_type& first, const vertex_type& second, const properties_type& properties)`**: +- **`edge_descriptor(const types::id_type first, const types::id_type second, const properties_type& properties)`**: - Constructs an edge between two vertices with specified properties. - *Constraints*: the `properties_type` must be non-default. - **Move constructor and assignment operator**: *default* @@ -133,88 +130,48 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - **`incident_vertices() const`**: - *Description*: Returns a pair of references to the two vertices connected by the edge. - *Returned value*: $(u, v)$ - - *Return type*: `types::homogeneous_pair` - -- **`incident_vertex_ids() const`**: - - *Description*: Returns the unique IDs of the vertices connected by the edge. - - *Returned value*: $(u_{id}, v_{id})$ - - *Return type*: `types::homogeneous_pair` + - *Return type*: `types::homogeneous_pair` - **`first() const`**: - *Description*: Returns a reference to the first vertex of the edge. - *Returned value*: $u$ - - *Return type*: `const vertex_type&` + - *Return type*: `types::id_type` - **`second() const`**: - *Description*: Returns a reference to the second vertex of the edge. - *Returned value*: $v$ - - *Return type*: `const vertex_type&` + - *Return type*: `const types::id_type` -- **`incident_vertex(vertex) const`**: +- **`incident_vertex(vertex_id) const`**: - *Description*: Returns the vertex on the other end of the edge relative to the provided vertex. Throws an error if the provided vertex is not incident with the edge. - *Returned value*: - - $v$ if $\text{vertex} = u$ - - $u$ if $\text{vertex} = v$ + - $v$ if $\text{vertex-id} = u$ + - $u$ if $\text{vertex-id} = v$ - error otherwise - *Parameters*: - - `vertex: const vertex_type&` – the vertex for which the opposite vertex is requested. - - *Return type*: `const vertex_type&` - -- **`incident_vertex(vertex_id) const`**: - - *Description*: Returns the vertex on the other end of the edge relative to the provided vertex ID. Throws an error if the provided vertex ID is invalid. - - *Returned value*: - - $v$ if $\text{vertex-id} = u_{id}$ - - $u$ if $\text{vertex-id} = v_{id}$ - - error otherwise - - *Parameters*: - - `vertex_id: const types::id_type` – the vertex ID for which the opposite vertex ID is requested. - - *Return type*: `types::id_type` - -- **`is_incident_with(vertex) const`**: - - *Description*: Returns `true` if the provided vertex is connected to the edge. - - *Returned value*: $\text{vertex} \in {u, v}$ - - *Parameters*: - - `vertex: const vertex_type&` – the vertex to check for incidence with the edge. - - *Return type*: `bool` + - `vertex_id: const types::id_type` – the vertex for which the opposite vertex is requested. + - *Return type*: `const types::id_type` - **`is_incident_with(vertex_id) const`**: - *Description*: Returns `true` if a vertex with the given ID is connected to the edge. - - *Returned value*: $\text{vertex-id} \in {u_{id}, v_{id}}$ + - *Returned value*: $\text{vertex-id} \in {u, v}$ - *Parameters*: - `vertex_id: const types::id_type` – the vertex ID to check for incidence with the edge. - *Return type*: `bool` -- **`is_incident_from(vertex) const`**: - - *Description*: Returns `true` if the provided vertex is the source of the edge (for directed edges). - - *Returned value*: - - For directed edges: $\text{vertex} = u$ - - For undirected edges: `is_incident_with(vertex)` - - *Parameters*: - - `vertex: const vertex_type&` – the vertex to check if it is the source. - - *Return type*: `bool` - - **`is_incident_from(vertex_id) const`**: - *Description*: Returns `true` if a vertex with the given ID is the source of the edge. - *Returned value*: - - For directed edges: $\text{vertex-id} = u_{id}$ - - For undirected edges: `is_incident_with(vertex)` + - For directed edges: $\text{vertex-id} = u$ + - For undirected edges: `is_incident_with(vertex_id)` - *Parameters*: - `vertex_id: const types::id_type` – the vertex ID to check if it is the source. - *Return type*: `bool` -- **`is_incident_to(vertex) const`**: - - *Description*: Returns `true` if the provided vertex is the target vertex of the edge (for directed edges). - - *Returned value*: - - For directed edges: $\text{vertex} = v$ - - For undirected edges: `is_incident_with(vertex)` - - *Parameters*: - - `vertex: const vertex_type&` – the vertex to check if it is the target. - - *Return type*: `bool` - - **`is_incident_to(vertex_id) const`**: - *Description*: Returns `true` if a vertex with the given ID is the target of the edge. - *Returned value*: - - For directed edges: $\text{vertex-id} = v_{id}$ + - For directed edges: $\text{vertex-id} = v$ - For undirected edges: `is_incident_with(vertex)` - *Parameters*: - `vertex_id: const types::id_type` – the vertex ID to check if it is the target. @@ -231,28 +188,23 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` ```cpp template < - type_traits::c_instantiation_of VertexType, type_traits::c_edge_directional_tag DirectionalTag = directed_t, type_traits::c_properties Properties = types::empty_properties> - using edge = edge_descriptor; + using edge = edge_descriptor; ``` - `directed_edge`: An alias for `edge_descriptor` specifically for directed edges. ```cpp - template < - type_traits::c_instantiation_of VertexType, - type_traits::c_properties Properties = types::empty_properties> - using directed_edge = edge_descriptor; + template + using directed_edge = edge_descriptor; ``` - `undirected_edge`: An alias for `edge_descriptor` specifically for undirected edges. ```cpp - template < - type_traits::c_instantiation_of VertexType, - type_traits::c_properties Properties = types::empty_properties> - using undirected_edge = edge_descriptor; + template + using undirected_edge = edge_descriptor; ```
diff --git a/include/gl/algorithm/breadth_first_search.hpp b/include/gl/algorithm/breadth_first_search.hpp index 2be03ded..1b46bdeb 100644 --- a/include/gl/algorithm/breadth_first_search.hpp +++ b/include/gl/algorithm/breadth_first_search.hpp @@ -13,10 +13,8 @@ namespace gl::algorithm { template < result_discriminator ResultDiscriminator = algorithm::ret, type_traits::c_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> impl::alg_return_type breadth_first_search( const GraphType& graph, const std::optional& root_vertex_id_opt = no_root_vertex, @@ -35,8 +33,8 @@ impl::alg_return_type breadth_firs impl::bfs( graph, impl::init_range(root_vertex_id_opt.value()), - impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_vertex_predicate(visited), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit @@ -47,8 +45,8 @@ impl::alg_return_type breadth_firs impl::bfs( graph, impl::init_range(root_id), - impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + impl::default_visit_vertex_predicate(visited), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit diff --git a/include/gl/algorithm/coloring.hpp b/include/gl/algorithm/coloring.hpp index ce3f5b7b..2787be70 100644 --- a/include/gl/algorithm/coloring.hpp +++ b/include/gl/algorithm/coloring.hpp @@ -12,10 +12,8 @@ using bicoloring_type = std::vector; template < type_traits::c_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> [[nodiscard]] std::optional bipartite_coloring( const GraphType& graph, const PreVisitCallback& pre_visit = {}, @@ -28,8 +26,7 @@ template < coloring_opt.emplace(graph.n_vertices(), bin_color_value::unset); auto& coloring = coloring_opt.value(); - for (const auto root_vertex : graph.vertices()) { - const auto root_id = root_vertex.id(); + for (const auto root_id : graph.vertex_ids()) { if (coloring[root_id].is_set()) continue; @@ -41,19 +38,18 @@ template < impl::init_range(root_id), algorithm::empty_callback{}, // visit predicate algorithm::empty_callback{}, // visit callback - [&coloring](const vertex_type& vertex, const edge_type& in_edge) + [&coloring](const types::id_type vertex_id, const edge_type& in_edge) -> predicate_result { // enqueue predicate if (in_edge.is_loop()) return false; - const auto vertex_id = vertex.id(); - const auto source_id = in_edge.incident_vertex(vertex).id(); + const auto pred_id = in_edge.incident_vertex(vertex_id); - if (coloring[vertex_id] == coloring[source_id]) + if (coloring[vertex_id] == coloring[pred_id]) return predicate_result::unknown; // graph is not bipartite - if (not coloring[vertex.id()].is_set()) { - coloring[vertex.id()] = coloring[source_id].next(); + if (not coloring[vertex_id].is_set()) { + coloring[vertex_id] = coloring[pred_id].next(); return true; } diff --git a/include/gl/algorithm/depth_first_search.hpp b/include/gl/algorithm/depth_first_search.hpp index 97c3f0eb..683b9fa5 100644 --- a/include/gl/algorithm/depth_first_search.hpp +++ b/include/gl/algorithm/depth_first_search.hpp @@ -12,10 +12,8 @@ namespace gl::algorithm { template < result_discriminator ResultDiscriminator = algorithm::ret, type_traits::c_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> impl::alg_return_type depth_first_search( const GraphType& graph, const std::optional& root_vertex_id_opt = no_root_vertex, @@ -33,21 +31,21 @@ impl::alg_return_type depth_first_ if (root_vertex_id_opt) { impl::dfs( graph, - graph.get_vertex(root_vertex_id_opt.value()), - impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + root_vertex_id_opt.value(), + impl::default_visit_vertex_predicate(visited), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit ); } else { - for (const auto& root_vertex : graph.vertices()) + for (const auto root_vertex_id : graph.vertex_ids()) impl::dfs( graph, - root_vertex, - impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + root_vertex_id, + impl::default_visit_vertex_predicate(visited), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit @@ -61,10 +59,8 @@ impl::alg_return_type depth_first_ template < result_discriminator ResultDiscriminator = algorithm::ret, type_traits::c_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> impl::alg_return_type recursive_depth_first_search( const GraphType& graph, const std::optional& root_vertex_id_opt = no_root_vertex, @@ -83,23 +79,23 @@ impl::alg_return_type recursive_de const auto root_id = root_vertex_id_opt.value(); impl::r_dfs( graph, - graph.get_vertex(root_id), root_id, - impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + root_id, // pred_id + impl::default_visit_vertex_predicate(visited), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit ); } else { - for (const auto& root_vertex : graph.vertices()) + for (const auto& root_id : graph.vertex_ids()) impl::r_dfs( graph, - root_vertex, - root_vertex.id(), - impl::default_visit_vertex_predicate(visited), - impl::default_visit_callback(visited, pd), + root_id, + root_id, // pred_id + impl::default_visit_vertex_predicate(visited), + impl::default_visit_callback(visited, pd), impl::default_enqueue_vertex_predicate(visited), pre_visit, post_visit diff --git a/include/gl/algorithm/dijkstra.hpp b/include/gl/algorithm/dijkstra.hpp index 345ff12a..6071cf7a 100644 --- a/include/gl/algorithm/dijkstra.hpp +++ b/include/gl/algorithm/dijkstra.hpp @@ -65,10 +65,8 @@ template template < type_traits::c_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> [[nodiscard]] paths_descriptor_type dijkstra_shortest_paths( const GraphType& graph, const types::id_type source_id, @@ -94,10 +92,9 @@ template < impl::init_range(source_id), algorithm::empty_callback{}, // visit predicate algorithm::empty_callback{}, // visit callback - [&paths, &negative_edge](const vertex_type& vertex, const edge_type& in_edge) + [&paths, &negative_edge](const types::id_type vertex_id, const edge_type& in_edge) -> predicate_result { // enqueue predicate - const auto vertex_id = vertex.id(); - const auto source_id = in_edge.incident_vertex(vertex).id(); + const auto pred_id = in_edge.incident_vertex(vertex_id); const auto edge_weight = get_weight(in_edge); if (edge_weight < constants::zero) { @@ -105,11 +102,11 @@ template < return predicate_result::unknown; } - const auto new_distance = paths.distances[source_id] + edge_weight; + const auto new_distance = paths.distances[pred_id] + edge_weight; if (not paths.predecessors[vertex_id].has_value() or new_distance < paths.distances[vertex_id]) { paths.distances[vertex_id] = new_distance; - paths.predecessors[vertex_id].emplace(source_id); + paths.predecessors[vertex_id].emplace(pred_id); return true; } @@ -121,8 +118,8 @@ template < const auto& edge = negative_edge.value().get(); throw std::invalid_argument(std::format( "[alg::dijkstra_shortest_paths] Found an edge with a negative weight: [{}, {} | w={}]", - edge.first().id(), - edge.second().id(), + edge.first(), + edge.second(), get_weight(edge) )); } diff --git a/include/gl/algorithm/impl/bfs.hpp b/include/gl/algorithm/impl/bfs.hpp index abc8dd4f..e729df3b 100644 --- a/include/gl/algorithm/impl/bfs.hpp +++ b/include/gl/algorithm/impl/bfs.hpp @@ -14,14 +14,12 @@ template < type_traits::c_graph GraphType, type_traits::c_sized_range_of InitQueueRangeType = std::vector, - type_traits::c_optional_vertex_callback VisitVertexPredicate, - type_traits::c_optional_vertex_callback VisitCallback, - type_traits::c_vertex_callback + type_traits::c_optional_id_callback VisitVertexPredicate, + type_traits::c_optional_id_callback VisitCallback, + type_traits::c_id_callback EnqueueVertexPred, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> bool bfs( const GraphType& graph, const InitQueueRangeType& initial_queue_content, @@ -46,31 +44,30 @@ bool bfs( const algorithm::vertex_info vinfo = vertex_queue.front(); vertex_queue.pop(); - const auto& vertex = graph.get_vertex(vinfo.id); if constexpr (not type_traits::c_empty_callback) - if (not visit_vertex_pred(vertex)) + if (not visit_vertex_pred(vinfo.id)) continue; if constexpr (not type_traits::c_empty_callback) - pre_visit(vertex); + pre_visit(vinfo.id); if constexpr (not type_traits::c_empty_callback) - if (not visit(vertex, vinfo.source_id)) + if (not visit(vinfo.id, vinfo.pred_id)) return false; for (const auto& edge : graph.adjacent_edges(vinfo.id)) { - const auto& incident_vertex = edge.incident_vertex(vertex); + const auto incident_vertex_id = edge.incident_vertex(vinfo.id); - const auto enqueue = enqueue_vertex_pred(incident_vertex, edge); + const auto enqueue = enqueue_vertex_pred(incident_vertex_id, edge); if (enqueue == predicate_result::unknown) return false; if (enqueue) - vertex_queue.emplace(incident_vertex.id(), vinfo.id); + vertex_queue.emplace(incident_vertex_id, vinfo.id); } if constexpr (not type_traits::c_empty_callback) - post_visit(vertex); + post_visit(vinfo.id); } return true; diff --git a/include/gl/algorithm/impl/common.hpp b/include/gl/algorithm/impl/common.hpp index e688ad7f..36d5d487 100644 --- a/include/gl/algorithm/impl/common.hpp +++ b/include/gl/algorithm/impl/common.hpp @@ -30,23 +30,19 @@ template < return InitRangeType{algorithm::vertex_info{root_vertex_id}}; } -template [[nodiscard]] gl_attr_force_inline auto default_visit_vertex_predicate(std::vector& visited) { - return [&](const typename GraphType::vertex_type& vertex) -> bool { - return not visited[vertex.id()]; - }; + return [&](const types::id_type vertex_id) -> bool { return not visited[vertex_id]; }; } -template +template [[nodiscard]] gl_attr_force_inline auto default_visit_callback( std::vector& visited, alg_return_type_non_void& pd ) { - return [&](const typename GraphType::vertex_type& vertex, const types::id_type source_id) { - const auto vertex_id = vertex.id(); + return [&](const types::id_type vertex_id, const types::id_type pred_id) { visited[vertex_id] = true; if constexpr (ResultDiscriminator == algorithm::ret) - pd[vertex_id].emplace(source_id); + pd[vertex_id].emplace(pred_id); return true; }; } @@ -56,10 +52,8 @@ template ) { using return_type = std::conditional_t; - return [&](const typename GraphType::vertex_type& vertex, - const typename GraphType::edge_type& in_edge) -> return_type { - return not visited[vertex.id()]; - }; + return [&](const types::id_type vertex_id, const typename GraphType::edge_type& in_edge + ) -> return_type { return not visited[vertex_id]; }; } } // namespace gl::algorithm::impl diff --git a/include/gl/algorithm/impl/dfs.hpp b/include/gl/algorithm/impl/dfs.hpp index 1e0c4fb4..d060635c 100644 --- a/include/gl/algorithm/impl/dfs.hpp +++ b/include/gl/algorithm/impl/dfs.hpp @@ -12,17 +12,15 @@ namespace gl::algorithm::impl { template < type_traits::c_graph GraphType, - type_traits::c_optional_vertex_callback VisitVertexPredicate, - type_traits::c_vertex_callback VisitCallback, - type_traits::c_vertex_callback + type_traits::c_optional_id_callback VisitVertexPredicate, + type_traits::c_optional_id_callback VisitCallback, + type_traits::c_id_callback EnqueueVertexPred, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> void dfs( const GraphType& graph, - const typename GraphType::vertex_type& root_vertex, + const types::id_type root_id, const VisitVertexPredicate& visit_vertex_pred, const VisitCallback& visit, const EnqueueVertexPred& enqueue_vertex_pred, @@ -32,53 +30,50 @@ void dfs( using vertex_stack_type = std::stack; if constexpr (not type_traits::c_empty_callback) - if (not visit_vertex_pred(root_vertex)) + if (not visit_vertex_pred(root_id)) return; // prepare the vertex stack vertex_stack_type vertex_stack; - vertex_stack.emplace(root_vertex.id()); + vertex_stack.emplace(root_id); // search the graph while (not vertex_stack.empty()) { const auto vinfo = vertex_stack.top(); vertex_stack.pop(); - const auto& vertex = graph.get_vertex(vinfo.id); if constexpr (not type_traits::c_empty_callback) - if (not visit_vertex_pred(vertex)) + if (not visit_vertex_pred(vinfo.id)) continue; if constexpr (not type_traits::c_empty_callback) - pre_visit(vertex); + pre_visit(vinfo.id); - visit(vertex, vinfo.source_id); + visit(vinfo.id, vinfo.pred_id); for (const auto& edge : graph.adjacent_edges(vinfo.id)) { - const auto& incident_vertex = edge.incident_vertex(vertex); - if (enqueue_vertex_pred(incident_vertex, edge)) - vertex_stack.emplace(incident_vertex.id(), vinfo.id); + const auto incident_vertex_id = edge.incident_vertex(vinfo.id); + if (enqueue_vertex_pred(incident_vertex_id, edge)) + vertex_stack.emplace(incident_vertex_id, vinfo.id); } if constexpr (not type_traits::c_empty_callback) - post_visit(vertex); + post_visit(vinfo.id); } } template < type_traits::c_graph GraphType, - type_traits::c_vertex_callback VisitVertexPredicate, - type_traits::c_vertex_callback VisitCallback, - type_traits::c_vertex_callback + type_traits::c_optional_id_callback VisitVertexPredicate, + type_traits::c_optional_id_callback VisitCallback, + type_traits::c_id_callback EnqueueVertexPred, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> void r_dfs( const GraphType& graph, - const typename GraphType::vertex_type& vertex, - const types::id_type source_id, + const types::id_type vertex_id, + const types::id_type pred_id, const VisitVertexPredicate& visit_vertex_pred, const VisitCallback& visit, const EnqueueVertexPred& enqueue_vertex_pred, @@ -86,22 +81,21 @@ void r_dfs( const PostVisitCallback& post_visit = {} ) { if constexpr (not type_traits::c_empty_callback) - if (not visit_vertex_pred(vertex)) + if (not visit_vertex_pred(vertex_id)) return; if constexpr (not type_traits::c_empty_callback) - pre_visit(vertex); + pre_visit(vertex_id); - visit(vertex, source_id); + visit(vertex_id, pred_id); // recursively search vertices adjacent to the current vertex - const auto vertex_id = vertex.id(); for (const auto& edge : graph.adjacent_edges(vertex_id)) { - const auto& incident_vertex = edge.incident_vertex(vertex); - if (enqueue_vertex_pred(incident_vertex, edge)) + const auto& incident_vertex_id = edge.incident_vertex(vertex_id); + if (enqueue_vertex_pred(incident_vertex_id, edge)) r_dfs( graph, - incident_vertex, + incident_vertex_id, vertex_id, visit_vertex_pred, visit, @@ -112,7 +106,7 @@ void r_dfs( } if constexpr (not type_traits::c_empty_callback) - post_visit(vertex); + post_visit(vertex_id); } } // namespace gl::algorithm::impl diff --git a/include/gl/algorithm/impl/pfs.hpp b/include/gl/algorithm/impl/pfs.hpp index 08036514..e943314e 100644 --- a/include/gl/algorithm/impl/pfs.hpp +++ b/include/gl/algorithm/impl/pfs.hpp @@ -15,14 +15,12 @@ template < std::predicate PQCompare, type_traits::c_sized_range_of InitQueueRangeType = std::vector, - type_traits::c_optional_vertex_callback VisitVertexPredicate, - type_traits::c_optional_callback VisitCallback, - type_traits::c_vertex_callback + type_traits::c_optional_id_callback VisitVertexPredicate, + type_traits::c_optional_id_callback VisitCallback, + type_traits::c_id_callback EnqueueVertexPred, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> bool pfs( const GraphType& graph, const PQCompare& pq_compare, @@ -49,30 +47,29 @@ bool pfs( const auto vinfo = vertex_queue.top(); vertex_queue.pop(); - const auto& vertex = graph.get_vertex(vinfo.id); if constexpr (not type_traits::c_empty_callback) - if (not visit_vertex_pred(vertex)) + if (not visit_vertex_pred(vinfo.id)) continue; if constexpr (not type_traits::c_empty_callback) - pre_visit(vertex); + pre_visit(vinfo.id); if constexpr (not type_traits::c_empty_callback) - if (not visit(vertex, vinfo.source_id)) + if (not visit(vinfo.id, vinfo.pred_id)) return false; for (const auto& edge : graph.adjacent_edges(vinfo.id)) { - const auto& incident_vertex = edge.incident_vertex(vertex); + const auto incident_vertex_id = edge.incident_vertex(vinfo.id); - const auto enqueue = enqueue_vertex_pred(incident_vertex, edge); + const auto enqueue = enqueue_vertex_pred(incident_vertex_id, edge); if (enqueue == predicate_result::unknown) return false; if (enqueue) - vertex_queue.emplace(incident_vertex.id(), vinfo.id); + vertex_queue.emplace(incident_vertex_id, vinfo.id); } if constexpr (not type_traits::c_empty_callback) - post_visit(vertex); + post_visit(vinfo.id); } return true; diff --git a/include/gl/algorithm/mst.hpp b/include/gl/algorithm/mst.hpp index 611fc49e..40928700 100644 --- a/include/gl/algorithm/mst.hpp +++ b/include/gl/algorithm/mst.hpp @@ -26,17 +26,9 @@ struct mst_descriptor { weight_type weight = static_cast(constants::zero); }; -template < - type_traits::c_undirected_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> +template [[nodiscard]] mst_descriptor edge_heap_prim_mst( - const GraphType& graph, - const std::optional root_id_opt, - const PreVisitCallback& pre_visit = {}, - const PostVisitCallback& post_visit = {} + const GraphType& graph, const std::optional root_id_opt ) { // type definitions @@ -80,7 +72,7 @@ template < const auto& min_edge = min_edge_info.edge.get(); const auto min_weight = get_weight(min_edge); - const auto& target_id = min_edge.incident_vertex(min_edge_info.source_id).id(); + const auto& target_id = min_edge.incident_vertex(min_edge_info.source_id); if (visited[target_id]) continue; @@ -93,25 +85,17 @@ template < // enqueue all edges adjacent to the `target` vertex if they lead to unvisited verties for (const auto& edge : graph.adjacent_edges(target_id)) - if (not visited[edge.incident_vertex(target_id).id()]) + if (not visited[edge.incident_vertex(target_id)]) edge_queue.emplace(edge, target_id); } return mst; } -template < - type_traits::c_undirected_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> +template requires type_traits::c_has_numeric_limits_max> [[nodiscard]] mst_descriptor vertex_heap_prim_mst( - const GraphType& graph, - const std::optional root_id_opt, - const PreVisitCallback& pre_visit = {}, - const PostVisitCallback& post_visit = {} + const GraphType& graph, const std::optional root_id_opt ) { // type definitions using edge_type = typename GraphType::edge_type; @@ -157,7 +141,7 @@ requires type_traits::c_has_numeric_limits_max(edge); - const auto incident_vertex_id = edge.incident_vertex(vertex_id).id(); + const auto incident_vertex_id = edge.incident_vertex(vertex_id); if (not in_mst[incident_vertex_id] && edge_weight < min_cost[incident_vertex_id]) { min_cost[incident_vertex_id] = edge_weight; diff --git a/include/gl/algorithm/topological_sort.hpp b/include/gl/algorithm/topological_sort.hpp index ee4d20ac..5ad23390 100644 --- a/include/gl/algorithm/topological_sort.hpp +++ b/include/gl/algorithm/topological_sort.hpp @@ -10,10 +10,8 @@ namespace gl::algorithm { template < type_traits::c_directed_graph GraphType, - type_traits::c_optional_vertex_callback PreVisitCallback = - algorithm::empty_callback, - type_traits::c_optional_vertex_callback PostVisitCallback = - algorithm::empty_callback> + type_traits::c_optional_id_callback PreVisitCallback = algorithm::empty_callback, + type_traits::c_optional_id_callback PostVisitCallback = algorithm::empty_callback> [[nodiscard]] std::optional> topological_sort( const GraphType& graph, const PreVisitCallback& pre_visit = {}, @@ -22,15 +20,13 @@ template < using vertex_type = typename GraphType::vertex_type; using edge_type = typename GraphType::edge_type; - const auto vertex_ids = graph.vertex_ids(); - // prepare the vertex in degree map std::vector in_degree_map = graph.in_degree_map(); // prepare the initial queue content (source vertices) std::vector source_vertex_list; source_vertex_list.reserve(graph.n_vertices()); - for (const auto id : vertex_ids) + for (const auto id : graph.vertex_ids()) if (in_degree_map[id] == constants::default_size) source_vertex_list.emplace_back(id); @@ -44,16 +40,16 @@ template < source_vertex_list, algorithm::empty_callback{}, // visit predicate [&topological_order]( - const vertex_type& vertex, const types::id_type source_id + const types::id_type vertex_id, const types::id_type source_id ) { // visit callback - topological_order.push_back(vertex.id()); + topological_order.push_back(vertex_id); return true; }, - [&in_degree_map](const vertex_type& vertex, const edge_type& in_edge) + [&in_degree_map](const types::id_type vertex_id, const edge_type& in_edge) -> predicate_result { // enqueue predicate if (in_edge.is_loop()) return false; - return --in_degree_map[vertex.id()] == constants::default_size; + return --in_degree_map[vertex_id] == constants::default_size; }, pre_visit, post_visit diff --git a/include/gl/algorithm/types.hpp b/include/gl/algorithm/types.hpp index 79c823b7..06fbb0f1 100644 --- a/include/gl/algorithm/types.hpp +++ b/include/gl/algorithm/types.hpp @@ -18,13 +18,13 @@ using enum result_discriminator; struct empty_callback {}; struct vertex_info { - vertex_info(types::id_type id) : id(id), source_id(id) {} + vertex_info(types::id_type id) : id(id), pred_id(id) {} - vertex_info(types::id_type id, types::id_type source_id) : id(id), source_id(source_id) {} + vertex_info(types::id_type id, types::id_type pred_id) : id(id), pred_id(pred_id) {} - // if id == source_id then vertex_id is the id of the starting vertex + // if id == pred_id then vertex_id is the id of the starting vertex types::id_type id; - types::id_type source_id; + types::id_type pred_id; }; template EdgeType> @@ -101,18 +101,16 @@ concept c_empty_callback = std::same_as; template concept c_optional_callback = c_empty_callback or std::is_invocable_r_v; -template -concept c_vertex_callback = - std::is_invocable_r_v; +template +concept c_id_callback = std::is_invocable_r_v; + +template +concept c_optional_id_callback = c_optional_callback; template concept c_edge_callback = std::is_invocable_r_v; -template -concept c_optional_vertex_callback = - c_optional_callback; - template concept c_optional_edge_callback = c_optional_callback; diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index dcca5a8d..ab6e664a 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -11,19 +11,13 @@ namespace gl { template < - type_traits::c_instantiation_of VertexType, type_traits::c_edge_directional_tag DirectionalTag = directed_t, type_traits::c_properties Properties = types::empty_properties> class edge_descriptor final { public: - using type = edge_descriptor; - using vertex_type = VertexType; + using type = edge_descriptor; using directional_tag = DirectionalTag; using properties_type = Properties; - using properties_ref_type = std::conditional_t< - type_traits::is_default_properties_type_v, - types::empty_properties, - properties_type&>; friend directional_tag; @@ -34,14 +28,14 @@ class edge_descriptor final { edge_descriptor(const edge_descriptor&) = delete; edge_descriptor& operator=(const edge_descriptor&) = delete; - explicit edge_descriptor(const vertex_type first, const vertex_type& second) - : _vertices(std::move(first), std::move(second)) {} + explicit edge_descriptor(const types::id_type first, const types::id_type second) + : _vertices(first, second) {} explicit edge_descriptor( - const vertex_type first, const vertex_type second, properties_type properties + const types::id_type first, const types::id_type second, properties_type properties ) requires(not type_traits::is_default_properties_type_v) - : _vertices(std::move(first), std::move(second)), _properties(properties) {} + : _vertices(first, second), _properties(properties) {} edge_descriptor(edge_descriptor&&) = default; edge_descriptor& operator=(edge_descriptor&&) = default; @@ -59,46 +53,33 @@ class edge_descriptor final { // clang-format off // gl_attr_force_inline misplacement - [[nodiscard]] gl_attr_force_inline - const types::homogeneous_pair& incident_vertices() const { + [[nodiscard]] gl_attr_force_inline types::homogeneous_pair incident_vertices() const { return this->_vertices; } - [[nodiscard]] gl_attr_force_inline const types::homogeneous_pair incident_vertex_ids() const { - return std::make_pair(this->_vertices.first.id(), this->_vertices.second.id()); - } - - [[nodiscard]] gl_attr_force_inline const vertex_type& first() const { + [[nodiscard]] gl_attr_force_inline const types::id_type first() const { return this->_vertices.first; } - [[nodiscard]] gl_attr_force_inline const vertex_type& second() const { + [[nodiscard]] gl_attr_force_inline const types::id_type second() const { return this->_vertices.second; } // clang-format on // returns the `other` vertex or throws error if the given vertex is not incident with the edge - [[nodiscard]] const vertex_type incident_vertex(const types::id_type vertex_id) const { - if (vertex_id == this->_vertices.first.id()) + [[nodiscard]] const types::id_type incident_vertex(const types::id_type vertex_id) const { + if (vertex_id == this->_vertices.first) return this->_vertices.second; - if (vertex_id == this->_vertices.second.id()) + if (vertex_id == this->_vertices.second) return this->_vertices.first; throw std::invalid_argument(std::format("Got invalid vertex id: {}", vertex_id)); } - [[nodiscard]] const vertex_type incident_vertex(const vertex_type& vertex) const { - return this->incident_vertex(vertex.id()); - } - [[nodiscard]] gl_attr_force_inline bool is_incident_with(const types::id_type vertex_id) const { - return vertex_id == this->_vertices.first.id() or vertex_id == this->_vertices.second.id(); - } - - [[nodiscard]] gl_attr_force_inline bool is_incident_with(const vertex_type& vertex) const { - return this->is_incident_with(vertex.id()); + return vertex_id == this->_vertices.first or vertex_id == this->_vertices.second; } // true if the given vertex is the `source` of the edge @@ -106,21 +87,11 @@ class edge_descriptor final { return directional_tag::is_incident_from(*this, vertex_id); } - // true if the given vertex is the `source` of the edge - [[nodiscard]] gl_attr_force_inline bool is_incident_from(const vertex_type& vertex) const { - return this->is_incident_from(vertex.id()); - } - // true if the given vertex is the `target` vertex of the edge [[nodiscard]] gl_attr_force_inline bool is_incident_to(const types::id_type vertex_id) const { return directional_tag::is_incident_to(*this, vertex_id); } - // true if the given vertex is the `target` vertex of the edge - [[nodiscard]] gl_attr_force_inline bool is_incident_to(const vertex_type& vertex) const { - return this->is_incident_to(vertex.id()); - } - [[nodiscard]] gl_attr_force_inline bool is_loop() const { return this->_vertices.first == this->_vertices.second; } @@ -135,75 +106,49 @@ class edge_descriptor final { } private: - class vertex_writer { - public: - vertex_writer(const vertex_type& vertex, bool within_context) - : _vertex_ref(vertex), _within_context(within_context) {} - - friend std::ostream& operator<<(std::ostream& os, const vertex_writer& vw) { - if (vw._within_context) - os << vw._vertex_ref.id(); - else - os << vw._vertex_ref; - return os; - } - - private: - const vertex_type& _vertex_ref; - bool _within_context; - }; - - void _write(std::ostream& os, bool within_context = false) const { + void _write(std::ostream& os) const { if constexpr (not type_traits::c_writable) { - this->_write_no_properties(os, within_context); + this->_write_no_properties(os); return; } else { if (not io::is_option_set(os, io::graph_option::with_edge_properties)) { - this->_write_no_properties(os, within_context); + this->_write_no_properties(os); return; } if (io::is_option_set(os, io::graph_option::verbose)) { - os << "[first: " << vertex_writer(this->first(), within_context) - << ", second: " << vertex_writer(this->second(), within_context) + os << "[first: " << this->_vertices.first << ", second: " << this->_vertices.second << " | properties: " << this->_properties << "]"; } else { - os << "[" << vertex_writer(this->first(), within_context) << ", " - << vertex_writer(this->second(), within_context) << " | " << this->_properties - << "]"; + os << "[" << this->_vertices.first << ", " << this->_vertices.second << " | " + << this->_properties << "]"; } } } - void _write_no_properties(std::ostream& os, bool within_context = false) const { + void _write_no_properties(std::ostream& os) const { if (io::is_option_set(os, io::graph_option::verbose)) - os << "[first: " << vertex_writer(this->first(), within_context) - << ", second: " << vertex_writer(this->second(), within_context) << "]"; + os << "[first: " << this->_vertices.first << ", second: " << this->_vertices.first + << "]"; else - os << "[" << vertex_writer(this->first(), within_context) << ", " - << vertex_writer(this->second(), within_context) << "]"; + os << "[" << this->_vertices.first << ", " << this->_vertices.second << "]"; } - types::homogeneous_pair _vertices; + types::homogeneous_pair _vertices; [[no_unique_address]] mutable properties_type _properties{}; }; template < - type_traits::c_instantiation_of VertexType, type_traits::c_edge_directional_tag DirectionalTag = directed_t, type_traits::c_properties Properties = types::empty_properties> -using edge = edge_descriptor; +using edge = edge_descriptor; -template < - type_traits::c_instantiation_of VertexType, - type_traits::c_properties Properties = types::empty_properties> -using directed_edge = edge_descriptor; +template +using directed_edge = edge_descriptor; -template < - type_traits::c_instantiation_of VertexType, - type_traits::c_properties Properties = types::empty_properties> -using undirected_edge = edge_descriptor; +template +using undirected_edge = edge_descriptor; } // namespace gl diff --git a/include/gl/edge_tags.hpp b/include/gl/edge_tags.hpp index cbb722f7..9bd5b455 100644 --- a/include/gl/edge_tags.hpp +++ b/include/gl/edge_tags.hpp @@ -21,13 +21,7 @@ concept c_edge_directional_tag = c_one_of; } // namespace type_traits -template -class vertex_descriptor; - -template < - type_traits::c_instantiation_of VertexType, - type_traits::c_edge_directional_tag EdgeTag, - type_traits::c_properties Properties> +template class edge_descriptor; namespace type_traits { @@ -58,7 +52,7 @@ struct directed_t { template EdgeType> requires(type_traits::is_directed_v) [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const typename EdgeType::vertex_type& first, const typename EdgeType::vertex_type& second + const types::id_type first, const types::id_type second ) { return std::make_unique(first, second); } @@ -66,8 +60,8 @@ struct directed_t { template EdgeType> requires(type_traits::is_directed_v) [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const typename EdgeType::vertex_type& first, - const typename EdgeType::vertex_type& second, + const types::id_type first, + const types::id_type second, const typename EdgeType::properties_type& properties ) { return std::make_unique(first, second, properties); @@ -78,7 +72,7 @@ struct directed_t { [[nodiscard]] gl_attr_force_inline static bool is_incident_from( const EdgeType& edge, const types::id_type vertex_id ) { - return vertex_id == edge._vertices.first.id(); + return vertex_id == edge._vertices.first; } template EdgeType> @@ -86,7 +80,7 @@ struct directed_t { [[nodiscard]] gl_attr_force_inline static bool is_incident_to( const EdgeType& edge, const types::id_type vertex_id ) { - return vertex_id == edge._vertices.second.id(); + return vertex_id == edge._vertices.second; } }; @@ -100,7 +94,7 @@ struct undirected_t { template EdgeType> requires(type_traits::is_undirected_v) [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const typename EdgeType::vertex_type& first, const typename EdgeType::vertex_type& second + const types::id_type first, const types::id_type second ) { return std::make_shared(first, second); } @@ -108,8 +102,8 @@ struct undirected_t { template EdgeType> requires(type_traits::is_undirected_v) [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const typename EdgeType::vertex_type& first, - const typename EdgeType::vertex_type& second, + const types::id_type first, + const types::id_type second, const typename EdgeType::properties_type& properties ) { return std::make_shared(first, second, properties); @@ -143,15 +137,15 @@ namespace detail { template EdgeType> [[nodiscard]] gl_attr_force_inline types::edge_ptr_type make_edge( - const typename EdgeType::vertex_type& first, const typename EdgeType::vertex_type& second + const types::id_type first, const types::id_type second ) { return EdgeType::directional_tag::template make(first, second); } template EdgeType> [[nodiscard]] gl_attr_force_inline types::edge_ptr_type make_edge( - const typename EdgeType::vertex_type& first, - const typename EdgeType::vertex_type& second, + const types::id_type first, + const types::id_type second, const typename EdgeType::properties_type& properties ) { return EdgeType::directional_tag::template make(first, second, properties); diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index e54cffa8..75190d2a 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -45,11 +45,11 @@ class graph final { graph() = default; - graph(const types::size_type n_vertices) + explicit graph(const types::size_type n_vertices) requires(type_traits::is_default_properties_type_v) : _n_vertices(n_vertices), _impl(n_vertices) {} - graph(const types::size_type n_vertices) + explicit graph(const types::size_type n_vertices) requires(not type_traits::is_default_properties_type_v) : _n_vertices(n_vertices), _impl(n_vertices) { this->_vertex_properties.reserve(n_vertices); @@ -276,7 +276,7 @@ class graph final { this->_verify_vertex_id(first_id); this->_verify_vertex_id(second_id); return this->_impl.add_edge( - detail::make_edge(this->get_vertex(first_id), this->get_vertex(second_id)) + detail::make_edge(first_id, second_id) ); } @@ -289,9 +289,7 @@ class graph final { { this->_verify_vertex_id(first_id); this->_verify_vertex_id(second_id); - return this->_impl.add_edge(detail::make_edge( - this->get_vertex(first_id), this->get_vertex(second_id), properties - )); + return this->_impl.add_edge(detail::make_edge(first_id, second_id, properties)); } // clang-format on @@ -299,7 +297,7 @@ class graph final { const edge_type& add_edge(const vertex_type& first, const vertex_type& second) { this->_verify_vertex(first); this->_verify_vertex(second); - return this->_impl.add_edge(detail::make_edge(first, second)); + return this->_impl.add_edge(detail::make_edge(first.id(), second.id())); } const edge_type& add_edge( @@ -309,18 +307,21 @@ class graph final { { this->_verify_vertex(first); this->_verify_vertex(second); - return this->_impl.add_edge(detail::make_edge(first, second, properties)); + return this->_impl.add_edge( + detail::make_edge(first.id(), second.id(), properties) + ); } template IdRange> void add_edges_from(const types::id_type source_id, const IdRange& target_id_range) { - const auto& source = this->get_vertex(source_id); + this->_verify_vertex_id(source_id); std::vector new_edges; new_edges.reserve(std::ranges::size(target_id_range)); for (const auto target_id : target_id_range) { - new_edges.push_back(detail::make_edge(source, this->get_vertex(target_id))); + this->_verify_vertex_id(target_id); + new_edges.push_back(detail::make_edge(source_id, target_id)); } this->_impl.add_edges_from(source_id, std::move(new_edges)); } @@ -335,7 +336,7 @@ class graph final { for (const auto& target_ref : target_range) { const auto& target = target_ref.get(); this->_verify_vertex(target); - new_edges.push_back(detail::make_edge(source, target)); + new_edges.push_back(detail::make_edge(source.id(), target.id())); } this->_impl.add_edges_from(source.id(), std::move(new_edges)); } @@ -453,13 +454,13 @@ class graph final { [[nodiscard]] bool are_incident(const vertex_type& vertex, const edge_type& edge) const { this->_verify_vertex(vertex); this->_verify_edge(edge); - return edge.is_incident_with(vertex); + return edge.is_incident_with(vertex.id()); } [[nodiscard]] bool are_incident(const edge_type& edge, const vertex_type& vertex) const { this->_verify_vertex(vertex); this->_verify_edge(edge); - return edge.is_incident_with(vertex); + return edge.is_incident_with(vertex.id()); } [[nodiscard]] bool are_incident(const edge_type& edge_1, const edge_type& edge_2) const { @@ -507,8 +508,8 @@ class graph final { if (not this->has_edge(edge)) throw std::invalid_argument(std::format( "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge.first().id(), - edge.second().id(), + edge.first(), + edge.second(), io::format(&edge) )); } @@ -522,8 +523,8 @@ class graph final { // update vertex ids in edges for (auto id : this->vertex_ids()) { for (auto& edge : this->_impl.adjacent_edges(id)) { - edge._vertices.first._id -= (edge._vertices.first._id > vertex_id); - edge._vertices.second._id -= (edge._vertices.second._id > vertex_id); + edge._vertices.first -= (edge._vertices.first > vertex_id); + edge._vertices.second -= (edge._vertices.second > vertex_id); } } @@ -543,14 +544,10 @@ class graph final { this->n_unique_edges() ); - constexpr bool within_context = true; for (const auto& vertex : this->vertices()) { os << "- " << vertex << "\n adjacent edges:\n"; - for (const auto& edge : this->_impl.adjacent_edges(vertex.id())) { - os << "\t- "; - edge._write(os, within_context); - os << '\n'; - } + for (const auto& edge : this->_impl.adjacent_edges(vertex.id())) + os << "\t- " << edge << '\n'; } } @@ -592,10 +589,10 @@ class graph final { if (with_edge_properties) { const auto print_incident_edges = [this, &os](const types::id_type vertex_id) { for (const auto& edge : this->_impl.adjacent_edges(vertex_id)) { - if (edge.first().id() != vertex_id) + if (edge.first() != vertex_id) continue; // vertex is not the source - os << edge.first().id() << ' ' << edge.second().id() << ' ' - << edge._properties << '\n'; + os << edge.first() << ' ' << edge.second() << ' ' << edge._properties + << '\n'; } }; @@ -608,9 +605,9 @@ class graph final { const auto print_incident_edges = [this, &os](const types::id_type vertex_id) { for (const auto& edge : this->_impl.adjacent_edges(vertex_id)) { - if (edge.first().id() != vertex_id) + if (edge.first() != vertex_id) continue; // vertex is not the source - os << edge.first().id() << ' ' << edge.second().id() << '\n'; + os << edge.first() << ' ' << edge.second() << '\n'; } }; diff --git a/include/gl/graph_traits.hpp b/include/gl/graph_traits.hpp index 729dd840..b11b844f 100644 --- a/include/gl/graph_traits.hpp +++ b/include/gl/graph_traits.hpp @@ -20,7 +20,7 @@ struct graph_traits { using vertex_ptr_type = types::vertex_ptr_type; using vertex_properties_type = typename vertex_type::properties_type; - using edge_type = edge_descriptor; + using edge_type = edge_descriptor; using edge_ptr_type = types::edge_ptr_type; using edge_directional_tag = typename edge_type::directional_tag; using edge_properties_type = typename edge_type::properties_type; diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index 529619bc..44a38cb4 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -126,7 +126,7 @@ class adjacency_list final { [[nodiscard]] gl_attr_force_inline bool has_edge(const edge_type& edge) const { // find the edge by address return std::ranges::contains( - this->_list[edge.first().id()], &edge, typename specialized_impl::address_projection{} + this->_list[edge.first()], &edge, typename specialized_impl::address_projection{} ); } diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index e771ce5e..7b3d0b6c 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -138,9 +138,7 @@ class adjacency_matrix final { } [[nodiscard]] bool has_edge(const edge_type& edge) const { - const auto first_id = edge.first().id(); - const auto second_id = edge.second().id(); - + const auto [first_id, second_id] = edge.incident_vertices(); if (not (this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(second_id))) return false; diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 0945b191..769b3dd9 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -34,8 +34,8 @@ requires std::is_invocable_r_v< if (it == edge_set.end()) throw std::invalid_argument(std::format( "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge->first().id(), - edge->second().id(), + edge->first(), + edge->second(), io::format(edge) )); @@ -70,7 +70,7 @@ struct directed_adjacency_list { types::size_type in_deg = constants::default_size; for (const auto& adjacent_edges : self._list) in_deg += std::ranges::count(adjacent_edges, vertex_id, [](const auto& edge) { - return edge->second().id(); + return edge->second(); }); return in_deg; @@ -93,7 +93,7 @@ struct directed_adjacency_list { for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { std::ranges::for_each(self._list[id], [&in_degree_map](const auto& edge) { - ++in_degree_map[edge->second().id()]; + ++in_degree_map[edge->second()]; }); } @@ -116,7 +116,7 @@ struct directed_adjacency_list { // update in degrees std::ranges::for_each(self._list[id], [°ree_map](const auto& edge) { - ++degree_map[edge->second().id()]; + ++degree_map[edge->second()]; }); } @@ -144,7 +144,7 @@ struct directed_adjacency_list { } static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - auto& adjacent_edges_first = self._list[edge->first().id()]; + auto& adjacent_edges_first = self._list[edge->first()]; auto& new_edge = adjacent_edges_first.emplace_back(std::move(edge)); ++self._n_unique_edges; return *new_edge; @@ -173,11 +173,11 @@ struct directed_adjacency_list { list at which the edge is located, but the parameter is necessary to match the function signature for undirected adjacency list */ - return edge->second().id() == vertex_id; + return edge->second() == vertex_id; } static void remove_edge(impl_type& self, const edge_type& edge) { - auto& adj_edges = self._list.at(edge.first().id()); + auto& adj_edges = self._list.at(edge.first()); adj_edges.erase(detail::strict_find(adj_edges, &edge)); --self._n_unique_edges; } @@ -253,7 +253,7 @@ struct undirected_adjacency_list { for (const auto& edge : self._list[vertex_id]) { if (edge->is_loop()) continue; // will be removed with the vertex's list - incident_vertex_id_set.insert(edge->incident_vertex(vertex_id).id()); + incident_vertex_id_set.insert(edge->incident_vertex(vertex_id)); } // remove all edges incident with the vertex (scan only the selected vertices) @@ -272,10 +272,10 @@ struct undirected_adjacency_list { } static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - auto& adjacent_edges_first = self._list[edge->first().id()]; + auto& adjacent_edges_first = self._list[edge->first()]; if (not edge->is_loop()) - self._list[edge->second().id()].push_back(edge); + self._list[edge->second()].push_back(edge); adjacent_edges_first.emplace_back(std::move(edge)); ++self._n_unique_edges; @@ -290,7 +290,7 @@ struct undirected_adjacency_list { for (auto& edge : new_edges) { if (not edge->is_loop()) - self._list[edge->second().id()].push_back(edge); + self._list[edge->second()].push_back(edge); adjacent_edges_source.emplace_back(std::move(edge)); } @@ -300,23 +300,23 @@ struct undirected_adjacency_list { [[nodiscard]] inline static bool is_edge_incident_with( const edge_ptr_type& edge, const types::id_type vertex_id, const types::id_type source_id ) { - if (edge->first().id() == source_id) - return edge->second().id() == vertex_id; - else if (edge->second().id() == source_id) - return edge->first().id() == vertex_id; + if (edge->first() == source_id) + return edge->second() == vertex_id; + else if (edge->second() == source_id) + return edge->first() == vertex_id; return false; } static void remove_edge(impl_type& self, const edge_type& edge) { if (edge.is_loop()) { - auto& adj_edges_first = self._list.at(edge.first().id()); + auto& adj_edges_first = self._list.at(edge.first()); adj_edges_first.erase( detail::strict_find(adj_edges_first, &edge) ); } else { - auto& adj_edges_first = self._list.at(edge.first().id()); - auto& adj_edges_second = self._list.at(edge.second().id()); + auto& adj_edges_first = self._list.at(edge.first()); + auto& adj_edges_second = self._list.at(edge.second()); adj_edges_first.erase( detail::strict_find(adj_edges_first, &edge) diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 2a77443f..b8a38973 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -23,12 +23,12 @@ template AdjacencyMatrix> typename AdjacencyMatrix::matrix_type& matrix, const typename AdjacencyMatrix::edge_type* edge ) { // get the edge and validate the address - auto& matrix_element = matrix.at(edge->first().id()).at(edge->second().id()); + auto& matrix_element = matrix.at(edge->first()).at(edge->second()); if (edge != matrix_element.get()) throw std::invalid_argument(std::format( "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge->first().id(), - edge->second().id(), + edge->first(), + edge->second(), io::format(edge) )); @@ -39,7 +39,7 @@ template AdjacencyMatrix> inline void check_edge_override( const AdjacencyMatrix& adj_matrix, const typename AdjacencyMatrix::edge_ptr_type& edge ) { - const auto [first_id, second_id] = edge->incident_vertex_ids(); + const auto [first_id, second_id] = edge->incident_vertices(); if (adj_matrix.has_edge(first_id, second_id)) throw std::logic_error(std::format( @@ -136,7 +136,7 @@ struct directed_adjacency_matrix { static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { detail::check_edge_override(self, edge); - auto& matrix_element = self._matrix[edge->first().id()][edge->second().id()]; + auto& matrix_element = self._matrix[edge->first()][edge->second()]; matrix_element = std::move(edge); ++self._n_unique_edges; @@ -152,7 +152,7 @@ struct directed_adjacency_matrix { auto& matrix_row_source = self._matrix[source_id]; for (auto& edge : new_edges) - matrix_row_source[edge->second().id()] = std::move(edge); + matrix_row_source[edge->second()] = std::move(edge); self._n_unique_edges += new_edges.size(); } @@ -231,8 +231,8 @@ struct undirected_adjacency_matrix { static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { detail::check_edge_override(self, edge); - const auto first_id = edge->first().id(); - const auto second_id = edge->second().id(); + const auto first_id = edge->first(); + const auto second_id = edge->second(); if (not edge->is_loop()) self._matrix[second_id][first_id] = edge; @@ -253,8 +253,8 @@ struct undirected_adjacency_matrix { auto& matrix_row_source = self._matrix[source_id]; for (auto& edge : new_edges) { if (not edge->is_loop()) - self._matrix[edge->second().id()][source_id] = edge; - matrix_row_source[edge->second().id()] = std::move(edge); + self._matrix[edge->second()][source_id] = edge; + matrix_row_source[edge->second()] = std::move(edge); } self._n_unique_edges += new_edges.size(); @@ -265,13 +265,10 @@ struct undirected_adjacency_matrix { detail::strict_get(self._matrix, &edge) = nullptr; } else { - const auto first_id = edge.first().id(); - const auto second_id = edge.second().id(); - detail::strict_get(self._matrix, &edge) = nullptr; // if the edge was found in the first matrix cell, // it will also be present in the second matrix cell - self._matrix[second_id][first_id] = nullptr; + self._matrix[edge.second()][edge.first()] = nullptr; } --self._n_unique_edges; } diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 34da2b33..569b2bd6 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -113,22 +113,4 @@ using vertex_ptr_type = std::unique_ptr; } // namespace types -namespace detail { - -template VertexType> -[[nodiscard]] gl_attr_force_inline types::vertex_ptr_type make_vertex( - const types::id_type id -) { - return std::make_unique(id); -} - -template VertexType> -[[nodiscard]] gl_attr_force_inline types::vertex_ptr_type make_vertex( - const types::id_type id, const typename VertexType::properties_type& properties -) { - return std::make_unique(id, properties); -} - -} // namespace detail - } // namespace gl diff --git a/tests/include/testing/gl/io_common.hpp b/tests/include/testing/gl/io_common.hpp index eefebbaf..c10ffc5b 100644 --- a/tests/include/testing/gl/io_common.hpp +++ b/tests/include/testing/gl/io_common.hpp @@ -14,7 +14,7 @@ void verify_graph_structure(const GraphType& actual, const GraphType& expected) // verify that the edges of the in graph are equivalent to the edges of the out graph CHECK(std::ranges::all_of(actual.vertices(), [&](const auto& v_actual) { return std::ranges::all_of(actual.adjacent_edges(v_actual), [&](const auto& edge) { - return expected.has_edge(edge.first().id(), edge.second().id()); + return expected.has_edge(edge.first(), edge.second()); }); })); } @@ -38,10 +38,7 @@ void verify_edge_properties(const GraphType& actual, const GraphType& expected) return std::ranges::all_of(actual.adjacent_edges(v_actual), [&](const auto& edge) { // get edge returns optional return edge.properties() - == expected.get_edge(edge.first().id(), edge.second().id()) - .value() - .get() - .properties(); + == expected.get_edge(edge.first(), edge.second()).value().get().properties(); }); })); } diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index 20d5c521..fd040f44 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -72,22 +72,16 @@ constexpr gl::types::size_type n_incident_edges_for_fully_connected_vertex = } // namespace struct test_directed_adjacency_list { - using vertex_type = gl::vertex_descriptor<>; - using edge_type = gl::directed_edge; + using edge_type = gl::directed_edge<>; using edge_ptr_type = gl::directed_t::edge_ptr_type; using sut_type = gl::impl::adjacency_list>; - test_directed_adjacency_list() { - for (const auto id : constants::vertex_id_view) - vertices.emplace_back(id); - } + test_directed_adjacency_list() {} const edge_type& add_edge( const gl::types::id_type first_id, const gl::types::id_type second_id ) { - return sut.add_edge( - gl::detail::make_edge(vertices[first_id], vertices[second_id]) - ); + return sut.add_edge(gl::detail::make_edge(first_id, second_id)); } void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { @@ -110,7 +104,6 @@ struct test_directed_adjacency_list { } sut_type sut{constants::n_elements}; - std::vector vertices; const gl::types::size_type n_unique_edges_in_full_graph = n_incident_edges_for_fully_connected_vertex * constants::n_elements; @@ -122,8 +115,8 @@ TEST_CASE_FIXTURE( const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); - REQUIRE(new_edge.is_incident_to(vertices[constants::vertex_id_2])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); + REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); @@ -153,15 +146,11 @@ TEST_CASE_FIXTURE( const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{ - vertices[constants::vertex_id_1], vertices[constants::vertex_id_2] - }; + const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{ - vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] - }; + const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -219,9 +208,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(test_directed_adjacency_list, "remove_edge should throw when an edge is invalid") { // not existing edge between valid vertices CHECK_THROWS_AS( - sut.remove_edge( - edge_type{vertices[constants::vertex_id_1], vertices[constants::vertex_id_2]} - ), + sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), std::invalid_argument ); } @@ -376,22 +363,16 @@ TEST_CASE_FIXTURE( } struct test_undirected_adjacency_list { - using vertex_type = gl::vertex_descriptor<>; - using edge_type = gl::undirected_edge; + using edge_type = gl::undirected_edge<>; using edge_ptr_type = gl::undirected_t::edge_ptr_type; using sut_type = gl::impl::adjacency_list>; - test_undirected_adjacency_list() { - for (const auto id : constants::vertex_id_view) - vertices.emplace_back(id); - } + test_undirected_adjacency_list() {} const edge_type& add_edge( const gl::types::id_type first_id, const gl::types::id_type second_id ) { - return sut.add_edge( - gl::detail::make_edge(vertices[first_id], vertices[second_id]) - ); + return sut.add_edge(gl::detail::make_edge(first_id, second_id)); } void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { @@ -417,7 +398,6 @@ struct test_undirected_adjacency_list { } sut_type sut{constants::n_elements}; - std::vector vertices; const gl::types::size_type n_unique_edges_in_full_graph = (n_incident_edges_for_fully_connected_vertex * constants::n_elements) / 2; @@ -427,8 +407,8 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_list, "add_edge should add the edge to the lists of both vertices" ) { const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); - REQUIRE(new_edge.is_incident_to(vertices[constants::vertex_id_2])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); + REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); @@ -452,7 +432,7 @@ TEST_CASE_FIXTURE( const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_1); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); REQUIRE(new_edge.is_loop()); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); const auto adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); REQUIRE_EQ(adjacent_edges.distance(), constants::one_element); @@ -481,15 +461,11 @@ TEST_CASE_FIXTURE( const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{ - vertices[constants::vertex_id_1], vertices[constants::vertex_id_2] - }; + const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{ - vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] - }; + const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -558,9 +534,7 @@ TEST_CASE_FIXTURE( ) { // not existing edge between valid vertices CHECK_THROWS_AS( - sut.remove_edge( - edge_type{vertices[constants::vertex_id_1], vertices[constants::vertex_id_2]} - ), + sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), std::invalid_argument ); } @@ -577,7 +551,7 @@ TEST_CASE_FIXTURE( const auto& edge_to_remove = adjacent_edges_first[constants::first_element_idx]; - const auto second_id = edge_to_remove.second().id(); + const auto second_id = edge_to_remove.second(); REQUIRE_EQ(sut.adjacent_edges(second_id).distance(), constants::one_element); sut.remove_edge(edge_to_remove); diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index 296d3eea..ef03e432 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -58,36 +58,34 @@ TEST_CASE_TEMPLATE_DEFINE( } SUBCASE("add_edge should throw an error if the vertices are already incident") { - using vertex_type = typename SutType::vertex_type; using edge_type = typename SutType::edge_type; SutType sut{constants::n_elements}; - - const vertex_type v1{constants::vertex_id_1}; - const vertex_type v2{constants::vertex_id_2}; - - sut.add_edge(gl::detail::make_edge(v1, v2)); + sut.add_edge( + gl::detail::make_edge(constants::vertex_id_1, constants::vertex_id_2) + ); REQUIRE(sut.has_edge(constants::vertex_id_1, constants::vertex_id_2)); - CHECK_THROWS_AS(sut.add_edge(gl::detail::make_edge(v1, v2)), std::logic_error); + CHECK_THROWS_AS( + sut.add_edge( + gl::detail::make_edge(constants::vertex_id_1, constants::vertex_id_2) + ), + std::logic_error + ); } SUBCASE("add_edges_from should throw an error if the vertices are already incident") { - using vertex_type = typename SutType::vertex_type; using edge_type = typename SutType::edge_type; using edge_ptr_type = typename SutType::edge_ptr_type; SutType sut{constants::n_elements}; - - const vertex_type v1{constants::vertex_id_1}; - const vertex_type v2{constants::vertex_id_2}; - const vertex_type v3{constants::vertex_id_3}; - - const auto vertex_refs = {std::cref(v1), std::cref(v2), std::cref(v3)}; + const auto vertices = { + constants::vertex_id_1, constants::vertex_id_2, constants::vertex_id_3 + }; std::vector new_edges; - for (const auto& target : vertex_refs) - new_edges.push_back(gl::detail::make_edge(v1, target.get())); + for (const auto& target : vertices) + new_edges.push_back(gl::detail::make_edge(constants::vertex_id_1, target)); sut.add_edges_from(constants::vertex_id_1, std::move(new_edges)); @@ -95,11 +93,13 @@ TEST_CASE_TEMPLATE_DEFINE( return sut.has_edge(constants::vertex_id_1, target_id); })); - std::ranges::for_each(vertex_refs, [&sut, &v1](const auto& target) { + std::ranges::for_each(vertices, [&sut](const auto target) { std::vector new_edges; - new_edges.push_back(gl::detail::make_edge(v1, target.get())); + new_edges.push_back(gl::detail::make_edge(constants::vertex_id_1, target)); - CHECK_THROWS_AS(sut.add_edges_from(v1.id(), std::move(new_edges)), std::logic_error); + CHECK_THROWS_AS( + sut.add_edges_from(constants::vertex_id_1, std::move(new_edges)), std::logic_error + ); }); } } @@ -118,22 +118,16 @@ constexpr gl::types::size_type n_incident_edges_for_fully_connected_vertex = } // namespace struct test_directed_adjacency_matrix { - using vertex_type = gl::vertex_descriptor<>; - using edge_type = gl::directed_edge; + using edge_type = gl::directed_edge<>; using edge_ptr_type = gl::directed_t::edge_ptr_type; using sut_type = gl::impl::adjacency_matrix>; - test_directed_adjacency_matrix() { - for (const auto id : constants::vertex_id_view) - vertices.emplace_back(id); - } + test_directed_adjacency_matrix() {} const edge_type& add_edge( const gl::types::id_type first_id, const gl::types::id_type second_id ) { - return sut.add_edge( - gl::detail::make_edge(vertices[first_id], vertices[second_id]) - ); + return sut.add_edge(gl::detail::make_edge(first_id, second_id)); } void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { @@ -156,7 +150,6 @@ struct test_directed_adjacency_matrix { } sut_type sut{constants::n_elements}; - std::vector vertices; static constexpr gl::types::size_type n_unique_edges_in_full_graph = n_incident_edges_for_fully_connected_vertex * constants::n_elements; @@ -168,8 +161,8 @@ TEST_CASE_FIXTURE( const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); - REQUIRE(new_edge.is_incident_to(vertices[constants::vertex_id_2])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); + REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); @@ -199,15 +192,11 @@ TEST_CASE_FIXTURE( const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{ - vertices[constants::vertex_id_1], vertices[constants::vertex_id_2] - }; + const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{ - vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] - }; + const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -236,9 +225,7 @@ TEST_CASE_FIXTURE( ) { // not existing edge between valid vertices CHECK_THROWS_AS( - sut.remove_edge( - edge_type{vertices[constants::vertex_id_1], vertices[constants::vertex_id_2]} - ), + sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), std::invalid_argument ); } @@ -394,22 +381,16 @@ TEST_CASE_FIXTURE( } struct test_undirected_adjacency_matrix { - using vertex_type = gl::vertex_descriptor<>; - using edge_type = gl::undirected_edge; + using edge_type = gl::undirected_edge<>; using edge_ptr_type = gl::undirected_t::edge_ptr_type; using sut_type = gl::impl::adjacency_matrix>; - test_undirected_adjacency_matrix() { - for (const auto id : constants::vertex_id_view) - vertices.emplace_back(id); - } + test_undirected_adjacency_matrix() {} const edge_type& add_edge( const gl::types::id_type first_id, const gl::types::id_type second_id ) { - return sut.add_edge( - gl::detail::make_edge(vertices[first_id], vertices[second_id]) - ); + return sut.add_edge(gl::detail::make_edge(first_id, second_id)); } void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { @@ -435,7 +416,6 @@ struct test_undirected_adjacency_matrix { } sut_type sut{constants::n_elements}; - std::vector vertices; static constexpr gl::types::size_type n_unique_edges_in_full_graph = (n_incident_edges_for_fully_connected_vertex * constants::n_elements) / 2; @@ -445,8 +425,8 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "add_edge should add the edge to the lists of both vertices" ) { const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); - REQUIRE(new_edge.is_incident_to(vertices[constants::vertex_id_2])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); + REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); @@ -470,7 +450,7 @@ TEST_CASE_FIXTURE( const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_1); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); REQUIRE(new_edge.is_loop()); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); const auto adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); REQUIRE_EQ(adjacent_edges.distance(), constants::one_element); @@ -499,15 +479,11 @@ TEST_CASE_FIXTURE( const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{ - vertices[constants::vertex_id_1], vertices[constants::vertex_id_2] - }; + const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{ - vertices[constants::vertex_id_2], vertices[constants::vertex_id_3] - }; + const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -538,9 +514,7 @@ TEST_CASE_FIXTURE( ) { // not existing edge between valid vertices CHECK_THROWS_AS( - sut.remove_edge( - edge_type{vertices[constants::vertex_id_1], vertices[constants::vertex_id_2]} - ), + sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), std::invalid_argument ); } @@ -557,7 +531,7 @@ TEST_CASE_FIXTURE( const auto& edge_to_remove = adjacent_edges_first[constants::first_element_idx]; - const auto second_id = edge_to_remove.second().id(); + const auto second_id = edge_to_remove.second(); REQUIRE_EQ(sut.adjacent_edges(second_id).distance(), constants::one_element); sut.remove_edge(edge_to_remove); diff --git a/tests/source/gl/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp index dee57627..7b3d9ae1 100644 --- a/tests/source/gl/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -59,15 +59,16 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_postvisit_order = expected_previsit_order; std::vector previsit_order, postvisit_order; + const auto vertex_properties = graph.vertex_properties_map(); gl::algorithm::breadth_first_search( graph, gl::algorithm::no_root_vertex, - [&](const auto& vertex) { // previsit - previsit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // previsit + previsit_order.push_back(vertex_id); }, - [&](const auto& vertex) { // postvisit - postvisit_order.push_back(vertex.id()); - vertex.properties().visited = true; + [&](const gl::types::id_type vertex_id) { // postvisit + postvisit_order.push_back(vertex_id); + vertex_properties[vertex_id].visited = true; } ); @@ -131,11 +132,11 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::breadth_first_search( graph, root_vertex_id, - [&](const auto& vertex) { // previsit - previsit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // previsit + previsit_order.push_back(vertex_id); }, - [&](const auto& vertex) { // postvisit - postvisit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // postvisit + postvisit_order.push_back(vertex_id); } ); diff --git a/tests/source/gl/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp index 96b2e03b..9848c625 100644 --- a/tests/source/gl/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -67,15 +67,16 @@ TEST_CASE_TEMPLATE_DEFINE( std::deque expected_postvisit_order = expected_previsit_order; std::vector previsit_order, postvisit_order; + const auto vertex_properties = graph.vertex_properties_map(); gl::algorithm::depth_first_search( graph, gl::algorithm::no_root_vertex, - [&](const auto& vertex) { // previsit - previsit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // previsit + previsit_order.push_back(vertex_id); }, - [&](const auto& vertex) { // postvisit - postvisit_order.push_back(vertex.id()); - vertex.properties().visited = true; + [&](const gl::types::id_type vertex_id) { // postvisit + postvisit_order.push_back(vertex_id); + vertex_properties[vertex_id].visited = true; } ); @@ -139,11 +140,11 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::depth_first_search( graph, root_vertex_id, - [&](const auto& vertex) { // previsit - previsit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // previsit + previsit_order.push_back(vertex_id); }, - [&](const auto& vertex) { // postvisit - postvisit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // postvisit + postvisit_order.push_back(vertex_id); } ); @@ -244,15 +245,16 @@ TEST_CASE_TEMPLATE_DEFINE( const auto expected_postvisit_order = std::views::reverse(expected_previsit_order); std::vector previsit_order, postvisit_order; + const auto vertex_properties = graph.vertex_properties_map(); gl::algorithm::recursive_depth_first_search( graph, gl::algorithm::no_root_vertex, - [&](const auto& vertex) { // previsit - previsit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // previsit + previsit_order.push_back(vertex_id); }, - [&](const auto& vertex) { // postvisit - postvisit_order.push_back(vertex.id()); - vertex.properties().visited = true; + [&](const gl::types::id_type vertex_id) { // postvisit + postvisit_order.push_back(vertex_id); + vertex_properties[vertex_id].visited = true; } ); @@ -321,11 +323,11 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::recursive_depth_first_search( graph, root_vertex_id, - [&](const auto& vertex) { // previsit - previsit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // previsit + previsit_order.push_back(vertex_id); }, - [&](const auto& vertex) { // postvisit - postvisit_order.push_back(vertex.id()); + [&](const gl::types::id_type vertex_id) { // postvisit + postvisit_order.push_back(vertex_id); } ); diff --git a/tests/source/gl/test_alg_mst.cpp b/tests/source/gl/test_alg_mst.cpp index fb579fe9..6f26545c 100644 --- a/tests/source/gl/test_alg_mst.cpp +++ b/tests/source/gl/test_alg_mst.cpp @@ -42,7 +42,7 @@ TEST_CASE_TEMPLATE_DEFINE( for (const auto vertex_id : sut.vertex_ids()) { for (const auto& edge : sut.adjacent_edges(vertex_id)) { edge.properties().weight = edge_weight; - expected_edges.emplace_back(edge.first().id(), edge.second().id()); + expected_edges.emplace_back(edge.first(), edge.second()); } } @@ -79,7 +79,7 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertex_ids(); + const auto [first_id, second_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), @@ -124,7 +124,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) for (const auto& edge : sut.adjacent_edges(vertex_id)) - expected_edges.emplace_back(edge.first().id(), edge.second().id()); + expected_edges.emplace_back(edge.first(), edge.second()); const weight_type expected_weight = sut.n_vertices() - constants::one; @@ -135,7 +135,7 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertex_ids(); + const auto [first_id, second_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), @@ -182,7 +182,7 @@ TEST_CASE_TEMPLATE_DEFINE( for (const auto vertex_id : sut.vertex_ids()) { for (const auto& edge : sut.adjacent_edges(vertex_id)) { edge.properties().weight = edge_weight; - expected_edges.emplace_back(edge.first().id(), edge.second().id()); + expected_edges.emplace_back(edge.first(), edge.second()); } } @@ -219,7 +219,7 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertex_ids(); + const auto [first_id, second_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), @@ -264,7 +264,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) for (const auto& edge : sut.adjacent_edges(vertex_id)) - expected_edges.emplace_back(edge.first().id(), edge.second().id()); + expected_edges.emplace_back(edge.first(), edge.second()); const weight_type expected_weight = sut.n_vertices() - constants::one; @@ -275,7 +275,7 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertex_ids(); + const auto [first_id, second_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), diff --git a/tests/source/gl/test_edge_descriptor.cpp b/tests/source/gl/test_edge_descriptor.cpp index e4210da1..9bdcebd1 100644 --- a/tests/source/gl/test_edge_descriptor.cpp +++ b/tests/source/gl/test_edge_descriptor.cpp @@ -13,20 +13,18 @@ TEST_SUITE_BEGIN("test_edge_descriptor"); struct test_edge_descriptor { using vertex_type = gl::vertex_descriptor<>; - vertex_type vd_1{constants::vertex_id_1}; - vertex_type vd_2{constants::vertex_id_2}; - vertex_type vd_3{constants::vertex_id_3}; - - vertex_type invalid_vd{constants::invalid_id}; + gl::types::id_type v1 = constants::vertex_id_1; + gl::types::id_type v2 = constants::vertex_id_2; + gl::types::id_type v3 = constants::vertex_id_3; }; TEST_CASE_FIXTURE( test_edge_descriptor, "is_directed() should return true only for edges with directed edge tag" ) { - gl::directed_edge> directed_edge{vd_1, vd_2}; + gl::directed_edge<> directed_edge{v1, v2}; CHECK(directed_edge.is_directed()); - gl::undirected_edge> undirected_edge{vd_1, vd_2}; + gl::undirected_edge<> undirected_edge{v1, v2}; CHECK_FALSE(undirected_edge.is_directed()); } @@ -34,10 +32,10 @@ TEST_CASE_FIXTURE( test_edge_descriptor, "is_undirected() should return true only for edges with bidirectional edge tag" ) { - gl::undirected_edge> undirected_edge{vd_1, vd_2}; + gl::undirected_edge<> undirected_edge{v1, v2}; CHECK(undirected_edge.is_undirected()); - gl::directed_edge> directed_edge{vd_1, vd_2}; + gl::directed_edge<> directed_edge{v1, v2}; CHECK_FALSE(directed_edge.is_undirected()); } @@ -47,66 +45,60 @@ TEST_CASE_TEMPLATE_DEFINE( test_edge_descriptor fixture; const types::used_property used{true}; - const EdgeType sut{fixture.vd_1, fixture.vd_2, used}; + const EdgeType sut{fixture.v1, fixture.v2, used}; CHECK_EQ(sut.properties(), used); } // TODO: fix .clang-format to split such lines -TEST_CASE_TEMPLATE_INSTANTIATE(properties_edge_directional_tag_template, gl::directed_edge, types::used_property>, gl::undirected_edge, types::used_property>); +TEST_CASE_TEMPLATE_INSTANTIATE(properties_edge_directional_tag_template, gl::directed_edge, gl::undirected_edge); TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent tests", EdgeType, edge_directional_tag_template ) { test_edge_descriptor fixture{}; - EdgeType sut{fixture.vd_1, fixture.vd_2}; + EdgeType sut{fixture.v1, fixture.v2}; - SUBCASE("incident_vertices should return the pair of vertices the edge was initialized with") { + SUBCASE("incident_vertices should return the pair of vertex IDS the edge was initialized with" + ) { const auto& vertices = sut.incident_vertices(); - CHECK_EQ(vertices.first, fixture.vd_1); - CHECK_EQ(vertices.second, fixture.vd_2); - } - - SUBCASE("incident_vertex_ids should return the pair of ids of the vertices the edge was " - "initialized with") { - const auto& vertex_ids = sut.incident_vertex_ids(); - CHECK_EQ(vertex_ids.first, fixture.vd_1.id()); - CHECK_EQ(vertex_ids.second, fixture.vd_2.id()); + CHECK_EQ(vertices.first, fixture.v1); + CHECK_EQ(vertices.second, fixture.v2); } SUBCASE("first should return the first vertex descriptor the edge was initialized with") { - CHECK_EQ(sut.first(), fixture.vd_1); + CHECK_EQ(sut.first(), fixture.v1); } SUBCASE("second should return the second vertex descriptor the edge was initialized with") { - CHECK_EQ(sut.second(), fixture.vd_2); + CHECK_EQ(sut.second(), fixture.v2); } SUBCASE("incident_vertex should throw if input vertex is not incident with the edge") { CHECK_THROWS_AS( - func::discard_result(sut.incident_vertex(fixture.vd_3)), std::invalid_argument + func::discard_result(sut.incident_vertex(fixture.v3)), std::invalid_argument ); } SUBCASE("incident_vertex should return the vertex incident with the input vertex") { - CHECK_EQ(sut.incident_vertex(fixture.vd_1), fixture.vd_2); - CHECK_EQ(sut.incident_vertex(fixture.vd_2), fixture.vd_1); + CHECK_EQ(sut.incident_vertex(fixture.v1), fixture.v2); + CHECK_EQ(sut.incident_vertex(fixture.v2), fixture.v1); } SUBCASE("is_incident_with should return true when the given vertex is one of the connected " "vertices") { - CHECK(sut.is_incident_with(fixture.vd_1)); - CHECK(sut.is_incident_with(fixture.vd_2)); + CHECK(sut.is_incident_with(fixture.v1)); + CHECK(sut.is_incident_with(fixture.v2)); - CHECK_FALSE(sut.is_incident_with(fixture.invalid_vd)); - CHECK_FALSE(sut.is_incident_with(fixture.invalid_vd)); + CHECK_FALSE(sut.is_incident_with(constants::invalid_id)); + CHECK_FALSE(sut.is_incident_with(constants::invalid_id)); } SUBCASE("is_loop should return true onlyu for edges where both vertices are the same") { CHECK_FALSE(sut.is_loop()); - const EdgeType loop{fixture.vd_1, fixture.vd_1}; + const EdgeType loop{fixture.v1, fixture.v1}; CHECK(loop.is_loop()); } } @@ -114,8 +106,8 @@ TEST_CASE_TEMPLATE_DEFINE( // TODO: fix .clang-format to split such lines TEST_CASE_TEMPLATE_INSTANTIATE( edge_directional_tag_template, - gl::directed_edge>, // default directed edge - gl::undirected_edge> // default undirected edge + gl::directed_edge<>, // default directed edge + gl::undirected_edge<> // default undirected edge ); TEST_SUITE_END(); // test_edge_descriptor diff --git a/tests/source/gl/test_edge_tags.cpp b/tests/source/gl/test_edge_tags.cpp index 1af2bab3..d6b620af 100644 --- a/tests/source/gl/test_edge_tags.cpp +++ b/tests/source/gl/test_edge_tags.cpp @@ -10,19 +10,15 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_edge_tags"); struct test_edge_tags { - using vertex_type = gl::vertex_descriptor<>; - - vertex_type vd_1{constants::vertex_id_1}; - vertex_type vd_2{constants::vertex_id_2}; - - vertex_type invalid_vd{constants::invalid_id}; + gl::types::id_type v1 = constants::vertex_id_1; + gl::types::id_type v2 = constants::vertex_id_2; }; struct test_directed_edge_tag : test_edge_tags { using sut_type = gl::directed_t; - using edge_type = gl::directed_edge; + using edge_type = gl::directed_edge<>; - const std::unique_ptr edge = gl::detail::make_edge(vd_1, vd_2); + const std::unique_ptr edge = gl::detail::make_edge(v1, v2); }; TEST_CASE_FIXTURE( @@ -33,18 +29,18 @@ TEST_CASE_FIXTURE( REQUIRE(edge->is_directed()); REQUIRE_FALSE(edge->is_undirected()); - CHECK_EQ(edge->first(), vd_1); - CHECK_EQ(edge->second(), vd_2); + CHECK_EQ(edge->first(), v1); + CHECK_EQ(edge->second(), v2); } TEST_CASE_FIXTURE( test_directed_edge_tag, "make_edge should return a unique ptr to a directed edge with the given properties" ) { - using property_edge_type = gl::directed_edge; + using property_edge_type = gl::directed_edge; const types::used_property used{true}; - const auto property_edge = gl::detail::make_edge(vd_1, vd_2, used); + const auto property_edge = gl::detail::make_edge(v1, v2, used); static_assert(std::is_same_v< std::remove_cvref_t, @@ -53,32 +49,32 @@ TEST_CASE_FIXTURE( REQUIRE(property_edge->is_directed()); REQUIRE_FALSE(property_edge->is_undirected()); - CHECK_EQ(property_edge->first(), vd_1); - CHECK_EQ(property_edge->second(), vd_2); + CHECK_EQ(property_edge->first(), v1); + CHECK_EQ(property_edge->second(), v2); CHECK_EQ(property_edge->properties(), used); } TEST_CASE_FIXTURE( test_directed_edge_tag, "is_incident_from should return true only for the first vertex" ) { - CHECK(sut_type::is_incident_from(*edge, vd_1.id())); - CHECK_FALSE(sut_type::is_incident_from(*edge, vd_2.id())); - CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd.id())); + CHECK(sut_type::is_incident_from(*edge, v1)); + CHECK_FALSE(sut_type::is_incident_from(*edge, v2)); + CHECK_FALSE(sut_type::is_incident_from(*edge, constants::invalid_id)); } TEST_CASE_FIXTURE( test_directed_edge_tag, "is_incident_to should return true only for the second vertex" ) { - CHECK(sut_type::is_incident_to(*edge, vd_2.id())); - CHECK_FALSE(sut_type::is_incident_to(*edge, vd_1.id())); - CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd.id())); + CHECK(sut_type::is_incident_to(*edge, v2)); + CHECK_FALSE(sut_type::is_incident_to(*edge, v1)); + CHECK_FALSE(sut_type::is_incident_to(*edge, constants::invalid_id)); } struct test_undirected_edge_tag : test_edge_tags { using sut_type = gl::undirected_t; - using edge_type = gl::undirected_edge; + using edge_type = gl::undirected_edge<>; - const std::shared_ptr edge = gl::detail::make_edge(vd_1, vd_2); + const std::shared_ptr edge = gl::detail::make_edge(v1, v2); }; TEST_CASE_FIXTURE( @@ -89,18 +85,18 @@ TEST_CASE_FIXTURE( REQUIRE(edge->is_undirected()); REQUIRE_FALSE(edge->is_directed()); - CHECK_EQ(edge->first(), vd_1); - CHECK_EQ(edge->second(), vd_2); + CHECK_EQ(edge->first(), v1); + CHECK_EQ(edge->second(), v2); } TEST_CASE_FIXTURE( test_directed_edge_tag, "make_edge should return a shared ptr to a directed edge with the given properties" ) { - using property_edge_type = gl::undirected_edge; + using property_edge_type = gl::undirected_edge; const types::used_property used{true}; - const auto property_edge = gl::detail::make_edge(vd_1, vd_2, used); + const auto property_edge = gl::detail::make_edge(v1, v2, used); static_assert(std::is_same_v< std::remove_cvref_t, @@ -109,27 +105,27 @@ TEST_CASE_FIXTURE( REQUIRE(property_edge->is_undirected()); REQUIRE_FALSE(property_edge->is_directed()); - CHECK_EQ(property_edge->first(), vd_1); - CHECK_EQ(property_edge->second(), vd_2); + CHECK_EQ(property_edge->first(), v1); + CHECK_EQ(property_edge->second(), v2); CHECK_EQ(property_edge->properties(), used); } TEST_CASE_FIXTURE( test_undirected_edge_tag, "is_incident_from should return true for both vertices" ) { - CHECK(sut_type::is_incident_from(*edge, vd_1.id())); - CHECK(sut_type::is_incident_from(*edge, vd_2.id())); + CHECK(sut_type::is_incident_from(*edge, v1)); + CHECK(sut_type::is_incident_from(*edge, v2)); - CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd.id())); - CHECK_FALSE(sut_type::is_incident_from(*edge, invalid_vd.id())); + CHECK_FALSE(sut_type::is_incident_from(*edge, constants::invalid_id)); + CHECK_FALSE(sut_type::is_incident_from(*edge, constants::invalid_id)); } TEST_CASE_FIXTURE(test_undirected_edge_tag, "is_incident_to should return true for both vertices") { - CHECK(sut_type::is_incident_to(*edge, vd_1.id())); - CHECK(sut_type::is_incident_to(*edge, vd_2.id())); + CHECK(sut_type::is_incident_to(*edge, v1)); + CHECK(sut_type::is_incident_to(*edge, v2)); - CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd.id())); - CHECK_FALSE(sut_type::is_incident_to(*edge, invalid_vd.id())); + CHECK_FALSE(sut_type::is_incident_to(*edge, constants::invalid_id)); + CHECK_FALSE(sut_type::is_incident_to(*edge, constants::invalid_id)); } TEST_SUITE_END(); // untest_edge_tags diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index aa6b3f1f..89472ccb 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -372,8 +372,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edge(ids) should properly add the new edge") { const auto& new_edge = sut.add_edge(constants::vertex_id_1, constants::vertex_id_2); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); - REQUIRE(new_edge.is_incident_to(vertices[constants::vertex_id_2])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); + REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); @@ -400,8 +400,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edge(vertices) should properly add the new edge") { const auto& new_edge = sut.add_edge(vertex_1, vertex_2); - REQUIRE(new_edge.is_incident_from(vertex_1)); - REQUIRE(new_edge.is_incident_to(vertex_2)); + REQUIRE(new_edge.is_incident_from(vertex_1.id())); + REQUIRE(new_edge.is_incident_to(vertex_2.id())); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); @@ -573,8 +573,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edge(ids) should properly add the new edge") { const auto& new_edge = sut.add_edge(constants::vertex_id_1, constants::vertex_id_2, constants::used); - REQUIRE(new_edge.is_incident_from(vertices[constants::vertex_id_1])); - REQUIRE(new_edge.is_incident_to(vertices[constants::vertex_id_2])); + REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); + REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); REQUIRE_EQ(new_edge.properties(), constants::used); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); @@ -608,8 +608,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edge(vertices) should properly add the new edge") { const auto& new_edge = sut.add_edge(vertex_1, vertex_2, constants::used); - REQUIRE(new_edge.is_incident_from(vertex_1)); - REQUIRE(new_edge.is_incident_to(vertex_2)); + REQUIRE(new_edge.is_incident_from(vertex_1.id())); + REQUIRE(new_edge.is_incident_to(vertex_2.id())); REQUIRE_EQ(new_edge.properties(), constants::used); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); diff --git a/tests/source/gl/test_graph_incidence.cpp b/tests/source/gl/test_graph_incidence.cpp index 3494451b..3885b74c 100644 --- a/tests/source/gl/test_graph_incidence.cpp +++ b/tests/source/gl/test_graph_incidence.cpp @@ -9,19 +9,15 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_graph_incidence"); -struct test_graph_incidence { - using vertex_type = gl::vertex_descriptor<>; - - vertex_type out_of_range_vertex{constants::out_of_range_element_idx}; -}; - TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_template) { - test_graph_incidence fixture; + using vertex_type = gl::vertex_descriptor<>; SutType sut{constants::n_elements}; + const auto& vd_1 = sut.get_vertex(constants::vertex_id_1); const auto& vd_2 = sut.get_vertex(constants::vertex_id_2); const auto& vd_3 = sut.get_vertex(constants::vertex_id_3); + vertex_type out_of_range_vertex{constants::out_of_range_element_idx}; SUBCASE("are_incident(vertex_id, vertex_id) should throw for out of range vertex ids") { CHECK_THROWS_AS( @@ -59,18 +55,14 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ SUBCASE("are_incident(vertex, vertex) should throw if at least one of the vertices is invalid" ) { CHECK_THROWS_AS( - func::discard_result( - sut.are_incident(fixture.out_of_range_vertex, fixture.out_of_range_vertex) - ), + func::discard_result(sut.are_incident(out_of_range_vertex, out_of_range_vertex)), std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(fixture.out_of_range_vertex, vd_2)), - std::out_of_range + func::discard_result(sut.are_incident(out_of_range_vertex, vd_2)), std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(vd_1, fixture.out_of_range_vertex)), - std::out_of_range + func::discard_result(sut.are_incident(vd_1, out_of_range_vertex)), std::out_of_range ); } @@ -95,26 +87,22 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ const auto& edge = sut.add_edge(vd_1, vd_2); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(fixture.out_of_range_vertex, edge)), - std::out_of_range + func::discard_result(sut.are_incident(out_of_range_vertex, edge)), std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(fixture.out_of_range_vertex, edge)), - std::out_of_range + func::discard_result(sut.are_incident(out_of_range_vertex, edge)), std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(edge, fixture.out_of_range_vertex)), - std::out_of_range + func::discard_result(sut.are_incident(edge, out_of_range_vertex)), std::out_of_range ); CHECK_THROWS_AS( - func::discard_result(sut.are_incident(edge, fixture.out_of_range_vertex)), - std::out_of_range + func::discard_result(sut.are_incident(edge, out_of_range_vertex)), std::out_of_range ); } SUBCASE("are_incident(vertex and edge pair) should throw if the edge is invalid") { - const typename SutType::edge_type invalid_edge{vd_1, vd_2}; + const typename SutType::edge_type invalid_edge{vd_1.id(), vd_2.id()}; CHECK_THROWS_AS( func::discard_result(sut.are_incident(vd_1, invalid_edge)), std::invalid_argument @@ -144,7 +132,7 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ SUBCASE("are_incident(edge, edge) should throw if either edge is invalid") { const auto& edge = sut.add_edge(vd_1, vd_2); - const typename SutType::edge_type invalid_edge{vd_1, vd_2}; + const typename SutType::edge_type invalid_edge{vd_1.id(), vd_2.id()}; CHECK_THROWS_AS( func::discard_result(sut.are_incident(edge, invalid_edge)), std::invalid_argument diff --git a/tests/source/gl/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp index 970eebd6..fc570671 100644 --- a/tests/source/gl/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -140,7 +140,7 @@ struct test_undirected_graph_io { for (const auto& vertex : sut_out.vertices()) { vertex.properties() = std::format("vertex_{}", v_idx++); for (const auto& edge : sut_out.adjacent_edges(vertex)) - if (edge.first().id() == vertex.id()) + if (edge.first() == vertex.id()) edge.properties() = std::format("edge_{}", e_idx++); } } diff --git a/tests/source/gl/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp index 7f6510db..306aa878 100644 --- a/tests/source/gl/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -148,34 +148,33 @@ template template [[nodiscard]] auto is_biconnected_to_binary_chlidren(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; - return [&graph](const vertex_type& source) { - const auto target_ids = gl::topology::detail::get_binary_target_ids(source.id()); + return [&graph](const gl::types::id_type source_id) { + const auto target_ids = gl::topology::detail::get_binary_target_ids(source_id); const gl::types::id_type parent_id = - source.id() == constants::zero + source_id == constants::zero ? constants::zero - : (source.id() - constants::one) / constants::two; + : (source_id - constants::one) / constants::two; if (target_ids.first >= graph.n_vertices()) { // no need to check second as second = first + 1 - const auto adjacent_edges = graph.adjacent_edges(source); + const auto adjacent_edges = graph.adjacent_edges(source_id); return adjacent_edges.distance() == constants::one - and adjacent_edges[constants::first_element_idx].incident_vertex(source).id() - == parent_id; + and adjacent_edges[constants::first_element_idx].incident_vertex(source_id + ) == parent_id; } - const auto& parent = graph.get_vertex(parent_id); - const auto& target_1 = graph.get_vertex(target_ids.first); - const auto& target_2 = graph.get_vertex(target_ids.second); + const auto& target_1 = target_ids.first; + const auto& target_2 = target_ids.second; - return std::ranges::all_of(graph.vertices(), [&](const auto& vertex) { - if (vertex == target_1 or vertex == target_2) - return graph.has_edge(source, vertex); + return std::ranges::all_of(graph.vertex_ids(), [&](const auto vertex_id) { + if (vertex_id == target_1 or vertex_id == target_2) + return graph.has_edge(source_id, vertex_id); - if (vertex == parent and source.id() != constants::zero) - return graph.has_edge(source, vertex); + if (vertex_id == parent_id and source_id != constants::zero) + return graph.has_edge(source_id, vertex_id); - return not graph.has_edge(source, vertex); + return not graph.has_edge(source_id, vertex_id); }); }; } @@ -347,7 +346,7 @@ TEST_CASE_TEMPLATE_DEFINE( verify_graph_size(bin_tree, expected_n_vertices, expected_n_connections); CHECK(std::ranges::all_of( - bin_tree.vertices(), predicate::is_biconnected_to_binary_chlidren(bin_tree) + bin_tree.vertex_ids(), predicate::is_biconnected_to_binary_chlidren(bin_tree) )); } } @@ -433,7 +432,7 @@ TEST_CASE_TEMPLATE_DEFINE( verify_graph_size(bin_tree, expected_n_vertices, expected_n_connections); CHECK(std::ranges::all_of( - bin_tree.vertices(), predicate::is_biconnected_to_binary_chlidren(bin_tree) + bin_tree.vertex_ids(), predicate::is_biconnected_to_binary_chlidren(bin_tree) )); } } diff --git a/tests/source/gl/test_vertex_degree_getters.cpp b/tests/source/gl/test_vertex_degree_getters.cpp index 079d4982..4c228ebd 100644 --- a/tests/source/gl/test_vertex_degree_getters.cpp +++ b/tests/source/gl/test_vertex_degree_getters.cpp @@ -84,7 +84,7 @@ TEST_CASE_TEMPLATE_DEFINE( i = constants::zero; CHECK(std::ranges::all_of( sut.vertices(), - [&](const auto vertex_id) { + [&](const gl::types::id_type vertex_id) { const bool result = sut.in_degree(vertex_id) == expected_in_deg_list[i] and sut.out_degree(vertex_id) == expected_out_deg_list[i] and sut.degree(vertex_id) == expected_deg_list[i]; @@ -157,7 +157,7 @@ TEST_CASE_TEMPLATE_DEFINE( i = constants::zero; CHECK(std::ranges::all_of( sut.vertices(), - [&](const auto vertex_id) { + [&](const gl::types::id_type vertex_id) { const auto expected_deg = expected_deg_list[i]; const bool result = sut.in_degree(vertex_id) == expected_deg and sut.out_degree(vertex_id) == expected_deg From 2b1c04c2798cd01d828b7df072e1b466c320e6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Wed, 12 Nov 2025 21:45:36 +0100 Subject: [PATCH 05/54] YT-CPPGL-56: Replace the usage of pointers for vertex and edge storage to simple composite types [Part 3] - Aligned the graph's adjacency storage types to store only edge IDs instead of edge pointers - Removed the `iterator_range`, `dereferencing_iterator` and `non_null_iterator` types and replaced them with standard library views - Removed the reference wrapper and optional reference wrapper type aliases - Introduced an `invalid_id` constant and aligned the vertex and edge descriptor types to use this constant for default construction - Enabled copy and move constructors and operators for both vertex and edge descriptor types - Removed the `edge_info` type used for graph MST finding --- README.md | 1 - docs/additional_functionality.md | 42 ++ docs/algorithms.md | 13 +- docs/core_util_types.md | 255 --------- docs/dev_notes.md | 15 +- docs/graph.md | 240 ++++---- docs/graph_elements.md | 116 +++- docs/io.md | 56 +- include/gl/algorithm/dijkstra.hpp | 10 +- include/gl/algorithm/mst.hpp | 40 +- include/gl/algorithm/types.hpp | 8 - include/gl/constants.hpp | 3 + include/gl/edge_descriptor.hpp | 126 ++++- include/gl/edge_tags.hpp | 71 --- include/gl/graph.hpp | 518 ++++++++++-------- include/gl/graph_traits.hpp | 2 - include/gl/impl/adjacency_list.hpp | 242 +++++--- include/gl/impl/adjacency_matrix.hpp | 267 ++++++--- .../gl/impl/specialized/adjacency_list.hpp | 239 +++----- .../gl/impl/specialized/adjacency_matrix.hpp | 226 ++++---- include/gl/types/dereferencing_iterator.hpp | 121 ---- include/gl/types/iterator_range.hpp | 211 ------- include/gl/types/non_null_iterator.hpp | 210 ------- include/gl/types/properties.hpp | 17 +- include/gl/types/types.hpp | 9 - include/gl/util/ranges.hpp | 23 + include/gl/vertex_descriptor.hpp | 61 ++- include/gl/views.hpp | 11 - tests/include/testing/gl/constants.hpp | 3 +- tests/include/testing/gl/io_common.hpp | 5 +- tests/include/testing/gl/transforms.hpp | 30 - tests/source/gl/test_adjacency_list.cpp | 382 +++++++------ tests/source/gl/test_adjacency_matrix.cpp | 429 +++++++++------ tests/source/gl/test_alg_dijkstra.cpp | 10 +- tests/source/gl/test_alg_mst.cpp | 44 +- .../source/gl/test_dereferencing_iterator.cpp | 208 ------- tests/source/gl/test_edge_descriptor.cpp | 101 +++- tests/source/gl/test_edge_tags.cpp | 103 +--- tests/source/gl/test_graph.cpp | 316 +++++------ tests/source/gl/test_graph_file_io.cpp | 10 +- tests/source/gl/test_graph_incidence.cpp | 22 +- tests/source/gl/test_graph_io.cpp | 2 +- .../gl/test_graph_topology_builders.cpp | 27 +- tests/source/gl/test_iterator_range.cpp | 186 ------- tests/source/gl/test_non_null_iterator.cpp | 208 ------- .../source/gl/test_vertex_degree_getters.cpp | 21 +- tests/source/gl/test_vertex_descriptor.cpp | 33 +- 47 files changed, 2128 insertions(+), 3165 deletions(-) delete mode 100644 include/gl/types/dereferencing_iterator.hpp delete mode 100644 include/gl/types/iterator_range.hpp delete mode 100644 include/gl/types/non_null_iterator.hpp create mode 100644 include/gl/util/ranges.hpp delete mode 100644 include/gl/views.hpp delete mode 100644 tests/include/testing/gl/transforms.hpp delete mode 100644 tests/source/gl/test_dereferencing_iterator.cpp delete mode 100644 tests/source/gl/test_iterator_range.cpp delete mode 100644 tests/source/gl/test_non_null_iterator.cpp diff --git a/README.md b/README.md index d0f9f074..e752a54d 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,6 @@ The instructions and requirements of working on the `CPP-GL` project can be foun ## Compiler support - | Compiler | Min version | | :-: | :-: | | GNU G++ | 14 | diff --git a/docs/additional_functionality.md b/docs/additional_functionality.md index 56b7890d..62b52ddd 100644 --- a/docs/additional_functionality.md +++ b/docs/additional_functionality.md @@ -1,6 +1,7 @@ # Additional functionality - [Force inlining](#force-inlining) +- [Ranges utility](#ranges-utility)
@@ -16,3 +17,44 @@ To enable the force inlining functionality you have to add the following in your > [!NOTE] > Force inlining is supported only for the GNU G++ and CLang++ compilers. + +
+
+ +## Ranges utility + +Small helpers for working with C++20 ranges. + +> [!NOTE] +> - Header: [gl/util/ranges.hpp](/include/gl/util/ranges.hpp) +> - Namespace: `gl::util` + +### View adapters + +- **`deref_view`** + - *Description*: A transform view that dereferences pointer-like elements in a range. Useful for turning a range of pointers or smart pointers into a range of references. + - *Usage*: `range | gl::util::deref_view` + - *Works with*: `T*`, `std::unique_ptr`, `std::shared_ptr`, and generally pointer-like objects supporting unary `*`. + - *Return type*: A lazy transformed view whose reference type is `decltype(*p)` of the underlying elements. + - *Example*: + ```cpp + std::vector> v; /* ... */ + for (int& x : v | gl::util::deref_view) { + // use x as an int& + } + ``` + +### Functions + +- **`range_size(r)`** + - *Template parameters*: + - `R: std::ranges::range` + - *Description*: Returns the size of a range. If `R` models `std::ranges::sized_range`, it forwards to `std::ranges::size(r)` in O(1). Otherwise, it computes `std::ranges::distance(std::begin(r), std::end(r))` in O(n). + - *Parameters*: + - `r: R&&` – the input range. + - *Return type*: Same type as produced by `std::ranges::size(r)` for sized ranges or `std::ranges::distance(...)` otherwise. + - *Complexity*: O(1) for sized ranges; O(n) otherwise. + - *Exceptions*: No specific exceptions; propagates iterator operations if they throw. + +> [!CAUTION] +> For non-sized, single-pass/input ranges `range_size` will consume the range when computing distance. diff --git a/docs/algorithms.md b/docs/algorithms.md index 56908859..c8e1dd89 100644 --- a/docs/algorithms.md +++ b/docs/algorithms.md @@ -62,17 +62,6 @@ This section covers the specific types and type traits used for the algorithm im - `id: types::id_type` - the ID of the vertex. - `source_id: types::id_type` - the ID of the source vertex, typically used during algorithms. -- `edge_info` - - *Description*: Holds information about an edge, including the edge itself and its source vertex's ID. - - *Template parameters*: - - `EdgeType: type_traits::c_instantiation_of` - the type of the edge. - - *Constructors*: - - `vertex_info(types::id_type id)` - initializes the object with the same value for `id` and `source_id` representing a starting vertex - - `vertex_info(types::id_type id, types::id_type source_id)` - - *Member variables*: - - `edge: types::const_ref_wrap` - a constant reference wrapper for the edge. - - `source_id: types::id_type` - the ID of the source vertex of the held edge. - - `predicate_result` - *Description*: Represents the result of a predicate evaluation. - *Type definitions*: @@ -383,7 +372,7 @@ This section covers the specific types and type traits used for the algorithm im > - *Constructors*: > - `mst_descriptor(types::size_type n_vertices)` - Initializes an empty `edges` list with the capacity of $\text{n-vertices} - 1$ and the total weight is set to $0$. > - *Member variables*: -> - `edges: std::vector>` - A list of constant edge references representing the edges of the spanning tree. +> - `edges: std::vector` - A list of edges of the spanning tree. > - `weight: weight_type` - The total weight of all edges of the spanning tree.
diff --git a/docs/core_util_types.md b/docs/core_util_types.md index 247c6a25..79f01137 100644 --- a/docs/core_util_types.md +++ b/docs/core_util_types.md @@ -9,7 +9,6 @@ This section describes a set of fundamental types provided by the library. While - [Common type traits](#common-type-traits) - [General type aliases](#general-type-aliases) - [Graph element property types](#graph-element-property-types) -- [Iterator-related types](#iterator-related-types) - [I/O types](#io-types)
@@ -33,9 +32,6 @@ The table below contains the basic type aliases defined in the library. | `size_type` | `std::uint64_t` | | `id_type` | `size_type` | | `homogeneous_pair` | `std::pair` | -| `optional_ref` | `std::optional>>` | -| `optional_cref` | `std::optional>>` | -| `const_ref_wrap` | `std::reference_wrapper` | > [!NOTE] > All types in the table above are defined in the `gl::types` namespace and in the [gl/types/types.hpp](/include/gl/types/types.hpp) header file. @@ -251,257 +247,6 @@ This section describes the type traits that are associated with the property typ

-## Iterator-related types - -This section provides the description of types used to manage range/collection iterators. - -### `class iterator_range` - -- *Description*: - The `iterator_range` class is a wrapper around a pair of forward iterators, providing a range interface with optional caching behavior. It allows for efficient iteration over a range of elements, with different caching strategies based on the specified `CacheMode`. - -- *Template parameters*: - - `Iterator: std::forward_iterator` - A type that meets the requirements of a forward iterator. - - `CacheMode: type_traits::c_cache_mode` - A type trait that defines the caching strategy. - **NOTE:** The `CacheMode` parameter can be one of the following: `gl::type_traits::{no_cache,lazy_cache,eager_cache}` - -> [!IMPORTANT] -> The default caching strategy for the `iterator_range` class can be set using the following macros: -> -> - `GL_CONFIG_IT_RANGE_DEFAULT_CACHE_MODE_EAGER` - default strategy set to `gl::type_traits::eager_cache` -> - `GL_CONFIG_IT_RANGE_DEFAULT_CACHE_MODE_LAZY` - default strategy set to `gl::type_traits::lazy_cache` -> - `GL_CONFIG_IT_RANGE_DEFAULT_CACHE_MODE_NONE` - default strategy set to `gl::type_traits::no_cache` - -- *Type definitions*: - - `iterator` - The type of the iterator. - - `const_iterator` - The type of the constant iterator **(for C++23 or newer)**. - - `distance_type` - The type used for representing the distance between iterators (`std::ptrdiff_t`). - - `value_type` - The type of the elements in the range (equivalent to `std::remove_reference_t`). - - `cache_mode` - The caching strategy used in the range (an alias for `CacheMode`). - -- *Constructors*: - - `iterator_range(iterator begin, iterator end)` - Initializes the iterator range using the specified caching strategy. - - `iterator_range(const iterator_range&)` - Copy constructor (*default*). - - `iterator_range(iterator_range&&)` - Move constructor (*default*). - -- *Assignment operators*: - - `operator=(const iterator_range&)` - Copy assignment operator (*default*). - - `operator=(iterator_range&&)` - Move assignment operator (*default*). - -- *Destructor*: - - `~iterator_range()` - Destructor (*default*). - -- *Member functions*: - - `begin() const -> iterator` - Returns an iterator to the beginning of the range. - - `end() const -> iterator` - Returns an iterator to the end of the range. - - `cbegin() const -> auto` - Returns a constant iterator to the beginning of the range **(for C++23 or newer)**. - - `cend() const -> auto` - Returns a constant iterator to the end of the range **(for C++23 or newer)**. - - `distance() const -> distance_type` - Returns the number of elements in the range, computed based on the caching strategy. - - `element_at(types::size_type position) -> value_type&` - Returns a reference to the element at the specified position. - - `element_at(types::size_type position) const -> const value_type&` - Returns a reference to the element at the specified position. - - `operator[](types::size_type position) -> value_type&` - Provides access to the element at the specified position (equivalent to `element_at`). - - `operator[](types::size_type position) const -> const value_type&` - Provides access to the element at the specified position (equivalent to `element_at`). - -> [!IMPORTANT] -> The `iterator_range` class is marked `final` by default. To use this class as a base class you have to add: -> -> ```cpp -> #define GL_CONFIG_IT_RANGE_NOT_FINAL -> ``` -> -> in your program or add a `-DGL_CONFIG_IT_RANGE_NOT_FINAL` flag when compiling. - -#### Associated functions - -- `gl::make_iterator_range(begin, end)` - - *Description*: Creates an `iterator_range` from the given iterators using the specified caching strategy. - - *Template parameters*: - - `Iterator: std::forward_iterator` - The iterator type. - - `CacheMode: type_traits::c_cache_mode` - The caching strategy. - - *Parameters*: - - `begin: Iterator` - the begin iterator - - `end: Iterator` - the end iterator - - *Return type*: `types::iterator_range` - -- `gl::make_iterator_range(range)` - - *Description*: Creates an `iterator_range` from the given range using its `begin` and `end` iterators and with the specified caching strategy - - *Template parameters*: - - `Range: c_range` - The range type. - - `CacheMode: type_traits::c_cache_mode` - The caching strategy. - - *Parameters*: - - `range: Range&` - the range used to create the `iterator_range` - - *Return type*: `types::iterator_range` - -- `gl::make_const_iterator_range(range)` - - *Description*: Creates an `iterator_range` from the given range using its `cbegin` and `cend` iterators and with the specified caching strategy - - *Template parameters*: - - `Range: c_range` - The range type. - - `CacheMode: type_traits::c_cache_mode` - The caching strategy. - - *Parameters*: - - `range: const Range&` - the range used to create the `iterator_range` - - *Return type*: `types::iterator_range` - -
- -### `class dereferencing_iterator` - -- *Description*: - The `dereferencing_iterator` class is a wrapper around a forward iterator that dereferences its elements, providing direct access to the underlying objects managed by strong pointer types. This iterator enables seamless iteration over collections of pointer elements. - -- *Template parameters*: - - `Iterator: std::forward_iterator` - A type that meets the requirements of a forward iterator and points to strong pointer types. - -- *Constraints*: - - `type_traits::c_strong_ptr` - The iterator's value type must be a strong (raw, unique, shared) pointer type. - -- *Type definitions*: - - `iterator_type` - The type of the underlying iterator. - - `value_type` - The type of the elements pointed to by the strong pointers (defined as `type_traits::ptr_element_type_t`). - - `reference` - A reference type to the dereferenced value (`value_type&`). - - `pointer` - A pointer type to the dereferenced value (`value_type*`). - - `difference_type` - The type used for representing the distance between iterators (inferred from `iterator_type`). - - `iterator_category` - The iterator category inferred from `iterator_type`. - -- *Constructors*: - - `dereferencing_iterator() = default` - Default constructor. - - `dereferencing_iterator(iterator_type it)` - Initializes the iterator with the specified iterator. - - `dereferencing_iterator(const dereferencing_iterator&) = default` - Copy constructor (*default*). - - `dereferencing_iterator(dereferencing_iterator&&) = default` - Move constructor (*default*). - -- *Assignment operators*: - - `dereferencing_iterator& operator=(const dereferencing_iterator&) = default` - Copy assignment operator (*default*). - - `dereferencing_iterator& operator=(dereferencing_iterator&&) = default` - Move assignment operator (*default*). - -- *Member functions*: - - `operator*() const -> reference` - Dereferences the iterator, returning a reference to the underlying value. - - `operator->() const -> pointer` - Returns a pointer to the underlying value, handling strong smart pointer types appropriately. - - `operator++() -> dereferencing_iterator&` - Prefix increment operator; advances the iterator. - - `operator++(int) -> dereferencing_iterator` - Postfix increment operator; advances the iterator and returns a copy of the previous state. - - `operator--() -> dereferencing_iterator&` (requires `std::bidirectional_iterator`) - Prefix decrement operator; moves the iterator backward. - - `operator--(int) -> dereferencing_iterator` (requires `std::bidirectional_iterator`) - Postfix decrement operator; moves the iterator backward and returns a copy of the previous state. - - `operator==(const dereferencing_iterator& other) const -> bool` - Compares two dereferencing iterators for equality. - - `operator!=(const dereferencing_iterator& other) const -> bool` - Compares two dereferencing iterators for inequality. - - `base() const -> iterator_type` - Returns the underlying iterator. - -#### Associated functions - -- `gl::deref_begin(Range& range)` - - *Description*: Returns a `dereferencing_iterator` pointing to the beginning of the specified range. - - *Template parameters*: - - `Range: c_range` - The range type. - - *Parameters*: - - `range: Range&` - The range from which to obtain the beginning iterator. - - *Return type*: `types::dereferencing_iterator` - -- `gl::deref_end(Range& range)` - - *Description*: Returns a `dereferencing_iterator` pointing to the end of the specified range. - - *Template parameters*: - - `Range: c_range` - The range type. - - *Parameters*: - - `range: Range&` - The range from which to obtain the end iterator. - - *Return type*: `types::dereferencing_iterator` - -- `gl::deref_cbegin(const Range& range)` - - *Description*: Returns a `dereferencing_iterator` pointing to the constant beginning of the specified range. - - *Template parameters*: - - `Range: c_const_range` - The constant range type. - - *Parameters*: - - `range: const Range&` - The range from which to obtain the constant beginning iterator. - - *Return type*: `types::dereferencing_iterator` - -- `gl::deref_cend(const Range& range)` - - *Description*: Returns a `dereferencing_iterator` pointing to the constant end of the specified range. - - *Template parameters*: - - `Range: c_const_range` - The constant range type. - - *Parameters*: - - `range: const Range&` - The range from which to obtain the constant end iterator. - - *Return type*: `types::dereferencing_iterator` - -
- -### `class non_null_iterator` - -- *Description*: - The `non_null_iterator` class is a wrapper around a forward iterator that skips null elements (i.e., null pointers) during iteration. This iterator is particularly useful when working with collections of pointers where you want to ignore any null values seamlessly. - -- *Template parameters*: - - `Iterator: std::forward_iterator` - A type that meets the requirements of a forward iterator and points to strong pointer types. - -- *Constraints*: - - `type_traits::c_strong_ptr` - The iterator's value type must be a strong (raw, unique, shared) pointer type. - -- *Type definitions*: - - `iterator_type` - The type of the underlying iterator. - - `value_type` - The type of the elements pointed to by the strong pointers (inferred from `iterator_type`). - - `reference` - The reference type of the elements in the underlying iterator (`typename std::iterator_traits::reference`). - - `pointer` - The pointer type of the elements in the underlying iterator (`typename std::iterator_traits::pointer`). - - `difference_type` - The type used for representing the distance between iterators (inferred from `iterator_type`). - - `iterator_category` - The iterator category inferred from `iterator_type`. - -- *Constructors*: - - `non_null_iterator() = default` - Default constructor. - - `non_null_iterator(iterator_type current, iterator_type end)` (for non-bidirectional iterators) - Initializes the iterator with the specified current and end iterators, skipping initial null elements if necessary. - - `non_null_iterator(iterator_type begin, iterator_type current, iterator_type end)` (for bidirectional iterators) - Initializes the iterator with specified begin, current, and end iterators, skipping initial null elements if necessary. - - `non_null_iterator(const non_null_iterator&) = default` - Copy constructor (*default*). - - `non_null_iterator(non_null_iterator&&) = default` - Move constructor (*default*). - -- *Assignment operators*: - - `non_null_iterator& operator=(const non_null_iterator&) = default` - Copy assignment operator (*default*). - - `non_null_iterator& operator=(non_null_iterator&&) = default` - Move assignment operator (*default*). - -- *Member functions*: - - `operator*() const -> reference` - Dereferences the iterator, returning a reference to the underlying non-null value. - - `operator->() const -> pointer` - Returns a pointer to the underlying non-null value. - - `operator++() -> non_null_iterator&` - Prefix increment operator; advances the iterator and skips any subsequent null elements. - - `operator++(int) -> non_null_iterator` - Postfix increment operator; advances the iterator and skips any subsequent null elements, returning a copy of the previous state. - - `operator--() -> non_null_iterator&` (requires `std::bidirectional_iterator`) - Prefix decrement operator; moves the iterator backward and skips any null elements. - - `operator--(int) -> non_null_iterator` (requires `std::bidirectional_iterator`) - Postfix decrement operator; moves the iterator backward and skips any null elements, returning a copy of the previous state. - - `operator==(const non_null_iterator& other) const -> bool` - Compares two non-null iterators for equality. - - `operator!=(const non_null_iterator& other) const -> bool` - Compares two non-null iterators for inequality. - - `base() const -> iterator_type` - Returns the current underlying iterator. - -- *Private member functions*: - - `_is_null(const reference& ptr) -> bool` - Checks if the given pointer is null. - - `_skip_null_elements_forward()` - Advances the current iterator past any null elements in the forward direction. - - `_skip_null_elements_backward()` (requires `std::bidirectional_iterator`) - Advances the current iterator past any null elements in the backward direction. - -#### Associated functions - -- `gl::non_null_begin(Range& range)` - - *Description*: Returns a `non_null_iterator` pointing to the beginning of the specified range while skipping null elements. - - *Template parameters*: - - `Range: c_range` - The range type. - - *Parameters*: - - `range: Range&` - The range from which to obtain the beginning iterator. - - *Return type*: `types::non_null_iterator` - -- `gl::non_null_end(Range& range)` - - *Description*: Returns a `non_null_iterator` pointing to the end of the specified range while skipping null elements. - - *Template parameters*: - - `Range: c_range` - The range type. - - *Parameters*: - - `range: Range&` - The range from which to obtain the end iterator. - - *Return type*: `types::non_null_iterator` - -- `gl::non_null_cbegin(const Range& range)` - - *Description*: Returns a `non_null_iterator` pointing to the constant beginning of the specified range while skipping null elements. - - *Template parameters*: - - `Range: c_const_range` - The constant range type. - - *Parameters*: - - `range: const Range&` - The range from which to obtain the constant beginning iterator. - - *Return type*: `types::non_null_iterator` - -- `gl::non_null_cend(const Range& range)` - - *Description*: Returns a `non_null_iterator` pointing to the constant end of the specified range while skipping null elements. - - *Template parameters*: - - `Range: c_const_range` - The constant range type. - - *Parameters*: - - `range: const Range&` - The range from which to obtain the constant end iterator. - - *Return type*: `types::non_null_iterator` - -
-
- ## I/O types This section provides the description of I/O types and utility defined in the library. diff --git a/docs/dev_notes.md b/docs/dev_notes.md index 01f03315..24193f32 100644 --- a/docs/dev_notes.md +++ b/docs/dev_notes.md @@ -4,26 +4,25 @@ Here you can find the necessery information to be able to work on the project
-## Building and testing +## Building and Testing > [!NOTE] > The project uses [doctest](https://github.com/doctest/doctest) framework for unit testing, however it is already installed in the [tests/external](/tests/external/) directory, so there is no need to install it sepparately. -### Build the testing executable +### Build the testing executables ```shell -cmake -B build -cd build -make # -j +cmake -B build -DBUILD_TESTS=ON +cmake --build build # -j ``` -This will build the test executable `run` in the `/build/tests` directory. +This will build the test executables `gl` (GL module tests) and `hgl` (HGL module tests) in the `/build/tests` directory. ### Run the tests ```shell -cd build -./tests/run # -ts= +./build/tests/gl # -ts= +./build/tests/hgl # -ts= ``` > [!NOTE] diff --git a/docs/graph.md b/docs/graph.md index ce56297f..c17f8617 100644 --- a/docs/graph.md +++ b/docs/graph.md @@ -121,14 +121,14 @@ Based on the specified traits, the `graph` class defines the following types:
-### Size operations +### Size Operations -- **`graph.n_vertices() const`**: +- **`graph.n_vertices() const noexcept`**: - *Description*: Returns the total number of vertices in the graph. - *Returned value*: $|V|$ where $V$ is the vertex set of the graph - *Return type*: `types::size_type` -- **`graph.n_unique_edges() const`**: +- **`graph.n_unique_edges() const noexcept`**: - Returns the number of unique edges in the graph. - *Returned value*: $|E|$ where $E$ is the edge set of the graph - *Return type*: `types::size_type` @@ -138,21 +138,21 @@ Based on the specified traits, the `graph` class defines the following types: ### Vertex Operations - **`graph.vertices() const`**: - - *Description*: Returns an view over all vertices in the graph. + - *Description*: Returns a view over all vertices in the graph. - *Returned value*: $V$ - - *Return type*: A random access *view* with values of type `vertex_type`. + - *Return type*: A *random access view* with values of type `vertex_type`. - **`graph.vertex_ids() const`**: - *Description*: Returns a range of vertex IDs, starting from the initial vertex ID to the number of vertices in the grap. - *Returned value*: $(v_{id} : v \in V)$ - - *Return type*: A random access *view* with values of type `types::id_type` : `std::ranges::iota_view`. + - *Return type*: A *random access view* with values of type `types::id_type` : `std::ranges::iota_view`. - **`graph.get_vertex(vertex_id) const`**: - *Description*: Retrieves the vertex object associated with the given vertex ID. - *Returned value*: $v \in V : v_{id} = \text{vertex-id}$ - *Parameters*: - `vertex_id: const types::id_type` - - *Return type*: `const vertex_type&` + - *Return type*: `vertex_type` - **`graph.has_vertex(vertex_id) const`**: - *Description*: Checks if a vertex exists for the given vertex ID. @@ -169,14 +169,14 @@ Based on the specified traits, the `graph` class defines the following types: - *Return type*: `bool` - **`graph.add_vertex()`**: - - *Description*: Adds a new vertex to the graph with default properties and returns a reference to the newly added vertex. - - *Return type*: `const vertex_type&` + - *Description*: Adds a new vertex to the graph with default properties and returns a descriptor object of the added vertex. + - *Return type*: `const vertex_type` - **`graph.add_vertex(properties)`**: - - *Description*: Adds a new vertex with specified properties and returns a reference to the newly added vertex. This overload is available when the vertex properties type is not the default. + - *Description*: Adds a new vertex with specified properties and returns a descriptor object of the newly added vertex. - *Parameters*: - `properties: const vertex_properties_type&` – Properties to assign to the new vertex. - - *Return type*: `const vertex_type&` + - *Return type*: `const vertex_type` - *Requires*: non-default `vertex_properties_type` - **`graph.add_vertices(n)`**: @@ -187,10 +187,8 @@ Based on the specified traits, the `graph` class defines the following types: - **`graph.add_vertices_with(properties_range)`**: - *Description*: Adds multiple vertices to the graph, each with the corresponding properties from the given range. The number of vertices added is determined by the size of `properties_range`. - - *Template parameters*: - - `VertexPropertiesRange: type_traits::c_sized_range_of` – A range of vertex properties that must satisfy the size and type constraints. - *Parameters*: - - `properties_range: const VertexPropertiesRange&` – A range of properties to assign to each of the new vertices. + - `properties_range: const type_traits::c_sized_range_of auto&` – A range of properties to assign to each of the new vertices. - *Return type*: `void` - *Requires*: non-default `vertex_properties_type` @@ -200,13 +198,13 @@ Based on the specified traits, the `graph` class defines the following types: - **`graph.remove_vertex(vertex_id)`**: - *Description*: Removes the vertex with the given ID from the graph. - *Parameters*: - - `vertex_id: types::size_type` – The ID of the vertex to remove. + - `vertex_id: types::size_type` – The ID of the vertex to be removed. - *Return type*: `void` - **`graph.remove_vertex(vertex)`**: - *Description*: Removes the specified vertex from the graph. - *Parameters*: - - `vertex: const vertex_type&` – A reference to the vertex to remove. + - `vertex: const vertex_type&` – A reference to the vertex to be removed. - *Return type*: `void` - **`graph.remove_vertices_from(vertex_id_range)`**: @@ -214,15 +212,13 @@ Based on the specified traits, the `graph` class defines the following types: - *Template parameters*: - `IdRange: type_traits::c_sized_range_of` – A range of vertex IDs, which must satisfy the size and type constraints. - *Parameters*: - - `vertex_id_range: const IdRange&` – A range of vertex IDs to remove. + - `vertex_id_range: const IdRange&` – A range of vertex IDs to be removed. - *Return type*: `void` - **`graph.remove_vertices_from(vertex_ref_range)`**: - *Description*: Removes multiple vertices from the graph based on a range of vertex references. The references are sorted in descending order and duplicates are removed before deletion. - - *Template parameters*: - - `VertexRefRange: type_traits::c_sized_range_of>` – A range of vertex references that must satisfy the size and type constraints. - *Parameters*: - - `vertex_ref_range: const VertexRefRange&` – A range of vertex references to remove. + - `vertex_ref_range: const type_traits::c_sized_range_of auto&` – A range of vertex references to be removed. - *Return type*: `void` - **`graph.in_degree(vertex) const`**: @@ -296,88 +292,112 @@ Based on the specified traits, the `graph` class defines the following types: - *Description*: Returns a vector containing the degrees of the corresponding vertices (degree at index `i` corresponds to the vertex with an ID equal `i`). - *Return type*: `std::vector` -- **`graph.vertex_properties_map() const`**: - - *Description*: Returns a *map-like view* the properties of the corresponding vertices (property at index `i` corresponds to the vertex with an ID equal `i`). - - *Return type*: A random access view with values of type `vertex_properties_type&` +- **`graph.at(vertex_id) const`**: + - *Description*: Returns a *random access view* over the adjacency storage entry for the given vertex ID. + - For an adjacency list representation, it returns a view over a list of edges adjacent to the vertex. + - For an adjacency matrix representation, it returns a view over a row of size $|V|$ corresponding to the vertex. **NOTE:** If the $i$-th entry of the row contains an *invalid edge descriptor*, it means there is no edge between the vertex with ID `vertex_id` and the vertex with ID `i`. + - *Parameters*: + - `vertex_id: types::id_type` – the ID of the vertex for which to find adjacent edges. + - *Return type*: A *random access view* with values of type `edge_type`. + +- **`graph.at(vertex) const`**: + - *Description*: An alias for `adjacent_edges(vertex)`. Returns a *random access view* over the adjacency storage entry for the given vertex. + - This is equivalent to calling `at(vertex.id())`. + - *Parameters*: + - `vertex: const vertex_type&` – the vertex for which to find adjacent edges. + - *Return type*: A *random access view* with values of type `edge_type`. + +- **`graph.adjacent_edges(vertex_id) const`**: + - *Description*: Returns a *forward view* over a collection of edges adjacent with the vertex with the specified ID. + - For an adjacency list representation, it returns a view over a list of edges adjacent to the vertex. + - For an adjacency matrix representation, it returns a *filtered view* over a row of size $|V|$ corresponding to the vertex, containing only the valid edges (i.e., edges that exist between the vertex with ID `vertex_id` and other vertices). + - *Parameters*: + - `vertex_id: types::id_type` – the ID of the vertex for which to find adjacent edges. + - *Return type*: A *forward view* with values of type `edge_type`. + +- **`graph.adjacent_edges(vertex) const`**: + - *Description*: Returns a *forward view* over a collection of edges adjacent with the specified vertex. + - This is equivalent to calling `adjacent_edges(vertex.id())`. + - *Parameters*: + - `vertex: const vertex_type&` – the vertex for which to find adjacent edges. + - *Return type*: A *forward view* with values of type `edge_type`.
### Edge Operations -- **`graph.add_edge(first_id, second_id)`**: +- **`graph.add_edge(source_id, target_id)`**: - *Description*: Adds a new edge between the vertices with the specified IDs and returns a reference to the newly added edge. - *Parameters*: - - `first_id: types::id_type` – the ID of the first vertex. - - `second_id: types::id_type` – the ID of the second vertex. - - *Return type*: `const edge_type&` + - `source_id: types::id_type` – the ID of the source vertex. + - `target_id: types::id_type` – the ID of the target vertex. + - *Return type*: `edge_type` -- **`graph.add_edge(first_id, second_id, properties)`**: +- **`graph.add_edge(source_id, target_id, properties)`**: - *Description*: Adds a new edge between the vertices with the specified IDs and returns a reference to the newly added edge. This overload is available when the edge properties type is not the default. - *Parameters*: - - `first_id: types::id_type` – the ID of the first vertex. - - `second_id: types::id_type` – the ID of the second vertex. + - `source_id: types::id_type` – the ID of the source vertex. + - `target_id: types::id_type` – the ID of the target vertex. - `properties: const edge_properties_type&` – properties to assign to the new edge. - - *Return type*: `const edge_type&` + - *Return type*: `const edge_type` - *Requires*: non-default `edge_properties_type` -- **`graph.add_edge(first, second)`**: +- **`graph.add_edge(source, target)`**: - *Description*: Adds a new edge between the specified vertices and returns a reference to the newly added edge. - *Parameters*: - - `first: const vertex_type&` – the first vertex. - - `second: const vertex_type&` – the second vertex. - - *Return type*: `const edge_type&` + - `source: const vertex_type&` – the source vertex. + - `target: const vertex_type&` – the target vertex. + - *Return type*: `const edge_type` -- **`graph.add_edge(first, second, properties)`**: +- **`graph.add_edge(source, target, properties)`**: - *Description*: Adds a new edge between the specified vertices and returns a reference to the newly added edge. This overload is available when the edge properties type is not the default. - *Parameters*: - - `first: const vertex_type&` – the first vertex. - - `second: const vertex_type&` – the second vertex. + - `source: const vertex_type&` – the source vertex. + - `target: const vertex_type&` – the target vertex. - `properties: const edge_properties_type&` – properties to assign to the new edge. - - *Return type*: `const edge_type&` + - *Return type*: `const edge_type` - *Requires*: non-default `edge_properties_type` - **`graph.add_edges_from(source_id, target_id_range)`**: - *Description*: Adds multiple edges from a source vertex (specified by ID) to a range of target vertices (also specified by IDs). - - *Template parameters*: - - `IdRange: type_traits::c_sized_range_of` – a range of vertex IDs that must satisfy the size and type constraints. - *Parameters*: - `source_id: types::id_type` – the ID of the source vertex. - - `target_id_range: const IdRange&` – a range of target vertex IDs to connect to the source vertex. + - `target_id_range: const type_traits::c_sized_range_of auto&` – a range of target vertex IDs to connect to the source vertex. - *Return type*: `void` + - *NOTE:* For an adjacency matrix representation passing a range with duplicate IDs will result in an error. - **`graph.add_edges_from(source, target_range)`**: - *Description*: Adds multiple edges from a specified source vertex to a range of target vertices (specified by references). - - *Template parameters*: - - `VertexRefRange: type_traits::c_sized_range_of>` – a range of vertex references that must satisfy the size and type constraints. - *Parameters*: - `source: const vertex_type&` – the source vertex. - - `target_range: const VertexRefRange&` – a range of target vertex references to connect to the source vertex. + - `target_range: const type_traits::c_sized_range_of auto&` – a range of target vertex references to connect to the source vertex. - *Return type*: `void` + - *NOTE:* For an adjacency matrix representation passing a range with duplicate vertices will result in an error. > [!IMPORTANT] -> Behaviour of adding an edge between `first` and `second`: +> Behaviour of adding an edge between `source` and `target`: > -> - for *directed* graphs: adds a one-directional edge where `first` is the source vertex and `second` is the target vertex +> - for *directed* graphs: adds a one-directional edge where `source` is the source vertex and `target` is the target vertex > - for *undirected* graphs: adds a bidirectional edge where both vertices are source and target vertices -- **`graph.has_edge(first_id, second_id) const`**: +- **`graph.has_edge(source_id, target_id) const`**: - *Description*: Returns true if there is an edge between the vertices with the specified IDs. - *Returned value*: - - For directed graphs: $\exists((u, v) \in E : \text{first-id} = u_{id} \land \text{second-id} = v_{id})$ - - For unidirected graphs: $\exists((u, v) \in E : (\text{first-id}, \text{second-id}) \in \{(u_{id}, v_{id}), (v_{id}, u_{id})\})$ + - For directed graphs: $\exists((u, v) \in E : \text{source-id} = u_{id} \land \text{target-id} = v_{id})$ + - For unidirected graphs: $\exists((u, v) \in E : (\text{source-id}, \text{target-id}) \in \{(u_{id}, v_{id}), (v_{id}, u_{id})\})$ - *Parameters*: - - `first_id: types::id_type` – the ID of the first vertex. - - `second_id: types::id_type` – the ID of the second vertex. + - `source_id: types::id_type` – the ID of the source vertex. + - `target_id: types::id_type` – the ID of the target vertex. - *Return type*: `bool` -- **`graph.has_edge(first, second) const`**: +- **`graph.has_edge(source, target) const`**: - *Description*: Returns true if there is an edge between the specified vertices. - *Returned value*: - - For directed graphs: $\exists((u, v) \in E : \text{first} = u \land \text{second} = v)$ - - For unidirected graphs: $\exists((u, v) \in E : (\text{first}, \text{second}) \in \{(u, v), (v, u)\})$ + - For directed graphs: $\exists((u, v) \in E : \text{source} = u \land \text{target} = v)$ + - For unidirected graphs: $\exists((u, v) \in E : (\text{source}, \text{target}) \in \{(u, v), (v, u)\})$ - *Parameters*: - - `first: const vertex_type&` – the first vertex. - - `second: const vertex_type&` – the second vertex. + - `source: const vertex_type&` – the source vertex. + - `target: const vertex_type&` – the target vertex. - *Return type*: `bool` - **`graph.has_edge(edge) const`**: @@ -386,33 +406,33 @@ Based on the specified traits, the `graph` class defines the following types: - `edge: const edge_type&` – the edge to check for existence. - *Return type*: `bool` -- **`graph.get_edge(first_id, second_id) const`**: - - *Description*: Returns an optional reference to the edge between the vertices with the specified IDs. If no edge exists, `std::nullopt` is returned (**NOTE:** for the `adjacency_list` implementation the first matchin edge is returned). +- **`graph.get_edge(source_id, target_id) const`**: + - *Description*: Returns an optional edge between the vertices with the specified IDs. If no edge exists, `std::nullopt` is returned (**NOTE:** for the `adjacency_list` implementation the source matchin edge is returned). - *Parameters*: - - `first_id: types::id_type` – the ID of the first vertex. - - `second_id: types::id_type` – the ID of the second vertex. - - *Return type*: `types::optional_cref` + - `source_id: types::id_type` – the ID of the source vertex. + - `target_id: types::id_type` – the ID of the target vertex. + - *Return type*: `std::optional` -- **`graph.get_edge(first, second) const`**: - - *Description*: Returns an optional reference to the edge between the specified vertices. If either vertex does not exist or if no edge exists between them, `std::nullopt` is returned for the `adjacency_list` implementation the first matchin edge is returned. +- **`graph.get_edge(source, target) const`**: + - *Description*: Returns an optional edge between the specified vertices. If either vertex does not exist or if no edge exists between them, `std::nullopt` is returned for the `adjacency_list` implementation the source matchin edge is returned. - *Parameters*: - - `first: const vertex_type&` – the first vertex. - - `second: const vertex_type&` – the second vertex. - - *Return type*: `types::optional_cref` + - `source: const vertex_type&` – the source vertex. + - `target: const vertex_type&` – the target vertex. + - *Return type*: `std::optional` -- **`graph.get_edges(first_id, second_id) const`**: - - *Description*: Returns a vector of optional references to the edges between the vertices with the specified IDs. If no edges exist, returns an empty vector. +- **`graph.get_edges(source_id, target_id) const`**: + - *Description*: Returns a vector of edges between the vertices with the specified IDs. If no edges exist, returns an empty vector. - *Parameters*: - - `first_id: types::id_type` – the ID of the first vertex. - - `second_id: types::id_type` – the ID of the second vertex. - - *Return type*: `std::vector>` + - `source_id: types::id_type` – the ID of the source vertex. + - `target_id: types::id_type` – the ID of the target vertex. + - *Return type*: `std::vector` -- **`graph.get_edges(first, second) const`**: - - *Description*: Returns a vector of optional references to the edges between the specified vertices. If either vertex does not exist or if no edges exist between them, returns an empty vector. +- **`graph.get_edges(source, target) const`**: + - *Description*: Returns a vector of edges between the specified vertices. If either vertex does not exist or if no edges exist between them, returns an empty vector. - *Parameters*: - - `first: const vertex_type&` – the first vertex. - - `second: const vertex_type&` – the second vertex. - - *Return type*: `std::vector>` + - `source: const vertex_type&` – the source vertex. + - `target: const vertex_type&` – the target vertex. + - *Return type*: `std::vector` - **`graph.remove_edge(edge)`**: - *Description*: Removes the specified edge from the graph. @@ -420,44 +440,30 @@ Based on the specified traits, the `graph` class defines the following types: - `edge: const edge_type&` – the edge to be removed. - *Return type*: `void` -- **`graph.remove_edges_from(edges)`**: +- **`graph.remove_edges(edges)`**: - *Description*: Removes multiple edges from the graph, as specified by the provided range of edge references. - - *Template parameters*: - - `EdgeRefRange: type_traits::c_range_of>` – a range of edge references that must satisfy the type constraints. - *Parameters*: - - `edges: const EdgeRefRange&` – a range of edges to remove from the graph. + - `edges: const type_traits::c_range_of auto&` – a range of edges to remove from the graph. - *Return type*: `void` -- **`graph.adjacent_edges(vertex_id) const`**: - - *Description*: Returns an iterator range of edges that are adjacent to the vertex with the specified ID. - - *Parameters*: - - `vertex_id: types::id_type` – the ID of the vertex for which to find adjacent edges. - - *Return type*: `types::iterator_range` - -- **`graph.adjacent_edges(vertex) const`**: - - *Description*: Returns an iterator range of edges that are adjacent to the specified vertex. - - *Parameters*: - - `vertex: const vertex_type&` – the vertex for which to find adjacent edges. - - *Return type*: `types::iterator_range` -
### Incidence Operations -- **`graph.are_incident(first_id, second_id) const`**: +- **`graph.are_incident(source_id, target_id) const`**: - *Description*: Returns true if the vertices with the specified IDs are incident to each other or the IDs are the same. - - *Returned value*: $\exists(e_1, e_2 \in E : \text{first-id} \in ids(e_1) \land \text{second-id} \in ids(e_2) \land vertices(e_1) \cap vertices(e_2) \ne \emptyset)$ + - *Returned value*: $\exists(e_1, e_2 \in E : \text{source-id} \in ids(e_1) \land \text{target-id} \in ids(e_2) \land vertices(e_1) \cap vertices(e_2) \ne \emptyset)$ - *Parameters*: - - `first_id: types::id_type` – the ID of the first vertex. - - `second_id: types::id_type` – the ID of the second vertex. + - `source_id: types::id_type` – the ID of the source vertex. + - `target_id: types::id_type` – the ID of the target vertex. - *Return type*: `bool` -- **`graph.are_incident(first, second) const`**: +- **`graph.are_incident(source, target) const`**: - *Description*: Returns true if the specified vertices are incident to each other or if they are the same. - - *Returned value*: $\exists(e_1, e_2 \in E : \text{first} \in vertices(e_1) \land \text{second} \in vertices(e_2) \land vertices(e_1) \cap vertices(e_2) \ne \emptyset)$ + - *Returned value*: $\exists(e_1, e_2 \in E : \text{source} \in vertices(e_1) \land \text{target} \in vertices(e_2) \land vertices(e_1) \cap vertices(e_2) \ne \emptyset)$ - *Parameters*: - - `first: const vertex_type&` – the first vertex. - - `second: const vertex_type&` – the second vertex. + - `source: const vertex_type&` – the source vertex. + - `target: const vertex_type&` – the target vertex. - *Return type*: `bool` - **`graph.are_incident(vertex, edge) const`**: @@ -480,10 +486,38 @@ Based on the specified traits, the `graph` class defines the following types: - *Description*: Returns true if the given edges are incident. - *Returned value*: $vertices(\text{edge-1}) \cap vertices(\text{edge-2}) \ne \emptyset$ - *Parameters*: - - `edge_1: const edge_type&` – the first edge. - - `edge_2: const edge_type&` – the second edge. + - `edge_1: const edge_type&` – the source edge. + - `edge_2: const edge_type&` – the target edge. - *Return type*: `bool` +
+ +### Properties Accessors + +- **`graph.vertex_properties_map() const`**: + - *Description*: Returns a *map-like view* over the properties of the corresponding vertices (property at index `i` corresponds to the vertex with an ID equal `i`). + - *Return type*: A random access view with values of type `vertex_properties_type&` + - *Requires*: non-default `vertex_properties_type` + +- **`graph.get_vertex_properties(vertex_id) const`**: + - *Description*: Returns a reference to the properties of the vertex with the specified ID. + - *Parameters*: + - `vertex_id: const types::id_type` – the ID of the vertex whose properties are to be retrieved. + - *Return type*: `vertex_properties_type&` + - *Requires*: non-default `vertex_properties_type` + +- **`graph.edge_properties_map() const`**: + - *Description*: Returns a *map-like view* over the properties of the corresponding edges (property at index `i` corresponds to the edge with an ID equal `i`). + - *Return type*: A random access view with values of type `edge_properties_type&` + - *Requires*: non-default `edge_properties_type` + +- **`graph.get_edge_properties(edge_id) const`**: + - *Description*: Returns a reference to the properties of the edge with the specified ID. + - *Parameters*: + - `edge_id: const types::id_type` – the ID of the edge whose properties are to be retrieved. + - *Return type*: `edge_properties_type&` + - *Requires*: non-default `edge_properties_type` + > [!CAUTION] > All graph operations which take vertex ids, vertex references or edge references as parameters throw the `std::invalid_argument` exception if such parameter is invalid. diff --git a/docs/graph_elements.md b/docs/graph_elements.md index 8ccd3be7..0741011e 100644 --- a/docs/graph_elements.md +++ b/docs/graph_elements.md @@ -6,13 +6,19 @@ This section provides an overview of the `vertex_descriptor` and `edge_descripto ## Table of content -- [The vertex class](#the-vertex-class) -- [The edge class](#the-edge-class) -- [Related pages](#related-pages) +- [The Vertex Class](#the-vertex-class) +- [The Edge Class](#the-edge-class) +- [Related Pages](#related-pages)
-## The vertex class +> [!IMPORTANT] +> - Both vertices and edges are identified using unique ID values of type [`types::id_type`](/docs/core_util_types.md#general-type-aliases). +> - `std::numeric_limits::max()` is reserved as an **invalid ID value** and vertex or edge class instances created with this ID value will be treated as invalid. + +
+ +## The Vertex Class The `vertex_descriptor` class represents the vertex in the graph, optionally carrying additional properties as specified by the user. This class is designed for use within the `graph` class and serves as a lightweight identifier for vertices, providing an interface for accessing vertex properties, comparing vertex descriptors, and outputting vertex information. @@ -28,9 +34,14 @@ By default, the `vertex_descriptor` class does not carry any properties. However - **`type`**: Alias for the `vertex_descriptor` itself. - **`properties_type`**: Type of the vertex properties as defined by the `Properties` template parameter. +- **`properties_ref_type`**: Reference type to the vertex properties. + - If the `Properties` type is `types::empty_properties`, this type is also `types::empty_properties`. + - Otherwise, it is `properties_type&`. ### Constructors +- **`vertex_descriptor()`**: + - Construct an *invalid* `vertex_descriptor` object. - **`vertex_descriptor(const types::id_type id)`**: - Constructs a `vertex_descriptor` with a unique vertex ID. - **`vertex_descriptor(const types::id_type id, const properties_type& properties)`**: @@ -39,8 +50,11 @@ By default, the `vertex_descriptor` class does not carry any properties. However - **Move constructor and assignment operator**: *default* - **Copy constructor and assignment operator**: *default* -- **Deleted**: - - **Default constructor**: prevent creating multiple vertices with the default ID within a graph. +### Static builders + +- `**invalid() noexcept**`: + - *Description*: Returns an invalid vertex descriptor object. + - *Return type*: `vertex_descriptor` ### Desctructor @@ -48,13 +62,18 @@ The destructor is *defaulted*, allowing proper cleanup of the `vertex_descriptor ### Member functions -- **`id() const`**: +- **`is_valid() const noexcept`**: + - *Description*: Returns `true` if the vertex has a valid ID, `false` otherwise. + - *Return type*: `bool` + +- **`id() const noexcept`**: - *Description*: Returns the unique identifier of the vertex. - *Return type*: `types::id_type` -- **`properties()`**: - - *Description*: Returns a mutable reference to the properties associated with the vertex. - - *Return type*: `properties_type&` +- **`properties() const`**: + - *Description*: Returns a reference to the properties associated with the vertex. + - *Return type*: `properties_ref_type` + - *Throws*: `std::logic_error` if the vertex is invalid. - **`operator==(const vertex_descriptor& other) const`**: - *Description*: Compares two `vertex_descriptor` objects for equality based on their vertex IDs. @@ -68,6 +87,17 @@ The destructor is *defaulted*, allowing proper cleanup of the `vertex_descriptor - `other: const vertex_descriptor&` – the vertex descriptor to compare with. - *Return type*: `std::strong_ordering` +- **`operator bool() const noexcept`**: + - *Description*: Equivalent to calling `is_valid()`. + - *Return type*: `bool` + +- **`operator<<(std::ostream& os, const vertex_descriptor& vertex)`**: + - *Description*: Outputs the vertex descriptor to the provided output stream in a human-readable format. + - *Parameters*: + - `os: std::ostream&` – the output stream to write to. + - `vertex: const vertex_descriptor&` – the vertex descriptor to output. + - *Return type*: `std::ostream&` + ### Additional utility - **`vertex`**: A convenient alias for `vertex_descriptor` with customizable properties. @@ -79,7 +109,7 @@ The destructor is *defaulted*, allowing proper cleanup of the `vertex_descriptor
-## The edge class +## The Edge Class The `edge_descriptor` class represents an edge between two vertices $(u, v)$ in the graph, with optional properties and support for both directed and undirected edges. This class is designed to work within the `Graph` class, handling relationships between vertex descriptors and providing a flexible interface for edge manipulation, comparison, and output. @@ -99,19 +129,27 @@ By default, the `edge_descriptor` class does not carry any properties and assume - **`type`**: Alias for the `edge_descriptor` itself. - **`directional_tag`**: The tag indicating whether the edge is directed or undirected. - **`properties_type`**: Type of the edge properties as defined by the `Properties` template parameter. +- **`properties_ref_type`**: Reference type to the edge properties. + - If the `Properties` type is `types::empty_properties`, this type is also `types::empty_properties`. + - Otherwise, it is `properties_type&`. ### Constructors -- **`edge_descriptor(const types::id_type first, const types::id_type second)`**: +- **`edge_descriptor()`**: + - Construct an *invalid* `edge_descriptor` object. +- **`edge_descriptor(const types::id_type source, const types::id_type target)`**: - Constructs an edge between two vertices. -- **`edge_descriptor(const types::id_type first, const types::id_type second, const properties_type& properties)`**: +- **`edge_descriptor(const types::id_type source, const types::id_type target, const properties_type& properties)`**: - Constructs an edge between two vertices with specified properties. - *Constraints*: the `properties_type` must be non-default. - **Move constructor and assignment operator**: *default* +- **Copy constructor and assignment operator**: *default* + +### Static builders -- **Deleted**: - - **Default constructor**: prevents creating edges without specifying incident vertices. - - **Copy constructor and assignment operator**: prevent copying of `edge_descriptor` instances, as they are meant to uniquely identify edges. +- **`invalid() noexcept`**: + - *Description*: Returns an invalid edge descriptor object. + - *Return type*: `edge_descriptor` ### Destructor @@ -119,30 +157,43 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` ### Member functions -- **`is_directed() const`**: +- **`is_directed() const noexcept`**: - *Description*: Returns `true` if the edge is directed. - *Return type*: `bool` -- **`is_undirected() const`**: +- **`is_undirected() const noexcept`**: - *Description*: Returns `true` if the edge is undirected. - *Return type*: `bool` -- **`incident_vertices() const`**: +- **`is_valid() const noexcept`**: + - *Description*: Returns `true` if the edge has a valid ID and valid incident vertices, `false` otherwise. + - *Return type*: `bool` + +- **`id() const noexcept`**: + - *Description*: Returns the unique identifier of the edge. + - *Return type*: `types::id_type` + +- **`incident_vertices() const noexcept`**: - *Description*: Returns a pair of references to the two vertices connected by the edge. - *Returned value*: $(u, v)$ - - *Return type*: `types::homogeneous_pair` + - *Return type*: `types::homogeneous_pair` -- **`first() const`**: - - *Description*: Returns a reference to the first vertex of the edge. +- **`incident_vertices_r() const noexcept`**: + - *Description*: Returns a pair of references to the two vertices connected by the edge in reverse order. + - *Returned value*: $(v, u)$ + - *Return type*: `types::homogeneous_pair` + +- **`source() const noexcept`**: + - *Description*: Returns an ID of the source vertex of the edge. - *Returned value*: $u$ - *Return type*: `types::id_type` -- **`second() const`**: - - *Description*: Returns a reference to the second vertex of the edge. +- **`target() const noexcept`**: + - *Description*: Returns an ID of the target vertex of the edge. - *Returned value*: $v$ - *Return type*: `const types::id_type` -- **`incident_vertex(vertex_id) const`**: +- **`incident_vertex(vertex_id) const noexcept`**: - *Description*: Returns the vertex on the other end of the edge relative to the provided vertex. Throws an error if the provided vertex is not incident with the edge. - *Returned value*: - $v$ if $\text{vertex-id} = u$ @@ -152,14 +203,14 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - `vertex_id: const types::id_type` – the vertex for which the opposite vertex is requested. - *Return type*: `const types::id_type` -- **`is_incident_with(vertex_id) const`**: +- **`is_incident_with(vertex_id) const noexcept`**: - *Description*: Returns `true` if a vertex with the given ID is connected to the edge. - *Returned value*: $\text{vertex-id} \in {u, v}$ - *Parameters*: - `vertex_id: const types::id_type` – the vertex ID to check for incidence with the edge. - *Return type*: `bool` -- **`is_incident_from(vertex_id) const`**: +- **`is_incident_from(vertex_id) const noexcept`**: - *Description*: Returns `true` if a vertex with the given ID is the source of the edge. - *Returned value*: - For directed edges: $\text{vertex-id} = u$ @@ -168,7 +219,7 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - `vertex_id: const types::id_type` – the vertex ID to check if it is the source. - *Return type*: `bool` -- **`is_incident_to(vertex_id) const`**: +- **`is_incident_to(vertex_id) const noexcept`**: - *Description*: Returns `true` if a vertex with the given ID is the target of the edge. - *Returned value*: - For directed edges: $\text{vertex-id} = v$ @@ -177,11 +228,16 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` - `vertex_id: const types::id_type` – the vertex ID to check if it is the target. - *Return type*: `bool` -- **`is_loop() const`**: +- **`is_loop() const noexcept`**: - *Description*: Returns `true` if the edge is a loop (i.e., both vertices are the same). - *Returned value*: $u = v$ - *Return type*: `bool` +- **`properties() const`**: + - *Description*: Returns a reference to the properties associated with the edge. + - *Return type*: `properties_ref_type` + - *Throws*: `std::logic_error` if the edge is invalid. + ### Additional utility - **`edge`**: A convenient alias for `edge_descriptor`, with customizable properties and directionality. @@ -210,7 +266,7 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor`

-## Related pages +## Related Pages - [The graph class - library's core](/docs/graph.md) - [I/O operations](/docs/io.md) diff --git a/docs/io.md b/docs/io.md index fcdd596f..4dcee1af 100644 --- a/docs/io.md +++ b/docs/io.md @@ -87,9 +87,9 @@ int main() { // set the vertex and edge properties of the graph std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : graph.vertices()) { - vertex.properties = {std::format("vertex_{}", ++v_idx)}; + vertex.properties() = std::format("vertex_{}", ++v_idx); for (const auto& edge : graph.adjacent_edges(vertex)) - edge.properties = {std::format("edge_{}", ++e_idx)}; + edge.properties() = std::format("edge_{}", ++e_idx); } // print the graph in a concise representation and without properties @@ -120,26 +120,26 @@ number of edges: 12 vertices: - [id: 0 | properties: "vertex_1"] adjacent edges: - - [first: 0, second: 2 | properties: "edge_1"] - - [first: 0, second: 3 | properties: "edge_2"] - - [first: 0, second: 4 | properties: "edge_3"] + - [source: 0, target: 2 | properties: "edge_1"] + - [source: 0, target: 3 | properties: "edge_2"] + - [source: 0, target: 4 | properties: "edge_3"] - [id: 1 | properties: "vertex_2"] adjacent edges: - - [first: 1, second: 2 | properties: "edge_4"] - - [first: 1, second: 3 | properties: "edge_5"] - - [first: 1, second: 4 | properties: "edge_6"] + - [source: 1, target: 2 | properties: "edge_4"] + - [source: 1, target: 3 | properties: "edge_5"] + - [source: 1, target: 4 | properties: "edge_6"] - [id: 2 | properties: "vertex_3"] adjacent edges: - - [first: 2, second: 0 | properties: "edge_7"] - - [first: 2, second: 1 | properties: "edge_8"] + - [source: 2, target: 0 | properties: "edge_7"] + - [source: 2, target: 1 | properties: "edge_8"] - [id: 3 | properties: "vertex_4"] adjacent edges: - - [first: 3, second: 0 | properties: "edge_9"] - - [first: 3, second: 1 | properties: "edge_10"] + - [source: 3, target: 0 | properties: "edge_9"] + - [source: 3, target: 1 | properties: "edge_10"] - [id: 4 | properties: "vertex_5"] adjacent edges: - - [first: 4, second: 0 | properties: "edge_11"] - - [first: 4, second: 1 | properties: "edge_12"] + - [source: 4, target: 0 | properties: "edge_11"] + - [source: 4, target: 1 | properties: "edge_12"] ```
@@ -216,9 +216,9 @@ int main() { // set the vertex and edge properties of the graph std::size_t v_idx = 0, e_idx = 0; for (const auto& vertex : clique.vertices()) { - vertex.properties = {std::format("vertex_{}", ++v_idx)}; + vertex.properties() = std::format("vertex_{}", ++v_idx); for (const auto& edge : clique.adjacent_edges(vertex)) - edge.properties = {std::format("edge_{}", ++e_idx)}; + edge.properties() = std::format("edge_{}", ++e_idx); } // set the std::cout stream options @@ -246,16 +246,16 @@ number of edges: 6 vertices: - [id: 0 | properties: "vertex_1"] adjacent edges: - - [first: 0, second: 1 | properties: "edge_1"] - - [first: 0, second: 2 | properties: "edge_2"] + - [source: 0, target: 1 | properties: "edge_1"] + - [source: 0, target: 2 | properties: "edge_2"] - [id: 1 | properties: "vertex_2"] adjacent edges: - - [first: 1, second: 0 | properties: "edge_3"] - - [first: 1, second: 2 | properties: "edge_4"] + - [source: 1, target: 0 | properties: "edge_3"] + - [source: 1, target: 2 | properties: "edge_4"] - [id: 2 | properties: "vertex_3"] adjacent edges: - - [first: 2, second: 0 | properties: "edge_5"] - - [first: 2, second: 1 | properties: "edge_6"] + - [source: 2, target: 0 | properties: "edge_5"] + - [source: 2, target: 1 | properties: "edge_6"] > loaded graph: type: directed @@ -264,16 +264,16 @@ number of edges: 6 vertices: - [id: 0 | properties: "vertex_1"] adjacent edges: - - [first: 0, second: 1 | properties: "edge_1"] - - [first: 0, second: 2 | properties: "edge_2"] + - [source: 0, target: 1 | properties: "edge_1"] + - [source: 0, target: 2 | properties: "edge_2"] - [id: 1 | properties: "vertex_2"] adjacent edges: - - [first: 1, second: 0 | properties: "edge_3"] - - [first: 1, second: 2 | properties: "edge_4"] + - [source: 1, target: 0 | properties: "edge_3"] + - [source: 1, target: 2 | properties: "edge_4"] - [id: 2 | properties: "vertex_3"] adjacent edges: - - [first: 2, second: 0 | properties: "edge_5"] - - [first: 2, second: 1 | properties: "edge_6"] + - [source: 2, target: 0 | properties: "edge_5"] + - [source: 2, target: 1 | properties: "edge_6"] ```
diff --git a/include/gl/algorithm/dijkstra.hpp b/include/gl/algorithm/dijkstra.hpp index 6071cf7a..472756a3 100644 --- a/include/gl/algorithm/dijkstra.hpp +++ b/include/gl/algorithm/dijkstra.hpp @@ -82,7 +82,7 @@ template < paths.predecessors.at(source_id).emplace(source_id); paths.distances[source_id] = distance_type{}; - std::optional> negative_edge; + std::optional negative_edge; impl::pfs( graph, @@ -98,7 +98,7 @@ template < const auto edge_weight = get_weight(in_edge); if (edge_weight < constants::zero) { - negative_edge = std::cref(in_edge); + negative_edge.emplace(in_edge); return predicate_result::unknown; } @@ -115,11 +115,11 @@ template < ); if (negative_edge.has_value()) { - const auto& edge = negative_edge.value().get(); + const auto& edge = negative_edge.value(); throw std::invalid_argument(std::format( "[alg::dijkstra_shortest_paths] Found an edge with a negative weight: [{}, {} | w={}]", - edge.first(), - edge.second(), + edge.source(), + edge.target(), get_weight(edge) )); } diff --git a/include/gl/algorithm/mst.hpp b/include/gl/algorithm/mst.hpp index 40928700..4a513d84 100644 --- a/include/gl/algorithm/mst.hpp +++ b/include/gl/algorithm/mst.hpp @@ -22,7 +22,7 @@ struct mst_descriptor { edges.reserve(n_vertices - constants::one); } - std::vector> edges; + std::vector edges; weight_type weight = static_cast(constants::zero); }; @@ -34,19 +34,17 @@ template using vertex_type = typename GraphType::vertex_type; using edge_type = typename GraphType::edge_type; - using edge_info_type = algorithm::edge_info; using distance_type = types::vertex_distance_type; - struct edge_info_comparator { + struct edge_comparator { [[nodiscard]] gl_attr_force_inline bool operator()( - const edge_info_type& lhs, const edge_info_type& rhs + const edge_type& lhs, const edge_type& rhs ) const { - return get_weight(lhs.edge.get()) > get_weight(rhs.edge.get()); + return get_weight(lhs) > get_weight(rhs); } }; - using queue_type = - std::priority_queue, edge_info_comparator>; + using queue_type = std::priority_queue, edge_comparator>; // prepare the necessary utility const auto n_vertices = graph.n_vertices(); @@ -58,7 +56,7 @@ template const types::id_type root_id = root_id_opt.value_or(constants::zero); for (const auto& edge : graph.adjacent_edges(root_id)) - edge_queue.emplace(edge, root_id); + edge_queue.emplace(edge); // mark the root vertex as visited visited[root_id] = true; @@ -66,27 +64,23 @@ template // find the mst while (n_vertices_in_mst < n_vertices) { - const auto min_edge_info = edge_queue.top(); + const auto min_edge = edge_queue.top(); edge_queue.pop(); - const auto& min_edge = min_edge_info.edge.get(); - const auto min_weight = get_weight(min_edge); - - const auto& target_id = min_edge.incident_vertex(min_edge_info.source_id); - if (visited[target_id]) + if (visited[min_edge.target()]) continue; // add the minimum weight edge to the mst mst.edges.emplace_back(min_edge); - mst.weight += min_weight; + mst.weight += get_weight(min_edge); - visited[target_id] = true; + visited[min_edge.target()] = true; ++n_vertices_in_mst; // enqueue all edges adjacent to the `target` vertex if they lead to unvisited verties - for (const auto& edge : graph.adjacent_edges(target_id)) - if (not visited[edge.incident_vertex(target_id)]) - edge_queue.emplace(edge, target_id); + for (const auto& edge : graph.adjacent_edges(min_edge.target())) + if (not visited[edge.incident_vertex(min_edge.target())]) + edge_queue.emplace(edge); } return mst; @@ -107,7 +101,7 @@ requires type_traits::c_has_numeric_limits_max in_mst(n_vertices, false); std::vector min_cost(n_vertices, std::numeric_limits::max()); - std::vector min_cost_edges(n_vertices, nullptr); + std::vector> min_cost_edges(n_vertices, std::nullopt); // set the distance to the root vertex to 0 min_cost.at(root_id_opt.value_or(constants::zero)) = constants::zero; @@ -132,8 +126,8 @@ requires type_traits::c_has_numeric_limits_max EdgeType> -struct edge_info { - using edge_type = EdgeType; - - types::const_ref_wrap edge; - types::id_type source_id; -}; - struct predicate_result { enum class eval : std::uint8_t { ok, not_ok, unknown }; using enum eval; diff --git a/include/gl/constants.hpp b/include/gl/constants.hpp index 3ccf2fb8..1422aad9 100644 --- a/include/gl/constants.hpp +++ b/include/gl/constants.hpp @@ -6,6 +6,8 @@ #include "types/types.hpp" +#include + namespace gl::constants { inline constexpr types::size_type zero{0ull}; @@ -15,5 +17,6 @@ inline constexpr types::size_type two{2ull}; inline constexpr types::size_type default_size{zero}; inline constexpr types::size_type begin_idx{zero}; inline constexpr types::id_type initial_id{zero}; +inline constexpr types::id_type invalid_id{std::numeric_limits::max()}; } // namespace gl::constants diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index ab6e664a..68d4f64b 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -18,50 +18,108 @@ class edge_descriptor final { using type = edge_descriptor; using directional_tag = DirectionalTag; using properties_type = Properties; + using properties_ref_type = std::conditional_t< + type_traits::c_empty_properties, + types::empty_properties, + properties_type&>; friend directional_tag; - template GraphTraits> - friend class graph; - - edge_descriptor() = delete; - edge_descriptor(const edge_descriptor&) = delete; - edge_descriptor& operator=(const edge_descriptor&) = delete; + edge_descriptor() { + *this = edge_descriptor::invalid(); + } - explicit edge_descriptor(const types::id_type first, const types::id_type second) - : _vertices(first, second) {} + explicit edge_descriptor( + const types::id_type id, const types::id_type source, const types::id_type target + ) + requires(type_traits::c_empty_properties) + : _id(id), _vertices(source, target) {} explicit edge_descriptor( - const types::id_type first, const types::id_type second, properties_type properties + const types::id_type id, + const types::id_type source, + const types::id_type target, + properties_type& properties ) - requires(not type_traits::is_default_properties_type_v) - : _vertices(first, second), _properties(properties) {} + requires(type_traits::c_non_empty_properties) + : _id(id), _vertices(source, target), _properties(properties) {} + + [[nodiscard]] gl_attr_force_inline static edge_descriptor invalid() noexcept + requires(type_traits::c_empty_properties) + { + return edge_descriptor(constants::invalid_id, constants::invalid_id, constants::invalid_id); + } + + [[nodiscard]] gl_attr_force_inline static edge_descriptor invalid() noexcept + requires(type_traits::c_non_empty_properties) + { + static properties_type invalid_properties{}; + return edge_descriptor( + constants::invalid_id, constants::invalid_id, constants::invalid_id, invalid_properties + ); + } + + edge_descriptor(const edge_descriptor&) = default; + edge_descriptor& operator=(const edge_descriptor&) = default; edge_descriptor(edge_descriptor&&) = default; edge_descriptor& operator=(edge_descriptor&&) = default; ~edge_descriptor() = default; - [[nodiscard]] constexpr bool is_directed() const { + [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept + requires(type_traits::is_directed_v) + { + return this->_id == other._id and (this->_vertices == other._vertices); + } + + [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept + requires(type_traits::is_undirected_v) + { + return this->_id == other._id + and (this->_vertices == other._vertices + or (this->_vertices == other.incident_vertices_r())); + } + + [[nodiscard]] gl_attr_force_inline operator bool() const noexcept { + return this->is_valid(); + } + + [[nodiscard]] constexpr bool is_directed() const noexcept { return type_traits::is_directed_v; } - [[nodiscard]] constexpr bool is_undirected() const { + [[nodiscard]] constexpr bool is_undirected() const noexcept { return type_traits::is_undirected_v; } - // clang-format off - // gl_attr_force_inline misplacement + [[nodiscard]] bool is_valid() const noexcept { + return this->_id != constants::invalid_id and this->_vertices.first != constants::invalid_id + and this->_vertices.second != constants::invalid_id; + } + + [[nodiscard]] gl_attr_force_inline types::id_type id() const noexcept { + return this->_id; + } - [[nodiscard]] gl_attr_force_inline types::homogeneous_pair incident_vertices() const { + [[nodiscard]] gl_attr_force_inline types::homogeneous_pair incident_vertices( + ) const noexcept { return this->_vertices; } - [[nodiscard]] gl_attr_force_inline const types::id_type first() const { + [[nodiscard]] gl_attr_force_inline types::homogeneous_pair incident_vertices_r( + ) const noexcept { + return std::make_pair(this->_vertices.second, this->_vertices.first); + } + + // clang-format off + // gl_attr_force_inline misplacement + + [[nodiscard]] gl_attr_force_inline const types::id_type source() const noexcept { return this->_vertices.first; } - [[nodiscard]] gl_attr_force_inline const types::id_type second() const { + [[nodiscard]] gl_attr_force_inline const types::id_type target() const noexcept { return this->_vertices.second; } @@ -78,26 +136,32 @@ class edge_descriptor final { throw std::invalid_argument(std::format("Got invalid vertex id: {}", vertex_id)); } - [[nodiscard]] gl_attr_force_inline bool is_incident_with(const types::id_type vertex_id) const { + [[nodiscard]] gl_attr_force_inline bool is_incident_with(const types::id_type vertex_id + ) const noexcept { return vertex_id == this->_vertices.first or vertex_id == this->_vertices.second; } // true if the given vertex is the `source` of the edge - [[nodiscard]] gl_attr_force_inline bool is_incident_from(const types::id_type vertex_id) const { + [[nodiscard]] gl_attr_force_inline bool is_incident_from(const types::id_type vertex_id + ) const noexcept { return directional_tag::is_incident_from(*this, vertex_id); } // true if the given vertex is the `target` vertex of the edge - [[nodiscard]] gl_attr_force_inline bool is_incident_to(const types::id_type vertex_id) const { + [[nodiscard]] gl_attr_force_inline bool is_incident_to(const types::id_type vertex_id + ) const noexcept { return directional_tag::is_incident_to(*this, vertex_id); } - [[nodiscard]] gl_attr_force_inline bool is_loop() const { + [[nodiscard]] gl_attr_force_inline bool is_loop() const noexcept { return this->_vertices.first == this->_vertices.second; } - [[nodiscard]] gl_attr_force_inline properties_type& properties() const { - return this->_properties; + [[nodiscard]] gl_attr_force_inline properties_ref_type properties() const { + if (not this->is_valid()) + throw std::logic_error("Cannot access properties of an invalid edge"); + + return this->_properties.get(); } friend inline std::ostream& operator<<(std::ostream& os, const edge_descriptor& edge) { @@ -118,26 +182,30 @@ class edge_descriptor final { } if (io::is_option_set(os, io::graph_option::verbose)) { - os << "[first: " << this->_vertices.first << ", second: " << this->_vertices.second - << " | properties: " << this->_properties << "]"; + os << "[source: " << this->_vertices.first << ", target: " << this->_vertices.second + << " | properties: " << this->_properties.get() << "]"; } else { os << "[" << this->_vertices.first << ", " << this->_vertices.second << " | " - << this->_properties << "]"; + << this->_properties.get() << "]"; } } } void _write_no_properties(std::ostream& os) const { if (io::is_option_set(os, io::graph_option::verbose)) - os << "[first: " << this->_vertices.first << ", second: " << this->_vertices.first + os << "[source: " << this->_vertices.first << ", target: " << this->_vertices.first << "]"; else os << "[" << this->_vertices.first << ", " << this->_vertices.second << "]"; } + types::id_type _id; types::homogeneous_pair _vertices; - [[no_unique_address]] mutable properties_type _properties{}; + [[no_unique_address]] std::conditional_t< + type_traits::c_empty_properties, + types::empty_properties, + std::reference_wrapper> _properties; }; template < diff --git a/include/gl/edge_tags.hpp b/include/gl/edge_tags.hpp index 9bd5b455..c0923eee 100644 --- a/include/gl/edge_tags.hpp +++ b/include/gl/edge_tags.hpp @@ -45,28 +45,6 @@ inline constexpr bool is_undirected_v = struct directed_t { using type = std::type_identity_t; - template EdgeType> - requires(type_traits::is_directed_v) - using edge_ptr_type = std::unique_ptr; - - template EdgeType> - requires(type_traits::is_directed_v) - [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const types::id_type first, const types::id_type second - ) { - return std::make_unique(first, second); - } - - template EdgeType> - requires(type_traits::is_directed_v) - [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const types::id_type first, - const types::id_type second, - const typename EdgeType::properties_type& properties - ) { - return std::make_unique(first, second, properties); - } - template EdgeType> requires(type_traits::is_directed_v) [[nodiscard]] gl_attr_force_inline static bool is_incident_from( @@ -87,28 +65,6 @@ struct directed_t { struct undirected_t { using type = std::type_identity_t; - template EdgeType> - requires(type_traits::is_undirected_v) - using edge_ptr_type = std::shared_ptr; - - template EdgeType> - requires(type_traits::is_undirected_v) - [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const types::id_type first, const types::id_type second - ) { - return std::make_shared(first, second); - } - - template EdgeType> - requires(type_traits::is_undirected_v) - [[nodiscard]] gl_attr_force_inline static edge_ptr_type make( - const types::id_type first, - const types::id_type second, - const typename EdgeType::properties_type& properties - ) { - return std::make_shared(first, second, properties); - } - template EdgeType> requires(type_traits::is_undirected_v) [[nodiscard]] gl_attr_force_inline static bool is_incident_from( @@ -126,31 +82,4 @@ struct undirected_t { } }; -namespace types { - -template EdgeType> -using edge_ptr_type = typename EdgeType::directional_tag::template edge_ptr_type; - -} // namespace types - -namespace detail { - -template EdgeType> -[[nodiscard]] gl_attr_force_inline types::edge_ptr_type make_edge( - const types::id_type first, const types::id_type second -) { - return EdgeType::directional_tag::template make(first, second); -} - -template EdgeType> -[[nodiscard]] gl_attr_force_inline types::edge_ptr_type make_edge( - const types::id_type first, - const types::id_type second, - const typename EdgeType::properties_type& properties -) { - return EdgeType::directional_tag::template make(first, second, properties); -} - -} // namespace detail - } // namespace gl diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 75190d2a..07e3d93d 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -8,8 +8,7 @@ #include "graph_traits.hpp" #include "impl/impl_tags.hpp" #include "io/stream_options_manipulator.hpp" -#include "types/iterator_range.hpp" -#include "views.hpp" +#include "util/ranges.hpp" #include @@ -19,42 +18,38 @@ template GraphTraits = graph_trai class graph final { public: using traits_type = GraphTraits; - using implementation_tag = typename traits_type::implementation_tag; + using implementation_type = typename implementation_tag::template type; + friend implementation_type; using vertex_type = typename traits_type::vertex_type; using vertex_properties_type = typename traits_type::vertex_properties_type; using vertex_properties_map_type = std::conditional_t< - type_traits::is_default_properties_type_v, + type_traits::c_empty_properties, types::empty_properties_map, std::vector>>; - // TODO: reverese iterators should be available for bidirectional ranges - using edge_type = typename traits_type::edge_type; - using edge_ptr_type = typename traits_type::edge_ptr_type; using edge_directional_tag = typename traits_type::edge_directional_tag; using edge_properties_type = typename traits_type::edge_properties_type; - using edge_list_type = typename implementation_type::edge_list_type; - using edge_iterator_type = typename implementation_type::edge_iterator_type; + using edge_properties_map_type = std::conditional_t< + type_traits::c_empty_properties, + types::empty_properties_map, + std::vector>>; graph(const graph&) = delete; graph& operator=(const graph&) = delete; graph() = default; - explicit graph(const types::size_type n_vertices) - requires(type_traits::is_default_properties_type_v) - : _n_vertices(n_vertices), _impl(n_vertices) {} - - explicit graph(const types::size_type n_vertices) - requires(not type_traits::is_default_properties_type_v) - : _n_vertices(n_vertices), _impl(n_vertices) { - this->_vertex_properties.reserve(n_vertices); - for (auto id : this->vertex_ids()) - this->_vertex_properties.push_back(std::make_unique()); + explicit graph(const types::size_type n_vertices) : _n_vertices(n_vertices), _impl(n_vertices) { + if constexpr (type_traits::c_non_empty_properties) { + this->_vertex_properties.reserve(n_vertices); + for (const auto _ : this->vertex_ids()) + this->_vertex_properties.push_back(std::make_unique()); + } } graph(graph&&) = default; @@ -64,25 +59,25 @@ class graph final { // --- general methods --- - [[nodiscard]] gl_attr_force_inline types::size_type n_vertices() const { + [[nodiscard]] gl_attr_force_inline types::size_type n_vertices() const noexcept { return this->_n_vertices; } - [[nodiscard]] gl_attr_force_inline types::size_type n_unique_edges() const { - return this->_impl.n_unique_edges(); + [[nodiscard]] gl_attr_force_inline types::size_type n_unique_edges() const noexcept { + return this->_n_unique_edges; } // --- vertex methods --- [[nodiscard]] gl_attr_force_inline auto vertices() const - requires(type_traits::is_default_properties_type_v) + requires(type_traits::c_empty_properties) { return this->vertex_ids() | std::views::transform([](const types::id_type id) { return vertex_descriptor{id}; }); } [[nodiscard]] gl_attr_force_inline auto vertices() const - requires(not type_traits::is_default_properties_type_v) + requires(type_traits::c_non_empty_properties) { return this->_vertex_properties | std::views::enumerate | std::views::transform([](const auto& x) { @@ -101,10 +96,10 @@ class graph final { [[nodiscard]] gl_attr_force_inline vertex_type get_vertex(const types::id_type vertex_id) const { this->_verify_vertex_id(vertex_id); - if constexpr (type_traits::is_default_properties_type_v) - return vertex_descriptor{vertex_id}; - else + if constexpr (type_traits::c_non_empty_properties) return vertex_descriptor{vertex_id, *this->_vertex_properties[vertex_id]}; + else + return vertex_descriptor{vertex_id}; } // clang-format on @@ -117,20 +112,21 @@ class graph final { return this->has_vertex(vertex.id()); } - vertex_type add_vertex() { + const vertex_type add_vertex() { this->_impl.add_vertex(); const auto new_vertex_id = this->_n_vertices++; - if constexpr (type_traits::is_default_properties_type_v) - return vertex_descriptor{new_vertex_id}; - else { + if constexpr (type_traits::c_non_empty_properties) { this->_vertex_properties.push_back(std::make_unique()); return vertex_descriptor{new_vertex_id, *this->_vertex_properties.back()}; } + else { + return vertex_descriptor{new_vertex_id}; + } } - vertex_type add_vertex(vertex_properties_type properties) - requires(not type_traits::is_default_properties_type_v) + const vertex_type add_vertex(vertex_properties_type properties) + requires(type_traits::c_non_empty_properties) { this->_impl.add_vertex(); this->_vertex_properties.push_back( @@ -143,7 +139,7 @@ class graph final { this->_impl.add_vertices(n); this->_n_vertices += n; - if constexpr (not type_traits::is_default_properties_type_v) { + if constexpr (type_traits::c_non_empty_properties) { const auto old_size = this->_vertex_properties.size(); this->_vertex_properties.reserve(this->_n_vertices); for (types::size_type i = old_size; i < this->_n_vertices; ++i) @@ -151,16 +147,17 @@ class graph final { } } - template VertexPropertiesRange> - void add_vertices_with(const VertexPropertiesRange& properties_range) - requires(not type_traits::is_default_properties_type_v) + void add_vertices_with( + const type_traits::c_sized_range_of auto& properties_range + ) + requires(type_traits::c_non_empty_properties) { const auto n = std::ranges::size(properties_range); this->_impl.add_vertices(n); this->_n_vertices += n; - if constexpr (not type_traits::is_default_properties_type_v) { + if constexpr (type_traits::c_non_empty_properties) { for (auto& properties : properties_range) { this->_vertex_properties.emplace_back( std::make_unique(properties) @@ -174,13 +171,13 @@ class graph final { this->_remove_vertex_impl(vertex_id); } - inline void remove_vertex(const vertex_type& vertex) { - this->_verify_vertex(vertex); - this->_remove_vertex_impl(vertex.id()); + gl_attr_force_inline void remove_vertex(const vertex_type& vertex) { + this->remove_vertex(vertex.id()); } - template IdRange> - void remove_vertices_from(const IdRange& vertex_id_range) { + void remove_vertices_from( + const type_traits::c_sized_range_of auto& vertex_id_range + ) { // sorts the ids in a descending order and removes duplicate ids std::set> vertex_id_set( std::ranges::begin(vertex_id_range), std::ranges::end(vertex_id_range) @@ -191,31 +188,14 @@ class graph final { this->_remove_vertex_impl(vertex_id); } - template > VertexRefRange> - void remove_vertices_from(const VertexRefRange& vertex_ref_range) { - // TODO [C++26]: replace with std::greater - struct vertex_ref_greater_comparator { - [[nodiscard]] bool operator()( - const types::const_ref_wrap& lhs, - const types::const_ref_wrap& rhs - ) const { - return lhs.get() > rhs.get(); - } - }; - - // sorts the ids in a descending order and removes duplicate ids - std::set, vertex_ref_greater_comparator> vertex_ref_set( - std::ranges::begin(vertex_ref_range), std::ranges::end(vertex_ref_range) - ); - + void remove_vertices_from(const type_traits::c_sized_range_of auto& vertex_range) { // TODO: optimize - for (const auto& vertex_ref : vertex_ref_set) - this->_remove_vertex_impl(vertex_ref.get().id()); - } - - [[nodiscard]] gl_attr_force_inline types::size_type in_degree(const vertex_type& vertex) const { - this->_verify_vertex(vertex); - return this->_impl.in_degree(vertex.id()); + // sort the ids in a descending order and removes duplicate ids + std::set vertex_set( + std::ranges::begin(vertex_range), std::ranges::end(vertex_range) + ); + for (const auto& vertex : vertex_set) + this->_remove_vertex_impl(vertex.id()); } [[nodiscard]] gl_attr_force_inline types::size_type in_degree(const types::id_type vertex_id @@ -224,14 +204,12 @@ class graph final { return this->_impl.in_degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { - return this->_impl.in_degree_map(); + [[nodiscard]] gl_attr_force_inline types::size_type in_degree(const vertex_type& vertex) const { + return this->in_degree(vertex.id()); } - [[nodiscard]] gl_attr_force_inline types::size_type out_degree(const vertex_type& vertex - ) const { - this->_verify_vertex(vertex); - return this->_impl.out_degree(vertex.id()); + [[nodiscard]] gl_attr_force_inline std::vector in_degree_map() const { + return this->_impl.in_degree_map(); } [[nodiscard]] gl_attr_force_inline types::size_type out_degree(const types::id_type vertex_id @@ -240,13 +218,13 @@ class graph final { return this->_impl.out_degree(vertex_id); } - [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { - return this->_impl.out_degree_map(); + [[nodiscard]] gl_attr_force_inline types::size_type out_degree(const vertex_type& vertex + ) const { + return this->out_degree(vertex.id()); } - [[nodiscard]] gl_attr_force_inline types::size_type degree(const vertex_type& vertex) const { - this->_verify_vertex(vertex); - return this->_impl.degree(vertex.id()); + [[nodiscard]] gl_attr_force_inline std::vector out_degree_map() const { + return this->_impl.out_degree_map(); } [[nodiscard]] gl_attr_force_inline types::size_type degree(const types::id_type vertex_id @@ -255,220 +233,280 @@ class graph final { return this->_impl.degree(vertex_id); } + [[nodiscard]] gl_attr_force_inline types::size_type degree(const vertex_type& vertex) const { + return this->degree(vertex.id()); + } + [[nodiscard]] gl_attr_force_inline std::vector degree_map() const { return this->_impl.degree_map(); } - [[nodiscard]] gl_attr_force_inline auto vertex_properties_map() const noexcept - requires(not type_traits::is_default_properties_type_v) - { - return views::deref(this->_vertex_properties); + [[nodiscard]] inline auto at(const types::id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + if constexpr (type_traits::c_non_empty_properties) + return this->_impl.at(vertex_id, this->_edge_properties); + else + return this->_impl.at(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto at(const vertex_type& vertex) const { + return this->at(vertex.id()); + } + + [[nodiscard]] inline auto adjacent_edges(const types::id_type vertex_id) const { + this->_verify_vertex_id(vertex_id); + if constexpr (type_traits::c_non_empty_properties) + return this->_impl.adjacent_edges(vertex_id, this->_edge_properties); + else + return this->_impl.adjacent_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto adjacent_edges(const vertex_type& vertex) const { + return this->adjacent_edges(vertex.id()); } // --- edge methods --- - // clang-format off - // gl_attr_force_inline misplacement + const edge_type add_edge(const types::id_type source_id, const types::id_type target_id) { + this->_verify_vertex_id(source_id); + this->_verify_vertex_id(target_id); - const edge_type& add_edge( - const types::id_type first_id, const types::id_type second_id - ) { - this->_verify_vertex_id(first_id); - this->_verify_vertex_id(second_id); - return this->_impl.add_edge( - detail::make_edge(first_id, second_id) - ); + const auto new_edge_id = this->_n_unique_edges++; + this->_impl.add_edge(new_edge_id, source_id, target_id); + + if constexpr (type_traits::c_non_empty_properties) { + const auto& p = + this->_edge_properties.emplace_back(std::make_unique()); + return edge_type{new_edge_id, source_id, target_id, *p}; + } + else { + return edge_type{new_edge_id, source_id, target_id}; + } } - const edge_type& add_edge( - const types::id_type first_id, - const types::id_type second_id, + const edge_type add_edge( + const types::id_type source_id, + const types::id_type target_id, const edge_properties_type& properties ) - requires(not type_traits::is_default_properties_type_v) + requires(type_traits::c_non_empty_properties) { - this->_verify_vertex_id(first_id); - this->_verify_vertex_id(second_id); - return this->_impl.add_edge(detail::make_edge(first_id, second_id, properties)); + this->_verify_vertex_id(source_id); + this->_verify_vertex_id(target_id); + + const auto new_edge_id = this->_n_unique_edges++; + this->_impl.add_edge(new_edge_id, source_id, target_id); + + auto& p = + this->_edge_properties.emplace_back(std::make_unique(properties)); + return edge_type{new_edge_id, source_id, target_id, *p}; } - // clang-format on + // clang-format off + // gl_attr_force_inline misplacement - const edge_type& add_edge(const vertex_type& first, const vertex_type& second) { - this->_verify_vertex(first); - this->_verify_vertex(second); - return this->_impl.add_edge(detail::make_edge(first.id(), second.id())); + gl_attr_force_inline const edge_type + add_edge(const vertex_type& source, const vertex_type& target) { + return this->add_edge(source.id(), target.id()); } - const edge_type& add_edge( - const vertex_type& first, const vertex_type& second, const edge_properties_type& properties + gl_attr_force_inline const edge_type add_edge( + const vertex_type& source, const vertex_type& target, const edge_properties_type& properties ) - requires(not type_traits::is_default_properties_type_v) + requires(type_traits::c_non_empty_properties) { - this->_verify_vertex(first); - this->_verify_vertex(second); - return this->_impl.add_edge( - detail::make_edge(first.id(), second.id(), properties) - ); + return this->add_edge(source.id(), target.id(), properties); } - template IdRange> - void add_edges_from(const types::id_type source_id, const IdRange& target_id_range) { - this->_verify_vertex_id(source_id); + // clang-format on - std::vector new_edges; - new_edges.reserve(std::ranges::size(target_id_range)); + void add_edges_from( + const types::id_type source_id, + const type_traits::c_sized_range_of auto& target_id_range + ) { + this->_verify_vertex_id(source_id); for (const auto target_id : target_id_range) { this->_verify_vertex_id(target_id); - new_edges.push_back(detail::make_edge(source_id, target_id)); + if constexpr (type_traits::c_non_empty_properties) + this->_edge_properties.emplace_back(std::make_unique()); } - this->_impl.add_edges_from(source_id, std::move(new_edges)); - } - template > VertexRefRange> - void add_edges_from(const vertex_type& source, const VertexRefRange& target_range) { - this->_verify_vertex(source); + const auto prev_n_edges = this->_n_unique_edges; + this->_n_unique_edges += std::ranges::size(target_id_range); + this->_impl.add_edges_from( + std::views::iota(prev_n_edges, this->_n_unique_edges), source_id, target_id_range + ); + } - std::vector new_edges; - new_edges.reserve(std::ranges::size(target_range)); + gl_attr_force_inline void add_edges_from( + const vertex_type& source, + const type_traits::c_sized_range_of auto& target_range + ) { + this->_verify_vertex_id(source.id()); - for (const auto& target_ref : target_range) { - const auto& target = target_ref.get(); - this->_verify_vertex(target); - new_edges.push_back(detail::make_edge(source.id(), target.id())); + for (const auto& target : target_range) { + this->_verify_vertex_id(target.id()); + if constexpr (type_traits::c_non_empty_properties) + this->_edge_properties.emplace_back(std::make_unique()); } - this->_impl.add_edges_from(source.id(), std::move(new_edges)); + + const auto prev_n_edges = this->_n_unique_edges; + this->_n_unique_edges += std::ranges::size(target_range); + this->_impl.add_edges_from( + std::views::iota(prev_n_edges, this->_n_unique_edges), + source.id(), + target_range | std::views::transform(&vertex_type::id) + ); } [[nodiscard]] gl_attr_force_inline bool has_edge( - const types::id_type first_id, const types::id_type second_id + const types::id_type source_id, const types::id_type target_id ) const { - return this->_impl.has_edge(first_id, second_id); + this->_verify_vertex_id(source_id); + this->_verify_vertex_id(target_id); + return this->_impl.has_edge(source_id, target_id); } - [[nodiscard]] bool has_edge(const vertex_type& first, const vertex_type& second) const { - this->_verify_vertex(first); - this->_verify_vertex(second); - return this->has_edge(first.id(), second.id()); + [[nodiscard]] gl_attr_force_inline bool has_edge( + const vertex_type& source, const vertex_type& target + ) const { + return this->has_edge(source.id(), target.id()); } [[nodiscard]] gl_attr_force_inline bool has_edge(const edge_type& edge) const { return this->_impl.has_edge(edge); } - [[nodiscard]] gl_attr_force_inline types::optional_cref get_edge( - const types::id_type first_id, const types::id_type second_id + [[nodiscard]] gl_attr_force_inline std::optional get_edge( + const types::id_type source_id, const types::id_type target_id ) const { - return this->_impl.get_edge(first_id, second_id); + this->_verify_vertex_id(source_id); + this->_verify_vertex_id(target_id); + + if constexpr (type_traits::c_non_empty_properties) + return this->_impl.get_edge(source_id, target_id, this->_edge_properties); + else + return this->_impl.get_edge(source_id, target_id); } - [[nodiscard]] types::optional_cref get_edge( - const vertex_type& first, const vertex_type& second + [[nodiscard]] gl_attr_force_inline std::optional get_edge( + const vertex_type& source, const vertex_type& target ) const { - if (not (this->has_vertex(first) and this->has_vertex(second))) - return std::nullopt; - - // TODO: optimize this so that the vertex ids are not checked twice - return this->_impl.get_edge(first.id(), second.id()); + return this->get_edge(source.id(), target.id()); } - [[nodiscard]] inline std::vector> get_edges( - const types::id_type first_id, const types::id_type second_id + [[nodiscard]] inline std::vector get_edges( + const types::id_type source_id, const types::id_type target_id ) const { - using edge_ref_set = std::vector>; - - this->_verify_vertex_id(first_id); - this->_verify_vertex_id(second_id); + this->_verify_vertex_id(source_id); + this->_verify_vertex_id(target_id); - if constexpr (std::same_as) { - return this->_impl.get_edges(first_id, second_id); - } - else { - const auto edge_opt = this->_impl.get_edge(first_id, second_id); - return edge_opt.has_value() ? edge_ref_set{edge_opt.value()} : edge_ref_set{}; - } + if constexpr (type_traits::c_non_empty_properties) + return this->_impl.get_edges(source_id, target_id, this->_edge_properties); + else + return this->_impl.get_edges(source_id, target_id); } - [[nodiscard]] std::vector> get_edges( - const vertex_type& first, const vertex_type& second + [[nodiscard]] gl_attr_force_inline std::vector get_edges( + const vertex_type& source, const vertex_type& target ) const { - return this->get_edges(first.id(), second.id()); + return this->get_edges(source.id(), target.id()); } gl_attr_force_inline void remove_edge(const edge_type& edge) { + this->_verify_edge(edge); + if constexpr (type_traits::c_non_empty_properties) + this->_edge_properties.erase(this->_edge_properties.begin() + edge.id()); this->_impl.remove_edge(edge); + this->_n_unique_edges--; } - template > EdgeRefRange> - inline void remove_edges_from(const EdgeRefRange edges) { - for (const auto& edge_ref : edges) - this->_impl.remove_edge(edge_ref.get()); - } - - [[nodiscard]] inline types::iterator_range adjacent_edges( - const types::id_type vertex_id - ) const { - this->_verify_vertex_id(vertex_id); - return this->_impl.adjacent_edges(vertex_id); - } + inline void remove_edges(const type_traits::c_range_of auto& edges) { + const auto removed_edge_ids = this->_impl.remove_edges(edges); + this->_n_unique_edges -= removed_edge_ids.size(); - [[nodiscard]] inline types::iterator_range adjacent_edges( - const vertex_type& vertex - ) const { - this->_verify_vertex(vertex); - return this->_impl.adjacent_edges(vertex.id()); + if constexpr (type_traits::c_non_empty_properties) { + // IDs are sorted and do not contain duplicates + for (const auto edge_id : std::views::reverse(removed_edge_ids)) + this->_edge_properties.erase(this->_edge_properties.begin() + edge_id); + } } // --- incidence methods --- - [[nodiscard]] bool are_incident(const types::id_type first_id, const types::id_type second_id) + [[nodiscard]] bool are_incident(const types::id_type source_id, const types::id_type target_id) const { - this->_verify_vertex_id(first_id); - - if (first_id == second_id) + this->_verify_vertex_id(source_id); + if (source_id == target_id) return true; - this->_verify_vertex_id(second_id); + this->_verify_vertex_id(target_id); if constexpr (type_traits::is_directed_v) - return this->has_edge(first_id, second_id) or this->has_edge(second_id, first_id); + return this->has_edge(source_id, target_id) or this->has_edge(target_id, source_id); else - return this->has_edge(first_id, second_id); + return this->has_edge(source_id, target_id); } - [[nodiscard]] bool are_incident(const vertex_type& first, const vertex_type& second) const { - this->_verify_vertex(first); - this->_verify_vertex(second); - - if (first == second) - return true; - - if constexpr (type_traits::is_directed_v) - return this->has_edge(first.id(), second.id()) - or this->has_edge(second.id(), first.id()); - else - return this->has_edge(first.id(), second.id()); + [[nodiscard]] gl_attr_force_inline bool are_incident( + const vertex_type& source, const vertex_type& target + ) const { + return this->are_incident(source.id(), target.id()); } [[nodiscard]] bool are_incident(const vertex_type& vertex, const edge_type& edge) const { - this->_verify_vertex(vertex); + this->_verify_vertex_id(vertex.id()); this->_verify_edge(edge); return edge.is_incident_with(vertex.id()); } - [[nodiscard]] bool are_incident(const edge_type& edge, const vertex_type& vertex) const { - this->_verify_vertex(vertex); - this->_verify_edge(edge); - return edge.is_incident_with(vertex.id()); + [[nodiscard]] gl_attr_force_inline bool are_incident( + const edge_type& edge, const vertex_type& vertex + ) const { + return this->are_incident(vertex, edge); } [[nodiscard]] bool are_incident(const edge_type& edge_1, const edge_type& edge_2) const { this->_verify_edge(edge_1); this->_verify_edge(edge_2); - return edge_1.is_incident_with(edge_2.first()) or edge_1.is_incident_with(edge_2.second()); + return edge_1.is_incident_with(edge_2.source()) or edge_1.is_incident_with(edge_2.target()); + } + + // --- property methods --- + + [[nodiscard]] gl_attr_force_inline auto vertex_properties_map() const noexcept + requires(type_traits::c_non_empty_properties) + { + return util::deref_view(this->_vertex_properties); } + [[nodiscard]] gl_attr_force_inline vertex_properties_type& get_vertex_properties( + const types::id_type id + ) const + requires(type_traits::c_non_empty_properties) + { + this->_verify_vertex_id(id); + return *this->_vertex_properties[id]; + } + + [[nodiscard]] gl_attr_force_inline auto edge_properties_map() const noexcept + requires(type_traits::c_non_empty_properties) + { + return util::deref_view(this->_edge_properties); + } + + [[nodiscard]] gl_attr_force_inline edge_properties_type& get_edge_properties( + const types::id_type id + ) const + requires(type_traits::c_non_empty_properties) + { + this->_verify_vertex_edge(id); + return *this->_edge_properties[id]; + } + + // --- stream operators --- + friend std::ostream& operator<<(std::ostream& os, const graph& g) { if (io::is_option_set(os, io::graph_option::gsf)) { g._gsf_write(os); @@ -500,38 +538,32 @@ class graph final { throw std::out_of_range(std::format("Got invalid vertex id [{}]", vertex_id)); } - gl_attr_force_inline void _verify_vertex(const vertex_type& vertex) const { - this->_verify_vertex_id(vertex.id()); - } - void _verify_edge(const edge_type& edge) const { - if (not this->has_edge(edge)) + if (edge.id() >= this->_n_unique_edges or not this->has_vertex(edge.source()) + or not this->has_vertex(edge.target())) throw std::invalid_argument(std::format( - "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge.first(), - edge.second(), - io::format(&edge) + "Got invalid edge [id = {}, vertices = ({}, {})]", + edge.id(), + edge.source(), + edge.target() )); } // --- vertex methods --- void _remove_vertex_impl(const types::id_type vertex_id) { - this->_impl.remove_vertex(vertex_id); + const auto removed_edge_ids = this->_impl.remove_vertex(vertex_id); this->_n_vertices--; + this->_n_unique_edges -= removed_edge_ids.size(); - // update vertex ids in edges - for (auto id : this->vertex_ids()) { - for (auto& edge : this->_impl.adjacent_edges(id)) { - edge._vertices.first -= (edge._vertices.first > vertex_id); - edge._vertices.second -= (edge._vertices.second > vertex_id); - } - } + if constexpr (type_traits::c_non_empty_properties) + this->_vertex_properties.erase(this->_vertex_properties.begin() + vertex_id); - if constexpr (not type_traits::is_default_properties_type_v) - this->_vertex_properties.erase( - std::next(std::begin(this->_vertex_properties), vertex_id) - ); + if constexpr (type_traits::c_non_empty_properties) { + // IDs are sorted and do not contain duplicates + for (const auto& edge_id : std::views::reverse(removed_edge_ids)) + this->_edge_properties.erase(this->_edge_properties.begin() + edge_id); + } } // --- io methods --- @@ -546,7 +578,7 @@ class graph final { for (const auto& vertex : this->vertices()) { os << "- " << vertex << "\n adjacent edges:\n"; - for (const auto& edge : this->_impl.adjacent_edges(vertex.id())) + for (const auto& edge : this->adjacent_edges(vertex.id())) os << "\t- " << edge << '\n'; } } @@ -558,7 +590,7 @@ class graph final { for (const auto& vertex : this->vertices()) { os << "- " << vertex << " :"; - for (const auto& edge : this->_impl.adjacent_edges(vertex.id())) + for (const auto& edge : this->adjacent_edges(vertex.id())) os << ' ' << edge; os << '\n'; } @@ -583,15 +615,15 @@ class graph final { if constexpr (type_traits::c_writable) if (with_vertex_properties) for (const auto& vertex : this->vertices()) - os << vertex._properties << '\n'; + os << vertex.properties() << '\n'; if constexpr (type_traits::c_writable) { if (with_edge_properties) { const auto print_incident_edges = [this, &os](const types::id_type vertex_id) { - for (const auto& edge : this->_impl.adjacent_edges(vertex_id)) { - if (edge.first() != vertex_id) + for (const auto& edge : this->adjacent_edges(vertex_id)) { + if (edge.source() != vertex_id) continue; // vertex is not the source - os << edge.first() << ' ' << edge.second() << ' ' << edge._properties + os << edge.source() << ' ' << edge.target() << ' ' << edge.properties() << '\n'; } }; @@ -604,10 +636,10 @@ class graph final { } const auto print_incident_edges = [this, &os](const types::id_type vertex_id) { - for (const auto& edge : this->_impl.adjacent_edges(vertex_id)) { - if (edge.first() != vertex_id) + for (const auto& edge : this->adjacent_edges(vertex_id)) { + if (edge.source() != vertex_id) continue; // vertex is not the source - os << edge.first() << ' ' << edge.second() << '\n'; + os << edge.source() << ' ' << edge.target() << '\n'; } }; @@ -661,29 +693,31 @@ class graph final { } else { // read edges with their properties - types::id_type first_id, second_id; + types::id_type source_id, target_id; edge_properties_type properties; for (types::size_type i = constants::begin_idx; i < n_edges; ++i) { - is >> first_id >> second_id >> properties; - this->add_edge(first_id, second_id, properties); + is >> source_id >> target_id >> properties; + this->add_edge(source_id, target_id, properties); } } } else { // read the edges - types::id_type first_id, second_id; + types::id_type source_id, target_id; for (types::size_type i = constants::begin_idx; i < n_edges; ++i) { - is >> first_id >> second_id; - this->add_edge(first_id, second_id); + is >> source_id >> target_id; + this->add_edge(source_id, target_id); } } } types::size_type _n_vertices = 0uz; - [[no_unique_address]] vertex_properties_map_type _vertex_properties{ - }; // add conditional getter and tests + types::size_type _n_unique_edges = 0uz; + + [[no_unique_address]] vertex_properties_map_type _vertex_properties{}; + [[no_unique_address]] edge_properties_map_type _edge_properties{}; implementation_type _impl{}; }; diff --git a/include/gl/graph_traits.hpp b/include/gl/graph_traits.hpp index b11b844f..f1dbd6f0 100644 --- a/include/gl/graph_traits.hpp +++ b/include/gl/graph_traits.hpp @@ -17,11 +17,9 @@ template < type_traits::c_graph_impl_tag ImplTag = impl::list_t> struct graph_traits { using vertex_type = vertex_descriptor; - using vertex_ptr_type = types::vertex_ptr_type; using vertex_properties_type = typename vertex_type::properties_type; using edge_type = edge_descriptor; - using edge_ptr_type = types::edge_ptr_type; using edge_directional_tag = typename edge_type::directional_tag; using edge_properties_type = typename edge_type::properties_type; diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index 44a38cb4..b954fda8 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -5,29 +5,24 @@ #pragma once #include "gl/constants.hpp" -#include "gl/types/dereferencing_iterator.hpp" -#include "gl/types/iterator_range.hpp" #include "gl/types/types.hpp" #include "specialized/adjacency_list.hpp" +#ifdef GL_TESTING +namespace gl_testing { +struct test_adjacency_list; +} // namespace gl_testing +#endif + namespace gl::impl { template class adjacency_list final { public: using vertex_type = typename GraphTraits::vertex_type; - using edge_type = typename GraphTraits::edge_type; - using edge_ptr_type = typename GraphTraits::edge_ptr_type; - using edge_directional_tag = typename GraphTraits::edge_directional_tag; - - using edge_list_type = std::vector; - using edge_iterator_type = - types::dereferencing_iterator; - - // TODO: reverese iterators should be available for bidirectional ranges - - using list_type = std::vector; + using edge_item_list_type = std::vector; + using adjacency_list_type = std::vector; adjacency_list(const adjacency_list&) = delete; adjacency_list& operator=(const adjacency_list&) = delete; @@ -41,26 +36,14 @@ class adjacency_list final { ~adjacency_list() = default; - // --- general methods --- - - [[nodiscard]] gl_attr_force_inline types::size_type n_vertices() const { - return this->_list.size(); - } - - [[nodiscard]] gl_attr_force_inline types::size_type n_unique_edges() const { - return this->_n_unique_edges; - } - // --- vertex methods --- gl_attr_force_inline void add_vertex() { - this->_list.emplace_back(edge_list_type{}); + this->_list.emplace_back(edge_item_list_type{}); } inline void add_vertices(const types::size_type n) { - this->_list.reserve(this->n_vertices() + n); - for (types::size_type _ = constants::begin_idx; _ < n; ++_) - this->_list.emplace_back(edge_list_type{}); + this->_list.resize(this->_list.size() + n, edge_item_list_type{}); } [[nodiscard]] gl_attr_force_inline types::size_type in_degree(const types::id_type vertex_id @@ -90,93 +73,192 @@ class adjacency_list final { return specialized_impl::degree_map(*this); } - gl_attr_force_inline void remove_vertex(const types::id_type& vertex_id) { - specialized_impl::remove_vertex(*this, vertex_id); + std::vector remove_vertex(const types::id_type vertex_id) { + auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + this->_remap_element_ids(vertex_id, removed_edge_ids); + return removed_edge_ids; } // --- edge methods --- - // clang-format off - // gl_attr_force_inline misplacement - - gl_attr_force_inline const edge_type& add_edge(edge_ptr_type edge) { - return specialized_impl::add_edge(*this, std::move(edge)); + gl_attr_force_inline void add_edge( + types::id_type id, types::id_type source_id, types::id_type target_id + ) { + specialized_impl::add_edge(*this, id, source_id, target_id); } - // clang-format on - gl_attr_force_inline void add_edges_from( - const types::id_type source_id, std::vector new_edges + const type_traits::c_sized_range_of auto& edge_ids, + const types::id_type source_id, + const type_traits::c_sized_range_of auto& target_ids ) { - specialized_impl::add_edges_from(*this, source_id, std::move(new_edges)); + specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } - [[nodiscard]] bool has_edge(const types::id_type first_id, const types::id_type second_id) - const { - const auto& adjacent_edges = this->_list[first_id]; - return std::ranges::find_if( - adjacent_edges, - [first_id, second_id](const auto& edge) { - return specialized_impl::is_edge_incident_with(edge, second_id, first_id); - } - ) - != adjacent_edges.end(); + [[nodiscard]] gl_attr_force_inline bool has_edge( + const types::id_type source_id, const types::id_type target_id + ) const { + return std::ranges::contains(this->_list[source_id], target_id, [](const auto& item) { + return item.target_id; + }); } [[nodiscard]] gl_attr_force_inline bool has_edge(const edge_type& edge) const { - // find the edge by address return std::ranges::contains( - this->_list[edge.first()], &edge, typename specialized_impl::address_projection{} + this->_list[edge.source()], specialized::adjacency_list_item{edge.id(), edge.target()} ); } - [[nodiscard]] types::optional_ref get_edge( - const types::id_type first_id, const types::id_type second_id - ) const { - const auto& adjacent_edges = this->_list[first_id]; - const auto it = - std::ranges::find_if(adjacent_edges, [first_id, second_id](const auto& edge) { - return specialized_impl::is_edge_incident_with(edge, second_id, first_id); - }); - - if (it == adjacent_edges.cend()) + [[nodiscard]] std::optional get_edge( + const types::id_type source_id, const types::id_type target_id + ) const + requires(type_traits::c_has_empty_properties) + { + const auto& adjacent_edges = this->_list[source_id]; + const auto item_it = std::ranges::find(adjacent_edges, target_id, [](const auto& item) { + return item.target_id; + }); + if (item_it == adjacent_edges.cend()) return std::nullopt; - return std::cref(**it); + return std::make_optional(item_it->id, source_id, target_id); } - [[nodiscard]] auto get_edges(const types::id_type first_id, const types::id_type second_id) - const { - using edge_ref_set = std::vector>; - const auto& adjacent_edges = this->_list[first_id]; - - edge_ref_set matching_edges{}; - matching_edges.reserve(adjacent_edges.size()); + [[nodiscard]] std::optional get_edge( + const types::id_type source_id, + const types::id_type target_id, + const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + const auto& adjacent_edges = this->_list[source_id]; + const auto item_it = std::ranges::find(adjacent_edges, target_id, [](const auto& item) { + return item.target_id; + }); + if (item_it == adjacent_edges.cend()) + return std::nullopt; + return std::make_optional( + item_it->id, source_id, target_id, *edge_properties_map[item_it->id] + ); + } - for (const auto& edge : adjacent_edges) - if (specialized_impl::is_edge_incident_with(edge, second_id, first_id)) - matching_edges.emplace_back(*edge); + [[nodiscard]] std::vector get_edges( + const types::id_type source_id, const types::id_type target_id + ) const + requires(type_traits::c_has_empty_properties) + { + return this->_list[source_id] | std::views::filter([&target_id](const auto& item) { + return item.target_id == target_id; + }) + | std::views::transform([source_id](const auto& item) { + return edge_type{item.id, source_id, item.target_id}; + }) + | std::ranges::to(); + } - matching_edges.shrink_to_fit(); - return matching_edges; + [[nodiscard]] std::vector get_edges( + const types::id_type source_id, + const types::id_type target_id, + const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + return this->_list[source_id] | std::views::filter([&target_id](const auto& item) { + return item.target_id == target_id; + }) + | std::views::transform([source_id, &edge_properties_map](const auto& item) { + return edge_type{ + item.id, source_id, item.target_id, *edge_properties_map[item.id] + }; + }) + | std::ranges::to(); } gl_attr_force_inline void remove_edge(const edge_type& edge) { specialized_impl::remove_edge(*this, edge); + for (auto& adj : this->_list) + for (auto& item : adj) + item.id -= static_cast(item.id > edge.id()); } - [[nodiscard]] gl_attr_force_inline types::iterator_range adjacent_edges( - const types::id_type vertex_id - ) const { - const auto& adjacent_edges = this->_list[vertex_id]; - return make_iterator_range(deref_cbegin(adjacent_edges), deref_cend(adjacent_edges)); + std::vector remove_edges(const type_traits::c_range_of auto& edges) { + for (const auto& edge : edges) + specialized_impl::remove_edge(*this, edge); + auto removed_edge_ids = + edges | std::views::transform([](const auto& edge) { return edge.id(); }) + | std::ranges::to(); + this->_remap_element_ids(constants::invalid_id, removed_edge_ids); + return removed_edge_ids; + } + + [[nodiscard]] gl_attr_force_inline auto adjacent_edges(const types::id_type vertex_id) const + requires(type_traits::c_has_empty_properties) + { + return this->_list[vertex_id] | std::views::transform([vertex_id](const auto& item) { + return edge_type{item.id, vertex_id, item.target_id}; + }); + } + + [[nodiscard]] gl_attr_force_inline auto adjacent_edges( + const types::id_type vertex_id, const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + return this->_list[vertex_id] + | std::views::transform([vertex_id, &edge_properties_map](const auto& item) { + return edge_type{ + item.id, vertex_id, item.target_id, *edge_properties_map[item.id] + }; + }); + } + + // --- access operators --- + + [[nodiscard]] gl_attr_force_inline auto at(const types::id_type vertex_id) const + requires(type_traits::c_has_empty_properties) + { + return this->adjacent_edges(vertex_id); + } + + [[nodiscard]] gl_attr_force_inline auto at( + const types::id_type vertex_id, const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + return this->adjacent_edges(vertex_id, edge_properties_map); } +#ifdef GL_TESTING + friend struct gl_testing::test_adjacency_list; +#endif + private: using specialized_impl = typename specialized::list_impl_traits::type; friend specialized_impl; - list_type _list{}; - types::size_type _n_unique_edges{constants::default_size}; + void _remap_element_ids( + const types::id_type removed_vertex_id, std::vector& removed_edge_ids + ) { + std::ranges::sort(removed_edge_ids); + removed_edge_ids.erase( + std::ranges::unique(removed_edge_ids).begin(), removed_edge_ids.end() + ); + + for (auto& adj : this->_list) { + for (auto& edge_item : adj) { + auto it = std::ranges::lower_bound(removed_edge_ids, edge_item.id); + if (it != removed_edge_ids.end() && *it == edge_item.id) + edge_item.id = constants::invalid_id; // edge was removed + else + // shift by the number of removed IDs < edge-id + edge_item.id -= std::ranges::distance(removed_edge_ids.begin(), it); + + // align the vertex id + edge_item.target_id -= edge_item.target_id > removed_vertex_id; + } + } + } + + adjacency_list_type _list{}; }; } // namespace gl::impl diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index 7b3d0b6c..ba72640f 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -5,30 +5,25 @@ #pragma once #include "gl/constants.hpp" -#include "gl/types/dereferencing_iterator.hpp" -#include "gl/types/iterator_range.hpp" -#include "gl/types/non_null_iterator.hpp" #include "gl/types/types.hpp" #include "specialized/adjacency_matrix.hpp" +#ifdef GL_TESTING +namespace gl_testing { +struct test_adjacency_matrix; +} // namespace gl_testing +#endif + + namespace gl::impl { template class adjacency_matrix final { public: using vertex_type = typename GraphTraits::vertex_type; - using edge_type = typename GraphTraits::edge_type; - using edge_ptr_type = typename GraphTraits::edge_ptr_type; - using edge_directional_tag = typename GraphTraits::edge_directional_tag; - - using edge_list_type = std::vector; - using edge_iterator_type = types::dereferencing_iterator< - types::non_null_iterator>; - - // TODO: reverese iterators should be available for bidirectional ranges - - using matrix_type = std::vector; + using edge_id_list_type = std::vector; + using matrix_type = std::vector; adjacency_matrix(const adjacency_matrix&) = delete; adjacency_matrix& operator=(const adjacency_matrix&) = delete; @@ -37,10 +32,8 @@ class adjacency_matrix final { adjacency_matrix(const types::size_type n_vertices) : _matrix(n_vertices) { // initialize a full n x n matrix with null elements - for (auto& row : this->_matrix) { - row.reserve(n_vertices); - std::generate_n(std::back_inserter(row), n_vertices, _make_null_edge); - } + for (auto& row : this->_matrix) + row.resize(n_vertices, constants::invalid_id); } adjacency_matrix(adjacency_matrix&&) = default; @@ -48,39 +41,22 @@ class adjacency_matrix final { ~adjacency_matrix() = default; - // --- general methods --- - - [[nodiscard]] gl_attr_force_inline types::size_type n_vertices() const { - return this->_matrix.size(); - } - - [[nodiscard]] gl_attr_force_inline types::size_type n_unique_edges() const { - return this->_n_unique_edges; - } - // --- vertex methods --- void add_vertex() { for (auto& row : this->_matrix) - row.emplace_back(_make_null_edge()); - auto& new_row = this->_matrix.emplace_back(); - new_row.reserve(this->n_vertices()); - std::generate_n(std::back_inserter(new_row), this->n_vertices(), _make_null_edge); + row.push_back(constants::invalid_id); + this->_matrix.emplace_back(this->_matrix.size() + 1uz, constants::invalid_id); } void add_vertices(const types::size_type n) { - const auto new_n_vertices = this->n_vertices() + n; + const auto new_n_vertices = this->_matrix.size() + n; - for (auto& row : this->_matrix) { - row.reserve(new_n_vertices); - std::generate_n(std::back_inserter(row), n, _make_null_edge); - } + for (auto& row : this->_matrix) + row.resize(new_n_vertices, constants::invalid_id); - for (types::size_type _ = constants::begin_idx; _ < n; ++_) { - auto& new_row = this->_matrix.emplace_back(); - new_row.reserve(new_n_vertices); - std::generate_n(std::back_inserter(new_row), new_n_vertices, _make_null_edge); - } + for (types::size_type _ = constants::begin_idx; _ < n; ++_) + this->_matrix.emplace_back(new_n_vertices, constants::invalid_id); } [[nodiscard]] gl_attr_force_inline types::size_type in_degree(const types::id_type vertex_id @@ -110,83 +86,208 @@ class adjacency_matrix final { return specialized_impl::degree_map(*this); } - gl_attr_force_inline void remove_vertex(const types::id_type vertex_id) { - specialized_impl::remove_vertex(*this, vertex_id); + std::vector remove_vertex(const types::id_type vertex_id) { + auto removed_edge_ids = specialized_impl::remove_vertex(*this, vertex_id); + this->_remap_element_ids(removed_edge_ids); + return removed_edge_ids; } // --- edge methods --- - // clang-format off - // gl_attr_force_inline misplacement - - gl_attr_force_inline const edge_type& add_edge(edge_ptr_type edge) { - return specialized_impl::add_edge(*this, std::move(edge)); + gl_attr_force_inline void add_edge( + types::id_type id, types::id_type source_id, types::id_type target_id + ) { + specialized_impl::add_edge(*this, id, source_id, target_id); } - // clang-format on - gl_attr_force_inline void add_edges_from( - const types::id_type source_id, std::vector new_edges + const type_traits::c_sized_range_of auto& edge_ids, + const types::id_type source_id, + const type_traits::c_sized_range_of auto& target_ids ) { - specialized_impl::add_edges_from(*this, source_id, std::move(new_edges)); + specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } [[nodiscard]] gl_attr_force_inline bool has_edge( - const types::id_type first_id, const types::id_type second_id + const types::id_type source_id, const types::id_type target_id ) const { - return this->_matrix[first_id][second_id] != nullptr; + return this->_matrix[source_id][target_id] != constants::invalid_id; } [[nodiscard]] bool has_edge(const edge_type& edge) const { - const auto [first_id, second_id] = edge.incident_vertices(); - if (not (this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(second_id))) - return false; - - const auto& matrix_element = this->_matrix[first_id][second_id]; - return matrix_element != nullptr and &edge == matrix_element.get(); + return this->_matrix[edge.source()][edge.target()] == edge.id(); } - [[nodiscard]] types::optional_ref get_edge( - const types::id_type first_id, const types::id_type second_id - ) const { - if (not (this->_is_valid_vertex_id(first_id) and this->_is_valid_vertex_id(second_id))) + [[nodiscard]] std::optional get_edge( + const types::id_type source_id, const types::id_type target_id + ) const + requires(type_traits::c_has_empty_properties) + { + const auto edge_id = this->_matrix[source_id][target_id]; + if (edge_id == constants::invalid_id) return std::nullopt; + return std::make_optional(edge_id, source_id, target_id); + } - const auto& matrix_element = this->_matrix[first_id][second_id]; - if (not matrix_element) + [[nodiscard]] std::optional get_edge( + const types::id_type source_id, + const types::id_type target_id, + const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + const auto edge_id = this->_matrix[source_id][target_id]; + if (edge_id == constants::invalid_id) return std::nullopt; - return std::cref(*matrix_element); + return std::make_optional( + edge_id, source_id, target_id, *edge_properties_map[edge_id] + ); + } + + [[nodiscard]] std::vector get_edges( + const types::id_type source_id, const types::id_type target_id + ) const + requires(type_traits::c_has_empty_properties) + { + const auto edge_id = this->_matrix[source_id][target_id]; + if (edge_id == constants::invalid_id) + return std::vector(); + return std::vector{ + edge_type{edge_id, source_id, target_id} + }; + } + + [[nodiscard]] std::vector get_edges( + const types::id_type source_id, + const types::id_type target_id, + const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + const auto edge_id = this->_matrix[source_id][target_id]; + if (edge_id == constants::invalid_id) + return std::vector(); + return std::vector{ + edge_type{edge_id, source_id, target_id, *edge_properties_map[edge_id]} + }; } gl_attr_force_inline void remove_edge(const edge_type& edge) { specialized_impl::remove_edge(*this, edge); + for (auto& row : this->_matrix) + for (auto& edge_id : row) + if (edge_id != constants::invalid_id and edge_id > edge.id()) + edge_id--; } - [[nodiscard]] inline types::iterator_range adjacent_edges( - const types::id_type vertex_id - ) const { - const auto& row = this->_matrix[vertex_id]; - return make_iterator_range( - types::dereferencing_iterator(non_null_cbegin(row)), - types::dereferencing_iterator(non_null_cend(row)) - ); + std::vector remove_edges(const type_traits::c_range_of auto& edges) { + for (const auto& edge : edges) + specialized_impl::remove_edge(*this, edge); + auto removed_edge_ids = + edges | std::views::transform([](const auto& edge) { return edge.id(); }) + | std::ranges::to(); + this->_remap_element_ids(removed_edge_ids); + return removed_edge_ids; } + [[nodiscard]] gl_attr_force_inline auto adjacent_edges(const types::id_type vertex_id) const + requires(type_traits::c_has_empty_properties) + { + return this->_matrix[vertex_id] | std::views::enumerate + | std::views::filter([](const auto& edge_info) { + const auto& [target_id, edge_id] = edge_info; + return edge_id != constants::invalid_id; + }) + | std::views::transform([vertex_id](const auto& edge_info) { + const auto& [target_id, edge_id] = edge_info; + return edge_type{edge_id, vertex_id, static_cast(target_id)}; + }); + } + + [[nodiscard]] gl_attr_force_inline auto adjacent_edges( + const types::id_type vertex_id, const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + return this->_matrix[vertex_id] | std::views::enumerate + | std::views::filter([](const auto& edge_info) { + const auto& [target_id, edge_id] = edge_info; + return edge_id != constants::invalid_id; + }) + | std::views::transform([vertex_id, &edge_properties_map](const auto& edge_info) { + const auto& [target_id, edge_id] = edge_info; + return edge_type{ + edge_id, + vertex_id, + static_cast(target_id), + *edge_properties_map[edge_id] + }; + }); + } + + // --- access operators --- + + [[nodiscard]] gl_attr_force_inline auto at(const types::id_type vertex_id) const + requires(type_traits::c_has_empty_properties) + { + return this->_matrix[vertex_id] | std::views::enumerate + | std::views::transform([vertex_id](const auto& edge_info) { + const auto& [target_id, edge_id] = edge_info; + return edge_id == constants::invalid_id + ? edge_type::invalid() + : edge_type{edge_id, vertex_id, static_cast(target_id)}; + }); + } + + [[nodiscard]] gl_attr_force_inline auto at( + const types::id_type vertex_id, const auto& edge_properties_map + ) const + requires(type_traits::c_has_non_empty_properties) + { + return this->_matrix[vertex_id] | std::views::enumerate + | std::views::transform([vertex_id, &edge_properties_map](const auto& edge_info) { + const auto& [target_id, edge_id] = edge_info; + return edge_id == constants::invalid_id + ? edge_type::invalid() + : edge_type{ + edge_id, + vertex_id, + static_cast(target_id), + *edge_properties_map[edge_id] + }; + }); + } + +#ifdef GL_TESTING + friend struct gl_testing::test_adjacency_matrix; +#endif + private: using specialized_impl = typename specialized::matrix_impl_traits::type; friend specialized_impl; - static constexpr edge_ptr_type _make_null_edge() { - return nullptr; - } + void _remap_element_ids(std::vector& removed_edge_ids) { + std::ranges::sort(removed_edge_ids); + removed_edge_ids.erase( + std::ranges::unique(removed_edge_ids).begin(), removed_edge_ids.end() + ); - [[nodiscard]] gl_attr_force_inline bool _is_valid_vertex_id(const types::id_type vertex_id - ) const { - return vertex_id < this->_matrix.size(); + for (auto& row : this->_matrix) { + for (auto& edge_id : row) { + if (edge_id == constants::invalid_id) + continue; + + auto it = std::ranges::lower_bound(removed_edge_ids, edge_id); + if (it != removed_edge_ids.end() && *it == edge_id) + edge_id = constants::invalid_id; // edge was removed + else + // shift by the number of removed IDs < edge-id + edge_id -= std::ranges::distance(removed_edge_ids.begin(), it); + } + } } matrix_type _matrix{}; - types::size_type _n_unique_edges{constants::default_size}; }; } // namespace gl::impl diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 769b3dd9..7567a9a2 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include namespace gl::impl { @@ -19,24 +19,26 @@ class adjacency_list; namespace specialized { +struct adjacency_list_item { + types::id_type id; + types::id_type target_id; + + [[nodiscard]] bool operator==(const adjacency_list_item&) const = default; +}; + namespace detail { -template AdjacencyList, typename AddressProjection> -requires std::is_invocable_r_v< - const typename AdjacencyList::edge_type*, - AddressProjection, - const typename AdjacencyList::edge_ptr_type&> -[[nodiscard]] typename AdjacencyList::edge_iterator_type::iterator_type strict_find( - typename AdjacencyList::edge_list_type& edge_set, const typename AdjacencyList::edge_type* edge +[[nodiscard]] auto strict_find( + type_traits::c_range_of auto& edge_list, const auto& edge ) { // find the edge by address - const auto it = std::ranges::find(edge_set, edge, AddressProjection{}); - if (it == edge_set.end()) + const auto it = std::ranges::find(edge_list, edge.id(), &adjacency_list_item::id); + if (it == edge_list.end()) throw std::invalid_argument(std::format( - "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge->first(), - edge->second(), - io::format(edge) + "Got invalid edge [id = {} | vertices = ({}, {})]", + edge.id(), + edge.source(), + edge.target() )); return it; @@ -48,30 +50,15 @@ template AdjacencyList> requires(type_traits::is_directed_v) struct directed_adjacency_list { using impl_type = AdjacencyList; - using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; - using edge_ptr_type = typename impl_type::edge_ptr_type; - using edge_list_type = typename impl_type::edge_list_type; - using edge_iterator_type = typename impl_type::edge_iterator_type::iterator_type; - - struct address_projection { - [[nodiscard]] gl_attr_force_inline auto operator()(const edge_type& edge) { - return &edge; - } - - [[nodiscard]] gl_attr_force_inline auto operator()(const edge_ptr_type& edge) { - return edge.get(); - } - }; [[nodiscard]] static types::size_type in_degree( const impl_type& self, const types::id_type vertex_id ) { types::size_type in_deg = constants::default_size; for (const auto& adjacent_edges : self._list) - in_deg += std::ranges::count(adjacent_edges, vertex_id, [](const auto& edge) { - return edge->second(); - }); + in_deg += + std::ranges::count(adjacent_edges, vertex_id, &adjacency_list_item::target_id); return in_deg; } @@ -92,8 +79,8 @@ struct directed_adjacency_list { std::vector in_degree_map(self._list.size(), constants::zero); for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { - std::ranges::for_each(self._list[id], [&in_degree_map](const auto& edge) { - ++in_degree_map[edge->second()]; + std::ranges::for_each(self._list[id], [&in_degree_map](const auto& item) { + ++in_degree_map[item.target_id]; }); } @@ -113,73 +100,65 @@ struct directed_adjacency_list { for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { degree_map[id] += self._list[id].size(); - - // update in degrees - std::ranges::for_each(self._list[id], [°ree_map](const auto& edge) { - ++degree_map[edge->second()]; + std::ranges::for_each(self._list[id], [°ree_map](const auto& item) { + ++degree_map[item.target_id]; }); } return degree_map; } - static void remove_vertex(impl_type& self, const types::id_type vertex_id) { + static std::vector remove_vertex( + impl_type& self, const types::id_type vertex_id + ) { + auto removed_edges = + self._list[vertex_id] | std::views::transform(&adjacency_list_item::id) + | std::ranges::to(); + // remove all edges incident to the vertex for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { auto& adj_edges = self._list[id]; if (id == vertex_id or adj_edges.empty()) continue; - const auto rem_subrange = - std::ranges::remove_if(adj_edges, [vertex_id](const auto& edge) { - return edge->is_incident_with(vertex_id); + const auto removed_subrng = + std::ranges::remove_if(adj_edges, [vertex_id, &removed_edges](const auto& item) { + if (item.target_id == vertex_id) { + removed_edges.push_back(item.id); + return true; + } + return false; }); - self._n_unique_edges -= std::ranges::distance(rem_subrange.begin(), rem_subrange.end()); - adj_edges.erase(rem_subrange.begin(), rem_subrange.end()); + adj_edges.erase(removed_subrng.begin(), removed_subrng.end()); } // remove the list of edges incident from the vertex entirely - self._n_unique_edges -= self._list[vertex_id].size(); self._list.erase(std::next(std::begin(self._list), vertex_id)); + return removed_edges; } - static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - auto& adjacent_edges_first = self._list[edge->first()]; - auto& new_edge = adjacent_edges_first.emplace_back(std::move(edge)); - ++self._n_unique_edges; - return *new_edge; + gl_attr_force_inline static void add_edge( + impl_type& self, types::id_type edge_id, types::id_type source_id, types::id_type target_id + ) { + self._list[source_id].emplace_back(edge_id, target_id); } static void add_edges_from( - impl_type& self, const types::id_type source_id, std::vector new_edges + impl_type& self, + const type_traits::c_sized_range_of auto& edge_ids, + const types::id_type source_id, + const type_traits::c_sized_range_of auto& target_ids ) { auto& adjacent_edges_source = self._list[source_id]; - adjacent_edges_source.reserve(adjacent_edges_source.size() + new_edges.size()); - - for (auto& edge : new_edges) - adjacent_edges_source.emplace_back(std::move(edge)); + adjacent_edges_source.reserve(adjacent_edges_source.size() + target_ids.size()); - self._n_unique_edges += new_edges.size(); - } - - [[nodiscard]] gl_attr_force_inline static bool is_edge_incident_with( - const edge_ptr_type& edge, - const types::id_type vertex_id, - [[maybe_unused]] const types::id_type source_id - ) { - /* - there is no need to check source_id because the implementation will not allow - adding an edge for which first_id() is not the index in the adjacency - list at which the edge is located, but the parameter is necessary to match the - function signature for undirected adjacency list - */ - return edge->second() == vertex_id; + for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) + adjacent_edges_source.emplace_back(edge_id, target_id); } - static void remove_edge(impl_type& self, const edge_type& edge) { - auto& adj_edges = self._list.at(edge.first()); - adj_edges.erase(detail::strict_find(adj_edges, &edge)); - --self._n_unique_edges; + gl_attr_force_inline static void remove_edge(impl_type& self, const edge_type& edge) { + auto& adj_edges = self._list[edge.source()]; + adj_edges.erase(detail::strict_find(adj_edges, edge)); } }; @@ -187,21 +166,7 @@ template AdjacencyList> requires(type_traits::is_undirected_v) struct undirected_adjacency_list { using impl_type = AdjacencyList; - using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; - using edge_ptr_type = typename impl_type::edge_ptr_type; - using edge_list_type = typename impl_type::edge_list_type; - using edge_iterator_type = typename impl_type::edge_iterator_type::iterator_type; - - struct address_projection { - [[nodiscard]] gl_attr_force_inline auto operator()(const edge_type& edge) { - return &edge; - } - - [[nodiscard]] gl_attr_force_inline auto operator()(const edge_ptr_type& edge) { - return edge.get(); - } - }; [[nodiscard]] gl_attr_force_inline static types::size_type in_degree( const impl_type& self, const types::id_type vertex_id @@ -219,8 +184,8 @@ struct undirected_adjacency_list { const impl_type& self, const types::id_type vertex_id ) { types::size_type degree = constants::default_size; - for (const auto& edge : self._list[vertex_id]) - degree += constants::one + static_cast(edge->is_loop()); + for (const auto& item : self._list[vertex_id]) + degree += constants::one + static_cast(item.target_id == vertex_id); return degree; } @@ -239,93 +204,69 @@ struct undirected_adjacency_list { [[nodiscard]] static std::vector degree_map(const impl_type& self) { std::vector degree_map; degree_map.reserve(self._list.size()); - for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) degree_map.push_back(degree(self, id)); - return degree_map; } - static void remove_vertex(impl_type& self, const types::id_type vertex_id) { - std::unordered_set incident_vertex_id_set; - - // select the vertices incident with the removed vertex - for (const auto& edge : self._list[vertex_id]) { - if (edge->is_loop()) + static std::vector remove_vertex( + impl_type& self, const types::id_type vertex_id + ) { + // remove all edges incident with the vertex (scan only the selected vertices) + for (const auto& item : self._list[vertex_id]) { + if (item.target_id == vertex_id) continue; // will be removed with the vertex's list - incident_vertex_id_set.insert(edge->incident_vertex(vertex_id)); - } - // remove all edges incident with the vertex (scan only the selected vertices) - for (const auto& incident_vertex_id : incident_vertex_id_set) { - auto& adj_edges = self._list[incident_vertex_id]; - const auto rem_subrange = - std::ranges::remove_if(adj_edges, [vertex_id](const auto& edge) { - return edge->is_incident_with(vertex_id); - }); - adj_edges.erase(rem_subrange.begin(), rem_subrange.end()); + auto& adj_edges = self._list[item.target_id]; + const auto removed_subrng = std::ranges::remove_if( + adj_edges, [vertex_id](const auto& item) { return item.target_id == vertex_id; } + ); + adj_edges.erase(removed_subrng.begin(), removed_subrng.end()); } // remove the list of edges incident from the vertex entirely - self._n_unique_edges -= self._list[vertex_id].size(); + const auto removed_edges = + self._list[vertex_id] | std::views::transform(&adjacency_list_item::id) + | std::ranges::to(); self._list.erase(std::next(std::begin(self._list), vertex_id)); + return removed_edges; } - static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - auto& adjacent_edges_first = self._list[edge->first()]; - - if (not edge->is_loop()) - self._list[edge->second()].push_back(edge); - adjacent_edges_first.emplace_back(std::move(edge)); - - ++self._n_unique_edges; - return *adjacent_edges_first.back(); + static void add_edge( + impl_type& self, types::id_type edge_id, types::id_type source_id, types::id_type target_id + ) { + self._list[source_id].emplace_back(edge_id, target_id); + if (target_id != source_id) + self._list[target_id].emplace_back(edge_id, source_id); } static void add_edges_from( - impl_type& self, const types::id_type source_id, std::vector new_edges + impl_type& self, + const type_traits::c_sized_range_of auto& edge_ids, + const types::id_type source_id, + const type_traits::c_sized_range_of auto& target_ids ) { auto& adjacent_edges_source = self._list[source_id]; - adjacent_edges_source.reserve(adjacent_edges_source.size() + new_edges.size()); + adjacent_edges_source.reserve(adjacent_edges_source.size() + target_ids.size()); - for (auto& edge : new_edges) { - if (not edge->is_loop()) - self._list[edge->second()].push_back(edge); - adjacent_edges_source.emplace_back(std::move(edge)); + for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) { + adjacent_edges_source.emplace_back(edge_id, target_id); + if (source_id != target_id) + self._list[target_id].emplace_back(edge_id, source_id); } - - self._n_unique_edges += new_edges.size(); - } - - [[nodiscard]] inline static bool is_edge_incident_with( - const edge_ptr_type& edge, const types::id_type vertex_id, const types::id_type source_id - ) { - if (edge->first() == source_id) - return edge->second() == vertex_id; - else if (edge->second() == source_id) - return edge->first() == vertex_id; - return false; } static void remove_edge(impl_type& self, const edge_type& edge) { + auto& adj_edges_first = self._list[edge.source()]; + auto& adj_edges_second = self._list[edge.target()]; + if (edge.is_loop()) { - auto& adj_edges_first = self._list.at(edge.first()); - adj_edges_first.erase( - detail::strict_find(adj_edges_first, &edge) - ); + adj_edges_first.erase(detail::strict_find(adj_edges_first, edge)); } else { - auto& adj_edges_first = self._list.at(edge.first()); - auto& adj_edges_second = self._list.at(edge.second()); - - adj_edges_first.erase( - detail::strict_find(adj_edges_first, &edge) - ); - // if the edge was found in the first list, it will also be present in the second list - adj_edges_second.erase(std::ranges::find(adj_edges_second, &edge, address_projection{}) - ); + adj_edges_first.erase(detail::strict_find(adj_edges_first, edge)); + adj_edges_second.erase(detail::strict_find(adj_edges_second, edge)); } - --self._n_unique_edges; } }; diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index b8a38973..911a02b9 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -18,32 +18,27 @@ namespace specialized { namespace detail { -template AdjacencyMatrix> -[[nodiscard]] typename AdjacencyMatrix::edge_ptr_type& strict_get( - typename AdjacencyMatrix::matrix_type& matrix, const typename AdjacencyMatrix::edge_type* edge -) { +[[nodiscard]] types::id_type& strict_get(auto& id_matrix, const auto& edge) { // get the edge and validate the address - auto& matrix_element = matrix.at(edge->first()).at(edge->second()); - if (edge != matrix_element.get()) + const auto [source_id, target_id] = edge.incident_vertices(); + auto& edge_id = id_matrix[source_id][target_id]; + if (edge.id() != edge_id) throw std::invalid_argument(std::format( - "Got invalid edge [vertices = ({}, {}) | addr = {}]", - edge->first(), - edge->second(), - io::format(edge) + "Got invalid edge [id = {} | vertices = ({}, {})]", edge.id(), source_id, target_id )); - return matrix_element; + return edge_id; } -template AdjacencyMatrix> inline void check_edge_override( - const AdjacencyMatrix& adj_matrix, const typename AdjacencyMatrix::edge_ptr_type& edge + const auto& id_matrix, const types::id_type source_id, const types::id_type target_id ) { - const auto [first_id, second_id] = edge->incident_vertices(); - - if (adj_matrix.has_edge(first_id, second_id)) + if (const auto edge_id = id_matrix[source_id][target_id]; edge_id != constants::invalid_id) throw std::logic_error(std::format( - "Cannot override an existing edge without remove: ({}, {})", first_id, second_id + "Cannot override an existing edge: [id = {}, vertices = ({}, {})]", + edge_id, + source_id, + target_id )); } @@ -55,33 +50,33 @@ struct directed_adjacency_matrix { using impl_type = AdjacencyMatrix; using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; - using edge_ptr_type = typename impl_type::edge_ptr_type; [[nodiscard]] gl_attr_force_inline static types::size_type in_degree( const impl_type& self, const types::id_type vertex_id ) { - return std::ranges::count_if( - self._matrix, - [](const auto& edge) { return edge != nullptr; }, - [vertex_id](const auto& row) -> const edge_ptr_type& { return row[vertex_id]; } - ); + return std::ranges::count_if(self._matrix, [vertex_id](const auto& row) { + return row[vertex_id] != constants::invalid_id; + }); } [[nodiscard]] gl_attr_force_inline static types::size_type out_degree( const impl_type& self, const types::id_type vertex_id ) { - return std::ranges::count_if(self._matrix[vertex_id], [](const auto& edge) { - return edge != nullptr; - }); + return self._matrix[vertex_id].size() + - std::ranges::count(self._matrix[vertex_id], constants::invalid_id); } [[nodiscard]] gl_attr_force_inline static types::size_type degree( const impl_type& self, const types::id_type vertex_id ) { types::size_type deg = constants::zero; - for (types::id_type i = constants::initial_id; i < self._matrix.size(); ++i) - deg += static_cast(self._matrix[vertex_id][i] != nullptr) - + static_cast(self._matrix[i][vertex_id] != nullptr); + for (types::id_type v_id = constants::initial_id; v_id < self._matrix.size(); ++v_id) + deg += static_cast( + self._matrix[vertex_id][v_id] != constants::invalid_id + ) + + static_cast( + self._matrix[v_id][vertex_id] != constants::invalid_id + ); return deg; } @@ -90,31 +85,29 @@ struct directed_adjacency_matrix { std::vector in_degree_map(self._matrix.size(), constants::zero); for (const auto& row : self._matrix) - for (auto [id, edge_ptr] : std::views::enumerate(row)) - in_degree_map[id] += static_cast(edge_ptr != nullptr); + for (auto [target_id, edge_id] : std::views::enumerate(row)) + in_degree_map[target_id] += + static_cast(edge_id != constants::invalid_id); return in_degree_map; } [[nodiscard]] static std::vector out_degree_map(const impl_type& self) { - std::vector out_degree_map; - out_degree_map.reserve(self._matrix.size()); - - for (types::id_type id = constants::initial_id; id < self._matrix.size(); ++id) - out_degree_map.push_back(out_degree(self, id)); - - return out_degree_map; + return std::views::iota(constants::initial_id, self._matrix.size()) + | std::views::transform([&](types::id_type id) { return out_degree(self, id); }) + | std::ranges::to(); } [[nodiscard]] static std::vector degree_map(const impl_type& self) { std::vector degree_map(self._matrix.size(), constants::zero); - for (types::id_type u_id = constants::initial_id; u_id < self._matrix.size(); ++u_id) { - const auto& row = self._matrix[u_id]; - for (types::id_type v_id = constants::initial_id; v_id < self._matrix.size(); ++v_id) { - if (row[v_id] != nullptr) { - ++degree_map[u_id]; - ++degree_map[v_id]; + for (types::id_type source_id = constants::initial_id; source_id < self._matrix.size(); + ++source_id) { + for (types::id_type target_id = constants::initial_id; target_id < self._matrix.size(); + ++target_id) { + if (self._matrix[source_id][target_id] != constants::invalid_id) { + ++degree_map[source_id]; + ++degree_map[target_id]; } } } @@ -122,44 +115,52 @@ struct directed_adjacency_matrix { return degree_map; } - static void remove_vertex(impl_type& self, const types::id_type vertex_id) { - self._n_unique_edges -= self.adjacent_edges(vertex_id).distance(); + static std::vector remove_vertex( + impl_type& self, const types::id_type vertex_id + ) { + auto removed_edges_view = + self._matrix[vertex_id] + | std::views::filter([](auto edge_id) { return edge_id != constants::invalid_id; }); + + // TODO: use std::ranges::to (requires newer compiler) + std::vector removed_edges( + removed_edges_view.begin(), removed_edges_view.end() + ); + self._matrix.erase(std::next(std::begin(self._matrix), vertex_id)); for (auto& row : self._matrix) { - const auto vertex_it = std::next(std::begin(row), vertex_id); - self._n_unique_edges -= static_cast(*vertex_it != nullptr); - row.erase(vertex_it); + if (const auto edge_id = row[vertex_id]; edge_id != constants::invalid_id) + removed_edges.push_back(edge_id); + row.erase(std::next(std::begin(row), vertex_id)); } - } - - static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - detail::check_edge_override(self, edge); - auto& matrix_element = self._matrix[edge->first()][edge->second()]; - matrix_element = std::move(edge); - ++self._n_unique_edges; + return removed_edges; + } - return *matrix_element; + static inline void add_edge( + impl_type& self, types::id_type edge_id, types::id_type source_id, types::id_type target_id + ) { + detail::check_edge_override(self._matrix, source_id, target_id); + self._matrix[source_id][target_id] = edge_id; } static void add_edges_from( - impl_type& self, const types::id_type source_id, std::vector new_edges + impl_type& self, + const type_traits::c_sized_range_of auto& edge_ids, + const types::id_type source_id, + const type_traits::c_sized_range_of auto& target_ids ) { - // validate new edges - for (auto& edge : new_edges) - detail::check_edge_override(self, edge); + for (const auto target_id : target_ids) + detail::check_edge_override(self._matrix, source_id, target_id); - auto& matrix_row_source = self._matrix[source_id]; - for (auto& edge : new_edges) - matrix_row_source[edge->second()] = std::move(edge); - - self._n_unique_edges += new_edges.size(); + auto& matrix_source_row = self._matrix[source_id]; + for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) + matrix_source_row[target_id] = edge_id; } static inline void remove_edge(impl_type& self, const edge_type& edge) { - detail::strict_get(self._matrix, &edge) = nullptr; - --self._n_unique_edges; + detail::strict_get(self._matrix, edge) = constants::invalid_id; } }; @@ -169,7 +170,6 @@ struct undirected_adjacency_matrix { using impl_type = AdjacencyMatrix; using vertex_type = typename impl_type::vertex_type; using edge_type = typename impl_type::edge_type; - using edge_ptr_type = typename impl_type::edge_ptr_type; [[nodiscard]] gl_attr_force_inline static types::size_type in_degree( const impl_type& self, const types::id_type vertex_id @@ -186,10 +186,11 @@ struct undirected_adjacency_matrix { [[nodiscard]] gl_attr_force_inline static types::size_type degree( const impl_type& self, const types::id_type vertex_id ) { - return std::ranges::count_if( - self._matrix[vertex_id], [](const auto& edge) { return edge != nullptr; } - ) - + static_cast(self._matrix[vertex_id][vertex_id] != nullptr); + return self._matrix.size() + - std::ranges::count(self._matrix[vertex_id], constants::invalid_id) + + static_cast( + self._matrix[vertex_id][vertex_id] != constants::invalid_id + ); } [[nodiscard]] gl_attr_force_inline static std::vector in_degree_map( @@ -207,12 +208,13 @@ struct undirected_adjacency_matrix { [[nodiscard]] static std::vector degree_map(const impl_type& self) { std::vector degree_map(self._matrix.size(), constants::zero); - for (types::id_type u_id = constants::initial_id; u_id < self._matrix.size(); ++u_id) { - const auto& row = self._matrix[u_id]; - for (types::id_type v_id = constants::initial_id; v_id <= u_id; ++v_id) { - if (row[v_id] != nullptr) { - ++degree_map[u_id]; - ++degree_map[v_id]; + for (types::id_type source_id = constants::initial_id; source_id < self._matrix.size(); + ++source_id) { + for (types::id_type target_id = constants::initial_id; target_id <= source_id; + ++target_id) { + if (self._matrix[source_id][target_id] != constants::invalid_id) { + ++degree_map[source_id]; + ++degree_map[target_id]; } } } @@ -220,57 +222,61 @@ struct undirected_adjacency_matrix { return degree_map; } - static void remove_vertex(impl_type& self, const types::id_type vertex_id) { - self._n_unique_edges -= self.adjacent_edges(vertex_id).distance(); - self._matrix.erase(std::next(std::begin(self._matrix), vertex_id)); + static std::vector remove_vertex( + impl_type& self, const types::id_type vertex_id + ) { + auto removed_edges_view = + self._matrix[vertex_id] + | std::views::filter([](auto edge_id) { return edge_id != constants::invalid_id; }); + // TODO: use std::ranges::to (requires newer compiler) + std::vector removed_edges( + removed_edges_view.begin(), removed_edges_view.end() + ); + self._matrix.erase(std::next(std::begin(self._matrix), vertex_id)); for (auto& row : self._matrix) row.erase(std::next(std::begin(row), vertex_id)); - } - - static const edge_type& add_edge(impl_type& self, edge_ptr_type edge) { - detail::check_edge_override(self, edge); - const auto first_id = edge->first(); - const auto second_id = edge->second(); + return removed_edges; + } - if (not edge->is_loop()) - self._matrix[second_id][first_id] = edge; - auto& matrix_element = self._matrix.at(first_id).at(second_id); - matrix_element = edge; + static void add_edge( + impl_type& self, types::id_type edge_id, types::id_type source_id, types::id_type target_id + ) { + detail::check_edge_override(self._matrix, source_id, target_id); - ++self._n_unique_edges; - return *matrix_element; + self._matrix[source_id][target_id] = edge_id; + if (target_id != source_id) + self._matrix[target_id][source_id] = edge_id; } static void add_edges_from( - impl_type& self, const types::id_type source_id, std::vector new_edges + impl_type& self, + const type_traits::c_sized_range_of auto& edge_ids, + const types::id_type source_id, + const type_traits::c_sized_range_of auto& target_ids ) { - // validate new edges - for (auto& edge : new_edges) - detail::check_edge_override(self, edge); - - auto& matrix_row_source = self._matrix[source_id]; - for (auto& edge : new_edges) { - if (not edge->is_loop()) - self._matrix[edge->second()][source_id] = edge; - matrix_row_source[edge->second()] = std::move(edge); + for (const auto target_id : target_ids) + detail::check_edge_override(self._matrix, source_id, target_id); + + auto& matrix_source_row = self._matrix[source_id]; + for (auto [edge_id, target_id] : std::views::zip(edge_ids, target_ids)) { + matrix_source_row[target_id] = edge_id; + if (target_id != source_id) + self._matrix[target_id][source_id] = edge_id; } - - self._n_unique_edges += new_edges.size(); } static void remove_edge(impl_type& self, const edge_type& edge) { if (edge.is_loop()) { - detail::strict_get(self._matrix, &edge) = nullptr; + detail::strict_get(self._matrix, edge) = constants::invalid_id; } else { - detail::strict_get(self._matrix, &edge) = nullptr; + detail::strict_get(self._matrix, edge) = constants::invalid_id; // if the edge was found in the first matrix cell, // it will also be present in the second matrix cell - self._matrix[edge.second()][edge.first()] = nullptr; + self._matrix[edge.target()][edge.source()] = constants::invalid_id; } - --self._n_unique_edges; } }; diff --git a/include/gl/types/dereferencing_iterator.hpp b/include/gl/types/dereferencing_iterator.hpp deleted file mode 100644 index e540e083..00000000 --- a/include/gl/types/dereferencing_iterator.hpp +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "gl/attributes/force_inline.hpp" -#include "type_traits.hpp" - -#include - -namespace gl { - -namespace types { - -/* -A forward iterator wrapper for pointer value type iterators -The dereference operator dereferences the underlying operator - and then the held pointer object -*/ - -template -requires(type_traits::c_strong_ptr) -class dereferencing_iterator { -private: - using iterator_value_type = typename std::iterator_traits::value_type; - -public: - using iterator_type = Iterator; - using value_type = type_traits::ptr_element_type_t; - using reference = value_type&; - using pointer = value_type*; - using difference_type = typename std::iterator_traits::difference_type; - using iterator_category = typename std::iterator_traits::iterator_category; - - dereferencing_iterator() = default; - - dereferencing_iterator(iterator_type it) : _it(it) {} - - dereferencing_iterator(const dereferencing_iterator&) = default; - dereferencing_iterator(dereferencing_iterator&&) = default; - - dereferencing_iterator& operator=(const dereferencing_iterator&) = default; - dereferencing_iterator& operator=(dereferencing_iterator&&) = default; - - [[nodiscard]] gl_attr_force_inline reference operator*() const { - return **this->_it; - } - - [[nodiscard]] gl_attr_force_inline pointer operator->() const { - if constexpr (type_traits::c_strong_smart_ptr) - return this->_it->get(); - else - return *this->_it; - } - - inline dereferencing_iterator& operator++() { - ++this->_it; - return *this; - } - - inline dereferencing_iterator operator++(int) { - dereferencing_iterator tmp = *this; - ++this->_it; - return tmp; - } - - inline dereferencing_iterator& operator--() - requires(std::bidirectional_iterator) - { - --this->_it; - return *this; - } - - inline dereferencing_iterator operator--(int) - requires(std::bidirectional_iterator) - { - dereferencing_iterator tmp = *this; - --this->_it; - return tmp; - } - - [[nodiscard]] gl_attr_force_inline bool operator==(const dereferencing_iterator& other) const { - return this->_it == other._it; - } - - [[nodiscard]] gl_attr_force_inline bool operator!=(const dereferencing_iterator& other) const { - return this->_it != other._it; - } - - [[nodiscard]] gl_attr_force_inline iterator_type base() const { - return this->_it; - } - -private: - iterator_type _it; -}; - -} // namespace types - -template -[[nodiscard]] gl_attr_force_inline auto deref_begin(Range& range) { - return types::dereferencing_iterator(std::ranges::begin(range)); -} - -template -[[nodiscard]] gl_attr_force_inline auto deref_end(Range& range) { - return types::dereferencing_iterator(std::ranges::end(range)); -} - -template -[[nodiscard]] gl_attr_force_inline auto deref_cbegin(Range& range) { - return types::dereferencing_iterator(std::ranges::cbegin(range)); -} - -template -[[nodiscard]] gl_attr_force_inline auto deref_cend(Range& range) { - return types::dereferencing_iterator(std::ranges::cend(range)); -} - -} // namespace gl diff --git a/include/gl/types/iterator_range.hpp b/include/gl/types/iterator_range.hpp deleted file mode 100644 index a2fc1110..00000000 --- a/include/gl/types/iterator_range.hpp +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "gl/attributes/force_inline.hpp" -#include "type_traits.hpp" -#include "types.hpp" - -#include -#include - -#if defined(GL_CONFIG_IT_RANGE_DEFAULT_CACHE_MODE_EAGER) -#define _GL_IT_RANGE_DEFAULT_CACHE_MODE gl::type_traits::eager_cache -#elif defined(GL_CONFIG_IT_RANGE_DEFAULT_CACHE_MODE_LAZY) -#define _GL_IT_RANGE_DEFAULT_CACHE_MODE gl::type_traits::lazy_cache -#elif defined(GL_CONFIG_IT_RANGE_DEFAULT_CACHE_MODE_NONE) -#define _GL_IT_RANGE_DEFAULT_CACHE_MODE gl::type_traits::no_cache -#else -#define _GL_IT_RANGE_DEFAULT_CACHE_MODE gl::type_traits::lazy_cache -#endif - -#ifdef GL_CONFIG_IT_RANGE_NOT_FINAL -#define _GL_IT_RANGE_NOT_FINAL -#else -#undef _GL_IT_RANGE_NOT_FINAL -#endif - -namespace gl { - -namespace types { - -/* -A begin, end iterator holder class used as a view to the underlying collection. -Designed to be compatible with the range-based loops and std algorithms. -*/ - -template < - std::forward_iterator Iterator, - type_traits::c_cache_mode CacheMode = _GL_IT_RANGE_DEFAULT_CACHE_MODE> -class iterator_range -#ifndef _GL_IT_RANGE_NOT_FINAL - final -#endif -{ -public: - using iterator = Iterator; - using const_iterator = std::const_iterator; - using distance_type = std::ptrdiff_t; - using value_type = std::remove_reference_t; - - using cache_mode = CacheMode; - - iterator_range() = delete; - - explicit iterator_range(iterator begin, iterator end) - requires(cache_mode::value == type_traits::cache_mode_value::eager) - { - this->_distance = std::ranges::distance(begin, end); - this->_range = std::make_pair(begin, end); - } - - explicit iterator_range(iterator begin, iterator end) - requires(cache_mode::value == type_traits::cache_mode_value::lazy) - { - this->_range = std::make_pair(begin, end); - this->_distance = _invalid_distance; - } - - explicit iterator_range(iterator begin, iterator end) - requires(cache_mode::value == type_traits::cache_mode_value::none) - { - this->_range = std::make_pair(begin, end); - } - - iterator_range(const iterator_range&) = default; - iterator_range(iterator_range&&) = default; - - iterator_range& operator=(const iterator_range&) = default; - iterator_range& operator=(iterator_range&&) = default; - -#ifndef _GL_IT_RANGE_NOT_FINAL - ~iterator_range() = default; -#else - virtual ~iterator_range() = default; -#endif - - bool operator==(const iterator_range&) const = default; - - [[nodiscard]] gl_attr_force_inline iterator begin() const { - return this->_range.first; - } - - [[nodiscard]] gl_attr_force_inline iterator end() const { - return this->_range.second; - } - - [[nodiscard]] gl_attr_force_inline auto cbegin() const { - return std::make_const_iterator(this->_range.first); - } - - [[nodiscard]] gl_attr_force_inline auto cend() const { - return std::make_const_iterator(this->_range.second); - } - - [[nodiscard]] gl_attr_force_inline distance_type distance() const - requires(cache_mode::value == type_traits::cache_mode_value::eager) - { - return this->_distance; - } - - [[nodiscard]] inline distance_type distance() const - requires(cache_mode::value == type_traits::cache_mode_value::lazy) - { - if (this->_is_distance_uninitialized()) - this->_distance = std::ranges::distance(this->begin(), this->end()); - return this->_distance; - } - - [[nodiscard]] gl_attr_force_inline distance_type distance() const - requires(cache_mode::value == type_traits::cache_mode_value::none) - { - return std::ranges::distance(this->begin(), this->end()); - } - - [[nodiscard]] inline value_type& element_at(types::size_type position) - requires(not type_traits::c_const_iterator) - { - this->_validate_element_position(position); - return *std::ranges::next(this->begin(), position); - } - - [[nodiscard]] inline const value_type& element_at(types::size_type position) const { - this->_validate_element_position(position); - return *std::ranges::next(this->begin(), position); - } - - [[nodiscard]] gl_attr_force_inline value_type& operator[](types::size_type position) - requires(not type_traits::c_const_iterator) - { - return this->element_at(position); - } - - // clang-format off - // gl_attr_force_inline misplacement - - [[nodiscard]] gl_attr_force_inline const value_type& operator[](types::size_type position) const { - return this->element_at(position); - } - - // clang-format on - -private: - [[nodiscard]] gl_attr_force_inline bool _is_distance_uninitialized() const - requires(cache_mode::value == type_traits::cache_mode_value::lazy) - { - return this->_distance == _invalid_distance; - } - - void _validate_element_position(const types::size_type position) const { - const auto current_distance = this->distance(); - if (position >= current_distance) - throw std::out_of_range( - std::format("Position index {} out of range [0, {}]", position, current_distance) - ); - } - - homogeneous_pair _range; - - [[no_unique_address]] mutable std::conditional_t< - cache_mode::value == type_traits::cache_mode_value::none, - std::monostate, - distance_type> _distance; - - static constexpr distance_type _invalid_distance = -1; - static constexpr distance_type _default_n = 1; -}; - -} // namespace types - -template < - std::forward_iterator Iterator, - type_traits::c_cache_mode CacheMode = _GL_IT_RANGE_DEFAULT_CACHE_MODE> -[[nodiscard]] gl_attr_force_inline types::iterator_range make_iterator_range( - Iterator begin, Iterator end -) { - return types::iterator_range{begin, end}; -} - -template < - type_traits::c_range Range, - type_traits::c_cache_mode CacheMode = _GL_IT_RANGE_DEFAULT_CACHE_MODE> -[[nodiscard]] gl_attr_force_inline types::iterator_range -make_iterator_range(Range& range) { - return types::iterator_range{ - std::ranges::begin(range), std::ranges::end(range) - }; -} - -template < - type_traits::c_range Range, - type_traits::c_cache_mode CacheMode = _GL_IT_RANGE_DEFAULT_CACHE_MODE> -[[nodiscard]] gl_attr_force_inline types::iterator_range -make_const_iterator_range(const Range& range) { - return types::iterator_range{ - std::ranges::cbegin(range), std::ranges::cend(range) - }; -} - -} // namespace gl diff --git a/include/gl/types/non_null_iterator.hpp b/include/gl/types/non_null_iterator.hpp deleted file mode 100644 index 35d14c5c..00000000 --- a/include/gl/types/non_null_iterator.hpp +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "gl/attributes/force_inline.hpp" -#include "type_traits.hpp" - -#include - -namespace gl { - -namespace types { - -/* -A forward iterator wrapper for pointer value type iterators -The increment/decrement operators move the underlying iterator - to the next/previous non-null element within the specified bounds -*/ - -template -requires(type_traits::c_strong_ptr) -class non_null_iterator { -public: - using iterator_type = Iterator; - using value_type = typename std::iterator_traits::value_type; - using reference = typename std::iterator_traits::reference; - using pointer = typename std::iterator_traits::pointer; - using difference_type = typename std::iterator_traits::difference_type; - using iterator_category = typename std::iterator_traits::iterator_category; - - non_null_iterator() = default; - - non_null_iterator(iterator_type current, iterator_type end) - requires(not std::bidirectional_iterator) - : _begin(), _current(current), _end(end) { - if (this->_current != this->_end and not *this->_current) - this->_skip_null_elements_forward(); - } - - non_null_iterator(iterator_type begin, iterator_type current, iterator_type end) - requires(std::bidirectional_iterator) - : _begin(begin), _current(current), _end(end) { - if (this->_current != this->_end and not *this->_current) - this->_skip_null_elements_forward(); - } - - non_null_iterator(const non_null_iterator&) = default; - non_null_iterator(non_null_iterator&&) = default; - - non_null_iterator& operator=(const non_null_iterator&) = default; - non_null_iterator& operator=(non_null_iterator&&) = default; - - [[nodiscard]] gl_attr_force_inline reference operator*() const { - return *this->_current; - } - - [[nodiscard]] gl_attr_force_inline pointer operator->() const { - return &(*this->_current); - } - - inline non_null_iterator& operator++() { - this->_skip_null_elements_forward(); - return *this; - } - - inline non_null_iterator operator++(int) { - non_null_iterator tmp = *this; - this->_skip_null_elements_forward(); - return tmp; - } - - inline non_null_iterator& operator--() - requires(std::bidirectional_iterator) - { - this->_skip_null_elements_backward(); - return *this; - } - - inline non_null_iterator operator--(int) - requires(std::bidirectional_iterator) - { - non_null_iterator tmp = *this; - this->_skip_null_elements_backward(); - return tmp; - } - - [[nodiscard]] gl_attr_force_inline bool operator==(const non_null_iterator& other) const { - return this->_current == other._current; - } - - [[nodiscard]] gl_attr_force_inline bool operator!=(const non_null_iterator& other) const { - return this->_current != other._current; - } - - [[nodiscard]] gl_attr_force_inline iterator_type base() const { - return this->_current; - } - -private: - static constexpr bool _is_null(const reference& ptr) { - return ptr == nullptr; - } - - inline void _skip_null_elements_forward() { - if (this->_current == this->_end) - return; - - this->_current = std::find_if_not(++this->_current, this->_end, _is_null); - } - - void _skip_null_elements_backward() - requires(std::bidirectional_iterator) - { - if (this->_current == this->_begin) - return; - - this->_current = - std::find_if_not( - ++std::make_reverse_iterator(this->_current), - std::make_reverse_iterator(this->_begin), - _is_null - ) - .base(); - - /* - reverse_iterator.base() points to an element one past the position - the iterator points to in a forward sequence so to make sure the new - current iterator is valid it needs to be adjusted by one backward - */ - if (this->_current != this->_begin) - --this->_current; - } - - [[no_unique_address]] std:: - conditional_t, iterator_type, std::monostate> - _begin; - - iterator_type _current; - iterator_type _end; -}; - -} // namespace types - -template -[[nodiscard]] auto non_null_begin(Range& range) -requires(not std::bidirectional_iterator) -{ - return types::non_null_iterator(std::ranges::begin(range), std::ranges::end(range)); -} - -template -[[nodiscard]] auto non_null_end(Range& range) -requires(not std::bidirectional_iterator) -{ - return types::non_null_iterator(std::ranges::end(range), std::ranges::end(range)); -} - -template -[[nodiscard]] auto non_null_cbegin(Range& range) -requires(not std::bidirectional_iterator) -{ - return types::non_null_iterator(std::ranges::cbegin(range), std::ranges::cend(range)); -} - -template -[[nodiscard]] auto non_null_cend(Range& range) -requires(not std::bidirectional_iterator) -{ - return types::non_null_iterator(std::ranges::cend(range), std::ranges::cend(range)); -} - -template -[[nodiscard]] auto non_null_begin(Range& range) -requires(std::bidirectional_iterator) -{ - return types::non_null_iterator( - std::ranges::begin(range), std::ranges::begin(range), std::ranges::end(range) - ); -} - -template -[[nodiscard]] auto non_null_end(Range& range) -requires(std::bidirectional_iterator) -{ - return types::non_null_iterator( - std::ranges::begin(range), std::ranges::end(range), std::ranges::end(range) - ); -} - -template -[[nodiscard]] auto non_null_cbegin(Range& range) -requires(std::bidirectional_iterator) -{ - return types::non_null_iterator( - std::ranges::cbegin(range), std::ranges::cbegin(range), std::ranges::cend(range) - ); -} - -template -[[nodiscard]] auto non_null_cend(Range& range) -requires(std::bidirectional_iterator) -{ - return types::non_null_iterator( - std::ranges::cbegin(range), std::ranges::cend(range), std::ranges::cend(range) - ); -} - -} // namespace gl diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index c60d1653..4fd0b0bf 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -249,9 +249,20 @@ using bin_color_value = typename types::binary_color::value; namespace type_traits { -template -constexpr inline bool is_default_properties_type_v = - std::is_same_v; +template +concept c_empty_properties = c_properties and std::same_as; + +template +concept c_non_empty_properties = c_properties and not c_empty_properties; + +template +concept c_has_empty_properties = + requires { typename T::properties_type; } and c_empty_properties; + +template +concept c_has_non_empty_properties = requires { + typename T::properties_type; +} and not c_empty_properties; template concept c_binary_color_properties_type = c_properties and requires(Properties p) { diff --git a/include/gl/types/types.hpp b/include/gl/types/types.hpp index d03b2b41..444930ae 100644 --- a/include/gl/types/types.hpp +++ b/include/gl/types/types.hpp @@ -16,13 +16,4 @@ using id_type = size_type; template using homogeneous_pair = std::pair; -template -using optional_ref = std::optional>>; - -template -using optional_cref = std::optional>>; - -template -using const_ref_wrap = std::reference_wrapper; - } // namespace gl::types diff --git a/include/gl/util/ranges.hpp b/include/gl/util/ranges.hpp new file mode 100644 index 00000000..fa915e0a --- /dev/null +++ b/include/gl/util/ranges.hpp @@ -0,0 +1,23 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include + +namespace gl::util { + +inline constexpr auto deref_view = + std::views::transform([](auto&& p) -> decltype(auto) { return *p; }); + +template +constexpr auto range_size(R&& r) { + if constexpr (std::ranges::sized_range) + return std::ranges::size(r); + else + // Will consume input ranges! + return std::ranges::distance(std::begin(r), std::end(r)); +} + +} // namespace gl::util diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 569b2bd6..5d544cd2 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -21,25 +21,35 @@ class vertex_descriptor final { using type = std::type_identity_t>; using properties_type = Properties; using properties_ref_type = std::conditional_t< - type_traits::is_default_properties_type_v, + type_traits::c_empty_properties, types::empty_properties, properties_type&>; - template GraphTraits> - friend class graph; - - vertex_descriptor() = delete; + vertex_descriptor() { + *this = vertex_descriptor::invalid(); + } - // TODO: private explicit vertex_descriptor(const types::id_type id) - requires(type_traits::is_default_properties_type_v) + requires(type_traits::c_empty_properties) : _id(id) {} - // TODO: private explicit vertex_descriptor(const types::id_type id, properties_type& properties) - requires(not type_traits::is_default_properties_type_v) + requires(type_traits::c_non_empty_properties) : _id(id), _properties(properties) {} + [[nodiscard]] gl_attr_force_inline static vertex_descriptor invalid() noexcept + requires(type_traits::c_empty_properties) + { + return vertex_descriptor(constants::invalid_id); + } + + [[nodiscard]] gl_attr_force_inline static vertex_descriptor invalid() noexcept + requires(type_traits::c_non_empty_properties) + { + static properties_type invalid_properties{}; + return vertex_descriptor(constants::invalid_id, invalid_properties); + } + vertex_descriptor(const vertex_descriptor&) = default; vertex_descriptor& operator=(const vertex_descriptor&) = default; @@ -56,12 +66,23 @@ class vertex_descriptor final { return this->_id <=> other._id; } - [[nodiscard]] gl_attr_force_inline types::id_type id() const { + [[nodiscard]] gl_attr_force_inline operator bool() const noexcept { + return this->is_valid(); + } + + [[nodiscard]] bool is_valid() const noexcept { + return this->_id != constants::invalid_id; + } + + [[nodiscard]] gl_attr_force_inline types::id_type id() const noexcept { return this->_id; } [[nodiscard]] gl_attr_force_inline properties_ref_type properties() const { - return this->_properties; + if (not this->is_valid()) + throw std::logic_error("Cannot access properties of an invalid vertex"); + + return this->_properties.get(); } friend inline std::ostream& operator<<(std::ostream& os, const vertex_descriptor& vertex) { @@ -82,10 +103,10 @@ class vertex_descriptor final { } if (io::is_option_set(os, io::graph_option::verbose)) { - os << "[id: " << this->_id << " | properties: " << this->_properties << "]"; + os << "[id: " << this->_id << " | properties: " << this->_properties.get() << "]"; } else { - os << "[" << this->_id << " | " << this->_properties << "]"; + os << "[" << this->_id << " | " << this->_properties.get() << "]"; } } } @@ -99,18 +120,14 @@ class vertex_descriptor final { } } - mutable types::id_type _id; - [[no_unique_address]] properties_ref_type _properties; + types::id_type _id; + [[no_unique_address]] std::conditional_t< + type_traits::c_empty_properties, + types::empty_properties, + std::reference_wrapper> _properties; }; template using vertex = vertex_descriptor; -namespace types { - -template VertexType> -using vertex_ptr_type = std::unique_ptr; - -} // namespace types - } // namespace gl diff --git a/include/gl/views.hpp b/include/gl/views.hpp deleted file mode 100644 index d8a6e977..00000000 --- a/include/gl/views.hpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -namespace gl::views { - -inline constexpr auto deref = std::views::transform([](auto&& p) -> decltype(auto) { return *p; }); - -} // namespace gl::views diff --git a/tests/include/testing/gl/constants.hpp b/tests/include/testing/gl/constants.hpp index bab05d53..5722d1a0 100644 --- a/tests/include/testing/gl/constants.hpp +++ b/tests/include/testing/gl/constants.hpp @@ -2,6 +2,7 @@ #include "types.hpp" +#include #include #include @@ -34,7 +35,7 @@ IC gl::types::size_type out_of_range_element_idx = n_elements; IC gl::types::id_type vertex_id_1 = first_element_idx; IC gl::types::id_type vertex_id_2 = vertex_id_1 + one_element; IC gl::types::id_type vertex_id_3 = vertex_id_2 + one_element; -IC gl::types::id_type invalid_id = std::numeric_limits::max(); +IC gl::types::id_type invalid_id = gl::constants::invalid_id; IC auto vertex_id_view = std::views::iota(first_element_idx, n_elements); diff --git a/tests/include/testing/gl/io_common.hpp b/tests/include/testing/gl/io_common.hpp index c10ffc5b..cb65b006 100644 --- a/tests/include/testing/gl/io_common.hpp +++ b/tests/include/testing/gl/io_common.hpp @@ -14,7 +14,7 @@ void verify_graph_structure(const GraphType& actual, const GraphType& expected) // verify that the edges of the in graph are equivalent to the edges of the out graph CHECK(std::ranges::all_of(actual.vertices(), [&](const auto& v_actual) { return std::ranges::all_of(actual.adjacent_edges(v_actual), [&](const auto& edge) { - return expected.has_edge(edge.first(), edge.second()); + return expected.has_edge(edge.source(), edge.target()); }); })); } @@ -36,9 +36,8 @@ template void verify_edge_properties(const GraphType& actual, const GraphType& expected) { CHECK(std::ranges::all_of(actual.vertices(), [&](const auto& v_actual) { return std::ranges::all_of(actual.adjacent_edges(v_actual), [&](const auto& edge) { - // get edge returns optional return edge.properties() - == expected.get_edge(edge.first(), edge.second()).value().get().properties(); + == expected.get_edge(edge.source(), edge.target())->properties(); }); })); } diff --git a/tests/include/testing/gl/transforms.hpp b/tests/include/testing/gl/transforms.hpp deleted file mode 100644 index ca694135..00000000 --- a/tests/include/testing/gl/transforms.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include - -#include - -namespace gl_testing::transforms { - -template < - gl::type_traits::c_instantiation_of VertexType = gl::vertex_descriptor<>> -inline gl::types::id_type extract_vertex_id(const VertexType& vertex) { - return vertex.id(); -} - -template -struct address_projection { - auto operator()(const T& ref) const { - return &ref; - } - - auto operator()(const std::unique_ptr& ptr) const { - return ptr.get(); - } - - auto operator()(const std::shared_ptr& ptr) const { - return ptr.get(); - } -}; - -} // namespace gl_testing::transforms diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index fd040f44..d2836758 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -1,6 +1,5 @@ #include "testing/gl/constants.hpp" #include "testing/gl/functional.hpp" -#include "testing/gl/transforms.hpp" #include #include @@ -14,24 +13,35 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_adjacency_list"); +struct test_adjacency_list { + const auto& get(const auto& sut) const { + return sut._list; + } + + gl::types::size_type size(const auto& sut) const { + return sut._list.size(); + } + + gl::types::size_type next_edge_id = 0uz; +}; + TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent tests", SutType, edge_directional_tag_sut_template ) { + test_adjacency_list fixture; + SUBCASE("should be initialized with no vertices and no edges by default") { SutType sut{}; - CHECK_EQ(sut.n_vertices(), constants::zero_elements); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); + CHECK_EQ(fixture.size(sut), constants::zero_elements); } SUBCASE("constructed with the n_vertices parameter should properly initialize the adjacency " "list") { SutType sut{constants::n_elements}; - REQUIRE_EQ(sut.n_vertices(), constants::n_elements); - REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); - - std::ranges::for_each(constants::vertex_id_view, [&sut](const gl::types::id_type vertex_id) { - CHECK_EQ(sut.adjacent_edges(vertex_id).distance(), constants::zero_elements); - }); + REQUIRE_EQ(fixture.size(sut), constants::n_elements); + CHECK(std::ranges::all_of(fixture.get(sut), [](const auto& adjacent_items) { + return adjacent_items.empty(); + })); } SUBCASE("add_vertex should properly extend the current adjacency list") { @@ -42,19 +52,23 @@ TEST_CASE_TEMPLATE_DEFINE( n_vertices <= target_n_vertices; n_vertices++) { sut.add_vertex(); - CHECK_EQ(sut.n_vertices(), n_vertices); + CHECK_EQ(fixture.size(sut), n_vertices); } - CHECK_EQ(sut.n_vertices(), target_n_vertices); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); + CHECK_EQ(fixture.size(sut), target_n_vertices); + CHECK(std::ranges::all_of(fixture.get(sut), [](const auto& adjacent_items) { + return adjacent_items.empty(); + })); } SUBCASE("add_vertices(n) should properly extend the current adjacency list") { SutType sut{}; sut.add_vertices(constants::n_elements); - CHECK_EQ(sut.n_vertices(), constants::n_elements); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); + CHECK_EQ(fixture.size(sut), constants::n_elements); + CHECK(std::ranges::all_of(fixture.get(sut), [](const auto& adjacent_items) { + return adjacent_items.empty(); + })); } } @@ -71,59 +85,67 @@ constexpr gl::types::size_type n_incident_edges_for_fully_connected_vertex = } // namespace -struct test_directed_adjacency_list { +struct test_directed_adjacency_list : public test_adjacency_list { using edge_type = gl::directed_edge<>; - using edge_ptr_type = gl::directed_t::edge_ptr_type; using sut_type = gl::impl::adjacency_list>; test_directed_adjacency_list() {} - const edge_type& add_edge( - const gl::types::id_type first_id, const gl::types::id_type second_id - ) { - return sut.add_edge(gl::detail::make_edge(first_id, second_id)); + edge_type add_edge(const gl::types::id_type source_id, const gl::types::id_type target_id) { + const auto new_edge_id = this->next_edge_id++; + sut.add_edge(new_edge_id, source_id, target_id); + return edge_type{new_edge_id, source_id, target_id}; } - void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { - for (const auto second_id : constants::vertex_id_view) { - if (second_id == first_id and no_loops) - continue; + void remove_edge(const edge_type& edge) { + sut.remove_edge(edge); + } - add_edge(first_id, second_id); + void fully_connect_vertex(const gl::types::id_type source_id, const bool no_loops = true) { + for (const auto target_id : constants::vertex_id_view) { + if (target_id == source_id and no_loops) + continue; + add_edge(source_id, target_id); } } void init_complete_graph(const bool no_loops = true) { - for (const auto first_id : constants::vertex_id_view) - fully_connect_vertex(first_id, no_loops); + for (const auto source_id : constants::vertex_id_view) + fully_connect_vertex(source_id, no_loops); if (no_loops) - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adjacent_items) { + return adjacent_items.size() == n_incident_edges_for_fully_connected_vertex; + })); else - REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements * constants::n_elements); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adjacent_items) { + return adjacent_items.size() + == n_incident_edges_for_fully_connected_vertex + constants::one; + })); } sut_type sut{constants::n_elements}; - - const gl::types::size_type n_unique_edges_in_full_graph = - n_incident_edges_for_fully_connected_vertex * constants::n_elements; }; TEST_CASE_FIXTURE( test_directed_adjacency_list, "add_edge should add the edge only to the source vertex list" ) { - const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); - REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - + const auto new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); - CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); - CHECK_EQ(sut.adjacent_edges(constants::vertex_id_2).distance(), constants::zero_elements); + CHECK_EQ(adjacent_edges_1.size(), constants::one_element); + CHECK_EQ(sut.adjacent_edges(constants::vertex_id_2).size(), constants::zero_elements); const auto& new_edge_extracted = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted, &new_edge); + CHECK_EQ(new_edge_extracted, new_edge); +} + +TEST_CASE_FIXTURE(test_directed_adjacency_list, "at should return the adjacent edges of a vertex") { + init_complete_graph(); + for (const auto vertex_id : std::views::iota(0uz, constants::n_elements)) + CHECK(std::ranges::equal(sut.at(vertex_id), sut.adjacent_edges(vertex_id))); } TEST_CASE_FIXTURE( @@ -143,14 +165,18 @@ TEST_CASE_FIXTURE( test_directed_adjacency_list, "has_edge(edge) should return true if the given edge is present in the graph" ) { - const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; + const edge_type invalid_edge{ + constants::invalid_id, constants::vertex_id_1, constants::vertex_id_2 + }; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; + const edge_type not_present_edge{ + valid_edge.id(), constants::vertex_id_2, constants::vertex_id_3 + }; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -170,8 +196,8 @@ TEST_CASE_FIXTURE( const auto edge_opt = sut.get_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(edge_opt.has_value()); - CHECK_EQ(&edge_opt->get(), &edge_1); - CHECK_NE(&edge_opt->get(), &edge_2); + CHECK_EQ(*edge_opt, edge_1); + CHECK_NE(*edge_opt, edge_2); CHECK_FALSE(sut.get_edge(constants::vertex_id_2, constants::vertex_id_2)); } @@ -187,19 +213,14 @@ TEST_CASE_FIXTURE( test_directed_adjacency_list, "get_edges(id, id) should return a valid edge view if the given vertices are connected" ) { - std::vector> expected_edges; + std::vector expected_edges; for (auto _ = constants::first_element_idx; _ < constants::n_elements; _++) - expected_edges.push_back(std::cref(add_edge(constants::vertex_id_1, constants::vertex_id_2)) - ); - - constexpr auto address_projection = [](const auto& edge_ref) { return &edge_ref.get(); }; + expected_edges.push_back(add_edge(constants::vertex_id_1, constants::vertex_id_2)); CHECK(std::ranges::equal( sut.get_edges(constants::vertex_id_1, constants::vertex_id_2), expected_edges, - std::ranges::equal_to{}, - address_projection, - address_projection + std::ranges::equal_to{} )); CHECK(sut.get_edges(constants::vertex_id_2, constants::vertex_id_2).empty()); @@ -207,10 +228,10 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE(test_directed_adjacency_list, "remove_edge should throw when an edge is invalid") { // not existing edge between valid vertices - CHECK_THROWS_AS( - sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), - std::invalid_argument - ); + const edge_type not_existing_edge{ + constants::invalid_id, constants::vertex_id_1, constants::vertex_id_2 + }; + CHECK_THROWS_AS(remove_edge(not_existing_edge), std::invalid_argument); } TEST_CASE_FIXTURE( @@ -219,28 +240,17 @@ TEST_CASE_FIXTURE( fully_connect_vertex(constants::vertex_id_1); auto adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); - REQUIRE_EQ(sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex); - REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_for_fully_connected_vertex); + REQUIRE_EQ(adjacent_edges.size(), n_incident_edges_for_fully_connected_vertex); const auto& edge_to_remove = adjacent_edges[constants::first_element_idx]; + remove_edge(edge_to_remove); - sut.remove_edge(edge_to_remove); + adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); REQUIRE_EQ( - sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex - constants::one_element - ); - - adjacent_edges = sut.adjacent_edges(constants::first_element_idx); - REQUIRE_EQ( - adjacent_edges.distance(), - n_incident_edges_for_fully_connected_vertex - constants::one_element + adjacent_edges.size(), n_incident_edges_for_fully_connected_vertex - constants::one_element ); // validate that the adjacent edges list has been properly aligned - CHECK_EQ( - std::ranges::find( - adjacent_edges, &edge_to_remove, transforms::address_projection{} - ), - adjacent_edges.end() - ); + CHECK_EQ(std::ranges::find(adjacent_edges, edge_to_remove), adjacent_edges.end()); } TEST_CASE_FIXTURE( @@ -340,61 +350,75 @@ TEST_CASE_FIXTURE( test_directed_adjacency_list, "remove_vertex should remove the given vertex and all edges incident with it" ) { - init_complete_graph(); - - const auto removed_vertex_id = constants::first_element_idx; - sut.remove_vertex(removed_vertex_id); - - constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; - constexpr auto n_incident_edges_after_remove = - n_incident_edges_for_fully_connected_vertex - constants::one_element; - - REQUIRE_EQ(sut.n_vertices(), n_vertices_after_remove); - REQUIRE_EQ(sut.n_unique_edges(), n_vertices_after_remove * n_incident_edges_after_remove); - - for (const auto vertex_id : - constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { - const auto adjacent_edges = sut.adjacent_edges(vertex_id); - REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { - return edge.is_incident_with(removed_vertex_id); - })); - } + const auto edge1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge3 = add_edge(constants::vertex_id_2, constants::vertex_id_1); + const auto edge2 = add_edge(constants::vertex_id_1, constants::vertex_id_3); + const auto edge4 = add_edge(constants::vertex_id_3, constants::vertex_id_1); + + const auto edge5 = add_edge(constants::vertex_id_2, constants::vertex_id_3); + const auto edge6 = add_edge(constants::vertex_id_3, constants::vertex_id_2); + + const auto removed_vertex_id = constants::vertex_id_1; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::types::size_type n_removed_edges = 4uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + for (const auto edge_id : {edge5.id(), edge6.id()}) + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge_id)); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + const auto adj_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(adj_edges_1.size(), 1uz); + CHECK_EQ(adj_edges_1.front().id(), edge5.id() - n_removed_edges); + CHECK_EQ(adj_edges_1.front().target(), constants::vertex_id_2); + + const auto adj_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + CHECK_EQ(adj_edges_2.size(), 1uz); + CHECK_EQ(adj_edges_2.front().id(), edge6.id() - n_removed_edges); + CHECK_EQ(adj_edges_2.front().target(), constants::vertex_id_1); } -struct test_undirected_adjacency_list { +struct test_undirected_adjacency_list : public test_adjacency_list { using edge_type = gl::undirected_edge<>; - using edge_ptr_type = gl::undirected_t::edge_ptr_type; using sut_type = gl::impl::adjacency_list>; test_undirected_adjacency_list() {} - const edge_type& add_edge( - const gl::types::id_type first_id, const gl::types::id_type second_id - ) { - return sut.add_edge(gl::detail::make_edge(first_id, second_id)); + edge_type add_edge(const gl::types::id_type source_id, const gl::types::id_type target_id) { + const auto new_edge_id = this->next_edge_id++; + sut.add_edge(new_edge_id, source_id, target_id); + return edge_type{new_edge_id, source_id, target_id}; } - void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { - for (const auto second_id : constants::vertex_id_view) { - if (second_id == first_id and no_loops) + void fully_connect_vertex(const gl::types::id_type source_id, const bool no_loops = true) { + for (const auto target_id : constants::vertex_id_view) { + if (target_id == source_id and no_loops) continue; - add_edge(first_id, second_id); + add_edge(source_id, target_id); } } void init_complete_graph(const bool no_loops = true) { - for (const auto first_id : constants::vertex_id_view) { - const auto bound = no_loops ? first_id : first_id + constants::one; - for (const auto second_id : std::views::iota(constants::vertex_id_1, bound)) - add_edge(first_id, second_id); + for (const auto source_id : constants::vertex_id_view) { + const auto bound = no_loops ? source_id : source_id + constants::one; + for (const auto target_id : std::views::iota(constants::vertex_id_1, bound)) + add_edge(source_id, target_id); } if (no_loops) - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adjacent_items) { + return adjacent_items.size() == n_incident_edges_for_fully_connected_vertex; + })); else - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph + constants::n_elements); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& adjacent_items) { + return adjacent_items.size() + == n_incident_edges_for_fully_connected_vertex + constants::one; + })); } sut_type sut{constants::n_elements}; @@ -406,39 +430,44 @@ struct test_undirected_adjacency_list { TEST_CASE_FIXTURE( test_undirected_adjacency_list, "add_edge should add the edge to the lists of both vertices" ) { - const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); - REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - REQUIRE_EQ(adjacent_edges_1.distance(), constants::one_element); - REQUIRE_EQ(adjacent_edges_2.distance(), constants::one_element); + REQUIRE_EQ(adjacent_edges_1.size(), constants::one_element); + REQUIRE_EQ(adjacent_edges_2.size(), constants::one_element); const auto& new_edge_extracted_1 = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); + CHECK_EQ(new_edge_extracted_1, new_edge); const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_2, &new_edge); + CHECK_EQ(new_edge_extracted_2, new_edge); } TEST_CASE_FIXTURE( test_undirected_adjacency_list, "add_edge should add the edge once to the vertex list if the edge is a loop" ) { - const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_1); - REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); + const auto new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_1); REQUIRE(new_edge.is_loop()); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); const auto adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); - REQUIRE_EQ(adjacent_edges.distance(), constants::one_element); + REQUIRE_EQ(adjacent_edges.size(), constants::one_element); const auto& new_edge_extracted_1 = adjacent_edges[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); + CHECK_EQ(new_edge_extracted_1, new_edge); +} + +TEST_CASE_FIXTURE( + test_undirected_adjacency_list, "at should return the adjacent edges of a vertex" +) { + init_complete_graph(); + for (const auto vertex_id : std::views::iota(0uz, constants::n_elements)) + CHECK(std::ranges::equal(sut.at(vertex_id), sut.adjacent_edges(vertex_id))); } TEST_CASE_FIXTURE( @@ -461,11 +490,15 @@ TEST_CASE_FIXTURE( const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; + const edge_type invalid_edge{ + constants::invalid_id, constants::vertex_id_1, constants::vertex_id_2 + }; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; + const edge_type not_present_edge{ + valid_edge.id(), constants::vertex_id_2, constants::vertex_id_3 + }; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -480,18 +513,18 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_list, "get_edge(id, id) should return the first valid edge if the given vertices are connected" ) { - const auto& edge_1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); - const auto& edge_2 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge_1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge_2 = add_edge(constants::vertex_id_1, constants::vertex_id_2); const auto edge_opt_1 = sut.get_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(edge_opt_1.has_value()); - CHECK_EQ(&edge_opt_1->get(), &edge_1); - CHECK_NE(&edge_opt_1->get(), &edge_2); + CHECK_EQ(*edge_opt_1, edge_1); + CHECK_NE(*edge_opt_1, edge_2); const auto edge_opt_2 = sut.get_edge(constants::vertex_id_2, constants::vertex_id_1); REQUIRE(edge_opt_2.has_value()); - CHECK_EQ(&edge_opt_2->get(), &edge_1); - CHECK_NE(&edge_opt_2->get(), &edge_2); + CHECK_EQ(*edge_opt_2, edge_1); + CHECK_NE(*edge_opt_2, edge_2); } TEST_CASE_FIXTURE( @@ -505,27 +538,20 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_list, "get_edges(id, id) should return a valid edge view if the given vertices are connected" ) { - std::vector> expected_edges; + std::vector expected_edges; for (auto _ = constants::first_element_idx; _ < constants::n_elements; _++) - expected_edges.push_back(std::cref(add_edge(constants::vertex_id_1, constants::vertex_id_2)) - ); - - constexpr auto address_projection = [](const auto& edge_ref) { return &edge_ref.get(); }; + expected_edges.push_back(add_edge(constants::vertex_id_1, constants::vertex_id_2)); CHECK(std::ranges::equal( sut.get_edges(constants::vertex_id_1, constants::vertex_id_2), expected_edges, - std::ranges::equal_to{}, - address_projection, - address_projection + std::ranges::equal_to{} )); CHECK(std::ranges::equal( sut.get_edges(constants::vertex_id_2, constants::vertex_id_1), expected_edges, - std::ranges::equal_to{}, - address_projection, - address_projection + std::ranges::equal_to{} )); } @@ -533,10 +559,10 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_list, "remove_edge should throw when an edge is invalid" ) { // not existing edge between valid vertices - CHECK_THROWS_AS( - sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), - std::invalid_argument - ); + const edge_type not_existing_edge{ + constants::invalid_id, constants::vertex_id_1, constants::vertex_id_2 + }; + CHECK_THROWS_AS(sut.remove_edge(not_existing_edge), std::invalid_argument); } TEST_CASE_FIXTURE( @@ -546,41 +572,27 @@ TEST_CASE_FIXTURE( fully_connect_vertex(constants::vertex_id_1); auto adjacent_edges_first = sut.adjacent_edges(constants::vertex_id_1); - REQUIRE_EQ(sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex); - REQUIRE_EQ(adjacent_edges_first.distance(), n_incident_edges_for_fully_connected_vertex); + REQUIRE_EQ(adjacent_edges_first.size(), n_incident_edges_for_fully_connected_vertex); const auto& edge_to_remove = adjacent_edges_first[constants::first_element_idx]; - const auto second_id = edge_to_remove.second(); - REQUIRE_EQ(sut.adjacent_edges(second_id).distance(), constants::one_element); + const auto target_id = edge_to_remove.target(); + REQUIRE_EQ(sut.adjacent_edges(target_id).size(), constants::one_element); sut.remove_edge(edge_to_remove); - REQUIRE_EQ( - sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex - constants::one_element - ); // validate that the first adjacent edges list has been properly aligned adjacent_edges_first = sut.adjacent_edges(constants::first_element_idx); REQUIRE_EQ( - adjacent_edges_first.distance(), + adjacent_edges_first.size(), n_incident_edges_for_fully_connected_vertex - constants::one_element ); - CHECK_EQ( - std::ranges::find( - adjacent_edges_first, &edge_to_remove, transforms::address_projection{} - ), - adjacent_edges_first.end() - ); + CHECK_EQ(std::ranges::find(adjacent_edges_first, edge_to_remove), adjacent_edges_first.end()); // validate that the second adjacent edges list has been properly aligned - const auto adjacent_edges_second = sut.adjacent_edges(second_id); - REQUIRE_EQ(adjacent_edges_second.distance(), constants::zero_elements); - CHECK_EQ( - std::ranges::find( - adjacent_edges_second, &edge_to_remove, transforms::address_projection{} - ), - adjacent_edges_second.end() - ); + const auto adjacent_edges_second = sut.adjacent_edges(target_id); + REQUIRE_EQ(adjacent_edges_second.size(), constants::zero_elements); + CHECK_EQ(std::ranges::find(adjacent_edges_second, edge_to_remove), adjacent_edges_second.end()); } TEST_CASE_FIXTURE( @@ -651,26 +663,34 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_list, "remove_vertex should remove the given vertex and all edges incident with it" ) { - init_complete_graph(); - - const auto removed_vertex_id = constants::first_element_idx; - sut.remove_vertex(removed_vertex_id); - - constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; - constexpr auto n_incident_edges_after_remove = - n_incident_edges_for_fully_connected_vertex - constants::one_element; + const auto edge1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge3 = add_edge(constants::vertex_id_2, constants::vertex_id_1); + const auto edge2 = add_edge(constants::vertex_id_1, constants::vertex_id_3); + const auto edge4 = add_edge(constants::vertex_id_3, constants::vertex_id_1); - REQUIRE_EQ(sut.n_vertices(), n_vertices_after_remove); - REQUIRE_EQ(sut.n_unique_edges(), (n_vertices_after_remove * n_incident_edges_after_remove) / 2); + const auto edge5 = add_edge(constants::vertex_id_2, constants::vertex_id_3); - for (const auto vertex_id : - constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { - const auto adjacent_edges = sut.adjacent_edges(vertex_id); - REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { - return edge.is_incident_with(removed_vertex_id); - })); - } + const auto removed_vertex_id = constants::first_element_idx; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::types::size_type n_removed_edges = 4uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge5.id())); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + const auto adj_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(adj_edges_1.size(), 1uz); + CHECK_EQ(adj_edges_1.front().id(), edge5.id() - n_removed_edges); + CHECK_EQ(adj_edges_1.front().target(), constants::vertex_id_2); + + const auto adj_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + CHECK_EQ(adj_edges_2.size(), 1uz); + CHECK_EQ(adj_edges_2.front().id(), edge5.id() - n_removed_edges); + CHECK_EQ(adj_edges_2.front().target(), constants::vertex_id_1); } TEST_SUITE_END(); // test_adjacency_list diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index ef03e432..4eb75f81 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -1,9 +1,9 @@ #include "testing/gl/constants.hpp" #include "testing/gl/functional.hpp" -#include "testing/gl/transforms.hpp" #include #include +#include #include @@ -14,24 +14,37 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_adjacency_matrix"); +struct test_adjacency_matrix { + const auto& get(const auto& sut) const { + return sut._matrix; + } + + gl::types::size_type size(const auto& sut) const { + return sut._matrix.size(); + } + + gl::types::size_type next_edge_id = 0uz; +}; + +inline constexpr auto is_valid_id = [](const auto& id) { return id != constants::invalid_id; }; + TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent tests", SutType, edge_directional_tag_sut_template ) { + test_adjacency_matrix fixture; + SUBCASE("should be initialized with no vertices and no edges by default") { SutType sut{}; - CHECK_EQ(sut.n_vertices(), constants::zero_elements); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); + CHECK_EQ(fixture.size(sut), constants::zero_elements); } SUBCASE("constructed with the n_vertices parameter should properly initialize the adjacency " "matrix") { SutType sut{constants::n_elements}; - REQUIRE_EQ(sut.n_vertices(), constants::n_elements); - REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); - - std::ranges::for_each(constants::vertex_id_view, [&sut](const gl::types::id_type vertex_id) { - CHECK_EQ(sut.adjacent_edges(vertex_id).distance(), constants::zero_elements); - }); + REQUIRE_EQ(fixture.size(sut), constants::n_elements); + CHECK(std::ranges::all_of(fixture.get(sut), [](const auto& matrix_row) { + return std::ranges::count_if(matrix_row, is_valid_id) == constants::zero; + })); } SUBCASE("add_vertex should properly extend the current adjacency matrix") { @@ -42,63 +55,58 @@ TEST_CASE_TEMPLATE_DEFINE( n_vertices <= target_n_vertices; n_vertices++) { sut.add_vertex(); - CHECK_EQ(sut.n_vertices(), n_vertices); + CHECK_EQ(fixture.size(sut), n_vertices); } - CHECK_EQ(sut.n_vertices(), target_n_vertices); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); + CHECK_EQ(fixture.size(sut), target_n_vertices); + CHECK(std::ranges::all_of(fixture.get(sut), [](const auto& matrix_row) { + return std::ranges::count_if(matrix_row, is_valid_id) == constants::zero; + })); } SUBCASE("add_vertices(n) should properly extend the current adjacency list") { SutType sut{}; sut.add_vertices(constants::n_elements); - CHECK_EQ(sut.n_vertices(), constants::n_elements); - CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); + CHECK_EQ(fixture.size(sut), constants::n_elements); + CHECK(std::ranges::all_of(fixture.get(sut), [](const auto& matrix_row) { + return std::ranges::count_if(matrix_row, is_valid_id) == constants::zero; + })); } SUBCASE("add_edge should throw an error if the vertices are already incident") { using edge_type = typename SutType::edge_type; SutType sut{constants::n_elements}; - sut.add_edge( - gl::detail::make_edge(constants::vertex_id_1, constants::vertex_id_2) - ); + sut.add_edge(fixture.next_edge_id++, constants::vertex_id_1, constants::vertex_id_2); REQUIRE(sut.has_edge(constants::vertex_id_1, constants::vertex_id_2)); CHECK_THROWS_AS( - sut.add_edge( - gl::detail::make_edge(constants::vertex_id_1, constants::vertex_id_2) - ), + sut.add_edge(fixture.next_edge_id++, constants::vertex_id_1, constants::vertex_id_2), std::logic_error ); } SUBCASE("add_edges_from should throw an error if the vertices are already incident") { using edge_type = typename SutType::edge_type; - using edge_ptr_type = typename SutType::edge_ptr_type; SutType sut{constants::n_elements}; - const auto vertices = { + const auto target_ids = { constants::vertex_id_1, constants::vertex_id_2, constants::vertex_id_3 }; - std::vector new_edges; - for (const auto& target : vertices) - new_edges.push_back(gl::detail::make_edge(constants::vertex_id_1, target)); - - sut.add_edges_from(constants::vertex_id_1, std::move(new_edges)); + sut.add_edges_from( + std::views::iota(0uz, target_ids.size()), constants::vertex_id_1, target_ids + ); REQUIRE(std::ranges::all_of(constants::vertex_id_view, [&sut](const auto target_id) { return sut.has_edge(constants::vertex_id_1, target_id); })); - std::ranges::for_each(vertices, [&sut](const auto target) { - std::vector new_edges; - new_edges.push_back(gl::detail::make_edge(constants::vertex_id_1, target)); - + std::ranges::for_each(target_ids, [&sut, &fixture](const auto target_id) { CHECK_THROWS_AS( - sut.add_edges_from(constants::vertex_id_1, std::move(new_edges)), std::logic_error + sut.add_edge(fixture.next_edge_id++, constants::vertex_id_1, target_id), + std::logic_error ); }); } @@ -117,36 +125,41 @@ constexpr gl::types::size_type n_incident_edges_for_fully_connected_vertex = } // namespace -struct test_directed_adjacency_matrix { +struct test_directed_adjacency_matrix : public test_adjacency_matrix { using edge_type = gl::directed_edge<>; - using edge_ptr_type = gl::directed_t::edge_ptr_type; using sut_type = gl::impl::adjacency_matrix>; test_directed_adjacency_matrix() {} - const edge_type& add_edge( - const gl::types::id_type first_id, const gl::types::id_type second_id - ) { - return sut.add_edge(gl::detail::make_edge(first_id, second_id)); + edge_type add_edge(const gl::types::id_type source_id, const gl::types::id_type target_id) { + const auto new_edge_id = this->next_edge_id++; + sut.add_edge(new_edge_id, source_id, target_id); + return edge_type{new_edge_id, source_id, target_id}; } - void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { - for (const auto second_id : constants::vertex_id_view) { - if (second_id == first_id and no_loops) + void fully_connect_vertex(const gl::types::id_type source_id, const bool no_loops = true) { + for (const auto target_id : constants::vertex_id_view) { + if (target_id == source_id and no_loops) continue; - add_edge(first_id, second_id); + add_edge(source_id, target_id); } } void init_complete_graph(const bool no_loops = true) { - for (const auto first_id : constants::vertex_id_view) - fully_connect_vertex(first_id, no_loops); + for (const auto source_id : constants::vertex_id_view) + fully_connect_vertex(source_id, no_loops); if (no_loops) - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { + return std::ranges::count_if(matrix_row, is_valid_id) + == n_incident_edges_for_fully_connected_vertex; + })); else - REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements * constants::n_elements); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { + return std::ranges::count_if(matrix_row, is_valid_id) + == n_incident_edges_for_fully_connected_vertex + constants::one; + })); } sut_type sut{constants::n_elements}; @@ -158,18 +171,47 @@ struct test_directed_adjacency_matrix { TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "add_edge should add the edge only to the source vertex list" ) { - const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); - REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); + const auto new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); - const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); - CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); - CHECK_EQ(sut.adjacent_edges(constants::vertex_id_2).distance(), constants::zero_elements); + auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); + CHECK_EQ( + gl::util::range_size(sut.adjacent_edges(constants::vertex_id_2)), constants::zero_elements + ); - const auto& new_edge_extracted = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted, &new_edge); + const auto& new_edge_extracted = *std::ranges::begin(adjacent_edges_1); + CHECK_EQ(new_edge_extracted, new_edge); +} + +TEST_CASE_FIXTURE( + test_directed_adjacency_matrix, + "at should return a view equivalent to the matrix row of the given vertex" +) { + for (const auto vertex_id : std::views::iota(0uz, constants::n_elements)) { + const auto edge = add_edge(vertex_id, (vertex_id + 1) % constants::n_elements); + auto row_view = sut.at(vertex_id); + + REQUIRE_EQ(std::ranges::count_if(row_view, &edge_type::is_valid), 1uz); + + const auto edge_it = std::ranges::find_if(row_view, &edge_type::is_valid); + REQUIRE_NE(edge_it, row_view.end()); + REQUIRE_EQ(*edge_it, edge); + } +} + +TEST_CASE_FIXTURE( + test_directed_adjacency_matrix, + "adjacent_edges should return a filtered view of edges adjacent with the given vertex" +) { + const auto vertex_id = constants::vertex_id_1; + const auto edge = add_edge(vertex_id, constants::vertex_id_2); + auto adjacent_edges = sut.adjacent_edges(vertex_id); + + REQUIRE_EQ(gl::util::range_size(adjacent_edges), 1uz); + CHECK_EQ(*std::ranges::begin(adjacent_edges), edge); } TEST_CASE_FIXTURE( @@ -189,14 +231,18 @@ TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "has_edge(edge_ptr) should return true if the given edge is present in the graph" ) { - const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; + const edge_type invalid_edge{ + constants::invalid_id, constants::vertex_id_1, constants::vertex_id_2 + }; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; + const edge_type not_present_edge{ + valid_edge.id(), constants::vertex_id_2, constants::vertex_id_3 + }; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -211,11 +257,11 @@ TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "get_edge(id, id) should return a valid edge if the given vertices are connected" ) { - const auto& edge_1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge_1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); const auto edge_opt = sut.get_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(edge_opt.has_value()); - CHECK_EQ(&edge_opt->get(), &edge_1); + CHECK_EQ(*edge_opt, edge_1); CHECK_FALSE(sut.get_edge(constants::vertex_id_2, constants::vertex_id_2)); } @@ -225,7 +271,7 @@ TEST_CASE_FIXTURE( ) { // not existing edge between valid vertices CHECK_THROWS_AS( - sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), + sut.remove_edge(edge_type{next_edge_id++, constants::vertex_id_1, constants::vertex_id_2}), std::invalid_argument ); } @@ -237,28 +283,18 @@ TEST_CASE_FIXTURE( fully_connect_vertex(constants::vertex_id_1); auto adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); - REQUIRE_EQ(sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex); - REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_for_fully_connected_vertex); - - const auto& edge_to_remove = adjacent_edges[constants::first_element_idx]; + REQUIRE_EQ(gl::util::range_size(adjacent_edges), n_incident_edges_for_fully_connected_vertex); + const auto edge_to_remove = *std::ranges::begin(adjacent_edges); sut.remove_edge(edge_to_remove); - REQUIRE_EQ( - sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex - constants::one_element - ); // validate that the adjacent edges list has been properly aligned adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); REQUIRE_EQ( - adjacent_edges.distance(), + gl::util::range_size(adjacent_edges), n_incident_edges_for_fully_connected_vertex - constants::one_element ); - CHECK_EQ( - std::ranges::find( - adjacent_edges, &edge_to_remove, transforms::address_projection{} - ), - adjacent_edges.end() - ); + CHECK_EQ(std::ranges::find(adjacent_edges, edge_to_remove), adjacent_edges.end()); } TEST_CASE_FIXTURE( @@ -358,61 +394,76 @@ TEST_CASE_FIXTURE( test_directed_adjacency_matrix, "remove_vertex should remove the given vertex and all edges incident with it" ) { - init_complete_graph(); - - const auto removed_vertex_id = constants::first_element_idx; - sut.remove_vertex(removed_vertex_id); - - constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; - constexpr auto n_incident_edges_after_remove = - n_incident_edges_for_fully_connected_vertex - constants::one_element; - - REQUIRE_EQ(sut.n_vertices(), n_vertices_after_remove); - REQUIRE_EQ(sut.n_unique_edges(), n_vertices_after_remove * n_incident_edges_after_remove); - - for (const auto vertex_id : - constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { - const auto adjacent_edges = sut.adjacent_edges(vertex_id); - REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { - return edge.is_incident_with(removed_vertex_id); - })); - } + const auto edge1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge3 = add_edge(constants::vertex_id_2, constants::vertex_id_1); + const auto edge2 = add_edge(constants::vertex_id_1, constants::vertex_id_3); + const auto edge4 = add_edge(constants::vertex_id_3, constants::vertex_id_1); + + const auto edge5 = add_edge(constants::vertex_id_2, constants::vertex_id_3); + const auto edge6 = add_edge(constants::vertex_id_3, constants::vertex_id_2); + + const auto removed_vertex_id = constants::vertex_id_1; + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::types::size_type n_removed_edges = 4uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id(), edge3.id(), edge4.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + for (const auto edge_id : {edge5.id(), edge6.id()}) + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge_id)); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + auto adj_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(gl::util::range_size(adj_edges_1), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).id(), edge5.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).target(), constants::vertex_id_2); + + auto adj_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + CHECK_EQ(gl::util::range_size(adj_edges_2), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).id(), edge6.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).target(), constants::vertex_id_1); } -struct test_undirected_adjacency_matrix { +struct test_undirected_adjacency_matrix : public test_adjacency_matrix { using edge_type = gl::undirected_edge<>; - using edge_ptr_type = gl::undirected_t::edge_ptr_type; using sut_type = gl::impl::adjacency_matrix>; test_undirected_adjacency_matrix() {} - const edge_type& add_edge( - const gl::types::id_type first_id, const gl::types::id_type second_id - ) { - return sut.add_edge(gl::detail::make_edge(first_id, second_id)); + edge_type add_edge(const gl::types::id_type source_id, const gl::types::id_type target_id) { + const auto new_edge_id = this->next_edge_id++; + sut.add_edge(new_edge_id, source_id, target_id); + return edge_type{new_edge_id, source_id, target_id}; } - void fully_connect_vertex(const gl::types::id_type first_id, const bool no_loops = true) { - for (const auto second_id : constants::vertex_id_view) { - if (second_id == first_id and no_loops) + void fully_connect_vertex(const gl::types::id_type source_id, const bool no_loops = true) { + for (const auto target_id : constants::vertex_id_view) { + if (target_id == source_id and no_loops) continue; - add_edge(first_id, second_id); + add_edge(source_id, target_id); } } void init_complete_graph(const bool no_loops = true) { - for (const auto first_id : constants::vertex_id_view) { - const auto bound = no_loops ? first_id : first_id + constants::one; - for (const auto second_id : std::views::iota(constants::vertex_id_1, bound)) - add_edge(first_id, second_id); + for (const auto source_id : constants::vertex_id_view) { + const auto bound = no_loops ? source_id : source_id + constants::one; + for (const auto target_id : std::views::iota(constants::vertex_id_1, bound)) + add_edge(source_id, target_id); } if (no_loops) - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { + return std::ranges::count_if(matrix_row, is_valid_id) + == n_incident_edges_for_fully_connected_vertex; + })); else - REQUIRE_EQ(sut.n_unique_edges(), n_unique_edges_in_full_graph + constants::n_elements); + REQUIRE(std::ranges::all_of(get(sut), [&](const auto& matrix_row) { + return std::ranges::count_if(matrix_row, is_valid_id) + == n_incident_edges_for_fully_connected_vertex + constants::one; + })); } sut_type sut{constants::n_elements}; @@ -424,39 +475,72 @@ struct test_undirected_adjacency_matrix { TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "add_edge should add the edge to the lists of both vertices" ) { - const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); - REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); + auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); - const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + REQUIRE_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); + REQUIRE_EQ(gl::util::range_size(adjacent_edges_2), constants::one_element); - REQUIRE_EQ(adjacent_edges_1.distance(), constants::one_element); - REQUIRE_EQ(adjacent_edges_2.distance(), constants::one_element); + const auto new_edge_extracted_1 = *std::ranges::begin(adjacent_edges_1); + CHECK_EQ(new_edge_extracted_1, new_edge); - const auto& new_edge_extracted_1 = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); - - const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_2, &new_edge); + const auto new_edge_extracted_2 = *std::ranges::begin(adjacent_edges_2); + CHECK_EQ(new_edge_extracted_2, new_edge); } TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "add_edge should add the edge once to the vertex list if the edge is a loop" ) { - const auto& new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_1); - REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); + const auto new_edge = add_edge(constants::vertex_id_1, constants::vertex_id_1); REQUIRE(new_edge.is_loop()); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); - const auto adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); - REQUIRE_EQ(adjacent_edges.distance(), constants::one_element); + auto adjacent_edges = sut.adjacent_edges(constants::vertex_id_1); + REQUIRE_EQ(gl::util::range_size(adjacent_edges), constants::one_element); + + const auto new_edge_extracted_1 = *std::ranges::begin(adjacent_edges); + CHECK_EQ(new_edge_extracted_1, new_edge); +} + +TEST_CASE_FIXTURE( + test_undirected_adjacency_matrix, + "at should return a view equivalent to the matrix row of the given vertex" +) { + const auto edge1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge2 = add_edge(constants::vertex_id_2, constants::vertex_id_3); + const auto edge3 = add_edge(constants::vertex_id_3, constants::vertex_id_1); + + auto v1_row_view = sut.at(constants::vertex_id_1); + REQUIRE_EQ(std::ranges::count_if(v1_row_view, &edge_type::is_valid), 2uz); + CHECK_EQ(v1_row_view[constants::vertex_id_2], edge1); + CHECK_EQ(v1_row_view[constants::vertex_id_3], edge3); + + auto v2_row_view = sut.at(constants::vertex_id_2); + REQUIRE_EQ(std::ranges::count_if(v2_row_view, &edge_type::is_valid), 2uz); + CHECK_EQ(v2_row_view[constants::vertex_id_1], edge1); + CHECK_EQ(v2_row_view[constants::vertex_id_3], edge2); + + auto v3_row_view = sut.at(constants::vertex_id_3); + REQUIRE_EQ(std::ranges::count_if(v3_row_view, &edge_type::is_valid), 2uz); + CHECK_EQ(v3_row_view[constants::vertex_id_1], edge3); + CHECK_EQ(v3_row_view[constants::vertex_id_2], edge2); +} + +TEST_CASE_FIXTURE( + test_undirected_adjacency_matrix, + "adjacent_edges should return a filtered view of edges adjacent with the given vertex" +) { + const auto vertex_id = constants::vertex_id_1; + const auto edge = add_edge(vertex_id, constants::vertex_id_2); + auto adjacent_edges = sut.adjacent_edges(vertex_id); - const auto& new_edge_extracted_1 = adjacent_edges[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); + REQUIRE_EQ(gl::util::range_size(adjacent_edges), 1uz); + CHECK_EQ(*std::ranges::begin(adjacent_edges), edge); } TEST_CASE_FIXTURE( @@ -476,14 +560,18 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "has_edge(edge_ptr) should return true if the given edge is present in the graph" ) { - const auto& valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto valid_edge = add_edge(constants::vertex_id_1, constants::vertex_id_2); CHECK(sut.has_edge(valid_edge)); - const edge_type invalid_edge{constants::vertex_id_1, constants::vertex_id_2}; + const edge_type invalid_edge{ + constants::invalid_id, constants::vertex_id_1, constants::vertex_id_2 + }; CHECK_FALSE(sut.has_edge(invalid_edge)); // edge connecting vertices not connected in the actual graph - const edge_type not_present_edge{constants::vertex_id_2, constants::vertex_id_3}; + const edge_type not_present_edge{ + valid_edge.id(), constants::vertex_id_2, constants::vertex_id_3 + }; CHECK_FALSE(sut.has_edge(not_present_edge)); } @@ -498,15 +586,15 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "get_edge(id, id) should return a valid edge if the given vertices are connected" ) { - const auto& edge_1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge_1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); const auto edge_opt_1 = sut.get_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(edge_opt_1.has_value()); - CHECK_EQ(&edge_opt_1->get(), &edge_1); + CHECK_EQ(*edge_opt_1, edge_1); const auto edge_opt_2 = sut.get_edge(constants::vertex_id_2, constants::vertex_id_1); REQUIRE(edge_opt_2.has_value()); - CHECK_EQ(&edge_opt_2->get(), &edge_1); + CHECK_EQ(*edge_opt_2, edge_1); } TEST_CASE_FIXTURE( @@ -514,7 +602,7 @@ TEST_CASE_FIXTURE( ) { // not existing edge between valid vertices CHECK_THROWS_AS( - sut.remove_edge(edge_type{constants::vertex_id_1, constants::vertex_id_2}), + sut.remove_edge(edge_type{next_edge_id++, constants::vertex_id_1, constants::vertex_id_2}), std::invalid_argument ); } @@ -526,41 +614,29 @@ TEST_CASE_FIXTURE( fully_connect_vertex(constants::vertex_id_1); auto adjacent_edges_first = sut.adjacent_edges(constants::vertex_id_1); - REQUIRE_EQ(sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex); - REQUIRE_EQ(adjacent_edges_first.distance(), n_incident_edges_for_fully_connected_vertex); + REQUIRE_EQ( + gl::util::range_size(adjacent_edges_first), n_incident_edges_for_fully_connected_vertex + ); - const auto& edge_to_remove = adjacent_edges_first[constants::first_element_idx]; + const auto edge_to_remove = *std::ranges::begin(adjacent_edges_first); - const auto second_id = edge_to_remove.second(); - REQUIRE_EQ(sut.adjacent_edges(second_id).distance(), constants::one_element); + const auto target_id = edge_to_remove.target(); + REQUIRE_EQ(gl::util::range_size(sut.adjacent_edges(target_id)), constants::one_element); sut.remove_edge(edge_to_remove); - REQUIRE_EQ( - sut.n_unique_edges(), n_incident_edges_for_fully_connected_vertex - constants::one_element - ); // validate that the first adjacent edges list has been properly aligned adjacent_edges_first = sut.adjacent_edges(constants::vertex_id_1); REQUIRE_EQ( - adjacent_edges_first.distance(), + gl::util::range_size(adjacent_edges_first), n_incident_edges_for_fully_connected_vertex - constants::one_element ); - CHECK_EQ( - std::ranges::find( - adjacent_edges_first, &edge_to_remove, transforms::address_projection{} - ), - adjacent_edges_first.end() - ); + CHECK_EQ(std::ranges::find(adjacent_edges_first, edge_to_remove), adjacent_edges_first.end()); // validate that the second adjacent edges list has been properly aligned - const auto adjacent_edges_second = sut.adjacent_edges(second_id); - REQUIRE_EQ(adjacent_edges_second.distance(), constants::zero_elements); - CHECK_EQ( - std::ranges::find( - adjacent_edges_second, &edge_to_remove, transforms::address_projection{} - ), - adjacent_edges_second.end() - ); + auto adjacent_edges_second = sut.adjacent_edges(target_id); + REQUIRE_EQ(gl::util::range_size(adjacent_edges_second), constants::zero_elements); + CHECK_EQ(std::ranges::find(adjacent_edges_second, edge_to_remove), adjacent_edges_second.end()); } TEST_CASE_FIXTURE( @@ -631,26 +707,31 @@ TEST_CASE_FIXTURE( test_undirected_adjacency_matrix, "remove_vertex should remove the given vertex and all edges incident with it" ) { - init_complete_graph(); + const auto edge1 = add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto edge2 = add_edge(constants::vertex_id_1, constants::vertex_id_3); + const auto edge3 = add_edge(constants::vertex_id_2, constants::vertex_id_3); const auto removed_vertex_id = constants::first_element_idx; - sut.remove_vertex(removed_vertex_id); - - constexpr auto n_vertices_after_remove = constants::n_elements - constants::one_element; - constexpr auto n_incident_edges_after_remove = - n_incident_edges_for_fully_connected_vertex - constants::one_element; - - REQUIRE_EQ(sut.n_vertices(), n_vertices_after_remove); - REQUIRE_EQ(sut.n_unique_edges(), (n_vertices_after_remove * n_incident_edges_after_remove) / 2); - - for (const auto vertex_id : - constants::vertex_id_view | std::views::take(n_vertices_after_remove)) { - const auto adjacent_edges = sut.adjacent_edges(vertex_id); - REQUIRE_EQ(adjacent_edges.distance(), n_incident_edges_after_remove); - CHECK_FALSE(std::ranges::any_of(adjacent_edges, [removed_vertex_id](const auto& edge) { - return edge.is_incident_with(removed_vertex_id); - })); - } + const auto removed_edge_ids = sut.remove_vertex(removed_vertex_id); + + constexpr gl::types::size_type n_removed_edges = 2uz; + REQUIRE_EQ(removed_edge_ids.size(), n_removed_edges); + for (const auto edge_id : {edge1.id(), edge2.id()}) + CHECK(std::ranges::contains(removed_edge_ids, edge_id)); + CHECK_FALSE(std::ranges::contains(removed_edge_ids, edge3.id())); + + // Check the structure of the graph considering the aligned IDs + CHECK_EQ(size(sut), constants::n_elements - 1uz); + + auto adj_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(gl::util::range_size(adj_edges_1), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).id(), edge3.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_1)).target(), constants::vertex_id_2); + + auto adj_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + CHECK_EQ(gl::util::range_size(adj_edges_2), 1uz); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).id(), edge3.id() - n_removed_edges); + CHECK_EQ((*std::ranges::begin(adj_edges_2)).target(), constants::vertex_id_1); } TEST_SUITE_END(); // test_adjacency_matrix diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index a903f565..a21d5f42 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -28,11 +28,8 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("should throw if there is an edge with a negative weight") { const auto sut = gl::topology::clique(constants::n_elements_alg); - sut.get_edge(constants::vertex_id_1, constants::vertex_id_2) - .value() - .get() - .properties() - .weight = -static_cast(constants::n_elements_alg); + sut.get_edge(constants::vertex_id_1, constants::vertex_id_2)->properties().weight = + -static_cast(constants::n_elements_alg); CHECK_THROWS_AS( func::discard_result(gl::algorithm::dijkstra_shortest_paths(sut, constants::vertex_id_1) @@ -60,8 +57,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto edge_weight = static_cast(constants::n_elements_alg); for (gl::types::id_type id = constants::vertex_id_2; id < constants::n_elements_alg; id++) { - sut.get_edge(constants::vertex_id_1, id).value().get().properties().weight = - edge_weight; + sut.get_edge(constants::vertex_id_1, id)->properties().weight = edge_weight; expected_distances.push_back(edge_weight); } } diff --git a/tests/source/gl/test_alg_mst.cpp b/tests/source/gl/test_alg_mst.cpp index 6f26545c..afb275d2 100644 --- a/tests/source/gl/test_alg_mst.cpp +++ b/tests/source/gl/test_alg_mst.cpp @@ -42,7 +42,7 @@ TEST_CASE_TEMPLATE_DEFINE( for (const auto vertex_id : sut.vertex_ids()) { for (const auto& edge : sut.adjacent_edges(vertex_id)) { edge.properties().weight = edge_weight; - expected_edges.emplace_back(edge.first(), edge.second()); + expected_edges.emplace_back(edge.source(), edge.target()); } } @@ -77,15 +77,14 @@ TEST_CASE_TEMPLATE_DEFINE( REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); - CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { - const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertices(); + CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge) { + const auto [source_id, target_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), [&](const vertex_id_pair& vids) { - return (first_id == vids.first && second_id == vids.second) - or (first_id == vids.second && second_id == vids.first); + return (source_id == vids.first && target_id == vids.second) + or (source_id == vids.second && target_id == vids.first); } ) != expected_edges.end(); @@ -124,7 +123,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) for (const auto& edge : sut.adjacent_edges(vertex_id)) - expected_edges.emplace_back(edge.first(), edge.second()); + expected_edges.emplace_back(edge.source(), edge.target()); const weight_type expected_weight = sut.n_vertices() - constants::one; @@ -133,15 +132,14 @@ TEST_CASE_TEMPLATE_DEFINE( REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); - CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { - const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertices(); + CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge) { + const auto [source_id, target_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), [&](const vertex_id_pair& vids) { - return (first_id == vids.first && second_id == vids.second) - or (first_id == vids.second && second_id == vids.first); + return (source_id == vids.first && target_id == vids.second) + or (source_id == vids.second && target_id == vids.first); } ) != expected_edges.end(); @@ -182,7 +180,7 @@ TEST_CASE_TEMPLATE_DEFINE( for (const auto vertex_id : sut.vertex_ids()) { for (const auto& edge : sut.adjacent_edges(vertex_id)) { edge.properties().weight = edge_weight; - expected_edges.emplace_back(edge.first(), edge.second()); + expected_edges.emplace_back(edge.source(), edge.target()); } } @@ -217,15 +215,14 @@ TEST_CASE_TEMPLATE_DEFINE( REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); - CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { - const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertices(); + CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge) { + const auto [source_id, target_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), [&](const vertex_id_pair& vids) { - return (first_id == vids.first && second_id == vids.second) - or (first_id == vids.second && second_id == vids.first); + return (source_id == vids.first && target_id == vids.second) + or (source_id == vids.second && target_id == vids.first); } ) != expected_edges.end(); @@ -264,7 +261,7 @@ TEST_CASE_TEMPLATE_DEFINE( std::vector expected_edges; for (const auto vertex_id : sut.vertex_ids()) for (const auto& edge : sut.adjacent_edges(vertex_id)) - expected_edges.emplace_back(edge.first(), edge.second()); + expected_edges.emplace_back(edge.source(), edge.target()); const weight_type expected_weight = sut.n_vertices() - constants::one; @@ -273,15 +270,14 @@ TEST_CASE_TEMPLATE_DEFINE( REQUIRE_EQ(mst.edges.size(), sut.n_vertices() - constants::one); REQUIRE_EQ(mst.weight, expected_weight); - CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge_ref) { - const auto& edge = edge_ref.get(); - const auto [first_id, second_id] = edge.incident_vertices(); + CHECK(std::ranges::all_of(mst.edges, [&expected_edges](const auto& edge) { + const auto [source_id, target_id] = edge.incident_vertices(); return std::find_if( expected_edges.begin(), expected_edges.end(), [&](const vertex_id_pair& vids) { - return (first_id == vids.first && second_id == vids.second) - or (first_id == vids.second && second_id == vids.first); + return (source_id == vids.first && target_id == vids.second) + or (source_id == vids.second && target_id == vids.first); } ) != expected_edges.end(); diff --git a/tests/source/gl/test_dereferencing_iterator.cpp b/tests/source/gl/test_dereferencing_iterator.cpp deleted file mode 100644 index 87d3fdba..00000000 --- a/tests/source/gl/test_dereferencing_iterator.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" - -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_dereferencing_iterator"); - -// TODO: add specific tests covering the individual methods - -struct data { - gl::types::id_type id; - std::string str; - - friend bool operator==(const data&, const data&) = default; -}; - -using data_uptr = std::unique_ptr; -using data_sptr = std::shared_ptr; -using data_rptr = data*; - -template -struct test_dereferencing_iterator { - using container_type = Container; - using data_ptr_type = typename container_type::value_type; - using sut_type = gl::types::dereferencing_iterator; - - struct address_projection { - auto operator()(data& data) const { - return &data; - } - - auto operator()(const data_ptr_type& data_ptr) const - requires(gl::type_traits::c_strong_smart_ptr) - { - return data_ptr.get(); - } - - auto operator()(data_ptr_type data_ptr) const - requires(not gl::type_traits::c_strong_smart_ptr) - { - return data_ptr; - } - }; - - test_dereferencing_iterator() { - for (auto i = constants::first_element_idx; i < constants::n_elements; i++) { - container.push_front(data_ptr_type{ - new data{i, std::to_string(i)} - }); - } - } - - container_type container; -}; - -TEST_CASE_TEMPLATE_DEFINE( - "dereferencing_iterator should provide an 'equivalent' iterator range as a reference type " - "range", - ContainerType, - container_type_template -) { - using container_type = ContainerType; - using fixture_type = test_dereferencing_iterator; - using sut_type = typename fixture_type::sut_type; - using address_projection = typename fixture_type::address_projection; - - static_assert(std::forward_iterator); - - fixture_type fixture; - - SUBCASE("normal iterator") { - const auto sut_range = gl::make_iterator_range( - gl::deref_begin(fixture.container), gl::deref_end(fixture.container) - ); - const auto ptr_range = gl::make_iterator_range(fixture.container); - - CHECK(std::ranges::equal( - sut_range, ptr_range, std::ranges::equal_to{}, address_projection{}, address_projection{} - )); - } - - SUBCASE("const iterator") { - const auto sut_crange = gl::make_iterator_range( - gl::deref_cbegin(fixture.container), gl::deref_cend(fixture.container) - ); - const auto ptr_crange = gl::make_const_iterator_range(fixture.container); - - CHECK(std::ranges::equal( - sut_crange, - ptr_crange, - std::ranges::equal_to{}, - address_projection{}, - address_projection{} - )); - } -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - container_type_template, - std::deque, // random access iterator - std::deque, // random access iterator - std::deque, // random access iterator - std::list, // bidirectional iterator - std::list, // bidirectional iterator - std::list, // bidirectional iterator - std::forward_list, // forward iterator - std::forward_list, // forward iterator - std::forward_list // forward iterator -); - -TEST_CASE_TEMPLATE_DEFINE( - "dereferencing_iterator should provide an 'equivalent' decrementable iterator range" - "as a reference type decrementable range", - ContainerType, - bidir_container_type_template -) { - using container_type = ContainerType; - using fixture_type = test_dereferencing_iterator; - using sut_type = typename fixture_type::sut_type; - using address_projection = typename fixture_type::address_projection; - - static_assert(std::bidirectional_iterator); - - fixture_type fixture; - - SUBCASE("normal iterator") { - // Create dereferencing iterator range - auto sut_it = gl::deref_end(fixture.container); - auto sut_begin = gl::deref_begin(fixture.container); - - auto ptr_it = std::ranges::end(fixture.container); - auto ptr_begin = std::ranges::begin(fixture.container); - - const auto exepcted_size = std::ranges::distance(ptr_begin, ptr_it); - - // Collect elements while iterating backwards - std::vector sut_elements; - std::vector ptr_elements; - const address_projection proj{}; - - do { - sut_elements.push_back(proj(*(--sut_it))); - ptr_elements.push_back(proj(*(--ptr_it))); - } while (sut_it != sut_begin && ptr_it != ptr_begin); - - REQUIRE_EQ(sut_it, sut_begin); - REQUIRE_EQ(ptr_it, ptr_begin); - - REQUIRE_EQ(sut_elements.size(), exepcted_size); - REQUIRE_EQ(ptr_elements.size(), exepcted_size); - - CHECK_EQ(sut_elements, ptr_elements); - } - - SUBCASE("const iterator") { - // Create dereferencing iterator range - auto sut_it = gl::deref_cend(fixture.container); - auto sut_begin = gl::deref_cbegin(fixture.container); - - auto ptr_it = std::ranges::cend(fixture.container); - auto ptr_begin = std::ranges::cbegin(fixture.container); - - const auto exepcted_size = std::ranges::distance(ptr_begin, ptr_it); - - // Collect elements while iterating backwards - std::vector sut_elements; - std::vector ptr_elements; - const address_projection proj{}; - - do { - sut_elements.push_back(proj(*(--sut_it))); - ptr_elements.push_back(proj(*(--ptr_it))); - } while (sut_it != sut_begin and ptr_it != ptr_begin); - - REQUIRE_EQ(sut_it, sut_begin); - REQUIRE_EQ(ptr_it, ptr_begin); - - REQUIRE_EQ(sut_elements.size(), exepcted_size); - REQUIRE_EQ(ptr_elements.size(), exepcted_size); - - CHECK_EQ(sut_elements, ptr_elements); - } -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - bidir_container_type_template, - std::deque, // random access iterator - std::deque, // random access iterator - std::deque, // random access iterator - std::list, // bidirectional iterator - std::list, // bidirectional iterator - std::list // bidirectional iterator -); - -TEST_SUITE_END(); // test_dereferencing_iterator - -} // namespace gl_testing diff --git a/tests/source/gl/test_edge_descriptor.cpp b/tests/source/gl/test_edge_descriptor.cpp index 9bdcebd1..b76439c9 100644 --- a/tests/source/gl/test_edge_descriptor.cpp +++ b/tests/source/gl/test_edge_descriptor.cpp @@ -13,18 +13,21 @@ TEST_SUITE_BEGIN("test_edge_descriptor"); struct test_edge_descriptor { using vertex_type = gl::vertex_descriptor<>; - gl::types::id_type v1 = constants::vertex_id_1; - gl::types::id_type v2 = constants::vertex_id_2; - gl::types::id_type v3 = constants::vertex_id_3; + static constexpr gl::types::id_type id1 = constants::first_element_idx; + static constexpr gl::types::id_type id2 = id1 + constants::one; + + static constexpr gl::types::id_type v1 = constants::vertex_id_1; + static constexpr gl::types::id_type v2 = constants::vertex_id_2; + static constexpr gl::types::id_type v3 = constants::vertex_id_3; }; TEST_CASE_FIXTURE( test_edge_descriptor, "is_directed() should return true only for edges with directed edge tag" ) { - gl::directed_edge<> directed_edge{v1, v2}; + gl::directed_edge<> directed_edge{id1, v1, v2}; CHECK(directed_edge.is_directed()); - gl::undirected_edge<> undirected_edge{v1, v2}; + gl::undirected_edge<> undirected_edge{id2, v1, v2}; CHECK_FALSE(undirected_edge.is_directed()); } @@ -32,22 +35,72 @@ TEST_CASE_FIXTURE( test_edge_descriptor, "is_undirected() should return true only for edges with bidirectional edge tag" ) { - gl::undirected_edge<> undirected_edge{v1, v2}; + gl::undirected_edge<> undirected_edge{id1, v1, v2}; CHECK(undirected_edge.is_undirected()); - gl::directed_edge<> directed_edge{v1, v2}; + gl::directed_edge<> directed_edge{id2, v1, v2}; CHECK_FALSE(directed_edge.is_undirected()); } +TEST_CASE_FIXTURE( + test_edge_descriptor, "edges should be equal only if their IDs and vertices are the same" +) { + // directed edges + gl::directed_edge<> dedge1{id1, v1, v2}; + gl::directed_edge<> dedge2{id1, v1, v2}; + gl::directed_edge<> dedge3{id2, v1, v2}; + gl::directed_edge<> dedge4{id1, v2, v1}; + + CHECK_EQ(dedge1, dedge2); + CHECK_NE(dedge1, dedge3); + CHECK_NE(dedge1, dedge4); + + // undirected edges + gl::undirected_edge<> uedge1{id1, v1, v2}; + gl::undirected_edge<> uedge2{id1, v1, v2}; + gl::undirected_edge<> uedge3{id2, v1, v2}; + gl::undirected_edge<> uedge4{id1, v2, v1}; + + CHECK_EQ(uedge1, uedge2); + CHECK_NE(uedge1, uedge3); + CHECK_EQ(uedge1, uedge4); +} + TEST_CASE_TEMPLATE_DEFINE( - "properties should be properly initialized", EdgeType, properties_edge_directional_tag_template + "properties accessing tests", EdgeType, properties_edge_directional_tag_template ) { test_edge_descriptor fixture; + types::used_property used{true}; + + SUBCASE("properties should be properly initialized for valid edges") { + const EdgeType sut{fixture.id1, fixture.v1, fixture.v2, used}; + CHECK_EQ(sut.properties(), used); + } + + SUBCASE("accessing properties should throw for invalid edges") { + CHECK_THROWS_AS( + func::discard_result( + EdgeType(constants::invalid_id, fixture.v1, fixture.v2, used).properties() + ), + std::logic_error + ); - const types::used_property used{true}; - const EdgeType sut{fixture.v1, fixture.v2, used}; + CHECK_THROWS_AS( + func::discard_result( + EdgeType(fixture.id1, constants::invalid_id, fixture.v2, used).properties() + ), + std::logic_error + ); + + CHECK_THROWS_AS( + func::discard_result( + EdgeType(fixture.id1, fixture.v1, constants::invalid_id, used).properties() + ), + std::logic_error + ); - CHECK_EQ(sut.properties(), used); + CHECK_THROWS_AS(func::discard_result(EdgeType::invalid().properties()), std::logic_error); + } } // TODO: fix .clang-format to split such lines @@ -57,8 +110,19 @@ TEST_CASE_TEMPLATE_DEFINE( "directional_tag-independent tests", EdgeType, edge_directional_tag_template ) { test_edge_descriptor fixture{}; + EdgeType sut{fixture.id1, fixture.v1, fixture.v2}; + + SUBCASE("an edge should be valid if it has a valid ID and vertices") { + CHECK(sut.is_valid()); + CHECK_FALSE(EdgeType{constants::invalid_id, constants::invalid_id, constants::invalid_id}); + CHECK_FALSE(EdgeType{constants::invalid_id, fixture.v1, fixture.v2}); + CHECK_FALSE(EdgeType{fixture.id1, constants::invalid_id, fixture.v2}); + CHECK_FALSE(EdgeType{fixture.id1, fixture.v1, constants::invalid_id}); + } - EdgeType sut{fixture.v1, fixture.v2}; + SUBCASE("id() should return the ID of the edge") { + CHECK_EQ(sut.id(), fixture.id1); + } SUBCASE("incident_vertices should return the pair of vertex IDS the edge was initialized with" ) { @@ -67,12 +131,19 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK_EQ(vertices.second, fixture.v2); } + SUBCASE("incident_vertices_r should return the pair of vertex IDS the edge was initialized " + "with but with switched order") { + const auto& vertices = sut.incident_vertices_r(); + CHECK_EQ(vertices.first, fixture.v2); + CHECK_EQ(vertices.second, fixture.v1); + } + SUBCASE("first should return the first vertex descriptor the edge was initialized with") { - CHECK_EQ(sut.first(), fixture.v1); + CHECK_EQ(sut.source(), fixture.v1); } SUBCASE("second should return the second vertex descriptor the edge was initialized with") { - CHECK_EQ(sut.second(), fixture.v2); + CHECK_EQ(sut.target(), fixture.v2); } SUBCASE("incident_vertex should throw if input vertex is not incident with the edge") { @@ -98,7 +169,7 @@ TEST_CASE_TEMPLATE_DEFINE( SUBCASE("is_loop should return true onlyu for edges where both vertices are the same") { CHECK_FALSE(sut.is_loop()); - const EdgeType loop{fixture.v1, fixture.v1}; + const EdgeType loop{fixture.id1, fixture.v1, fixture.v1}; CHECK(loop.is_loop()); } } diff --git a/tests/source/gl/test_edge_tags.cpp b/tests/source/gl/test_edge_tags.cpp index d6b620af..58c56cba 100644 --- a/tests/source/gl/test_edge_tags.cpp +++ b/tests/source/gl/test_edge_tags.cpp @@ -10,122 +10,57 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_edge_tags"); struct test_edge_tags { - gl::types::id_type v1 = constants::vertex_id_1; - gl::types::id_type v2 = constants::vertex_id_2; + static constexpr gl::types::id_type id = constants::first_element_idx; + static constexpr gl::types::id_type v1 = constants::vertex_id_1; + static constexpr gl::types::id_type v2 = constants::vertex_id_2; }; struct test_directed_edge_tag : test_edge_tags { using sut_type = gl::directed_t; using edge_type = gl::directed_edge<>; - const std::unique_ptr edge = gl::detail::make_edge(v1, v2); + const edge_type edge{id, v1, v2}; }; -TEST_CASE_FIXTURE( - test_directed_edge_tag, "make_edge should return a unique ptr to a directed edge" -) { - static_assert(std::is_same_v, std::unique_ptr>); - - REQUIRE(edge->is_directed()); - REQUIRE_FALSE(edge->is_undirected()); - - CHECK_EQ(edge->first(), v1); - CHECK_EQ(edge->second(), v2); -} - -TEST_CASE_FIXTURE( - test_directed_edge_tag, - "make_edge should return a unique ptr to a directed edge with the given properties" -) { - using property_edge_type = gl::directed_edge; - - const types::used_property used{true}; - const auto property_edge = gl::detail::make_edge(v1, v2, used); - - static_assert(std::is_same_v< - std::remove_cvref_t, - std::unique_ptr>); - - REQUIRE(property_edge->is_directed()); - REQUIRE_FALSE(property_edge->is_undirected()); - - CHECK_EQ(property_edge->first(), v1); - CHECK_EQ(property_edge->second(), v2); - CHECK_EQ(property_edge->properties(), used); -} - TEST_CASE_FIXTURE( test_directed_edge_tag, "is_incident_from should return true only for the first vertex" ) { - CHECK(sut_type::is_incident_from(*edge, v1)); - CHECK_FALSE(sut_type::is_incident_from(*edge, v2)); - CHECK_FALSE(sut_type::is_incident_from(*edge, constants::invalid_id)); + CHECK(sut_type::is_incident_from(edge, v1)); + CHECK_FALSE(sut_type::is_incident_from(edge, v2)); + CHECK_FALSE(sut_type::is_incident_from(edge, constants::invalid_id)); } TEST_CASE_FIXTURE( test_directed_edge_tag, "is_incident_to should return true only for the second vertex" ) { - CHECK(sut_type::is_incident_to(*edge, v2)); - CHECK_FALSE(sut_type::is_incident_to(*edge, v1)); - CHECK_FALSE(sut_type::is_incident_to(*edge, constants::invalid_id)); + CHECK(sut_type::is_incident_to(edge, v2)); + CHECK_FALSE(sut_type::is_incident_to(edge, v1)); + CHECK_FALSE(sut_type::is_incident_to(edge, constants::invalid_id)); } struct test_undirected_edge_tag : test_edge_tags { using sut_type = gl::undirected_t; using edge_type = gl::undirected_edge<>; - const std::shared_ptr edge = gl::detail::make_edge(v1, v2); + const edge_type edge{id, v1, v2}; }; -TEST_CASE_FIXTURE( - test_undirected_edge_tag, "make_edge should return a shared ptr to a directed edge" -) { - static_assert(std::is_same_v, std::shared_ptr>); - - REQUIRE(edge->is_undirected()); - REQUIRE_FALSE(edge->is_directed()); - - CHECK_EQ(edge->first(), v1); - CHECK_EQ(edge->second(), v2); -} - -TEST_CASE_FIXTURE( - test_directed_edge_tag, - "make_edge should return a shared ptr to a directed edge with the given properties" -) { - using property_edge_type = gl::undirected_edge; - - const types::used_property used{true}; - const auto property_edge = gl::detail::make_edge(v1, v2, used); - - static_assert(std::is_same_v< - std::remove_cvref_t, - std::shared_ptr>); - - REQUIRE(property_edge->is_undirected()); - REQUIRE_FALSE(property_edge->is_directed()); - - CHECK_EQ(property_edge->first(), v1); - CHECK_EQ(property_edge->second(), v2); - CHECK_EQ(property_edge->properties(), used); -} - TEST_CASE_FIXTURE( test_undirected_edge_tag, "is_incident_from should return true for both vertices" ) { - CHECK(sut_type::is_incident_from(*edge, v1)); - CHECK(sut_type::is_incident_from(*edge, v2)); + CHECK(sut_type::is_incident_from(edge, v1)); + CHECK(sut_type::is_incident_from(edge, v2)); - CHECK_FALSE(sut_type::is_incident_from(*edge, constants::invalid_id)); - CHECK_FALSE(sut_type::is_incident_from(*edge, constants::invalid_id)); + CHECK_FALSE(sut_type::is_incident_from(edge, constants::invalid_id)); + CHECK_FALSE(sut_type::is_incident_from(edge, constants::invalid_id)); } TEST_CASE_FIXTURE(test_undirected_edge_tag, "is_incident_to should return true for both vertices") { - CHECK(sut_type::is_incident_to(*edge, v1)); - CHECK(sut_type::is_incident_to(*edge, v2)); + CHECK(sut_type::is_incident_to(edge, v1)); + CHECK(sut_type::is_incident_to(edge, v2)); - CHECK_FALSE(sut_type::is_incident_to(*edge, constants::invalid_id)); - CHECK_FALSE(sut_type::is_incident_to(*edge, constants::invalid_id)); + CHECK_FALSE(sut_type::is_incident_to(edge, constants::invalid_id)); + CHECK_FALSE(sut_type::is_incident_to(edge, constants::invalid_id)); } TEST_SUITE_END(); // untest_edge_tags diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index 89472ccb..f5a676fc 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -1,9 +1,9 @@ #include "testing/gl/constants.hpp" #include "testing/gl/functional.hpp" -#include "testing/gl/transforms.hpp" #include "testing/gl/types.hpp" #include +#include #include @@ -23,8 +23,8 @@ struct test_graph { requires(gl::type_traits::is_directed_v) void init_complete_graph(GraphType& graph) { const auto vertices = graph.vertices(); - for (const auto& first : vertices) - for (const auto& second : vertices) + for (const auto first : vertices) + for (const auto second : vertices) if (first != second) graph.add_edge(first, second); @@ -39,8 +39,8 @@ struct test_graph { requires(gl::type_traits::is_undirected_v) void init_complete_graph(GraphType& graph) { const auto vertices = graph.vertices(); - for (const auto& first : vertices) - for (const auto& second : vertices) + for (const auto first : vertices) + for (const auto second : vertices) if (first < second) graph.add_edge(first, second); @@ -57,7 +57,7 @@ struct test_graph { graph.vertex_ids(), [this, &graph, expected_n_edges = n_incident_edges_for_fully_connected_vertex(graph)]( const gl::types::id_type vertex_id - ) { return graph.adjacent_edges(vertex_id).distance() == expected_n_edges; } + ) { return gl::util::range_size(graph.adjacent_edges(vertex_id)) == expected_n_edges; } )); } @@ -90,8 +90,7 @@ using add_edge_property = gl::graph_traits< using vertex_id_list = std::vector; -template VertexType> -using vertex_ref_list = std::vector>; +inline constexpr auto get_id = [](auto&& element) -> gl::types::id_type { return element.id(); }; TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_template) { using fixture_type = test_graph; @@ -116,8 +115,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut_type sut{constants::n_elements}; REQUIRE(std::ranges::equal( - sut.vertices() | std::views::transform(transforms::extract_vertex_id<>), - constants::vertex_id_view + sut.vertices() | std::views::transform(get_id), constants::vertex_id_view )); REQUIRE(std::ranges::equal(sut.vertex_ids(), constants::vertex_id_view)); @@ -130,7 +128,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK(std::ranges::all_of( constants::vertex_id_view, [&sut](const gl::types::id_type vertex_id) { - return not static_cast(sut.adjacent_edges(vertex_id).distance()); + return sut.adjacent_edges(vertex_id).empty(); } )); } @@ -142,10 +140,10 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp constexpr gl::types::size_type target_n_vertices = constants::n_elements; for (gl::types::id_type v_id = constants::zero_elements; v_id < target_n_vertices; v_id++) { - const auto& vertex = sut.add_vertex(); + const auto vertex = sut.add_vertex(); CHECK_EQ(vertex.id(), v_id); CHECK_EQ(sut.n_vertices(), v_id + constants::one_element); - CHECK_EQ(sut.adjacent_edges(v_id).distance(), constants::empty_distance); + CHECK(sut.adjacent_edges(v_id).empty()); } CHECK_EQ(sut.n_vertices(), target_n_vertices); @@ -155,7 +153,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp using properties_traits_type = add_vertex_property; gl::graph sut; - const auto& vertex = sut.add_vertex(constants::visited); + const auto vertex = sut.add_vertex(constants::visited); REQUIRE_EQ(sut.n_vertices(), constants::one_element); CHECK_EQ(vertex.id(), constants::vertex_id_1); @@ -189,7 +187,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut.vertices(), properties_list, std::ranges::equal_to{}, - [](const auto& vertex) { return vertex.properties(); } + [](const auto vertex) { return vertex.properties(); } )); } @@ -217,16 +215,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("vertices should return the correct vertex list iterator range") { sut_type sut{constants::n_elements}; - // clang-format off - CHECK(std::ranges::equal( - sut.vertices(), - constants::vertex_id_view, - std::ranges::equal_to{}, - transforms::extract_vertex_id + sut.vertices(), constants::vertex_id_view, std::ranges::equal_to{}, get_id )); - - // clang-format on } SUBCASE("vertex_ids should return the correct vertex list iterator range") { @@ -259,7 +250,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE(std::ranges::all_of( vertex_id_view, [&sut, expected_n_incident_edges](const gl::types::id_type vertex_id) { - return sut.adjacent_edges(vertex_id).distance() == expected_n_incident_edges; + return gl::util::range_size(sut.adjacent_edges(vertex_id)) + == expected_n_incident_edges; } )); @@ -293,7 +285,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE(std::ranges::all_of( vertex_id_view, [&sut, expected_n_incident_edges](const gl::types::id_type vertex_id) { - return sut.adjacent_edges(vertex_id).distance() == expected_n_incident_edges; + return gl::util::range_size(sut.adjacent_edges(vertex_id)) + == expected_n_incident_edges; } )); @@ -320,7 +313,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK(std::ranges::all_of( sut.vertices(), [&sut, expected_n_adjacent_edges](const auto& vertex) { - return sut.adjacent_edges(vertex).distance() == expected_n_adjacent_edges; + return gl::util::range_size(sut.adjacent_edges(vertex)) + == expected_n_adjacent_edges; } )); } @@ -332,10 +326,10 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut_type sut{n_vertices}; fixture.init_complete_graph(sut); - const auto& v1 = sut.get_vertex(constants::vertex_id_1); - const auto& v3 = sut.get_vertex(constants::vertex_id_3); + const auto v1 = sut.get_vertex(constants::vertex_id_1); + const auto v3 = sut.get_vertex(constants::vertex_id_3); - sut.remove_vertices_from(vertex_ref_list{v1, v3, v1}); + sut.remove_vertices_from(std::vector{v1, v3, v1}); constexpr auto expected_n_vertices = n_vertices - constants::two; REQUIRE_EQ(sut.n_vertices(), expected_n_vertices); @@ -344,7 +338,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK(std::ranges::all_of( sut.vertices(), [&sut, expected_n_adjacent_edges](const auto& vertex) { - return sut.adjacent_edges(vertex).distance() == expected_n_adjacent_edges; + return gl::util::range_size(sut.adjacent_edges(vertex)) + == expected_n_adjacent_edges; } )); } @@ -355,9 +350,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp sut_type sut{constants::n_elements}; const auto vertices = sut.vertices(); - const auto& vertex_1 = vertices[constants::vertex_id_1]; - const auto& vertex_2 = vertices[constants::vertex_id_2]; - const auto& vertex_3 = vertices[constants::vertex_id_3]; + const auto vertex_1 = vertices[constants::vertex_id_1]; + const auto vertex_2 = vertices[constants::vertex_id_2]; + const auto vertex_3 = vertices[constants::vertex_id_3]; SUBCASE("add_edge(ids) should throw if either vertex id is invalid") { CHECK_THROWS_AS( @@ -371,25 +366,25 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } SUBCASE("add_edge(ids) should properly add the new edge") { - const auto& new_edge = sut.add_edge(constants::vertex_id_1, constants::vertex_id_2); + const auto new_edge = sut.add_edge(constants::vertex_id_1, constants::vertex_id_2); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); - CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); - const auto& new_edge_extracted_1 = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); + auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); + const auto new_edge_extracted_1 = *std::ranges::begin(adjacent_edges_1); + CHECK_EQ(new_edge_extracted_1, new_edge); - const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); if constexpr (gl::type_traits::is_undirected_v) { - CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); - const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_2, &new_edge); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::one_element); + const auto new_edge_extracted_2 = *std::ranges::begin(adjacent_edges_2); + CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(adjacent_edges_2.distance(), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::zero_elements); } } @@ -399,25 +394,25 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } SUBCASE("add_edge(vertices) should properly add the new edge") { - const auto& new_edge = sut.add_edge(vertex_1, vertex_2); + const auto new_edge = sut.add_edge(vertex_1, vertex_2); REQUIRE(new_edge.is_incident_from(vertex_1.id())); REQUIRE(new_edge.is_incident_to(vertex_2.id())); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); - CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); - const auto& new_edge_extracted_1 = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); + auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); + const auto new_edge_extracted_1 = *std::ranges::begin(adjacent_edges_1); + CHECK_EQ(new_edge_extracted_1, new_edge); - const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); if constexpr (gl::type_traits::is_undirected_v) { - CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); - const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_2, &new_edge); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::one_element); + const auto new_edge_extracted_2 = *std::ranges::begin(adjacent_edges_2); + CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(adjacent_edges_2.distance(), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::zero_elements); } } @@ -461,14 +456,14 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); CHECK_THROWS_AS( - sut.add_edges_from(fixture.out_of_range_vertex, vertex_ref_list{}), + sut.add_edges_from(fixture.out_of_range_vertex, std::vector{}), std::out_of_range ); CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); CHECK_THROWS_AS( sut.add_edges_from( - vertex_1, vertex_ref_list{vertex_2, fixture.out_of_range_vertex} + vertex_1, std::vector{vertex_2, fixture.out_of_range_vertex} ), std::out_of_range ); @@ -478,8 +473,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("add_edges_from(vertices) should properly extend the graph if all ids are valid") { REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); - const auto& source = vertex_1; - const vertex_ref_list target_list{vertex_1, vertex_2, vertex_3}; + const auto source = vertex_1; + const std::vector target_list{vertex_1, vertex_2, vertex_3}; sut.add_edges_from(source, target_list); @@ -490,16 +485,16 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } SUBCASE("remove_edge should properly remove the edge for both incident vertices") { - const auto& added_edge = sut.add_edge(vertex_1, vertex_2); + const auto added_edge = sut.add_edge(vertex_1, vertex_2); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - REQUIRE_EQ(adjacent_edges_1.distance(), constants::one_element); + REQUIRE_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); if constexpr (gl::type_traits::is_undirected_v) - REQUIRE_EQ(adjacent_edges_2.distance(), constants::one_element); + REQUIRE_EQ(gl::util::range_size(adjacent_edges_2), constants::one_element); sut.remove_edge(added_edge); CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); @@ -507,41 +502,37 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - CHECK_EQ(adjacent_edges_1.distance(), constants::zero_elements); - CHECK_EQ(adjacent_edges_2.distance(), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_1), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::zero_elements); } - SUBCASE("remove_edges_from should properly erase all given edges") { + SUBCASE("remove_edges should properly erase all given edges") { REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); - const auto& edge_1 = sut.add_edge(vertex_1, vertex_2); - const auto& edge_2 = sut.add_edge(vertex_2, vertex_3); - const auto& edge_3 = sut.add_edge(vertex_3, vertex_1); + const auto edge_1 = sut.add_edge(vertex_1, vertex_2); + const auto edge_2 = sut.add_edge(vertex_2, vertex_3); + const auto edge_3 = sut.add_edge(vertex_3, vertex_1); // an additional edge to verify that only the given edges are removed - const auto& vertex_4 = sut.add_vertex(); - const auto& edge_4 = sut.add_edge(vertex_1, vertex_4); + const auto vertex_4 = sut.add_vertex(); + const auto edge_4 = sut.add_edge(vertex_1, vertex_4); REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements + constants::one_element); - std::vector> edges_to_remove{ - edge_1, edge_2, edge_3 - }; - - const auto has_edge = [&sut](const auto& edge_ref) { - return sut.has_edge(edge_ref.get()); - }; + std::vector edges_to_remove{edge_1, edge_2, edge_3}; - REQUIRE(std::ranges::all_of(edges_to_remove, has_edge)); + REQUIRE(std::ranges::all_of(edges_to_remove, [&sut](const auto& edge) { + return sut.has_edge(edge); + })); REQUIRE(sut.has_edge(edge_4)); - sut.remove_edges_from(edges_to_remove); + sut.remove_edges(edges_to_remove); CHECK_EQ(sut.n_unique_edges(), constants::one_element); CHECK_FALSE(sut.has_edge(vertex_1, vertex_2)); CHECK_FALSE(sut.has_edge(vertex_2, vertex_3)); CHECK_FALSE(sut.has_edge(vertex_3, vertex_1)); - CHECK(sut.has_edge(edge_4)); + CHECK(sut.has_edge(edge_4.source(), edge_4.target())); } } @@ -551,9 +542,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp gl::graph sut{constants::n_elements}; const auto vertices = sut.vertices(); - const auto& vertex_1 = vertices[constants::vertex_id_1]; - const auto& vertex_2 = vertices[constants::vertex_id_2]; - const auto& vertex_3 = vertices[constants::vertex_id_3]; + const auto vertex_1 = vertices[constants::vertex_id_1]; + const auto vertex_2 = vertices[constants::vertex_id_2]; + const auto vertex_3 = vertices[constants::vertex_id_3]; SUBCASE("add_edge(ids) should throw if either vertex id is invalid") { CHECK_THROWS_AS( @@ -571,7 +562,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } SUBCASE("add_edge(ids) should properly add the new edge") { - const auto& new_edge = + const auto new_edge = sut.add_edge(constants::vertex_id_1, constants::vertex_id_2, constants::used); REQUIRE(new_edge.is_incident_from(constants::vertex_id_1)); REQUIRE(new_edge.is_incident_to(constants::vertex_id_2)); @@ -579,19 +570,19 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); - CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); - const auto& new_edge_extracted_1 = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); + auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); + const auto new_edge_extracted_1 = *std::ranges::begin(adjacent_edges_1); + CHECK_EQ(new_edge_extracted_1, new_edge); - const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); if constexpr (gl::type_traits::is_undirected_v) { - CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); - const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_2, &new_edge); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::one_element); + const auto new_edge_extracted_2 = *std::ranges::begin(adjacent_edges_2); + CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(adjacent_edges_2.distance(), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::zero_elements); } } @@ -607,40 +598,40 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } SUBCASE("add_edge(vertices) should properly add the new edge") { - const auto& new_edge = sut.add_edge(vertex_1, vertex_2, constants::used); + const auto new_edge = sut.add_edge(vertex_1, vertex_2, constants::used); REQUIRE(new_edge.is_incident_from(vertex_1.id())); REQUIRE(new_edge.is_incident_to(vertex_2.id())); REQUIRE_EQ(new_edge.properties(), constants::used); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); - const auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); - CHECK_EQ(adjacent_edges_1.distance(), constants::one_element); - const auto& new_edge_extracted_1 = adjacent_edges_1[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_1, &new_edge); + auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); + CHECK_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); + const auto new_edge_extracted_1 = *std::ranges::begin(adjacent_edges_1); + CHECK_EQ(new_edge_extracted_1, new_edge); - const auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); + auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); if constexpr (gl::type_traits::is_undirected_v) { - CHECK_EQ(adjacent_edges_2.distance(), constants::one_element); - const auto& new_edge_extracted_2 = adjacent_edges_2[constants::first_element_idx]; - CHECK_EQ(&new_edge_extracted_2, &new_edge); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::one_element); + const auto new_edge_extracted_2 = *std::ranges::begin(adjacent_edges_2); + CHECK_EQ(new_edge_extracted_2, new_edge); } else { - CHECK_EQ(adjacent_edges_2.distance(), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::zero_elements); } } SUBCASE("remove_edge should properly remove the edge for both incident vertices") { - const auto& added_edge = sut.add_edge(vertex_1, vertex_2, constants::used); + const auto added_edge = sut.add_edge(vertex_1, vertex_2, constants::used); REQUIRE_EQ(sut.n_unique_edges(), constants::one_element); auto adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); auto adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - REQUIRE_EQ(adjacent_edges_1.distance(), constants::one_element); + REQUIRE_EQ(gl::util::range_size(adjacent_edges_1), constants::one_element); if constexpr (gl::type_traits::is_undirected_v) - REQUIRE_EQ(adjacent_edges_2.distance(), constants::one_element); + REQUIRE_EQ(gl::util::range_size(adjacent_edges_2), constants::one_element); sut.remove_edge(added_edge); CHECK_EQ(sut.n_unique_edges(), constants::zero_elements); @@ -648,49 +639,45 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp adjacent_edges_1 = sut.adjacent_edges(constants::vertex_id_1); adjacent_edges_2 = sut.adjacent_edges(constants::vertex_id_2); - CHECK_EQ(adjacent_edges_1.distance(), constants::zero_elements); - CHECK_EQ(adjacent_edges_2.distance(), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_1), constants::zero_elements); + CHECK_EQ(gl::util::range_size(adjacent_edges_2), constants::zero_elements); } - SUBCASE("remove_edges_from should properly erase all given edges") { + SUBCASE("remove_edges should properly erase all given edges") { REQUIRE_EQ(sut.n_unique_edges(), constants::zero_elements); - const auto& edge_1 = sut.add_edge(vertex_1, vertex_2, constants::not_used); - const auto& edge_2 = sut.add_edge(vertex_2, vertex_3, constants::not_used); - const auto& edge_3 = sut.add_edge(vertex_3, vertex_1, constants::not_used); + const auto edge_1 = sut.add_edge(vertex_1, vertex_2, constants::not_used); + const auto edge_2 = sut.add_edge(vertex_2, vertex_3, constants::not_used); + const auto edge_3 = sut.add_edge(vertex_3, vertex_1, constants::not_used); // an additional edge to verify that only the given edges are removed - const auto& vertex_4 = sut.add_vertex(); - const auto& edge_4 = sut.add_edge(vertex_1, vertex_4, constants::used); + const auto vertex_4 = sut.add_vertex(); + const auto edge_4 = sut.add_edge(vertex_1, vertex_4, constants::used); REQUIRE_EQ(sut.n_unique_edges(), constants::n_elements + constants::one_element); - const std::vector> edges_to_remove{ - edge_1, edge_2, edge_3 - }; - - const auto has_edge = [&sut](const auto& edge_ref) { - return sut.has_edge(edge_ref.get()); - }; + const std::vector edges_to_remove{edge_1, edge_2, edge_3}; - REQUIRE(std::ranges::all_of(edges_to_remove, has_edge)); + REQUIRE(std::ranges::all_of(edges_to_remove, [&sut](const auto& edge) { + return sut.has_edge(edge); + })); REQUIRE(sut.has_edge(edge_4)); - sut.remove_edges_from(edges_to_remove); + sut.remove_edges(edges_to_remove); CHECK_EQ(sut.n_unique_edges(), constants::one_element); CHECK_FALSE(sut.has_edge(vertex_1, vertex_2)); CHECK_FALSE(sut.has_edge(vertex_2, vertex_3)); CHECK_FALSE(sut.has_edge(vertex_3, vertex_1)); - CHECK(sut.has_edge(edge_4)); + CHECK(sut.has_edge(edge_4.source(), edge_4.target())); } } SUBCASE("has_edge(vertex, vertex) should throw if one of the vertices is invalid") { sut_type sut{constants::n_elements}; - const auto& vd_1 = sut.get_vertex(constants::vertex_id_1); - const auto& vd_2 = sut.get_vertex(constants::vertex_id_2); + const auto vd_1 = sut.get_vertex(constants::vertex_id_1); + const auto vd_2 = sut.get_vertex(constants::vertex_id_2); CHECK_THROWS_AS( func::discard_result(sut.has_edge(fixture.out_of_range_vertex, vd_2)), std::out_of_range @@ -704,9 +691,9 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp "vertices in the graph") { sut_type sut{constants::n_elements}; - const auto& vd_1 = sut.get_vertex(constants::vertex_id_1); - const auto& vd_2 = sut.get_vertex(constants::vertex_id_2); - const auto& vd_3 = sut.get_vertex(constants::vertex_id_3); + const auto vd_1 = sut.get_vertex(constants::vertex_id_1); + const auto vd_2 = sut.get_vertex(constants::vertex_id_2); + const auto vd_3 = sut.get_vertex(constants::vertex_id_3); sut.add_edge(vd_1, vd_2); @@ -715,19 +702,21 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_FALSE(sut.has_edge(vd_2, vd_3)); } - SUBCASE("get_edge(vertex, vertex) should return nullopt if either vertex is invalid") { + SUBCASE("get_edge(vertex, vertex) should throw if either vertex is invalid") { sut_type sut{constants::n_elements}; - const auto& valid_vertex = sut.get_vertex(constants::vertex_id_1); + const auto valid_vertex = sut.get_vertex(constants::vertex_id_1); const vertex_type out_of_range_vertex{constants::out_of_range_element_idx}; - CHECK_FALSE(sut.get_edge(valid_vertex, out_of_range_vertex)); - CHECK_FALSE(sut.get_edge(out_of_range_vertex, valid_vertex)); - CHECK_FALSE(sut.get_edge(out_of_range_vertex, out_of_range_vertex)); - - const vertex_type invalid_vertex{constants::vertex_id_1}; - CHECK_FALSE(sut.get_edge(valid_vertex, invalid_vertex)); - CHECK_FALSE(sut.get_edge(invalid_vertex, valid_vertex)); - CHECK_FALSE(sut.get_edge(invalid_vertex, invalid_vertex)); + CHECK_THROWS_AS( + func::discard_result(sut.get_edge(valid_vertex, out_of_range_vertex)), std::out_of_range + ); + CHECK_THROWS_AS( + func::discard_result(sut.get_edge(out_of_range_vertex, valid_vertex)), std::out_of_range + ); + CHECK_THROWS_AS( + func::discard_result(sut.get_edge(out_of_range_vertex, out_of_range_vertex)), + std::out_of_range + ); } SUBCASE("get_edge(vertex, vertex) should return nullopt if the given vertices are not incident" @@ -741,26 +730,26 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("get_edge(vertex, vertex) should return a valid edge if the given vetices are incident" ) { sut_type sut{constants::n_elements}; - const auto& vd_1 = sut.get_vertex(constants::vertex_id_1); - const auto& vd_2 = sut.get_vertex(constants::vertex_id_2); + const auto vd_1 = sut.get_vertex(constants::vertex_id_1); + const auto vd_2 = sut.get_vertex(constants::vertex_id_2); - const auto& edge = sut.add_edge(vd_1, vd_2); + const auto edge = sut.add_edge(vd_1, vd_2); const auto edge_opt_1 = sut.get_edge(vd_1, vd_2); REQUIRE(edge_opt_1.has_value()); - CHECK_EQ(&edge_opt_1->get(), &edge); + CHECK_EQ(*edge_opt_1, edge); if constexpr (gl::type_traits::is_undirected_v) { const auto edge_opt_2 = sut.get_edge(vd_2, vd_1); REQUIRE(edge_opt_2.has_value()); - CHECK_EQ(&edge_opt_2->get(), &edge); + CHECK_EQ(*edge_opt_2, edge); } else { CHECK_FALSE(sut.get_edge(vd_2, vd_1).has_value()); } } - SUBCASE("get_edges(id, id) should throw if either id is invalid") { + SUBCASE("get_edges(id, id) should return an empty list if either id is invalid") { sut_type sut{constants::n_elements}; CHECK_THROWS_AS( @@ -786,28 +775,21 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("get_edges(id, id) should return a valid edge reference vector if the given vertices " "are incident") { sut_type sut{constants::n_elements}; - std::vector> expected_edges; + std::vector expected_edges; if constexpr (std::same_as) { for (auto _ = constants::first_element_idx; _ < constants::n_elements; _++) - expected_edges.push_back( - std::cref(sut.add_edge(constants::vertex_id_1, constants::vertex_id_2)) + expected_edges.emplace_back( + sut.add_edge(constants::vertex_id_1, constants::vertex_id_2) ); } else { - expected_edges.push_back( - std::cref(sut.add_edge(constants::vertex_id_1, constants::vertex_id_2)) + expected_edges.emplace_back(sut.add_edge(constants::vertex_id_1, constants::vertex_id_2) ); } - constexpr auto address_projection = [](const auto& edge_ref) { return &edge_ref.get(); }; - CHECK(std::ranges::equal( - sut.get_edges(constants::vertex_id_1, constants::vertex_id_2), - expected_edges, - std::ranges::equal_to{}, - address_projection, - address_projection + sut.get_edges(constants::vertex_id_1, constants::vertex_id_2), expected_edges )); if constexpr (gl::type_traits::is_directed_v) { @@ -815,11 +797,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp } else { CHECK(std::ranges::equal( - sut.get_edges(constants::vertex_id_2, constants::vertex_id_1), - expected_edges, - std::ranges::equal_to{}, - address_projection, - address_projection + sut.get_edges(constants::vertex_id_2, constants::vertex_id_1), expected_edges )); } } @@ -827,8 +805,8 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("get_edges(vertex, vertex) should throw if either vertex is invalid") { sut_type sut{constants::n_elements}; - const auto& vd_1 = sut.get_vertex(constants::vertex_id_1); - const auto& vd_2 = sut.get_vertex(constants::vertex_id_2); + const auto vd_1 = sut.get_vertex(constants::vertex_id_1); + const auto vd_2 = sut.get_vertex(constants::vertex_id_2); CHECK_THROWS_AS( func::discard_result(sut.get_edges(fixture.out_of_range_vertex, vd_2)), @@ -853,7 +831,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp CHECK_NOTHROW([&sut]() { CHECK_EQ( - sut.adjacent_edges(constants::first_element_idx).distance(), + gl::util::range_size(sut.adjacent_edges(constants::first_element_idx)), constants::zero_elements ); }()); @@ -869,10 +847,10 @@ TEST_CASE_TEMPLATE_DEFINE("graph structure tests", TraitsType, graph_traits_temp SUBCASE("adjacent_edges(vertex) should return a proper iterator range for a valid vertex") { sut_type sut{constants::one_element}; - const auto& vertex = sut.get_vertex(constants::first_element_idx); + const auto vertex = sut.get_vertex(constants::first_element_idx); CHECK_NOTHROW([&sut, &vertex]() { - CHECK_EQ(sut.adjacent_edges(vertex).distance(), constants::zero_elements); + CHECK_EQ(gl::util::range_size(sut.adjacent_edges(vertex)), constants::zero_elements); }()); } } @@ -885,13 +863,10 @@ TEST_CASE_TEMPLATE_INSTANTIATE( gl::matrix_graph_traits // undirected adjacency matrix ); -TEST_CASE_TEMPLATE_DEFINE( - "vertex_properties_map() should return a correct map", TraitsType, vp_graph_traits_template -) { +TEST_CASE_TEMPLATE_DEFINE("vertex properties getter tests", TraitsType, vp_graph_traits_template) { using sut_type = gl::graph; sut_type sut{constants::n_elements}; - for (auto vertex : sut.vertices()) vertex.properties() = std::format("vertex_{}", vertex.id()); @@ -900,6 +875,7 @@ TEST_CASE_TEMPLATE_DEFINE( for (auto [id, property] : std::views::zip(sut.vertex_ids(), map)) { CHECK_EQ(property, std::format("vertex_{}", id)); CHECK_EQ(map[id], std::format("vertex_{}", id)); + CHECK_EQ(sut.get_vertex_properties(id), std::format("vertex_{}", id)); } } @@ -911,4 +887,6 @@ TEST_CASE_TEMPLATE_INSTANTIATE( gl::matrix_graph_traits // undirected adjacency matrix ); +TEST_SUITE_END(); // test_graph + } // namespace gl_testing diff --git a/tests/source/gl/test_graph_file_io.cpp b/tests/source/gl/test_graph_file_io.cpp index 829e0cfd..36053dc4 100644 --- a/tests/source/gl/test_graph_file_io.cpp +++ b/tests/source/gl/test_graph_file_io.cpp @@ -14,7 +14,7 @@ namespace fs = std::filesystem; namespace gl_testing { -#define CUSTOM_REQUIRE_THROWS_FS_ERROR(expr, errc) \ +#define GL_REQUIRE_THROWS_FS_ERROR(expr, errc) \ try { \ expr; \ FAIL("Expected `std::filesystem::filesystem_error` but no exception was thrown"); \ @@ -73,22 +73,18 @@ TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_te if (not file.is_open()) FAIL("Could not initialize an empty file"); - CUSTOM_REQUIRE_THROWS_FS_ERROR( + GL_REQUIRE_THROWS_FS_ERROR( gl::io::save(fixture.sut_out, fixture.path), std::errc::file_exists ); } SUBCASE("load shoul throw if a file does not exist") { - CUSTOM_REQUIRE_THROWS_FS_ERROR( + GL_REQUIRE_THROWS_FS_ERROR( func::discard_result(gl::io::load(fixture.path)), std::errc::no_such_file_or_directory ); } - // TODO: add - // * invalid file type tests - // * could not open file tests - SUBCASE("file io should properly save and load a graph in a gsf format") { gl::io::save(fixture.sut_out, fixture.path); const auto sut_in = gl::io::load(fixture.path); diff --git a/tests/source/gl/test_graph_incidence.cpp b/tests/source/gl/test_graph_incidence.cpp index 3885b74c..f06aed38 100644 --- a/tests/source/gl/test_graph_incidence.cpp +++ b/tests/source/gl/test_graph_incidence.cpp @@ -14,9 +14,9 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ SutType sut{constants::n_elements}; - const auto& vd_1 = sut.get_vertex(constants::vertex_id_1); - const auto& vd_2 = sut.get_vertex(constants::vertex_id_2); - const auto& vd_3 = sut.get_vertex(constants::vertex_id_3); + const auto vd_1 = sut.get_vertex(constants::vertex_id_1); + const auto vd_2 = sut.get_vertex(constants::vertex_id_2); + const auto vd_3 = sut.get_vertex(constants::vertex_id_3); vertex_type out_of_range_vertex{constants::out_of_range_element_idx}; SUBCASE("are_incident(vertex_id, vertex_id) should throw for out of range vertex ids") { @@ -84,7 +84,7 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ } SUBCASE("are_incident(vertex and edge pair) should throw if the vertex is invalid") { - const auto& edge = sut.add_edge(vd_1, vd_2); + const auto edge = sut.add_edge(vd_1, vd_2); CHECK_THROWS_AS( func::discard_result(sut.are_incident(out_of_range_vertex, edge)), std::out_of_range @@ -102,7 +102,7 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ } SUBCASE("are_incident(vertex and edge pair) should throw if the edge is invalid") { - const typename SutType::edge_type invalid_edge{vd_1.id(), vd_2.id()}; + const typename SutType::edge_type invalid_edge{constants::invalid_id, vd_1.id(), vd_2.id()}; CHECK_THROWS_AS( func::discard_result(sut.are_incident(vd_1, invalid_edge)), std::invalid_argument @@ -121,7 +121,7 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ SUBCASE("are_incident(vertex and edge pair) should return true only when the edge and the " "vertex are incident with each other") { - const auto& edge = sut.add_edge(vd_1, vd_2); + const auto edge = sut.add_edge(vd_1, vd_2); CHECK(sut.are_incident(vd_1, edge)); CHECK(sut.are_incident(vd_2, edge)); @@ -131,8 +131,8 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ } SUBCASE("are_incident(edge, edge) should throw if either edge is invalid") { - const auto& edge = sut.add_edge(vd_1, vd_2); - const typename SutType::edge_type invalid_edge{vd_1.id(), vd_2.id()}; + const auto edge = sut.add_edge(vd_1, vd_2); + const typename SutType::edge_type invalid_edge{constants::invalid_id, vd_1.id(), vd_2.id()}; CHECK_THROWS_AS( func::discard_result(sut.are_incident(edge, invalid_edge)), std::invalid_argument @@ -144,9 +144,9 @@ TEST_CASE_TEMPLATE_DEFINE("incidence functions tests", SutType, graph_type_templ SUBCASE("are_incident(edge, edge) should return true only when the edges share a common vertex" ) { - const auto& edge_1 = sut.add_edge(vd_1, vd_2); - const auto& edge_2 = sut.add_edge(vd_2, vd_3); - const auto& loop_3 = sut.add_edge(vd_3, vd_3); + const auto edge_1 = sut.add_edge(vd_1, vd_2); + const auto edge_2 = sut.add_edge(vd_2, vd_3); + const auto loop_3 = sut.add_edge(vd_3, vd_3); CHECK(sut.are_incident(edge_1, edge_2)); CHECK(sut.are_incident(edge_2, edge_1)); diff --git a/tests/source/gl/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp index fc570671..6797c6de 100644 --- a/tests/source/gl/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -140,7 +140,7 @@ struct test_undirected_graph_io { for (const auto& vertex : sut_out.vertices()) { vertex.properties() = std::format("vertex_{}", v_idx++); for (const auto& edge : sut_out.adjacent_edges(vertex)) - if (edge.first() == vertex.id()) + if (edge.source() == vertex.id()) edge.properties() = std::format("edge_{}", e_idx++); } } diff --git a/tests/source/gl/test_graph_topology_builders.cpp b/tests/source/gl/test_graph_topology_builders.cpp index 306aa878..2d66d189 100644 --- a/tests/source/gl/test_graph_topology_builders.cpp +++ b/tests/source/gl/test_graph_topology_builders.cpp @@ -83,7 +83,7 @@ template [[nodiscard]] auto is_vertex_connected_to_next_only(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { - const auto& next_vertex = + const auto next_vertex = graph.get_vertex((source.id() + constants::one_element) % graph.n_vertices()); return std::ranges::all_of(graph.vertices(), [&](const auto& vertex) { @@ -96,7 +96,7 @@ template [[nodiscard]] auto is_vertex_connected_to_prev_only(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { - const auto& prev_vertex = graph.get_vertex( + const auto prev_vertex = graph.get_vertex( (source.id() + graph.n_vertices() - constants::one_element) % graph.n_vertices() ); @@ -110,10 +110,10 @@ template [[nodiscard]] auto is_vertex_connected_to_id_adjacent(const GraphType& graph) { using vertex_type = typename GraphType::vertex_type; return [&graph](const vertex_type& source) { - const auto& next_vertex = + const auto next_vertex = graph.get_vertex((source.id() + constants::one_element) % graph.n_vertices()); - const auto& prev_vertex = graph.get_vertex( + const auto prev_vertex = graph.get_vertex( (source.id() + graph.n_vertices() - constants::one_element) % graph.n_vertices() ); @@ -132,10 +132,10 @@ template if (target_ids.first >= graph.n_vertices()) // no need to check second as second = first + 1 - return graph.adjacent_edges(source).distance() == constants::zero; + return gl::util::range_size(graph.adjacent_edges(source)) == constants::zero; - const auto& target_1 = graph.get_vertex(target_ids.first); - const auto& target_2 = graph.get_vertex(target_ids.second); + const auto target_1 = graph.get_vertex(target_ids.first); + const auto target_2 = graph.get_vertex(target_ids.second); return std::ranges::all_of(graph.vertices(), [&](const auto& vertex) { if (vertex == target_1 or vertex == target_2) @@ -157,15 +157,14 @@ template if (target_ids.first >= graph.n_vertices()) { // no need to check second as second = first + 1 - const auto adjacent_edges = graph.adjacent_edges(source_id); + auto adjacent_edges = graph.adjacent_edges(source_id); - return adjacent_edges.distance() == constants::one - and adjacent_edges[constants::first_element_idx].incident_vertex(source_id - ) == parent_id; + return gl::util::range_size(adjacent_edges) == constants::one + and (*std::ranges::begin(adjacent_edges)).incident_vertex(source_id) == parent_id; } - const auto& target_1 = target_ids.first; - const auto& target_2 = target_ids.second; + const auto target_1 = target_ids.first; + const auto target_2 = target_ids.second; return std::ranges::all_of(graph.vertex_ids(), [&](const auto vertex_id) { if (vertex_id == target_1 or vertex_id == target_2) @@ -212,7 +211,7 @@ TEST_CASE_TEMPLATE_DEFINE( const auto vertices = biclique.vertices(); const auto vertices_a = vertices | std::views::take(constants::n_elements_top); - const auto vertices_b = gl::make_iterator_range( + const auto vertices_b = std::ranges::subrange( std::ranges::next(vertices.begin(), constants::n_elements_top), std::ranges::next(vertices.begin(), expected_n_vertices) ); diff --git a/tests/source/gl/test_iterator_range.cpp b/tests/source/gl/test_iterator_range.cpp deleted file mode 100644 index f672d515..00000000 --- a/tests/source/gl/test_iterator_range.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" - -#include - -#include - -#include -#include -#include -#include -#include - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_iterator_range"); - -template -struct test_iterator_range_type_params { - using container_type = Container; - using cache_mode = CacheMode; -}; - -template -struct test_iterator_range { - using container_type = typename TypeParams::container_type; - using iterator_type = typename container_type::iterator; - using sut_type = gl::types::iterator_range; - - test_iterator_range() - : container(constants::n_elements), sut(container.begin(), container.end()) { - std::iota(container.begin(), container.end(), constants::first_element_idx); - } - - container_type container; - - sut_type sut; -}; - -TEST_CASE_TEMPLATE_DEFINE("iterator_range tests", TypeParams, type_params_template) { - using fixture_type = test_iterator_range; - using container_type = typename fixture_type::container_type; - using iterator_type = typename fixture_type::iterator_type; - using sut_type = typename fixture_type::sut_type; - using cache_mode = typename sut_type::cache_mode; - - fixture_type fixture; - - auto& sut = fixture.sut; - auto& container = fixture.container; - - SUBCASE("should properly initialze the begin and end iterators") { - CHECK_EQ(sut.begin(), container.begin()); - CHECK_EQ(sut.end(), container.end()); - -#if __cplusplus >= 202302L - CHECK_EQ(sut.cbegin(), container.cbegin()); - CHECK_EQ(sut.cend(), container.cend()); -#endif - } - - SUBCASE("should properly initialize the begin and end iterator for a range constructor") { - sut_type range_constructed_sut = - gl::make_iterator_range(container); - - CHECK_EQ(range_constructed_sut.begin(), std::ranges::begin(container)); - CHECK_EQ(range_constructed_sut.end(), std::ranges::end(container)); - -#if __cplusplus >= 202302L - CHECK_EQ(range_constructed_sut.cbegin(), std::ranges::cbegin(container)); - CHECK_EQ(range_constructed_sut.cend(), std::ranges::cend(container)); -#endif - } - - SUBCASE("equality operator should return true only when both iterators are equal") { - sut_type range{sut}; - REQUIRE_EQ(sut, range); - - range = sut_type{std::next(sut.begin()), sut.end()}; - CHECK_NE(sut, range); - - range = sut_type{sut.begin(), std::next(sut.begin(), sut.distance() - 1)}; - CHECK_NE(sut, range); - } - - SUBCASE("should be compatible with range based loops") { - std::size_t element = constants::first_element_idx; - - for (const std::size_t range_element : sut) - CHECK_EQ(range_element, element++); - } - - SUBCASE("should be compatible with std functions") { - CHECK(std::equal(std::begin(sut), std::end(sut), container.begin())); - -#if __cplusplus >= 202302L - CHECK(std::equal(std::cbegin(sut), std::cend(sut), container.cbegin())); -#endif - } - - SUBCASE("should be compatible with std::ranges functions") { - CHECK(std::ranges::equal(sut, container)); - } - - SUBCASE("distance should return the distance between begin and end iterators") { - CHECK_EQ(sut.distance(), constants::n_elements); - } - - SUBCASE("element_at should throw when index is out of range") { - CHECK_THROWS_AS( - func::discard_result(sut.element_at(constants::n_elements)), std::out_of_range - ); - } - - SUBCASE("element_at should return a reference to the correct element") { - iterator_type it = container.begin(); - for (std::size_t n = 0; n < constants::n_elements; n++) - CHECK_EQ(std::addressof(sut.element_at(n)), std::addressof(*it++)); - } - - SUBCASE("subscript operator should throw when index is out of range") { - CHECK_THROWS_AS(func::discard_result(sut[constants::n_elements]), std::out_of_range); - } - - SUBCASE("subscript operator should return a reference to the correct element") { - iterator_type it = container.begin(); - for (std::size_t n = 0; n < constants::n_elements; n++) - CHECK_EQ(std::addressof(sut[n]), std::addressof(*it++)); - } - - SUBCASE("make_iterator_range should return a properly initialized iterator_range") { - const auto range = - gl::make_iterator_range(container.begin(), container.end()); - CHECK(std::ranges::equal(range, container)); - } - - SUBCASE("make_iterator_range should return an iterator_range properly initialized with the " - "container") { - const auto range = gl::make_iterator_range(container); - CHECK(std::ranges::equal(range, container)); - } - - SUBCASE("make_const_iterator_range should return an iterator_range properly initialized with " - "the container") { - const auto range = gl::make_const_iterator_range(container); - CHECK(std::ranges::equal(range, container)); - } -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - type_params_template, - // cache_mode_value::none - test_iterator_range_type_params< - std::vector, - gl::type_traits::no_cache>, // random access iterator - test_iterator_range_type_params< - std::list, - gl::type_traits::no_cache>, // bidirectional iterator - test_iterator_range_type_params< - std::forward_list, - gl::type_traits::no_cache>, // forward iterator - // cache_mode_value::lazy - test_iterator_range_type_params< - std::vector, - gl::type_traits::lazy_cache>, // random access iterator - test_iterator_range_type_params< - std::list, - gl::type_traits::lazy_cache>, // bidirectional iterator - test_iterator_range_type_params< - std::forward_list, - gl::type_traits::lazy_cache>, // forward iterator - // cache_mode_value::eager - test_iterator_range_type_params< - std::vector, - gl::type_traits::eager_cache>, // random access iterator - test_iterator_range_type_params< - std::list, - gl::type_traits::eager_cache>, // bidirectional iterator - test_iterator_range_type_params< - std::forward_list, - gl::type_traits::eager_cache> // forward iterator -); - -TEST_SUITE_END(); // test_iterator_range - -} // namespace gl_testing diff --git a/tests/source/gl/test_non_null_iterator.cpp b/tests/source/gl/test_non_null_iterator.cpp deleted file mode 100644 index b0df5370..00000000 --- a/tests/source/gl/test_non_null_iterator.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" - -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_non_null_iterator"); - -// TODO: add specific tests covering the individual methods - -struct data { - gl::types::id_type id; - std::string str; - - friend bool operator==(const data&, const data&) = default; -}; - -using data_uptr = std::unique_ptr; -using data_sptr = std::shared_ptr; -using data_rptr = data*; - -template -struct test_non_null_iterator { - using container_type = Container; - using data_ptr_type = typename container_type::value_type; - using sut_type = gl::types::non_null_iterator; - - struct reference_projection { - auto& operator()(const data_ptr_type& data_ptr) const - requires(gl::type_traits::c_strong_smart_ptr) - { - return *data_ptr; - } - - auto& operator()(data_ptr_type data_ptr) const - requires(not gl::type_traits::c_strong_smart_ptr) - { - return *data_ptr; - } - }; - - test_non_null_iterator() { - for (auto i = constants::first_element_idx; i < constants::n_elements; i++) { - container.push_front(nullptr); - container.push_front(data_ptr_type{ - new data{i, std::to_string(i)} - }); - container.push_front(nullptr); - - non_null_container.push_front(data_ptr_type{ - new data{i, std::to_string(i)} - }); - } - } - - container_type container; - container_type non_null_container; -}; - -TEST_CASE_TEMPLATE_DEFINE( - "non_null_iterator should provide an 'equivalent' iterator range as a non null container", - ContainerType, - container_type_template -) { - using container_type = ContainerType; - using fixture_type = test_non_null_iterator; - using sut_type = typename fixture_type::sut_type; - using reference_projection = typename fixture_type::reference_projection; - - static_assert(std::forward_iterator); - - fixture_type fixture; - - SUBCASE("normal iterator") { - const auto sut_range = gl::make_iterator_range( - gl::non_null_begin(fixture.container), gl::non_null_end(fixture.container) - ); - const auto non_null_range = gl::make_iterator_range(fixture.non_null_container); - - CHECK(std::ranges::equal( - sut_range, - non_null_range, - std::ranges::equal_to{}, - reference_projection{}, - reference_projection{} - )); - } - - SUBCASE("cosnt iterator") { - const auto sut_range = gl::make_iterator_range( - gl::non_null_cbegin(fixture.container), gl::non_null_cend(fixture.container) - ); - const auto non_null_range = gl::make_const_iterator_range(fixture.non_null_container); - - CHECK(std::ranges::equal( - sut_range, - non_null_range, - std::ranges::equal_to{}, - reference_projection{}, - reference_projection{} - )); - } -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - container_type_template, - std::deque, // random access iterator - std::deque, // random access iterator - std::deque, // random access iterator - std::list, // bidirectional iterator - std::list, // bidirectional iterator - std::list, // bidirectional iterator - std::forward_list, // forward iterator - std::forward_list, // forward iterator - std::forward_list // forward iterator -); - -TEST_CASE_TEMPLATE_DEFINE( - "non_null_iterator should provide an 'equivalent' decrementable iterator range as a non null " - "decrementable container", - ContainerType, - bidir_container_type_template -) { - using container_type = ContainerType; - using fixture_type = test_non_null_iterator; - using sut_type = typename fixture_type::sut_type; - using reference_projection = typename fixture_type::reference_projection; - - static_assert(std::bidirectional_iterator); - - fixture_type fixture; - - SUBCASE("normal iterator") { - // Create non_null iterator range - auto sut_it = gl::non_null_end(fixture.container); - auto sut_begin = gl::non_null_begin(fixture.container); - - auto ptr_it = std::ranges::end(fixture.non_null_container); - auto ptr_begin = std::ranges::begin(fixture.non_null_container); - - const auto exepcted_size = std::ranges::distance(ptr_begin, ptr_it); - - // Collect elements while iterating backwards - std::vector> sut_elements; - std::vector> ptr_elements; - const reference_projection proj{}; - - do { - sut_elements.push_back(proj(*(--sut_it))); - ptr_elements.push_back(proj(*(--ptr_it))); - } while (ptr_it != ptr_begin); - - REQUIRE_EQ(sut_elements.size(), exepcted_size); - REQUIRE_EQ(ptr_elements.size(), exepcted_size); - - CHECK_EQ(sut_elements, ptr_elements); - } - - SUBCASE("const iterator") { - // Create non_null iterator range - auto sut_it = gl::non_null_cend(fixture.container); - auto sut_begin = gl::non_null_cbegin(fixture.container); - - auto ptr_it = std::ranges::cend(fixture.non_null_container); - auto ptr_begin = std::ranges::cbegin(fixture.non_null_container); - - const auto exepcted_size = std::ranges::distance(ptr_begin, ptr_it); - - // Collect elements while iterating backwards - std::vector> sut_elements; - std::vector> ptr_elements; - const reference_projection proj{}; - - do { - sut_elements.push_back(proj(*(--sut_it))); - ptr_elements.push_back(proj(*(--ptr_it))); - } while (ptr_it != ptr_begin); - - REQUIRE_EQ(sut_elements.size(), exepcted_size); - REQUIRE_EQ(ptr_elements.size(), exepcted_size); - - CHECK_EQ(sut_elements, ptr_elements); - } -} - -TEST_CASE_TEMPLATE_INSTANTIATE( - bidir_container_type_template, - std::deque, // random access iterator - std::deque, // random access iterator - std::deque, // random access iterator - std::list, // bidirectional iterator - std::list, // bidirectional iterator - std::list // bidirectional iterator -); - -TEST_SUITE_END(); // test_non_null_iterator - -} // namespace gl_testing diff --git a/tests/source/gl/test_vertex_degree_getters.cpp b/tests/source/gl/test_vertex_degree_getters.cpp index 4c228ebd..ace4296f 100644 --- a/tests/source/gl/test_vertex_degree_getters.cpp +++ b/tests/source/gl/test_vertex_degree_getters.cpp @@ -1,5 +1,4 @@ #include "testing/gl/constants.hpp" -#include "testing/gl/transforms.hpp" #include #include @@ -12,6 +11,8 @@ namespace gl_testing { TEST_SUITE_BEGIN("test_vertex_degree_getters"); +inline constexpr auto get_id = [](auto&& element) -> gl::types::id_type { return element.id(); }; + TEST_CASE_TEMPLATE_DEFINE( "vertex degree getter tests for directed graphs", TraitsType, directed_graph_traits_template ) { @@ -85,13 +86,14 @@ TEST_CASE_TEMPLATE_DEFINE( CHECK(std::ranges::all_of( sut.vertices(), [&](const gl::types::id_type vertex_id) { - const bool result = sut.in_degree(vertex_id) == expected_in_deg_list[i] - and sut.out_degree(vertex_id) == expected_out_deg_list[i] - and sut.degree(vertex_id) == expected_deg_list[i]; + const bool result = + sut.in_degree(vertex_id) == expected_in_deg_list[i] + and sut.out_degree(vertex_id) == expected_out_deg_list[i] + and sut.degree(vertex_id) == expected_deg_list[i]; ++i; return result; }, - transforms::extract_vertex_id + get_id )); } @@ -159,13 +161,14 @@ TEST_CASE_TEMPLATE_DEFINE( sut.vertices(), [&](const gl::types::id_type vertex_id) { const auto expected_deg = expected_deg_list[i]; - const bool result = sut.in_degree(vertex_id) == expected_deg - and sut.out_degree(vertex_id) == expected_deg - and sut.degree(vertex_id) == expected_deg; + const bool result = + sut.in_degree(vertex_id) == expected_deg + and sut.out_degree(vertex_id) == expected_deg + and sut.degree(vertex_id) == expected_deg; ++i; return result; }, - transforms::extract_vertex_id + get_id )); } diff --git a/tests/source/gl/test_vertex_descriptor.cpp b/tests/source/gl/test_vertex_descriptor.cpp index e3d4e109..a757e805 100644 --- a/tests/source/gl/test_vertex_descriptor.cpp +++ b/tests/source/gl/test_vertex_descriptor.cpp @@ -1,4 +1,5 @@ #include "testing/gl/constants.hpp" +#include "testing/gl/functional.hpp" #include "testing/gl/types.hpp" #include @@ -14,13 +15,6 @@ TEST_CASE("id() should return the correct vertex id") { CHECK_EQ(sut.id(), constants::vertex_id_1); } -TEST_CASE("properties should be properly initialized") { - types::visited_property property{constants::visited}; - - const gl::vertex_descriptor sut{constants::vertex_id_1, property}; - CHECK_EQ(&sut.properties(), &property); -} - TEST_CASE("vertex_descriptor objects should be compared by id") { const gl::vertex_descriptor vd_1{constants::vertex_id_1}; const gl::vertex_descriptor vd_2{constants::vertex_id_2}; @@ -38,6 +32,31 @@ TEST_CASE("vertex_descriptor objects should be compared by id") { CHECK_GT(vd_2, vd_1); } +TEST_CASE("vertex_descriptor should be valid only if it has a valid id") { + CHECK(gl::vertex_descriptor{constants::vertex_id_1}.is_valid()); + + CHECK_FALSE(gl::vertex_descriptor<>::invalid().is_valid()); + CHECK_FALSE(gl::vertex_descriptor{constants::invalid_id}.is_valid()); +} + +TEST_CASE("properties should be properly initialized") { + types::visited_property property{constants::visited}; + + const gl::vertex_descriptor sut{constants::vertex_id_1, property}; + CHECK_EQ(&sut.properties(), &property); +} + +TEST_CASE("accessing properties should throw for an invalid vertex") { + using sut_type = gl::vertex_descriptor; + types::visited_property property{constants::visited}; + + CHECK_THROWS_AS(func::discard_result(sut_type::invalid().properties()), std::logic_error); + CHECK_THROWS_AS( + func::discard_result(sut_type{constants::invalid_id, property}.properties()), + std::logic_error + ); +} + TEST_SUITE_END(); // test_vertex_descriptor } // namespace gl_testing From 22c989c1846114cce259c4d1145aebe6e93ddf48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Tue, 25 Nov 2025 22:06:34 +0100 Subject: [PATCH 06/54] YT-CPPHGL-3: Define the vertex and edge descriptor types - Added a `hgl::vertex_descriptor` generic alias for the `gl::vertex_descriptor` type. - Defined the hyperedge directional tag types, i.e. `undirected_t` and `bf_directed_t` - Implemented a simple `hyperedge_descriptor` generic type - Aligned the directional-related type constraints in the `gl` module to match the new `hgl` constraints - Removed redundant `gl` module code --- docs/algorithms.md | 6 +- docs/core_util_types.md | 2 +- docs/graph.md | 28 ++-- docs/graph_elements.md | 18 +++ include/gl/algorithm/coloring.hpp | 2 +- include/gl/algorithm/dijkstra.hpp | 5 +- include/gl/algorithm/impl/bfs.hpp | 4 +- include/gl/algorithm/impl/common.hpp | 2 +- include/gl/algorithm/impl/pfs.hpp | 4 +- include/gl/algorithm/mst.hpp | 10 +- include/gl/algorithm/topological_sort.hpp | 4 +- include/gl/algorithm/types.hpp | 8 +- include/gl/constants.hpp | 8 +- include/gl/edge_descriptor.hpp | 10 +- include/gl/edge_tags.hpp | 30 ++--- include/gl/graph.hpp | 65 +++++++-- include/gl/graph_file_io.hpp | 2 +- include/gl/graph_utility.hpp | 64 --------- include/gl/impl/adjacency_list.hpp | 4 +- include/gl/impl/adjacency_matrix.hpp | 6 +- .../gl/impl/specialized/adjacency_list.hpp | 26 ++-- .../gl/impl/specialized/adjacency_matrix.hpp | 24 ++-- include/gl/io/format.hpp | 2 +- include/gl/topology/binary_tree.hpp | 31 ++--- include/gl/topology/bipartite.hpp | 2 +- include/gl/topology/clique.hpp | 2 +- include/gl/topology/cycle.hpp | 6 +- include/gl/topology/path.hpp | 13 +- include/gl/types/properties.hpp | 6 +- include/gl/types/traits/cache_mode.hpp | 25 ---- include/gl/types/traits/concepts.hpp | 123 ------------------ include/gl/types/traits/type_extractors.hpp | 27 ---- include/gl/types/type_traits.hpp | 121 ++++++++++++++++- include/gl/util/pow.hpp | 16 +-- include/hgl/constants.hpp | 13 ++ include/hgl/hyperedge_tags.hpp | 47 +++++++ include/hgl/hypergraph_elements.hpp | 123 ++++++++++++++++++ include/hgl/types/type_traits.hpp | 13 ++ include/hgl/types/types.hpp | 14 ++ tests/CMakeLists.txt | 8 +- .../gl/{alg_common.hpp => alg_utils.hpp} | 0 tests/include/testing/gl/constants.hpp | 3 - tests/include/testing/gl/io_common.hpp | 2 +- tests/include/testing/gl/types.hpp | 2 - tests/source/gl/test_alg_bfs.cpp | 2 +- tests/source/gl/test_alg_coloring.cpp | 6 +- tests/source/gl/test_alg_dfs.cpp | 2 +- tests/source/gl/test_alg_dijkstra.cpp | 4 +- tests/source/gl/test_alg_mst.cpp | 2 +- tests/source/gl/test_alg_topological_sort.cpp | 2 +- tests/source/gl/test_alg_types.cpp | 2 +- tests/source/gl/test_graph.cpp | 20 +-- .../gl/test_graph_topology_builders.cpp | 2 +- tests/source/hgl/test_hgl_init.cpp | 13 -- tests/source/hgl/test_hypergraph_elements.cpp | 70 ++++++++++ 55 files changed, 623 insertions(+), 433 deletions(-) delete mode 100644 include/gl/graph_utility.hpp delete mode 100644 include/gl/types/traits/cache_mode.hpp delete mode 100644 include/gl/types/traits/concepts.hpp delete mode 100644 include/gl/types/traits/type_extractors.hpp create mode 100644 include/hgl/constants.hpp create mode 100644 include/hgl/hyperedge_tags.hpp create mode 100644 include/hgl/hypergraph_elements.hpp create mode 100644 include/hgl/types/type_traits.hpp create mode 100644 include/hgl/types/types.hpp rename tests/include/testing/gl/{alg_common.hpp => alg_utils.hpp} (100%) delete mode 100644 tests/source/hgl/test_hgl_init.cpp create mode 100644 tests/source/hgl/test_hypergraph_elements.cpp diff --git a/docs/algorithms.md b/docs/algorithms.md index c8e1dd89..b39e2bd3 100644 --- a/docs/algorithms.md +++ b/docs/algorithms.md @@ -286,7 +286,7 @@ This section covers the specific types and type traits used for the algorithm im > The `algorithm::paths_descriptor` structure is defined as follows: > > - *Template parameters*: -> - `VertexDistanceType: type_traits::c_basic_arithmetic` - The type of the distance between vertices in the graph. +> - `VertexDistanceType: type_traits::c_arithmetic` - The type of the distance between vertices in the graph. > - *Type definitions*: > - `distance_type` - An alias for `VertexDistanceType`. > - *Constructors*: @@ -444,7 +444,7 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - *Template parameters*: - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - - `InitQueueRangeType: type_traits::c_sized_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. + - `InitQueueRangeType: type_traits::c_forward_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. - `VisitVertexPredicate: type_traits::c_optional_id_callback` - The vertex visiting unary predicate type. - `VisitCallback: type_traits::c_optional_id_callback` - The vertex visting callback type (arguments: `vertex_id, pred_id`). - `EnqueueVertexPred: type_traits::c_id_callback` - The vertex enqueue predicate type (arguments: `vertex_id, in_edge`) @@ -475,7 +475,7 @@ Additionaly you can use the depth-first/breadth-first search algorithm templates - *Template parameters*: - `GraphType: type_traits::c_graph` - The type of the graph on which the search is performed. - `PQCompare: std::predicate` - The type of the vertex priority queue comparator. - - `InitQueueRangeType: type_traits::c_sized_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. + - `InitQueueRangeType: type_traits::c_forward_range_of` (default = `std::vector`) - The type of the `vertex_info` range which will be inserted into the queue at the beginning of the algorithm. - `VisitVertexPredicate: type_traits::c_optional_id_callback` - The vertex visiting unary predicate type. - `VisitCallback: type_traits::c_optional_id_callback` - The vertex visting callback type (arguments: `vertex, source_id`). - `EnqueueVertexPred: type_traits::c_id_callback` - The vertex enqueue predicate type (arguments: `vertex, in_edge`) diff --git a/docs/core_util_types.md b/docs/core_util_types.md index 79f01137..16e3946e 100644 --- a/docs/core_util_types.md +++ b/docs/core_util_types.md @@ -240,7 +240,7 @@ This section describes the type traits that are associated with the property typ concept c_weight_properties_type = c_properties && requires(Properties p) { typename Properties::weight_type; { p.weight } -> std::same_as; - requires c_basic_arithmetic; + requires c_arithmetic; }; ``` diff --git a/docs/graph.md b/docs/graph.md index c17f8617..4476947a 100644 --- a/docs/graph.md +++ b/docs/graph.md @@ -90,11 +90,11 @@ Based on the specified traits, the `graph` class defines the following types: | `implementation_type` | The underlying data structure used to store the graph's edges and the adjacency information | | `vertex_type` | The type of the vertex element (an instantiation of `vertex_descriptor`) | | `vertex_properties_type` | The type of the properties element associated with each vertex | -| `vertex_iterator_type` | The iterator type used for vertex traversal in the graph | +| `vertex_properties_map_type` | The type of the properties map element associated with each vertex | | `edge_type` | The type of the edge element (an instantiation of `edge_descriptor`) | | `edge_directional_tag` | The `EdgeDirectionalTag` parameter of the `graph_traits` structure | | `edge_properties_type` | The type of the properties element associated with each edge | -| `edge_iterator_type` | The iterator type used for edge traversal in the graph | +| `edge_properties_map_type` | The type of the properties map element associated with each edge |

@@ -106,7 +106,7 @@ Based on the specified traits, the `graph` class defines the following types: - **`graph()`**: - Default constructor. Creates an empty graph with no vertices or edges. -- **`graph(n_vertices)`**: +- **`explicit graph(n_vertices)`**: - Constructs a graph with the specified number of vertices. Each vertex is initialized with default properties and no adjacent edges. > [!IMPORTANT] @@ -195,6 +195,9 @@ Based on the specified traits, the `graph` class defines the following types: > [!IMPORTANT] > Simliarily to the graph constructors using the `add_vertices*` methods is more efficient than calling `add_vertex` multiple times which might cause more vector reallocations. +> [!WARNING] +> Removing vertices may invalidate previously created vertex and edge descriptor objects. + - **`graph.remove_vertex(vertex_id)`**: - *Description*: Removes the vertex with the given ID from the graph. - *Parameters*: @@ -210,15 +213,15 @@ Based on the specified traits, the `graph` class defines the following types: - **`graph.remove_vertices_from(vertex_id_range)`**: - *Description*: Removes multiple vertices from the graph based on a range of vertex IDs. The IDs are sorted in descending order and duplicate IDs are removed before deletion. - *Template parameters*: - - `IdRange: type_traits::c_sized_range_of` – A range of vertex IDs, which must satisfy the size and type constraints. + - `IdRange: type_traits::c_forward_range_of` – A range of vertex IDs, which must satisfy the size and type constraints. - *Parameters*: - `vertex_id_range: const IdRange&` – A range of vertex IDs to be removed. - *Return type*: `void` -- **`graph.remove_vertices_from(vertex_ref_range)`**: +- **`graph.remove_vertices_from(vertex_range)`**: - *Description*: Removes multiple vertices from the graph based on a range of vertex references. The references are sorted in descending order and duplicates are removed before deletion. - *Parameters*: - - `vertex_ref_range: const type_traits::c_sized_range_of auto&` – A range of vertex references to be removed. + - `vertex_range: const type_traits::c_forward_range_of auto&` – A range of vertex references to be removed. - *Return type*: `void` - **`graph.in_degree(vertex) const`**: @@ -434,6 +437,9 @@ Based on the specified traits, the `graph` class defines the following types: - `target: const vertex_type&` – the target vertex. - *Return type*: `std::vector` +> [!WARNING] +> Removing edges may invalidate previously created edge descriptor objects. + - **`graph.remove_edge(edge)`**: - *Description*: Removes the specified edge from the graph. - *Parameters*: @@ -524,10 +530,6 @@ Based on the specified traits, the `graph` class defines the following types:

-## Additional utility - -In addition to the core functionality of the `graph` class, the [gl/graph_utility.hpp](/include/gl/graph_utility.hpp) file provides a set of utility functions and type traits that offer extended support for graph manipulation and property handling. Below is an overview of the key utilities provided. - ### Type traits To write safe and more expressive graph utility of your own, you can use the defined concepts and type traits: @@ -537,9 +539,9 @@ To write safe and more expressive graph utility of your own, you can use the def | **Trait** | **Description** | | :- | :- | -| `c_graph` | Ensures that the template parameter `T` is a specialization of the `graph` class | -| `c_directed_graph` | Equivalent to `c_graph and is_directed_v`
Ensures that the template parameter `T` is a *directed* specialization of the `graph` class | -| `c_undirected_graph` | Equivalent to `c_graph and is_undirected_v`
Ensures that the template parameter `T` is an *undirected* specialization of the `graph` class | +| `c_graph` | Ensures that the template parameter `G` is a specialization of the `graph` class | +| `c_directed_graph` | Equivalent to `c_graph and c_directed_edge`
Ensures that the template parameter `G` is a *directed* specialization of the `graph` class | +| `c_undirected_graph` | Equivalent to `c_graph and c_undirected_edge`
Ensures that the template parameter `G` is an *undirected* specialization of the `graph` class | > [!TIP] > More (not graph class specific) type traits and concepts are defined in the [gl/types/traits/](/include/gl/types/traits/) directory. diff --git a/docs/graph_elements.md b/docs/graph_elements.md index 0741011e..f043f1e4 100644 --- a/docs/graph_elements.md +++ b/docs/graph_elements.md @@ -262,6 +262,24 @@ The destructor is *defaulted*, allowing proper cleanup of the `edge_descriptor` template using undirected_edge = edge_descriptor; ``` + +- `type_traits::c_directed_edge`: A type constraint that accepts only directed edges. + + ```cpp + template + concept c_directed_edge = + c_instantiation_of + and std::same_as; + ``` + +- `type_traits::c_undirected_edge`: A type constraint that accepts only undirected edges. + + ```cpp + template + concept c_undirected_edge = + c_instantiation_of + and std::same_as; + ```

diff --git a/include/gl/algorithm/coloring.hpp b/include/gl/algorithm/coloring.hpp index 2787be70..d54d80ac 100644 --- a/include/gl/algorithm/coloring.hpp +++ b/include/gl/algorithm/coloring.hpp @@ -78,7 +78,7 @@ requires(type_traits::c_binary_color_properties_type namespace gl::algorithm { -template +template struct paths_descriptor : public predecessors_descriptor { using predecessor_type = typename predecessors_descriptor::predecessor_type; using distance_type = VertexDistanceType; @@ -97,7 +96,7 @@ template < const auto pred_id = in_edge.incident_vertex(vertex_id); const auto edge_weight = get_weight(in_edge); - if (edge_weight < constants::zero) { + if (edge_weight < 0) { negative_edge.emplace(in_edge); return predicate_result::unknown; } diff --git a/include/gl/algorithm/impl/bfs.hpp b/include/gl/algorithm/impl/bfs.hpp index e729df3b..0ae99e66 100644 --- a/include/gl/algorithm/impl/bfs.hpp +++ b/include/gl/algorithm/impl/bfs.hpp @@ -12,7 +12,7 @@ namespace gl::algorithm::impl { template < type_traits::c_graph GraphType, - type_traits::c_sized_range_of InitQueueRangeType = + type_traits::c_forward_range_of InitQueueRangeType = std::vector, type_traits::c_optional_id_callback VisitVertexPredicate, type_traits::c_optional_id_callback VisitCallback, @@ -29,7 +29,7 @@ bool bfs( const PreVisitCallback& pre_visit = {}, const PostVisitCallback& post_visit = {} ) { - if (initial_queue_content.size() == constants::default_size) + if (std::ranges::empty(initial_queue_content)) return false; // prepare the vertex queue diff --git a/include/gl/algorithm/impl/common.hpp b/include/gl/algorithm/impl/common.hpp index 36d5d487..463cc92b 100644 --- a/include/gl/algorithm/impl/common.hpp +++ b/include/gl/algorithm/impl/common.hpp @@ -24,7 +24,7 @@ init_return_value(const GraphType& graph) { } template < - type_traits::c_sized_range_of InitRangeType = + type_traits::c_forward_range_of InitRangeType = std::vector> [[nodiscard]] gl_attr_force_inline InitRangeType init_range(types::id_type root_vertex_id) { return InitRangeType{algorithm::vertex_info{root_vertex_id}}; diff --git a/include/gl/algorithm/impl/pfs.hpp b/include/gl/algorithm/impl/pfs.hpp index e943314e..615f43ae 100644 --- a/include/gl/algorithm/impl/pfs.hpp +++ b/include/gl/algorithm/impl/pfs.hpp @@ -13,7 +13,7 @@ namespace gl::algorithm::impl { template < type_traits::c_graph GraphType, std::predicate PQCompare, - type_traits::c_sized_range_of InitQueueRangeType = + type_traits::c_forward_range_of InitQueueRangeType = std::vector, type_traits::c_optional_id_callback VisitVertexPredicate, type_traits::c_optional_id_callback VisitCallback, @@ -31,7 +31,7 @@ bool pfs( const PreVisitCallback& pre_visit = {}, const PostVisitCallback& post_visit = {} ) { - if (initial_queue_content.size() == constants::default_size) + if (std::ranges::empty(initial_queue_content)) return false; // prepare the vertex queue diff --git a/include/gl/algorithm/mst.hpp b/include/gl/algorithm/mst.hpp index 4a513d84..2df54c3e 100644 --- a/include/gl/algorithm/mst.hpp +++ b/include/gl/algorithm/mst.hpp @@ -19,11 +19,11 @@ struct mst_descriptor { using weight_type = types::vertex_distance_type; mst_descriptor(const types::size_type n_vertices) { - edges.reserve(n_vertices - constants::one); + edges.reserve(n_vertices - 1uz); } std::vector edges; - weight_type weight = static_cast(constants::zero); + weight_type weight = static_cast(0); }; template @@ -53,14 +53,14 @@ template queue_type edge_queue; // insert the edges adjacent to the root vertex to the queue - const types::id_type root_id = root_id_opt.value_or(constants::zero); + const types::id_type root_id = root_id_opt.value_or(constants::initial_id); for (const auto& edge : graph.adjacent_edges(root_id)) edge_queue.emplace(edge); // mark the root vertex as visited visited[root_id] = true; - types::size_type n_vertices_in_mst = constants::one; + types::size_type n_vertices_in_mst = 1uz; // find the mst while (n_vertices_in_mst < n_vertices) { @@ -104,7 +104,7 @@ requires type_traits::c_has_numeric_limits_max> min_cost_edges(n_vertices, std::nullopt); // set the distance to the root vertex to 0 - min_cost.at(root_id_opt.value_or(constants::zero)) = constants::zero; + min_cost.at(root_id_opt.value_or(constants::initial_id)) = static_cast(0); auto heap_comparator = [&min_cost](const types::id_type lhs, const types::id_type rhs) { return min_cost[lhs] > min_cost[rhs]; // min-heap based on min_cost diff --git a/include/gl/algorithm/topological_sort.hpp b/include/gl/algorithm/topological_sort.hpp index 5ad23390..3d439985 100644 --- a/include/gl/algorithm/topological_sort.hpp +++ b/include/gl/algorithm/topological_sort.hpp @@ -27,7 +27,7 @@ template < std::vector source_vertex_list; source_vertex_list.reserve(graph.n_vertices()); for (const auto id : graph.vertex_ids()) - if (in_degree_map[id] == constants::default_size) + if (in_degree_map[id] == 0uz) source_vertex_list.emplace_back(id); std::optional> topological_order_opt = @@ -49,7 +49,7 @@ template < -> predicate_result { // enqueue predicate if (in_edge.is_loop()) return false; - return --in_degree_map[vertex_id] == constants::default_size; + return --in_degree_map[vertex_id] == 0uz; }, pre_visit, post_visit diff --git a/include/gl/algorithm/types.hpp b/include/gl/algorithm/types.hpp index 0152514f..404b509b 100644 --- a/include/gl/algorithm/types.hpp +++ b/include/gl/algorithm/types.hpp @@ -4,7 +4,7 @@ #pragma once -#include "gl/graph_utility.hpp" +#include "gl/graph.hpp" #include @@ -101,11 +101,13 @@ concept c_optional_id_callback = c_optional_callback concept c_edge_callback = - std::is_invocable_r_v; + c_graph + and std::is_invocable_r_v; template concept c_optional_edge_callback = - c_optional_callback; + c_graph + and c_optional_callback; } // namespace type_traits diff --git a/include/gl/constants.hpp b/include/gl/constants.hpp index 1422aad9..6085d4d1 100644 --- a/include/gl/constants.hpp +++ b/include/gl/constants.hpp @@ -10,13 +10,7 @@ namespace gl::constants { -inline constexpr types::size_type zero{0ull}; -inline constexpr types::size_type one{1ull}; -inline constexpr types::size_type two{2ull}; - -inline constexpr types::size_type default_size{zero}; -inline constexpr types::size_type begin_idx{zero}; -inline constexpr types::id_type initial_id{zero}; +inline constexpr types::id_type initial_id{std::numeric_limits::min()}; inline constexpr types::id_type invalid_id{std::numeric_limits::max()}; } // namespace gl::constants diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index 68d4f64b..71962133 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -68,13 +68,13 @@ class edge_descriptor final { ~edge_descriptor() = default; [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept - requires(type_traits::is_directed_v) + requires(type_traits::c_directed_edge) { return this->_id == other._id and (this->_vertices == other._vertices); } [[nodiscard]] bool operator==(const edge_descriptor& other) const noexcept - requires(type_traits::is_undirected_v) + requires(type_traits::c_undirected_edge) { return this->_id == other._id and (this->_vertices == other._vertices @@ -86,11 +86,11 @@ class edge_descriptor final { } [[nodiscard]] constexpr bool is_directed() const noexcept { - return type_traits::is_directed_v; + return type_traits::c_directed_edge; } [[nodiscard]] constexpr bool is_undirected() const noexcept { - return type_traits::is_undirected_v; + return type_traits::c_undirected_edge; } [[nodiscard]] bool is_valid() const noexcept { @@ -157,7 +157,7 @@ class edge_descriptor final { return this->_vertices.first == this->_vertices.second; } - [[nodiscard]] gl_attr_force_inline properties_ref_type properties() const { + [[nodiscard]] properties_ref_type properties() const { if (not this->is_valid()) throw std::logic_error("Cannot access properties of an invalid edge"); diff --git a/include/gl/edge_tags.hpp b/include/gl/edge_tags.hpp index c0923eee..94cf71d4 100644 --- a/include/gl/edge_tags.hpp +++ b/include/gl/edge_tags.hpp @@ -21,24 +21,20 @@ concept c_edge_directional_tag = c_one_of; } // namespace type_traits -template +template class edge_descriptor; namespace type_traits { -template -inline constexpr bool is_directed_v = false; - -template EdgeType> -inline constexpr bool is_directed_v = - std::is_same_v; - -template -inline constexpr bool is_undirected_v = false; +template +concept c_directed_edge = + c_instantiation_of + and std::same_as; -template EdgeType> -inline constexpr bool is_undirected_v = - std::is_same_v; +template +concept c_undirected_edge = + c_instantiation_of + and std::same_as; } // namespace type_traits @@ -46,7 +42,7 @@ struct directed_t { using type = std::type_identity_t; template EdgeType> - requires(type_traits::is_directed_v) + requires(type_traits::c_directed_edge) [[nodiscard]] gl_attr_force_inline static bool is_incident_from( const EdgeType& edge, const types::id_type vertex_id ) { @@ -54,7 +50,7 @@ struct directed_t { } template EdgeType> - requires(type_traits::is_directed_v) + requires(type_traits::c_directed_edge) [[nodiscard]] gl_attr_force_inline static bool is_incident_to( const EdgeType& edge, const types::id_type vertex_id ) { @@ -66,7 +62,7 @@ struct undirected_t { using type = std::type_identity_t; template EdgeType> - requires(type_traits::is_undirected_v) + requires(type_traits::c_undirected_edge) [[nodiscard]] gl_attr_force_inline static bool is_incident_from( const EdgeType& edge, const types::id_type vertex_id ) { @@ -74,7 +70,7 @@ struct undirected_t { } template EdgeType> - requires(type_traits::is_undirected_v) + requires(type_traits::c_undirected_edge) [[nodiscard]] gl_attr_force_inline static bool is_incident_to( const EdgeType& edge, const types::id_type vertex_id ) { diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 07e3d93d..db1c0ee6 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -176,7 +176,7 @@ class graph final { } void remove_vertices_from( - const type_traits::c_sized_range_of auto& vertex_id_range + const type_traits::c_forward_range_of auto& vertex_id_range ) { // sorts the ids in a descending order and removes duplicate ids std::set> vertex_id_set( @@ -191,7 +191,7 @@ class graph final { void remove_vertices_from(const type_traits::c_sized_range_of auto& vertex_range) { // TODO: optimize // sort the ids in a descending order and removes duplicate ids - std::set vertex_set( + std::set> vertex_set( std::ranges::begin(vertex_range), std::ranges::end(vertex_range) ); for (const auto& vertex : vertex_set) @@ -443,7 +443,7 @@ class graph final { this->_verify_vertex_id(target_id); - if constexpr (type_traits::is_directed_v) + if constexpr (type_traits::c_directed_edge) return this->has_edge(source_id, target_id) or this->has_edge(target_id, source_id); else return this->has_edge(source_id, target_id); @@ -528,7 +528,7 @@ class graph final { private: [[nodiscard]] static constexpr std::string _directed_type_str() { - return type_traits::is_directed_v ? "directed" : "undirected"; + return type_traits::c_directed_edge ? "directed" : "undirected"; } // --- graph element verification methods --- @@ -605,7 +605,7 @@ class graph final { // print graph size os << std::format( "{} {} {} {} {}\n", - static_cast(type_traits::is_directed_v), + static_cast(type_traits::c_directed_edge), this->n_vertices(), this->n_unique_edges(), static_cast(with_vertex_properties), @@ -651,7 +651,7 @@ class graph final { bool directed; is >> directed; - if (directed != type_traits::is_directed_v) + if (directed != type_traits::c_directed_edge) throw std::ios_base::failure(std::format( "Invalid graph specification: directional tag does not match - should be {}", _directed_type_str() @@ -674,7 +674,7 @@ class graph final { else { // read vertex properties and use them to initialze the vertices std::vector vertex_properties(n_vertices); - for (types::size_type i = constants::begin_idx; i < n_vertices; ++i) + for (types::id_type i = 0uz; i < n_vertices; ++i) is >> vertex_properties[i]; this->add_vertices_with(vertex_properties); } @@ -696,7 +696,7 @@ class graph final { types::id_type source_id, target_id; edge_properties_type properties; - for (types::size_type i = constants::begin_idx; i < n_edges; ++i) { + for (types::size_type _ = 0uz; _ < n_edges; ++_) { is >> source_id >> target_id >> properties; this->add_edge(source_id, target_id, properties); } @@ -706,7 +706,7 @@ class graph final { // read the edges types::id_type source_id, target_id; - for (types::size_type i = constants::begin_idx; i < n_edges; ++i) { + for (types::size_type _ = 0uz; _ < n_edges; ++_) { is >> source_id >> target_id; this->add_edge(source_id, target_id); } @@ -722,4 +722,51 @@ class graph final { implementation_type _impl{}; }; +// --- general graph utility --- + +namespace type_traits { + +template +concept c_graph = c_instantiation_of; + +template +concept c_directed_graph = c_graph and c_directed_edge; + +template +concept c_undirected_graph = c_graph and c_undirected_edge; + +} // namespace type_traits + +// --- utility associated with graph's elements' properties --- + +namespace types { + +using default_vertex_distance_type = std::int64_t; + +template +struct vertex_distance { + using type = default_vertex_distance_type; +}; + +template +requires(type_traits::c_weight_properties_type) +struct vertex_distance { + using type = typename GraphType::edge_properties_type::weight_type; +}; + +template +using vertex_distance_type = typename vertex_distance::type; + +} // namespace types + +template +[[nodiscard]] gl_attr_force_inline types::vertex_distance_type get_weight( + const typename GraphType::edge_type& edge +) { + if constexpr (type_traits::c_weight_properties_type) + return edge.properties().weight; + else + return static_cast(1ll); +} + } // namespace gl diff --git a/include/gl/graph_file_io.hpp b/include/gl/graph_file_io.hpp index 4ffe1b00..f7f56296 100644 --- a/include/gl/graph_file_io.hpp +++ b/include/gl/graph_file_io.hpp @@ -4,7 +4,7 @@ #pragma once -#include "graph_utility.hpp" +#include "graph.hpp" #include #include diff --git a/include/gl/graph_utility.hpp b/include/gl/graph_utility.hpp deleted file mode 100644 index 337002d6..00000000 --- a/include/gl/graph_utility.hpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "graph.hpp" - -namespace gl { - -// --- general graph utility --- - -namespace type_traits { - -template -concept c_graph = c_instantiation_of; - -template -inline constexpr bool is_directed_v = is_directed_v; - -template -inline constexpr bool is_undirected_v = is_undirected_v; - -template -concept c_directed_graph = c_graph and is_directed_v; - -template -concept c_undirected_graph = c_graph and is_undirected_v; - -} // namespace type_traits - -// --- utility associated with graph's elements' properties --- - -namespace types { - -using default_vertex_distance_type = std::int64_t; - -template -struct vertex_distance { - using type = default_vertex_distance_type; -}; - -template -requires(type_traits::c_weight_properties_type) -struct vertex_distance { - using type = typename GraphType::edge_properties_type::weight_type; -}; - -template -using vertex_distance_type = typename vertex_distance::type; - -} // namespace types - -template -[[nodiscard]] gl_attr_force_inline types::vertex_distance_type get_weight( - const typename GraphType::edge_type& edge -) { - if constexpr (type_traits::c_weight_properties_type) - return edge.properties().weight; - else - return static_cast(1ll); -} - -} // namespace gl diff --git a/include/gl/impl/adjacency_list.hpp b/include/gl/impl/adjacency_list.hpp index b954fda8..4e5e1880 100644 --- a/include/gl/impl/adjacency_list.hpp +++ b/include/gl/impl/adjacency_list.hpp @@ -88,9 +88,9 @@ class adjacency_list final { } gl_attr_force_inline void add_edges_from( - const type_traits::c_sized_range_of auto& edge_ids, + const type_traits::c_forward_range_of auto& edge_ids, const types::id_type source_id, - const type_traits::c_sized_range_of auto& target_ids + const type_traits::c_forward_range_of auto& target_ids ) { specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } diff --git a/include/gl/impl/adjacency_matrix.hpp b/include/gl/impl/adjacency_matrix.hpp index ba72640f..9d89af22 100644 --- a/include/gl/impl/adjacency_matrix.hpp +++ b/include/gl/impl/adjacency_matrix.hpp @@ -55,7 +55,7 @@ class adjacency_matrix final { for (auto& row : this->_matrix) row.resize(new_n_vertices, constants::invalid_id); - for (types::size_type _ = constants::begin_idx; _ < n; ++_) + for (types::size_type _ = 0uz; _ < n; ++_) this->_matrix.emplace_back(new_n_vertices, constants::invalid_id); } @@ -101,9 +101,9 @@ class adjacency_matrix final { } gl_attr_force_inline void add_edges_from( - const type_traits::c_sized_range_of auto& edge_ids, + const type_traits::c_forward_range_of auto& edge_ids, const types::id_type source_id, - const type_traits::c_sized_range_of auto& target_ids + const type_traits::c_forward_range_of auto& target_ids ) { specialized_impl::add_edges_from(*this, edge_ids, source_id, target_ids); } diff --git a/include/gl/impl/specialized/adjacency_list.hpp b/include/gl/impl/specialized/adjacency_list.hpp index 7567a9a2..ca8ed21b 100644 --- a/include/gl/impl/specialized/adjacency_list.hpp +++ b/include/gl/impl/specialized/adjacency_list.hpp @@ -47,7 +47,7 @@ namespace detail { } // namespace detail template AdjacencyList> -requires(type_traits::is_directed_v) +requires(type_traits::c_directed_edge) struct directed_adjacency_list { using impl_type = AdjacencyList; using edge_type = typename impl_type::edge_type; @@ -55,7 +55,7 @@ struct directed_adjacency_list { [[nodiscard]] static types::size_type in_degree( const impl_type& self, const types::id_type vertex_id ) { - types::size_type in_deg = constants::default_size; + types::size_type in_deg = 0uz; for (const auto& adjacent_edges : self._list) in_deg += std::ranges::count(adjacent_edges, vertex_id, &adjacency_list_item::target_id); @@ -76,7 +76,7 @@ struct directed_adjacency_list { } [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { - std::vector in_degree_map(self._list.size(), constants::zero); + std::vector in_degree_map(self._list.size(), 0uz); for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { std::ranges::for_each(self._list[id], [&in_degree_map](const auto& item) { @@ -96,7 +96,7 @@ struct directed_adjacency_list { } [[nodiscard]] static std::vector degree_map(const impl_type& self) { - std::vector degree_map(self._list.size(), constants::zero); + std::vector degree_map(self._list.size(), 0uz); for (types::id_type id = constants::initial_id; id < self._list.size(); ++id) { degree_map[id] += self._list[id].size(); @@ -145,9 +145,9 @@ struct directed_adjacency_list { static void add_edges_from( impl_type& self, - const type_traits::c_sized_range_of auto& edge_ids, + const type_traits::c_forward_range_of auto& edge_ids, const types::id_type source_id, - const type_traits::c_sized_range_of auto& target_ids + const type_traits::c_forward_range_of auto& target_ids ) { auto& adjacent_edges_source = self._list[source_id]; adjacent_edges_source.reserve(adjacent_edges_source.size() + target_ids.size()); @@ -163,7 +163,7 @@ struct directed_adjacency_list { }; template AdjacencyList> -requires(type_traits::is_undirected_v) +requires(type_traits::c_undirected_edge) struct undirected_adjacency_list { using impl_type = AdjacencyList; using edge_type = typename impl_type::edge_type; @@ -183,9 +183,9 @@ struct undirected_adjacency_list { [[nodiscard]] static types::size_type degree( const impl_type& self, const types::id_type vertex_id ) { - types::size_type degree = constants::default_size; + types::size_type degree = 0uz; for (const auto& item : self._list[vertex_id]) - degree += constants::one + static_cast(item.target_id == vertex_id); + degree += 1uz + static_cast(item.target_id == vertex_id); return degree; } @@ -242,9 +242,9 @@ struct undirected_adjacency_list { static void add_edges_from( impl_type& self, - const type_traits::c_sized_range_of auto& edge_ids, + const type_traits::c_forward_range_of auto& edge_ids, const types::id_type source_id, - const type_traits::c_sized_range_of auto& target_ids + const type_traits::c_forward_range_of auto& target_ids ) { auto& adjacent_edges_source = self._list[source_id]; adjacent_edges_source.reserve(adjacent_edges_source.size() + target_ids.size()); @@ -276,13 +276,13 @@ struct list_impl_traits { }; template AdjacencyList> -requires(type_traits::is_directed_v) +requires(type_traits::c_directed_edge) struct list_impl_traits { using type = directed_adjacency_list; }; template AdjacencyList> -requires(type_traits::is_undirected_v) +requires(type_traits::c_undirected_edge) struct list_impl_traits { using type = undirected_adjacency_list; }; diff --git a/include/gl/impl/specialized/adjacency_matrix.hpp b/include/gl/impl/specialized/adjacency_matrix.hpp index 911a02b9..f7b15c1f 100644 --- a/include/gl/impl/specialized/adjacency_matrix.hpp +++ b/include/gl/impl/specialized/adjacency_matrix.hpp @@ -45,7 +45,7 @@ inline void check_edge_override( } // namespace detail template AdjacencyMatrix> -requires(type_traits::is_directed_v) +requires(type_traits::c_directed_edge) struct directed_adjacency_matrix { using impl_type = AdjacencyMatrix; using vertex_type = typename impl_type::vertex_type; @@ -69,7 +69,7 @@ struct directed_adjacency_matrix { [[nodiscard]] gl_attr_force_inline static types::size_type degree( const impl_type& self, const types::id_type vertex_id ) { - types::size_type deg = constants::zero; + types::size_type deg = 0uz; for (types::id_type v_id = constants::initial_id; v_id < self._matrix.size(); ++v_id) deg += static_cast( self._matrix[vertex_id][v_id] != constants::invalid_id @@ -82,7 +82,7 @@ struct directed_adjacency_matrix { } [[nodiscard]] static std::vector in_degree_map(const impl_type& self) { - std::vector in_degree_map(self._matrix.size(), constants::zero); + std::vector in_degree_map(self._matrix.size(), 0uz); for (const auto& row : self._matrix) for (auto [target_id, edge_id] : std::views::enumerate(row)) @@ -99,7 +99,7 @@ struct directed_adjacency_matrix { } [[nodiscard]] static std::vector degree_map(const impl_type& self) { - std::vector degree_map(self._matrix.size(), constants::zero); + std::vector degree_map(self._matrix.size(), 0uz); for (types::id_type source_id = constants::initial_id; source_id < self._matrix.size(); ++source_id) { @@ -147,9 +147,9 @@ struct directed_adjacency_matrix { static void add_edges_from( impl_type& self, - const type_traits::c_sized_range_of auto& edge_ids, + const type_traits::c_forward_range_of auto& edge_ids, const types::id_type source_id, - const type_traits::c_sized_range_of auto& target_ids + const type_traits::c_forward_range_of auto& target_ids ) { for (const auto target_id : target_ids) detail::check_edge_override(self._matrix, source_id, target_id); @@ -165,7 +165,7 @@ struct directed_adjacency_matrix { }; template AdjacencyMatrix> -requires(type_traits::is_undirected_v) +requires(type_traits::c_undirected_edge) struct undirected_adjacency_matrix { using impl_type = AdjacencyMatrix; using vertex_type = typename impl_type::vertex_type; @@ -206,7 +206,7 @@ struct undirected_adjacency_matrix { } [[nodiscard]] static std::vector degree_map(const impl_type& self) { - std::vector degree_map(self._matrix.size(), constants::zero); + std::vector degree_map(self._matrix.size(), 0uz); for (types::id_type source_id = constants::initial_id; source_id < self._matrix.size(); ++source_id) { @@ -252,9 +252,9 @@ struct undirected_adjacency_matrix { static void add_edges_from( impl_type& self, - const type_traits::c_sized_range_of auto& edge_ids, + const type_traits::c_forward_range_of auto& edge_ids, const types::id_type source_id, - const type_traits::c_sized_range_of auto& target_ids + const type_traits::c_forward_range_of auto& target_ids ) { for (const auto target_id : target_ids) detail::check_edge_override(self._matrix, source_id, target_id); @@ -286,13 +286,13 @@ struct matrix_impl_traits { }; template AdjacencyMatrix> -requires(type_traits::is_directed_v) +requires(type_traits::c_directed_edge) struct matrix_impl_traits { using type = directed_adjacency_matrix; }; template AdjacencyMatrix> -requires(type_traits::is_undirected_v) +requires(type_traits::c_undirected_edge) struct matrix_impl_traits { using type = undirected_adjacency_matrix; }; diff --git a/include/gl/io/format.hpp b/include/gl/io/format.hpp index 0de7e9ad..fd4f5178 100644 --- a/include/gl/io/format.hpp +++ b/include/gl/io/format.hpp @@ -5,7 +5,7 @@ #pragma once #include "gl/attributes/force_inline.hpp" -#include "gl/types/traits/concepts.hpp" +#include "gl/types/type_traits.hpp" namespace gl::io { diff --git a/include/gl/topology/binary_tree.hpp b/include/gl/topology/binary_tree.hpp index 0b71f12e..0ef3869d 100644 --- a/include/gl/topology/binary_tree.hpp +++ b/include/gl/topology/binary_tree.hpp @@ -5,7 +5,7 @@ #pragma once #include "gl/constants.hpp" -#include "gl/graph_utility.hpp" +#include "gl/graph.hpp" #include "gl/util/pow.hpp" namespace gl::topology { @@ -13,12 +13,10 @@ namespace gl::topology { namespace detail { [[nodiscard]] gl_attr_force_inline auto get_binary_target_ids(const types::size_type source_id) { - return std::make_pair( - constants::two * source_id + constants::one, constants::two * source_id + constants::two - ); + return std::make_pair(2uz * source_id + 1uz, 2uz * source_id + 2uz); } -constexpr types::size_type min_non_trivial_bin_tree_depth = constants::two; +constexpr types::size_type min_non_trivial_bin_tree_depth = 2uz; } // namespace detail @@ -27,16 +25,16 @@ template if (depth < detail::min_non_trivial_bin_tree_depth) return GraphType{depth}; - constexpr types::size_type base = constants::two; - constexpr types::size_type i_begin = constants::zero; - const types::size_type i_end = depth - constants::one; + constexpr types::size_type base = 2uz; + constexpr types::size_type i_begin = 0uz; + const types::size_type i_end = depth - 1uz; - const auto n_vertices = util::upow_sum(constants::two, i_begin, i_end); + const auto n_vertices = util::upow_sum(base, i_begin, i_end); GraphType graph{n_vertices}; const auto n_source_vertices = n_vertices - util::upow(base, i_end); - for (types::id_type source_id = constants::zero; source_id < n_source_vertices; ++source_id) { + for (types::id_type source_id = 0uz; source_id < n_source_vertices; ++source_id) { const auto target_ids = detail::get_binary_target_ids(source_id); graph.add_edges_from( source_id, std::vector{target_ids.first, target_ids.second} @@ -48,21 +46,20 @@ template template [[nodiscard]] GraphType bidirectional_regular_binary_tree(const types::size_type depth) { - if constexpr (type_traits::is_directed_v) { + if constexpr (type_traits::c_directed_graph) { if (depth < detail::min_non_trivial_bin_tree_depth) return GraphType{depth}; - constexpr types::size_type base = constants::two; - constexpr types::size_type i_begin = constants::zero; - const types::size_type i_end = depth - constants::one; + constexpr types::size_type base = 2uz; + constexpr types::size_type i_begin = 0uz; + const types::size_type i_end = depth - 1uz; - const auto n_vertices = util::upow_sum(constants::two, i_begin, i_end); + const auto n_vertices = util::upow_sum(base, i_begin, i_end); GraphType graph{n_vertices}; const auto n_source_vertices = n_vertices - util::upow(base, i_end); - for (types::id_type source_id = constants::zero; source_id < n_source_vertices; - ++source_id) { + for (types::id_type source_id = 0uz; source_id < n_source_vertices; ++source_id) { const auto target_ids = detail::get_binary_target_ids(source_id); graph.add_edges_from( source_id, std::vector{target_ids.first, target_ids.second} diff --git a/include/gl/topology/bipartite.hpp b/include/gl/topology/bipartite.hpp index 16480fbd..d00fa5bf 100644 --- a/include/gl/topology/bipartite.hpp +++ b/include/gl/topology/bipartite.hpp @@ -19,7 +19,7 @@ template for (types::id_type source_id = constants::initial_id; source_id < n_vertices_a; ++source_id) { for (types::id_type target_id = n_vertices_a; target_id < n_vertices; ++target_id) { graph.add_edge(source_id, target_id); - if constexpr (type_traits::is_directed_v) + if constexpr (type_traits::c_directed_graph) graph.add_edge(target_id, source_id); } } diff --git a/include/gl/topology/clique.hpp b/include/gl/topology/clique.hpp index 57bd0747..2df2d875 100644 --- a/include/gl/topology/clique.hpp +++ b/include/gl/topology/clique.hpp @@ -16,7 +16,7 @@ template for (types::id_type source_id = constants::initial_id; source_id < n_vertices; ++source_id) { for (types::id_type target_id = constants::initial_id; target_id < source_id; ++target_id) { graph.add_edge(source_id, target_id); - if constexpr (type_traits::is_directed_v) + if constexpr (type_traits::c_directed_graph) graph.add_edge(target_id, source_id); } } diff --git a/include/gl/topology/cycle.hpp b/include/gl/topology/cycle.hpp index 4e0033fb..d0bfb8c9 100644 --- a/include/gl/topology/cycle.hpp +++ b/include/gl/topology/cycle.hpp @@ -14,19 +14,19 @@ template GraphType graph{n_vertices}; for (types::id_type source_id = constants::initial_id; source_id < n_vertices; ++source_id) - graph.add_edge(source_id, (source_id + constants::one) % n_vertices); + graph.add_edge(source_id, (source_id + 1uz) % n_vertices); return graph; } template [[nodiscard]] GraphType bidirectional_cycle(const types::size_type n_vertices) { - if constexpr (type_traits::is_directed_v) { + if constexpr (type_traits::c_directed_graph) { GraphType graph{n_vertices}; for (types::id_type source_id = constants::initial_id; source_id < n_vertices; ++source_id) { - const auto target_id = (source_id + constants::one) % n_vertices; + const auto target_id = (source_id + 1uz) % n_vertices; graph.add_edge(source_id, target_id); graph.add_edge(target_id, source_id); } diff --git a/include/gl/topology/path.hpp b/include/gl/topology/path.hpp index 46cea023..5088e89f 100644 --- a/include/gl/topology/path.hpp +++ b/include/gl/topology/path.hpp @@ -13,22 +13,19 @@ template [[nodiscard]] GraphType path(const types::size_type n_vertices) { GraphType graph{n_vertices}; - for (types::id_type source_id = constants::initial_id; source_id < n_vertices - constants::one; - ++source_id) - graph.add_edge(source_id, source_id + constants::one); + for (types::id_type source_id = 0uz; source_id < n_vertices - 1uz; ++source_id) + graph.add_edge(source_id, source_id + 1uz); return graph; } template [[nodiscard]] GraphType bidirectional_path(const types::size_type n_vertices) { - if constexpr (type_traits::is_directed_v) { + if constexpr (type_traits::c_directed_graph) { GraphType graph{n_vertices}; - for (types::id_type source_id = constants::initial_id; - source_id < n_vertices - constants::one; - ++source_id) { - const auto target_id = source_id + constants::one; + for (types::id_type source_id = 0uz; source_id < n_vertices - 1uz; ++source_id) { + const auto target_id = source_id + 1uz; graph.add_edge(source_id, target_id); graph.add_edge(target_id, source_id); } diff --git a/include/gl/types/properties.hpp b/include/gl/types/properties.hpp index 4fd0b0bf..987ea345 100644 --- a/include/gl/types/properties.hpp +++ b/include/gl/types/properties.hpp @@ -5,7 +5,7 @@ #pragma once #include "gl/attributes/force_inline.hpp" -#include "traits/concepts.hpp" +#include "type_traits.hpp" #include #include @@ -223,7 +223,7 @@ struct binary_color_property { // --- edge properties --- -template +template struct weight_property { using weight_type = WeightType; weight_type weight = static_cast(1ll); @@ -276,7 +276,7 @@ template concept c_weight_properties_type = c_properties and requires(Properties p) { typename Properties::weight_type; { p.weight } -> std::same_as; - requires c_basic_arithmetic; + requires c_arithmetic; }; } // namespace type_traits diff --git a/include/gl/types/traits/cache_mode.hpp b/include/gl/types/traits/cache_mode.hpp deleted file mode 100644 index ab50bd90..00000000 --- a/include/gl/types/traits/cache_mode.hpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "concepts.hpp" - -namespace gl::type_traits { - -enum class cache_mode_value { none, lazy, eager }; - -template -struct cache_mode { - static constexpr cache_mode_value value = CacheModeValue; -}; - -using no_cache = cache_mode; -using lazy_cache = cache_mode; -using eager_cache = cache_mode; - -template -concept c_cache_mode = c_one_of; - -} // namespace gl::type_traits diff --git a/include/gl/types/traits/concepts.hpp b/include/gl/types/traits/concepts.hpp deleted file mode 100644 index d01503e5..00000000 --- a/include/gl/types/traits/concepts.hpp +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "gl/types/types.hpp" - -#include -#include -#include -#include - -namespace gl::type_traits { - -template -concept c_properties = - std::semiregular and std::move_constructible and std::assignable_from; - -template typename> -struct is_instantiation_of : std::false_type {}; - -template