From 90a99736a0efcd5ca1f649b2db6ca664aa4b8a71 Mon Sep 17 00:00:00 2001 From: Simone Balducci <93096843+sbaldu@users.noreply.github.com> Date: Mon, 5 Jun 2023 08:04:36 +0200 Subject: [PATCH] Feature getters for set of neighbors (#318) * Remove static_cast of 0 to template type T in best_first_search * Change all the raw pointers in the library to smart pointers * Rewrite tests to use smart pointers * Finish converting tests to smart pointers * Add setter for nodes in Edge nodePair * Fix addition of edges and construction of adjacency matrix * Add overloads of algorithms which take raw pointers to mantain interface * Fix typos in tests * Change dynamic_casts into dynamic_pointer_casts * Decrease the number of random edges and nodes in RW and partition tests * Add out/inOutEdges convenience methods for Graph * Add tests for the out/inOutEdges methods * Add overload of equality operators and hash functions for pointers of nodes * Implement new hash functions for pointers of nodes and edges * Increase number of nodes and edges in partition and RW tests * Fix typo * Use shared pointers in random node/edge generators * Convert pointers in TransitiveReductionTest.cpp * Add overload of addEdge for raw pointers * Fix typo * Fix edge insertion in FloydWarshall benchmark * Fix typo in benchmark * Remove all the calls to new in the tests * Fix typo in benchmark FloydWarshall Fix typo in benchmark FloydWarshall * Conversion of example codes * Formatting * Add test for overloads of out/inOutEdges * Change name of out/inOutEdges methods to out/inOutNeighbors --------- Co-authored-by: sbaldu --- include/Graph/Graph.hpp | 152 +++++++++++++++++++---- test/GraphTest.cpp | 261 ++++++++++++++++++++++++++++++++++------ 2 files changed, 357 insertions(+), 56 deletions(-) diff --git a/include/Graph/Graph.hpp b/include/Graph/Graph.hpp index a903a75ab..5981aa9bc 100644 --- a/include/Graph/Graph.hpp +++ b/include/Graph/Graph.hpp @@ -60,6 +60,7 @@ #include "Partitioning/Utility/Globals.hpp" #include "Utility/ConstString.hpp" #include "Utility/ConstValue.hpp" +#include "Utility/PointerHash.hpp" #include "Utility/Reader.hpp" #include "Utility/ThreadSafe.hpp" #include "Utility/Typedef.hpp" @@ -209,7 +210,8 @@ class Graph { * @returns a list of Nodes of the graph * */ - virtual const std::unordered_set>, nodeHash> getNodeSet() const; + virtual const std::unordered_set>, nodeHash> + getNodeSet() const; /** * \brief * Function that sets the data contained in a node @@ -244,6 +246,47 @@ class Graph { * corrispondent to the link Note: No Thread Safe */ virtual const std::shared_ptr> getAdjMatrix() const; + /** + * \brief This function generates a set of nodes linked to the provided node in a + * directed graph + * Note: No Thread Safe + * + * @param Pointer to the node + * + */ + virtual const std::unordered_set>, nodeHash> outNeighbors( + const Node *node) const; + /** + * \brief This function generates a set of nodes linked to the provided node in a + * directed graph + * Note: No Thread Safe + * + * @param Pointer to the node + * + */ + virtual const std::unordered_set>, nodeHash> outNeighbors( + shared> node) const; + /** + * \brief This function generates a set of nodes linked to the provided node in + * any graph + * Note: No Thread Safe + * + * @param Pointer to the node + * + */ + virtual const std::unordered_set>, nodeHash> + inOutNeighbors(const Node *node) const; + /** + * \brief + * \brief This function generates a set of nodes linked to the provided node in + * any graph + * Note: No Thread Safe + * + * @param Pointer to the node + * + */ + virtual const std::unordered_set>, nodeHash> + inOutNeighbors(shared> node) const; /** * @brief This function finds the subset of given a nodeId * Subset is stored in a map where keys are the hash-id of the node & values @@ -261,7 +304,8 @@ class Graph { * @brief This function finds the subset of given a nodeId * Subset is stored in a map where keys are the hash-id of the node & values * is the subset. - * @param shared pointer to subset query subset, we want to find target in this subset + * @param shared pointer to subset query subset, we want to find target in + * this subset * @param elem elem that we wish to find in the subset * * @return parent node of elem @@ -744,7 +788,8 @@ bool Graph::findEdge(shared> v1, shared> v2, } template -const std::unordered_set>, nodeHash> Graph::getNodeSet() const { +const std::unordered_set>, nodeHash> +Graph::getNodeSet() const { std::unordered_set>, nodeHash> nodeSet; for (const auto &edgeSetIt : edgeSet) { nodeSet.insert(edgeSetIt->getNodePair().first); @@ -863,11 +908,11 @@ int Graph::writeToDot(const std::string &workingDir, } if (edgePtr->isWeighted().has_value() && edgePtr->isWeighted().value()) { // Weights in dot files must be integers - edgeLine += - " [weight=" + - std::to_string(static_cast( - std::dynamic_pointer_cast(edgePtr)->getWeight())) + - ']'; + edgeLine += " [weight=" + + std::to_string(static_cast( + std::dynamic_pointer_cast(edgePtr) + ->getWeight())) + + ']'; } edgeLine += ";\n"; ofileGraph << edgeLine; @@ -917,7 +962,8 @@ void Graph::writeGraphToStream(std::ostream &oGraph, std::ostream &oNodeFeat, oEdgeWeight << edge->getId() << sep << (edge->isWeighted().has_value() && edge->isWeighted().value() - ? (std::dynamic_pointer_cast(edge))->getWeight() + ? (std::dynamic_pointer_cast(edge)) + ->getWeight() : 0.0) << sep << (edge->isWeighted().has_value() && edge->isWeighted().value() ? 1 @@ -1179,7 +1225,8 @@ template unsigned long long Graph::setFind( std::unordered_map *subsets, const unsigned long long nodeId) const { - auto subsets_ptr = make_shared>(*subsets); + auto subsets_ptr = + make_shared>(*subsets); // find root and make root as parent of i // (path compression) if ((*subsets)[nodeId].parent != nodeId) { @@ -1205,18 +1252,21 @@ unsigned long long Graph::setFind( } template -void Graph::setUnion( - std::unordered_map *subsets, - const unsigned long long elem1, const unsigned long long elem2) const { - /* auto subsets_ptr = make_shared>(*subsets); */ +void Graph::setUnion(std::unordered_map *subsets, + const unsigned long long elem1, + const unsigned long long elem2) const { + /* auto subsets_ptr = make_shared>(*subsets); */ // if both sets have same parent // then there's nothing to be done - /* if ((*subsets_ptr)[elem1].parent == (*subsets_ptr)[elem2].parent) return; */ + /* if ((*subsets_ptr)[elem1].parent == (*subsets_ptr)[elem2].parent) return; + */ /* auto elem1Parent = Graph::setFind(subsets_ptr, elem1); */ /* auto elem2Parent = Graph::setFind(subsets_ptr, elem2); */ /* if ((*subsets_ptr)[elem1Parent].rank < (*subsets_ptr)[elem2Parent].rank) */ /* (*subsets_ptr)[elem1].parent = elem2Parent; */ - /* else if ((*subsets_ptr)[elem1Parent].rank > (*subsets_ptr)[elem2Parent].rank) */ + /* else if ((*subsets_ptr)[elem1Parent].rank > + * (*subsets_ptr)[elem2Parent].rank) */ /* (*subsets_ptr)[elem2].parent = elem1Parent; */ /* else { */ /* (*subsets_ptr)[elem2].parent = elem1Parent; */ @@ -1329,6 +1379,59 @@ const std::shared_ptr> Graph::getAdjMatrix() const { return adj; } +template +const std::unordered_set>, nodeHash> Graph::outNeighbors( + const Node *node) const { + auto node_shared = make_shared>(*node); + + return outNeighbors(node_shared); +} + +template +const std::unordered_set>, nodeHash> Graph::outNeighbors( + shared> node) const { + auto adj = getAdjMatrix(); + if (adj->find(node) == adj->end()) { + return std::unordered_set>, nodeHash>(); + } + auto nodeEdgePairs = adj->at(node); + + std::unordered_set>, nodeHash> outNeighbors; + for (auto pair : nodeEdgePairs) { + if (pair.second->isDirected().has_value() && + pair.second->isDirected().value()) { + outNeighbors.insert(pair.first); + } + } + + return outNeighbors; +} + +template +const std::unordered_set>, nodeHash> +Graph::inOutNeighbors(const Node *node) const { + auto node_shared = make_shared>(*node); + + return inOutNeighbors(node_shared); +} + +template +const std::unordered_set>, nodeHash> +Graph::inOutNeighbors(shared> node) const { + auto adj = Graph::getAdjMatrix(); + if (adj->find(node) == adj->end()) { + return std::unordered_set>, nodeHash>(); + } + auto nodeEdgePairs = adj->at(node); + + std::unordered_set>, nodeHash> inOutNeighbors; + for (auto pair : nodeEdgePairs) { + inOutNeighbors.insert(pair.first); + } + + return inOutNeighbors; +} + template const DijkstraResult Graph::dijkstra(const Node &source, const Node &target) const { @@ -1466,7 +1569,8 @@ const BellmanFordResult Graph::bellmanford(const Node &source, return result; } // setting all the distances initially to INF_DOUBLE - std::unordered_map>, double, nodeHash> dist, currentDist; + std::unordered_map>, double, nodeHash> dist, + currentDist; // n denotes the number of vertices in graph auto n = nodeSet.size(); for (const auto &elem : nodeSet) { @@ -1555,7 +1659,8 @@ const Graph Graph::transitiveReduction() const { Graph result(this->edgeSet); unsigned long long edgeId = 0; - std::unordered_set>, nodeHash> nodes = this->getNodeSet(); + std::unordered_set>, nodeHash> nodes = + this->getNodeSet(); for (auto x : nodes) { for (auto y : nodes) { if (this->findEdge(x, y, edgeId)) { @@ -1849,7 +1954,8 @@ const MstResult Graph::kruskal() const { sortedEdges; for (const auto &edge : edgeSet) { if (edge->isWeighted().has_value() && edge->isWeighted().value()) { - auto weight = (std::dynamic_pointer_cast(edge))->getWeight(); + auto weight = + (std::dynamic_pointer_cast(edge))->getWeight(); sortedEdges.push(std::make_pair(weight, edge)); } else { // No Weighted Edge @@ -2838,8 +2944,12 @@ double Graph::fordFulkersonMaxFlow(const Node &source, return -1; } double maxFlow = 0; - std::unordered_map>, shared>, nodeHash> parent; - std::unordered_map>, std::unordered_map>, double, nodeHash>, nodeHash> + std::unordered_map>, shared>, nodeHash> + parent; + std::unordered_map< + shared>, + std::unordered_map>, double, nodeHash>, + nodeHash> weightMap; // build weight map auto edgeSet = this->getEdgeSet(); diff --git a/test/GraphTest.cpp b/test/GraphTest.cpp index 24a6592ba..f035a6d0b 100644 --- a/test/GraphTest.cpp +++ b/test/GraphTest.cpp @@ -1,4 +1,6 @@ + #include + #include "CXXGraph.hpp" #include "gtest/gtest.h" @@ -6,10 +8,10 @@ template using unique = std::unique_ptr; template -using shared= std::shared_ptr; +using shared = std::shared_ptr; -using std::make_unique; using std::make_shared; +using std::make_unique; TEST(GraphTest, Constructor_1) { CXXGraph::Node node1("1", 1); @@ -70,14 +72,12 @@ TEST(GraphTest, GetNodeSet_1) { CXXGraph::Graph graph(edgeSet); auto nodeSet = graph.getNodeSet(); ASSERT_EQ(nodeSet.size(), 2); - ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node1](auto node){ - return node->getUserId() == node1.getUserId(); - }) != - nodeSet.end()); - ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node2](auto node){ - return node->getUserId() == node2.getUserId(); - }) != - nodeSet.end()); + ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node1](auto node) { + return node->getUserId() == node1.getUserId(); + }) != nodeSet.end()); + ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node2](auto node) { + return node->getUserId() == node2.getUserId(); + }) != nodeSet.end()); } TEST(GraphTest, GetNodeSet_2) { @@ -94,18 +94,15 @@ TEST(GraphTest, GetNodeSet_2) { CXXGraph::Graph graph(edgeSet); auto nodeSet = graph.getNodeSet(); ASSERT_EQ(nodeSet.size(), 3); - ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node1](auto node){ - return node->getUserId() == node1.getUserId(); - }) != - nodeSet.end()); - ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node2](auto node){ - return node->getUserId() == node2.getUserId(); - }) != - nodeSet.end()); - ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node3](auto node){ - return node->getUserId() == node3.getUserId(); - }) != - nodeSet.end()); + ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node1](auto node) { + return node->getUserId() == node1.getUserId(); + }) != nodeSet.end()); + ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node2](auto node) { + return node->getUserId() == node2.getUserId(); + }) != nodeSet.end()); + ASSERT_TRUE(std::find_if(nodeSet.begin(), nodeSet.end(), [&node3](auto node) { + return node->getUserId() == node3.getUserId(); + }) != nodeSet.end()); } TEST(GraphTest, adj_print_1) { @@ -219,29 +216,223 @@ TEST(GraphTest, set_data) { std::map initial_values; // Construct map with the initial values of the nodes data - for (const auto& nodeIt : graph.getNodeSet()) { - initial_values[nodeIt->getUserId()] = nodeIt->getData(); + for (const auto &nodeIt : graph.getNodeSet()) { + initial_values[nodeIt->getUserId()] = nodeIt->getData(); } // Change the data contained in the nodes singularly std::map new_values; - for (const auto& nodeIt : graph.getNodeSet()) { - int r = std::rand(); - graph.setNodeData(nodeIt->getUserId(), r); - new_values[nodeIt->getUserId()] = r; + for (const auto &nodeIt : graph.getNodeSet()) { + int r = std::rand(); + graph.setNodeData(nodeIt->getUserId(), r); + new_values[nodeIt->getUserId()] = r; } // Check the final values of the node data - for (const auto& nodeIt : graph.getNodeSet()) { - ASSERT_EQ(nodeIt->getData(), new_values[nodeIt->getUserId()]); + for (const auto &nodeIt : graph.getNodeSet()) { + ASSERT_EQ(nodeIt->getData(), new_values[nodeIt->getUserId()]); } // Now set the data of all the nodes at once std::map data_values; - for (const auto& nodeIt : graph.getNodeSet()) { - int r = std::rand(); - data_values[nodeIt->getUserId()] = r; + for (const auto &nodeIt : graph.getNodeSet()) { + int r = std::rand(); + data_values[nodeIt->getUserId()] = r; } graph.setNodeData(data_values); - for (const auto& nodeIt : graph.getNodeSet()) { - ASSERT_EQ(nodeIt->getData(), data_values[nodeIt->getUserId()]); + for (const auto &nodeIt : graph.getNodeSet()) { + ASSERT_EQ(nodeIt->getData(), data_values[nodeIt->getUserId()]); + } +} + +TEST(GraphTest, test_outEdges) { + CXXGraph::Node node1("1", 1); + CXXGraph::Node node2("2", 2); + CXXGraph::Node node3("3", 3); + CXXGraph::Node node4("4", 4); + CXXGraph::Node node5("5", 5); + CXXGraph::Node node6("6", 6); + CXXGraph::Node node7("7", 7); + CXXGraph::Node node8("8", 8); + CXXGraph::DirectedEdge edge1(1, node1, node2); + CXXGraph::DirectedEdge edge2(2, node1, node3); + CXXGraph::DirectedEdge edge3(3, node2, node4); + CXXGraph::DirectedEdge edge4(4, node2, node5); + CXXGraph::DirectedEdge edge5(5, node3, node4); + CXXGraph::DirectedEdge edge6(6, node3, node5); + CXXGraph::DirectedEdge edge7(7, node4, node6); + CXXGraph::DirectedEdge edge8(8, node4, node7); + CXXGraph::DirectedEdge edge9(7, node5, node6); + CXXGraph::DirectedEdge edge10(8, node5, node7); + CXXGraph::DirectedEdge edge11(8, node6, node8); + CXXGraph::DirectedEdge edge12(8, node7, node8); + CXXGraph::T_EdgeSet edgeSet; + edgeSet.insert(make_shared>(edge1)); + edgeSet.insert(make_shared>(edge2)); + edgeSet.insert(make_shared>(edge3)); + edgeSet.insert(make_shared>(edge4)); + edgeSet.insert(make_shared>(edge5)); + edgeSet.insert(make_shared>(edge6)); + edgeSet.insert(make_shared>(edge7)); + edgeSet.insert(make_shared>(edge8)); + edgeSet.insert(make_shared>(edge9)); + edgeSet.insert(make_shared>(edge10)); + edgeSet.insert(make_shared>(edge11)); + edgeSet.insert(make_shared>(edge12)); + CXXGraph::Graph graph(edgeSet); + + // Check node 1 + for (auto x : graph.outNeighbors(&node1)) { + ASSERT_TRUE(x == make_shared>(node2) || + x == make_shared>(node3)); + } + // Check node 2 + for (auto x : graph.outNeighbors(&node2)) { + ASSERT_TRUE(x == make_shared>(node4) || + x == make_shared>(node5)); + ASSERT_FALSE(x == make_shared>(node1)); + } + // Check node 5 + for (auto x : graph.outNeighbors(&node5)) { + ASSERT_TRUE(x == make_shared>(node6) || + x == make_shared>(node7)); + ASSERT_FALSE(x == make_shared>(node2)); + ASSERT_FALSE(x == make_shared>(node3)); + } + // Check that node 8 does not have any neighbors + ASSERT_EQ(graph.outNeighbors(&node8).size(), 0); +} + +// Test the overload that takes shared_ptr as input +TEST(GraphTest, test_outEdges_shared) { + CXXGraph::Node node1("1", 1); + CXXGraph::Node node2("2", 2); + CXXGraph::Node node3("3", 3); + CXXGraph::Node node4("4", 4); + CXXGraph::Node node5("5", 5); + CXXGraph::Node node6("6", 6); + CXXGraph::Node node7("7", 7); + CXXGraph::Node node8("8", 8); + CXXGraph::DirectedEdge edge1(1, node1, node2); + CXXGraph::DirectedEdge edge2(2, node1, node3); + CXXGraph::DirectedEdge edge3(3, node2, node4); + CXXGraph::DirectedEdge edge4(4, node2, node5); + CXXGraph::DirectedEdge edge5(5, node3, node4); + CXXGraph::DirectedEdge edge6(6, node3, node5); + CXXGraph::DirectedEdge edge7(7, node4, node6); + CXXGraph::DirectedEdge edge8(8, node4, node7); + CXXGraph::DirectedEdge edge9(7, node5, node6); + CXXGraph::DirectedEdge edge10(8, node5, node7); + CXXGraph::DirectedEdge edge11(8, node6, node8); + CXXGraph::DirectedEdge edge12(8, node7, node8); + CXXGraph::T_EdgeSet edgeSet; + edgeSet.insert(make_shared>(edge1)); + edgeSet.insert(make_shared>(edge2)); + edgeSet.insert(make_shared>(edge3)); + edgeSet.insert(make_shared>(edge4)); + edgeSet.insert(make_shared>(edge5)); + edgeSet.insert(make_shared>(edge6)); + edgeSet.insert(make_shared>(edge7)); + edgeSet.insert(make_shared>(edge8)); + edgeSet.insert(make_shared>(edge9)); + edgeSet.insert(make_shared>(edge10)); + edgeSet.insert(make_shared>(edge11)); + edgeSet.insert(make_shared>(edge12)); + CXXGraph::Graph graph(edgeSet); + + for (auto x : graph.outNeighbors(make_shared>(node1))) { + ASSERT_TRUE(x == make_shared>(node2) || + x == make_shared>(node3)); + } + + auto node2_shared = make_shared>(node2); + for (auto x : graph.outNeighbors(node2_shared)) { + ASSERT_TRUE(x == make_shared>(node4) || + x == make_shared>(node5)); + ASSERT_FALSE(x == make_shared>(node1)); + } +} + +TEST(GraphTest, test_inOutEdges) { + CXXGraph::Node node1("1", 1); + CXXGraph::Node node2("2", 2); + CXXGraph::Node node3("3", 3); + CXXGraph::Node node4("4", 4); + CXXGraph::Node node5("5", 5); + CXXGraph::Node node6("6", 6); + CXXGraph::Node node7("7", 7); + CXXGraph::Node node8("8", 8); + CXXGraph::UndirectedEdge edge1(1, node1, node2); + CXXGraph::UndirectedEdge edge2(2, node1, node3); + CXXGraph::UndirectedEdge edge3(3, node2, node3); + CXXGraph::UndirectedEdge edge4(4, node2, node4); + CXXGraph::UndirectedEdge edge5(5, node4, node5); + CXXGraph::UndirectedEdge edge6(6, node4, node6); + CXXGraph::UndirectedEdge edge7(7, node6, node7); + CXXGraph::UndirectedEdge edge8(8, node6, node8); + CXXGraph::T_EdgeSet edgeSet; + edgeSet.insert(make_shared>(edge1)); + edgeSet.insert(make_shared>(edge2)); + edgeSet.insert(make_shared>(edge3)); + edgeSet.insert(make_shared>(edge4)); + edgeSet.insert(make_shared>(edge5)); + edgeSet.insert(make_shared>(edge6)); + edgeSet.insert(make_shared>(edge7)); + edgeSet.insert(make_shared>(edge8)); + CXXGraph::Graph graph(edgeSet); + + // Check node 1 + for (auto x : graph.inOutNeighbors(&node1)) { + ASSERT_TRUE(x == make_shared>(node2) || + x == make_shared>(node3)); + } + // Check node 4 + for (auto x : graph.inOutNeighbors(&node4)) { + ASSERT_TRUE(x == make_shared>(node2) || + x == make_shared>(node3) || + x == make_shared>(node5) || + x == make_shared>(node6)); + } + // Check node 7 + for (auto x : graph.inOutNeighbors(&node7)) { + ASSERT_TRUE(x == make_shared>(node6)); + } +} + +// Test the overload that takes shared_ptr as input +TEST(GraphTest, test_inOutEdges_shared) { + CXXGraph::Node node1("1", 1); + CXXGraph::Node node2("2", 2); + CXXGraph::Node node3("3", 3); + CXXGraph::Node node4("4", 4); + CXXGraph::Node node5("5", 5); + CXXGraph::Node node6("6", 6); + CXXGraph::Node node7("7", 7); + CXXGraph::Node node8("8", 8); + CXXGraph::UndirectedEdge edge1(1, node1, node2); + CXXGraph::UndirectedEdge edge2(2, node1, node3); + CXXGraph::UndirectedEdge edge3(3, node2, node3); + CXXGraph::UndirectedEdge edge4(4, node2, node4); + CXXGraph::UndirectedEdge edge5(5, node4, node5); + CXXGraph::UndirectedEdge edge6(6, node4, node6); + CXXGraph::UndirectedEdge edge7(7, node6, node7); + CXXGraph::UndirectedEdge edge8(8, node6, node8); + CXXGraph::T_EdgeSet edgeSet; + edgeSet.insert(make_shared>(edge1)); + edgeSet.insert(make_shared>(edge2)); + edgeSet.insert(make_shared>(edge3)); + edgeSet.insert(make_shared>(edge4)); + edgeSet.insert(make_shared>(edge5)); + edgeSet.insert(make_shared>(edge6)); + edgeSet.insert(make_shared>(edge7)); + edgeSet.insert(make_shared>(edge8)); + CXXGraph::Graph graph(edgeSet); + + for (auto x : graph.inOutNeighbors(make_shared>(node1))) { + ASSERT_TRUE(x == make_shared>(node2) || + x == make_shared>(node3)); + } + + auto node7_shared = make_shared>(node7); + for (auto x : graph.inOutNeighbors(node7_shared)) { + ASSERT_TRUE(x == make_shared>(node6)); } }