From ca8d23b91b083871ce339966590d8cf4c8e125df Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 17 Sep 2020 17:39:13 -0400 Subject: [PATCH] Add extend_from_edge_list methods (#136) * Add extend_from_edge_list methods This commit adds methods to extend a graph from an edge list. This works like the add_edges_from methods but also will add nodes if there is an index in the edge list not present in the graph already. * Apply suggestions from code review Co-authored-by: Lauren Capelluto --- src/digraph.rs | 58 ++++++++++++++++++++++++++++ src/graph.rs | 56 +++++++++++++++++++++++++++ tests/graph/test_edges.py | 60 +++++++++++++++++++++++++++++ tests/test_edges.py | 81 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+) diff --git a/src/digraph.rs b/src/digraph.rs index aef041e93..545f0d01d 100644 --- a/src/digraph.rs +++ b/src/digraph.rs @@ -752,6 +752,64 @@ impl PyDiGraph { Ok(out_list) } + /// Extend graph from an edge list + /// + /// This method differs from :meth:`add_edges_from_no_data` in that it will + /// add nodes if a node index is not present in the edge list. + /// + /// :param list edge_list: A list of tuples of the form ``(source, target)`` + /// where source and target are integer node indices. If the node index + /// is not present in the graph, nodes will be added (with a node + /// weight of ``None``) to that index. + #[text_signature = "(edge_list, /)"] + pub fn extend_from_edge_list( + &mut self, + py: Python, + edge_list: Vec<(usize, usize)>, + ) -> PyResult<()> { + for (source, target) in edge_list { + let max_index = cmp::max(source, target); + while max_index >= self.node_count() { + self.graph.add_node(py.None()); + } + self._add_edge( + NodeIndex::new(source), + NodeIndex::new(target), + py.None(), + )?; + } + Ok(()) + } + + /// Extend graph from a weighted edge list + /// + /// This method differs from :meth:`add_edges_from` in that it will + /// add nodes if a node index is not present in the edge list. + /// + /// :param list edge_list: A list of tuples of the form + /// ``(source, target, weight)`` where source and target are integer + /// node indices. If the node index is not present in the graph + /// nodes will be added (with a node weight of ``None``) to that index. + #[text_signature = "(edge_lsit, /)"] + pub fn extend_from_weighted_edge_list( + &mut self, + py: Python, + edge_list: Vec<(usize, usize, PyObject)>, + ) -> PyResult<()> { + for (source, target, weight) in edge_list { + let max_index = cmp::max(source, target); + while max_index >= self.node_count() { + self.graph.add_node(py.None()); + } + self._add_edge( + NodeIndex::new(source), + NodeIndex::new(target), + weight, + )?; + } + Ok(()) + } + /// Remove an edge between 2 nodes. /// /// Note if there are multiple edges between the specified nodes only one diff --git a/src/graph.rs b/src/graph.rs index aa8ea6eab..edefc0fd3 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -545,6 +545,62 @@ impl PyGraph { Ok(out_list) } + /// Extend graph from an edge list + /// + /// This method differs from :meth:`add_edges_from_no_data` in that it will + /// add nodes if a node index is not present in the edge list. + /// + /// :param list edge_list: A list of tuples of the form ``(source, target)`` + /// where source and target are integer node indices. If the node index + /// is not present in the graph, nodes will be added (with a node + /// weight of ``None``) to that index. + #[text_signature = "(edge_list, /)"] + pub fn extend_from_edge_list( + &mut self, + py: Python, + edge_list: Vec<(usize, usize)>, + ) { + for (source, target) in edge_list { + let max_index = cmp::max(source, target); + while max_index >= self.node_count() { + self.graph.add_node(py.None()); + } + self.graph.add_edge( + NodeIndex::new(source), + NodeIndex::new(target), + py.None(), + ); + } + } + + /// Extend graph from a weighted edge list + /// + /// This method differs from :meth:`add_edges_from` in that it will + /// add nodes if a node index is not present in the edge list. + /// + /// :param list edge_list: A list of tuples of the form + /// ``(source, target, weight)`` where source and target are integer + /// node indices. If the node index is not present in the graph, + /// nodes will be added (with a node weight of ``None``) to that index. + #[text_signature = "(edge_lsit, /)"] + pub fn extend_from_weighted_edge_list( + &mut self, + py: Python, + edge_list: Vec<(usize, usize, PyObject)>, + ) { + for (source, target, weight) in edge_list { + let max_index = cmp::max(source, target); + while max_index >= self.node_count() { + self.graph.add_node(py.None()); + } + self.graph.add_edge( + NodeIndex::new(source), + NodeIndex::new(target), + weight, + ); + } + } + /// Remove an edge between 2 nodes. /// /// Note if there are multiple edges between the specified nodes only one diff --git a/tests/graph/test_edges.py b/tests/graph/test_edges.py index 723bb4531..8ed7861ae 100644 --- a/tests/graph/test_edges.py +++ b/tests/graph/test_edges.py @@ -193,3 +193,63 @@ def test_weighted_edge_list(self): def test_weighted_edge_list_empty(self): graph = retworkx.PyGraph() self.assertEqual([], graph.weighted_edge_list()) + + def test_extend_from_edge_list(self): + graph = retworkx.PyGraph() + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), + (0, 3)] + graph.extend_from_edge_list(edge_list) + self.assertEqual(len(graph), 4) + self.assertEqual([None] * 5, graph.edges()) + self.assertEqual(3, graph.degree(0)) + self.assertEqual(2, graph.degree(1)) + self.assertEqual(3, graph.degree(2)) + self.assertEqual(2, graph.degree(3)) + + def test_extend_from_edge_list_empty(self): + graph = retworkx.PyGraph() + graph.extend_from_edge_list([]) + self.assertEqual(0, len(graph)) + + def test_extend_from_edge_list_nodes_exist(self): + graph = retworkx.PyGraph() + graph.add_nodes_from(list(range(4))) + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), + (0, 3)] + graph.extend_from_edge_list(edge_list) + self.assertEqual(len(graph), 4) + self.assertEqual([None] * 5, graph.edges()) + self.assertEqual(3, graph.degree(0)) + self.assertEqual(2, graph.degree(1)) + self.assertEqual(3, graph.degree(2)) + self.assertEqual(2, graph.degree(3)) + + def test_extend_from_weighted_edge_list(self): + graph = retworkx.PyGraph() + edge_list = [(0, 1, 'a'), (1, 2, 'b'), (0, 2, 'c'), (2, 3, 'd'), + (0, 3, 'e')] + graph.extend_from_weighted_edge_list(edge_list) + self.assertEqual(len(graph), 4) + self.assertEqual(['a', 'b', 'c', 'd', 'e'], graph.edges()) + self.assertEqual(3, graph.degree(0)) + self.assertEqual(2, graph.degree(1)) + self.assertEqual(3, graph.degree(2)) + self.assertEqual(2, graph.degree(3)) + + def test_extend_from_weighted_edge_list_empty(self): + graph = retworkx.PyGraph() + graph.extend_from_weighted_edge_list([]) + self.assertEqual(0, len(graph)) + + def test_extend_from_weighted_edge_list_nodes_exist(self): + graph = retworkx.PyGraph() + graph.add_nodes_from(list(range(4))) + edge_list = [(0, 1, 'a'), (1, 2, 'b'), (0, 2, 'c'), (2, 3, 'd'), + (0, 3, 'e')] + graph.extend_from_weighted_edge_list(edge_list) + self.assertEqual(len(graph), 4) + self.assertEqual(['a', 'b', 'c', 'd', 'e'], graph.edges()) + self.assertEqual(3, graph.degree(0)) + self.assertEqual(2, graph.degree(1)) + self.assertEqual(3, graph.degree(2)) + self.assertEqual(2, graph.degree(3)) diff --git a/tests/test_edges.py b/tests/test_edges.py index 630a94232..5d1c20df0 100644 --- a/tests/test_edges.py +++ b/tests/test_edges.py @@ -240,3 +240,84 @@ def test_weighted_edge_list(self): def test_weighted_edge_list_empty(self): dag = retworkx.PyDiGraph() self.assertEqual([], dag.weighted_edge_list()) + + def test_extend_from_edge_list(self): + dag = retworkx.PyDAG() + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), + (0, 3)] + dag.extend_from_edge_list(edge_list) + self.assertEqual(len(dag), 4) + self.assertEqual([None] * 5, dag.edges()) + self.assertEqual(3, dag.out_degree(0)) + self.assertEqual(0, dag.in_degree(0)) + self.assertEqual(1, dag.out_degree(1)) + self.assertEqual(1, dag.out_degree(2)) + self.assertEqual(2, dag.in_degree(3)) + + def test_extend_from_edge_list_empty(self): + dag = retworkx.PyDAG() + dag.extend_from_edge_list([]) + self.assertEqual(0, len(dag)) + + def test_cycle_checking_at_init_extend_from_weighted_edge_list(self): + dag = retworkx.PyDAG(True) + node_a = dag.add_node('a') + node_b = dag.add_child(node_a, 'b', {}) + node_c = dag.add_child(node_b, 'c', {}) + with self.assertRaises(retworkx.DAGWouldCycle): + dag.extend_from_weighted_edge_list([(node_a, node_c, {}), + (node_c, node_b, {})]) + + def test_extend_from_edge_list_nodes_exist(self): + dag = retworkx.PyDiGraph() + dag.add_nodes_from(list(range(4))) + edge_list = [(0, 1), (1, 2), (0, 2), (2, 3), + (0, 3)] + dag.extend_from_edge_list(edge_list) + self.assertEqual(len(dag), 4) + self.assertEqual([None] * 5, dag.edges()) + self.assertEqual(3, dag.out_degree(0)) + self.assertEqual(0, dag.in_degree(0)) + self.assertEqual(1, dag.out_degree(1)) + self.assertEqual(1, dag.out_degree(2)) + self.assertEqual(2, dag.in_degree(3)) + + def test_extend_from_weighted_edge_list(self): + dag = retworkx.PyDAG() + edge_list = [(0, 1, 'a'), (1, 2, 'b'), (0, 2, 'c'), (2, 3, 'd'), + (0, 3, 'e')] + dag.extend_from_weighted_edge_list(edge_list) + self.assertEqual(len(dag), 4) + self.assertEqual(['a', 'b', 'c', 'd', 'e'], dag.edges()) + self.assertEqual(3, dag.out_degree(0)) + self.assertEqual(0, dag.in_degree(0)) + self.assertEqual(1, dag.out_degree(1)) + self.assertEqual(1, dag.out_degree(2)) + self.assertEqual(2, dag.in_degree(3)) + + def test_extend_from_weighted_edge_list_empty(self): + dag = retworkx.PyDAG() + dag.extend_from_weighted_edge_list([]) + self.assertEqual(0, len(dag)) + + def test_cycle_checking_at_init_nodes_extend_from_edge_list(self): + dag = retworkx.PyDAG(True) + node_a = dag.add_node('a') + node_b = dag.add_child(node_a, 'b', {}) + node_c = dag.add_child(node_b, 'c', {}) + with self.assertRaises(retworkx.DAGWouldCycle): + dag.extend_from_edge_list([(node_a, node_c), (node_c, node_b)]) + + def test_extend_from_weighted_edge_list_nodes_exist(self): + dag = retworkx.PyDiGraph() + dag.add_nodes_from(list(range(4))) + edge_list = [(0, 1, 'a'), (1, 2, 'b'), (0, 2, 'c'), (2, 3, 'd'), + (0, 3, 'e')] + dag.extend_from_weighted_edge_list(edge_list) + self.assertEqual(len(dag), 4) + self.assertEqual(['a', 'b', 'c', 'd', 'e'], dag.edges()) + self.assertEqual(3, dag.out_degree(0)) + self.assertEqual(0, dag.in_degree(0)) + self.assertEqual(1, dag.out_degree(1)) + self.assertEqual(1, dag.out_degree(2)) + self.assertEqual(2, dag.in_degree(3))