Skip to content

Commit

Permalink
Add extend_from_edge_list methods (#136)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
mtreinish and lcapelluto authored Sep 17, 2020
1 parent 0b97d96 commit ca8d23b
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/digraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions tests/graph/test_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
81 changes: 81 additions & 0 deletions tests/test_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

0 comments on commit ca8d23b

Please sign in to comment.