Skip to content

Commit a38ed21

Browse files
authored
Allow some graph and digraph methods to take iterables/generators (#1292)
* graph and digraph methods can use iterables * ran cargo fmt * linting * remove_edges_from and remove_nodes_from * cleaned up docs * added tests
1 parent c223d21 commit a38ed21

File tree

7 files changed

+254
-83
lines changed

7 files changed

+254
-83
lines changed

rustworkx/rustworkx.pyi

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,14 +1153,14 @@ class PyGraph(Generic[_S, _T]):
11531153
def add_edge(self, node_a: int, node_b: int, edge: _T, /) -> int: ...
11541154
def add_edges_from(
11551155
self,
1156-
obj_list: Sequence[tuple[int, int, _T]],
1156+
obj_list: Iterable[tuple[int, int, _T]],
11571157
/,
11581158
) -> list[int]: ...
11591159
def add_edges_from_no_data(
1160-
self: PyGraph[_S, _T | None], obj_list: Sequence[tuple[int, int]], /
1160+
self: PyGraph[_S, _T | None], obj_list: Iterable[tuple[int, int]], /
11611161
) -> list[int]: ...
11621162
def add_node(self, obj: _S, /) -> int: ...
1163-
def add_nodes_from(self, obj_list: Sequence[_S], /) -> NodeIndices: ...
1163+
def add_nodes_from(self, obj_list: Iterable[_S], /) -> NodeIndices: ...
11641164
def adj(self, node: int, /) -> dict[int, _T]: ...
11651165
def clear(self) -> None: ...
11661166
def clear_edges(self) -> None: ...
@@ -1188,11 +1188,11 @@ class PyGraph(Generic[_S, _T]):
11881188
def edges(self) -> list[_T]: ...
11891189
def edge_subgraph(self, edge_list: Sequence[tuple[int, int]], /) -> PyGraph[_S, _T]: ...
11901190
def extend_from_edge_list(
1191-
self: PyGraph[_S | None, _T | None], edge_list: Sequence[tuple[int, int]], /
1191+
self: PyGraph[_S | None, _T | None], edge_list: Iterable[tuple[int, int]], /
11921192
) -> None: ...
11931193
def extend_from_weighted_edge_list(
11941194
self: PyGraph[_S | None, _T],
1195-
edge_list: Sequence[tuple[int, int, _T]],
1195+
edge_list: Iterable[tuple[int, int, _T]],
11961196
/,
11971197
) -> None: ...
11981198
def filter_edges(self, filter_function: Callable[[_T], bool]) -> EdgeIndices: ...
@@ -1238,9 +1238,9 @@ class PyGraph(Generic[_S, _T]):
12381238
) -> PyGraph: ...
12391239
def remove_edge(self, node_a: int, node_b: int, /) -> None: ...
12401240
def remove_edge_from_index(self, edge: int, /) -> None: ...
1241-
def remove_edges_from(self, index_list: Sequence[tuple[int, int]], /) -> None: ...
1241+
def remove_edges_from(self, index_list: Iterable[tuple[int, int]], /) -> None: ...
12421242
def remove_node(self, node: int, /) -> None: ...
1243-
def remove_nodes_from(self, index_list: Sequence[int], /) -> None: ...
1243+
def remove_nodes_from(self, index_list: Iterable[int], /) -> None: ...
12441244
def subgraph(self, nodes: Sequence[int], /, preserve_attrs: bool = ...) -> PyGraph[_S, _T]: ...
12451245
def substitute_node_with_subgraph(
12461246
self,
@@ -1304,14 +1304,14 @@ class PyDiGraph(Generic[_S, _T]):
13041304
def add_edge(self, parent: int, child: int, edge: _T, /) -> int: ...
13051305
def add_edges_from(
13061306
self,
1307-
obj_list: Sequence[tuple[int, int, _T]],
1307+
obj_list: Iterable[tuple[int, int, _T]],
13081308
/,
13091309
) -> list[int]: ...
13101310
def add_edges_from_no_data(
1311-
self: PyDiGraph[_S, _T | None], obj_list: Sequence[tuple[int, int]], /
1311+
self: PyDiGraph[_S, _T | None], obj_list: Iterable[tuple[int, int]], /
13121312
) -> list[int]: ...
13131313
def add_node(self, obj: _S, /) -> int: ...
1314-
def add_nodes_from(self, obj_list: Sequence[_S], /) -> NodeIndices: ...
1314+
def add_nodes_from(self, obj_list: Iterable[_S], /) -> NodeIndices: ...
13151315
def add_parent(self, child: int, obj: _S, edge: _T, /) -> int: ...
13161316
def adj(self, node: int, /) -> dict[int, _T]: ...
13171317
def adj_direction(self, node: int, direction: bool, /) -> dict[int, _T]: ...
@@ -1341,11 +1341,11 @@ class PyDiGraph(Generic[_S, _T]):
13411341
def edges(self) -> list[_T]: ...
13421342
def edge_subgraph(self, edge_list: Sequence[tuple[int, int]], /) -> PyDiGraph[_S, _T]: ...
13431343
def extend_from_edge_list(
1344-
self: PyDiGraph[_S | None, _T | None], edge_list: Sequence[tuple[int, int]], /
1344+
self: PyDiGraph[_S | None, _T | None], edge_list: Iterable[tuple[int, int]], /
13451345
) -> None: ...
13461346
def extend_from_weighted_edge_list(
13471347
self: PyDiGraph[_S | None, _T],
1348-
edge_list: Sequence[tuple[int, int, _T]],
1348+
edge_list: Iterable[tuple[int, int, _T]],
13491349
/,
13501350
) -> None: ...
13511351
def filter_edges(self, filter_function: Callable[[_T], bool]) -> EdgeIndices: ...
@@ -1413,7 +1413,7 @@ class PyDiGraph(Generic[_S, _T]):
14131413
) -> PyDiGraph: ...
14141414
def remove_edge(self, parent: int, child: int, /) -> None: ...
14151415
def remove_edge_from_index(self, edge: int, /) -> None: ...
1416-
def remove_edges_from(self, index_list: Sequence[tuple[int, int]], /) -> None: ...
1416+
def remove_edges_from(self, index_list: Iterable[tuple[int, int]], /) -> None: ...
14171417
def remove_node(self, node: int, /) -> None: ...
14181418
def remove_node_retain_edges(
14191419
self,
@@ -1431,7 +1431,7 @@ class PyDiGraph(Generic[_S, _T]):
14311431
*,
14321432
use_outgoing: bool = ...,
14331433
) -> None: ...
1434-
def remove_nodes_from(self, index_list: Sequence[int], /) -> None: ...
1434+
def remove_nodes_from(self, index_list: Iterable[int], /) -> None: ...
14351435
def subgraph(
14361436
self, nodes: Sequence[int], /, preserve_attrs: bool = ...
14371437
) -> PyDiGraph[_S, _T]: ...

src/digraph.rs

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,20 +1273,18 @@ impl PyDiGraph {
12731273

12741274
/// Add new edges to the dag.
12751275
///
1276-
/// :param list obj_list: A list of tuples of the form
1276+
/// :param iterable obj_list: An iterable of tuples of the form
12771277
/// ``(parent, child, obj)`` to attach to the graph. ``parent`` and
12781278
/// ``child`` are integer indices describing where an edge should be
12791279
/// added, and obj is the python object for the edge data.
12801280
///
12811281
/// :returns: A list of int indices of the newly created edges
12821282
/// :rtype: list
12831283
#[pyo3(text_signature = "(self, obj_list, /)")]
1284-
pub fn add_edges_from(
1285-
&mut self,
1286-
obj_list: Vec<(usize, usize, PyObject)>,
1287-
) -> PyResult<Vec<usize>> {
1288-
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
1289-
for obj in obj_list {
1284+
pub fn add_edges_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult<Vec<usize>> {
1285+
let mut out_list = Vec::new();
1286+
for py_obj in obj_list.iter()? {
1287+
let obj = py_obj?.extract::<(usize, usize, PyObject)>()?;
12901288
let edge = self.add_edge(obj.0, obj.1, obj.2)?;
12911289
out_list.push(edge);
12921290
}
@@ -1295,7 +1293,7 @@ impl PyDiGraph {
12951293

12961294
/// Add new edges to the dag without python data.
12971295
///
1298-
/// :param list obj_list: A list of tuples of the form
1296+
/// :param iterable obj_list: An iterable of tuples of the form
12991297
/// ``(parent, child)`` to attach to the graph. ``parent`` and
13001298
/// ``child`` are integer indices describing where an edge should be
13011299
/// added. Unlike :meth:`add_edges_from` there is no data payload and
@@ -1307,10 +1305,11 @@ impl PyDiGraph {
13071305
pub fn add_edges_from_no_data(
13081306
&mut self,
13091307
py: Python,
1310-
obj_list: Vec<(usize, usize)>,
1308+
obj_list: Bound<'_, PyAny>,
13111309
) -> PyResult<Vec<usize>> {
1312-
let mut out_list: Vec<usize> = Vec::with_capacity(obj_list.len());
1313-
for obj in obj_list {
1310+
let mut out_list = Vec::new();
1311+
for py_obj in obj_list.iter()? {
1312+
let obj = py_obj?.extract::<(usize, usize)>()?;
13141313
let edge = self.add_edge(obj.0, obj.1, py.None())?;
13151314
out_list.push(edge);
13161315
}
@@ -1322,17 +1321,18 @@ impl PyDiGraph {
13221321
/// This method differs from :meth:`add_edges_from_no_data` in that it will
13231322
/// add nodes if a node index is not present in the edge list.
13241323
///
1325-
/// :param list edge_list: A list of tuples of the form ``(source, target)``
1324+
/// :param iterable edge_list: An iterable of tuples of the form ``(source, target)``
13261325
/// where source and target are integer node indices. If the node index
13271326
/// is not present in the graph, nodes will be added (with a node
13281327
/// weight of ``None``) to that index.
13291328
#[pyo3(text_signature = "(self, edge_list, /)")]
13301329
pub fn extend_from_edge_list(
13311330
&mut self,
13321331
py: Python,
1333-
edge_list: Vec<(usize, usize)>,
1332+
edge_list: Bound<'_, PyAny>,
13341333
) -> PyResult<()> {
1335-
for (source, target) in edge_list {
1334+
for py_obj in edge_list.iter()? {
1335+
let (source, target) = py_obj?.extract::<(usize, usize)>()?;
13361336
let max_index = cmp::max(source, target);
13371337
while max_index >= self.node_count() {
13381338
self.graph.add_node(py.None());
@@ -1347,17 +1347,18 @@ impl PyDiGraph {
13471347
/// This method differs from :meth:`add_edges_from` in that it will
13481348
/// add nodes if a node index is not present in the edge list.
13491349
///
1350-
/// :param list edge_list: A list of tuples of the form
1350+
/// :param iterable edge_list: An iterable of tuples of the form
13511351
/// ``(source, target, weight)`` where source and target are integer
13521352
/// node indices. If the node index is not present in the graph
13531353
/// nodes will be added (with a node weight of ``None``) to that index.
13541354
#[pyo3(text_signature = "(self, edge_list, /)")]
13551355
pub fn extend_from_weighted_edge_list(
13561356
&mut self,
13571357
py: Python,
1358-
edge_list: Vec<(usize, usize, PyObject)>,
1358+
edge_list: Bound<'_, PyAny>,
13591359
) -> PyResult<()> {
1360-
for (source, target, weight) in edge_list {
1360+
for py_obj in edge_list.iter()? {
1361+
let (source, target, weight) = py_obj?.extract::<(usize, usize, PyObject)>()?;
13611362
let max_index = cmp::max(source, target);
13621363
while max_index >= self.node_count() {
13631364
self.graph.add_node(py.None());
@@ -1500,17 +1501,16 @@ impl PyDiGraph {
15001501
/// Note if there are multiple edges between the specified nodes only one
15011502
/// will be removed.
15021503
///
1503-
/// :param list index_list: A list of node index pairs to remove from
1504+
/// :param iterable index_list: An iterable of node index pairs to remove from
15041505
/// the graph
15051506
///
15061507
/// :raises NoEdgeBetweenNodes: If there are no edges between a specified
15071508
/// pair of nodes.
15081509
#[pyo3(text_signature = "(self, index_list, /)")]
1509-
pub fn remove_edges_from(&mut self, index_list: Vec<(usize, usize)>) -> PyResult<()> {
1510-
for (p_index, c_index) in index_list
1511-
.iter()
1512-
.map(|(x, y)| (NodeIndex::new(*x), NodeIndex::new(*y)))
1513-
{
1510+
pub fn remove_edges_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> {
1511+
for py_obj in index_list.iter()? {
1512+
let (x, y) = py_obj?.extract::<(usize, usize)>()?;
1513+
let (p_index, c_index) = (NodeIndex::new(x), NodeIndex::new(y));
15141514
let edge_index = match self.graph.find_edge(p_index, c_index) {
15151515
Some(edge_index) => edge_index,
15161516
None => return Err(NoEdgeBetweenNodes::new_err("No edge found between nodes")),
@@ -1949,30 +1949,32 @@ impl PyDiGraph {
19491949

19501950
/// Add new nodes to the graph.
19511951
///
1952-
/// :param list obj_list: A list of python objects to attach to the graph
1952+
/// :param iterable obj_list: An iterable of python objects to attach to the graph
19531953
/// as new nodes
19541954
///
19551955
/// :returns: A list of int indices of the newly created nodes
19561956
/// :rtype: NodeIndices
19571957
#[pyo3(text_signature = "(self, obj_list, /)")]
1958-
pub fn add_nodes_from(&mut self, obj_list: Vec<PyObject>) -> NodeIndices {
1959-
let out_list: Vec<usize> = obj_list
1960-
.into_iter()
1961-
.map(|obj| self.graph.add_node(obj).index())
1962-
.collect();
1963-
NodeIndices { nodes: out_list }
1958+
pub fn add_nodes_from(&mut self, obj_list: Bound<'_, PyAny>) -> PyResult<NodeIndices> {
1959+
let mut out_list = Vec::new();
1960+
for py_obj in obj_list.iter()? {
1961+
let obj = py_obj?.extract::<PyObject>()?;
1962+
out_list.push(self.graph.add_node(obj).index());
1963+
}
1964+
Ok(NodeIndices { nodes: out_list })
19641965
}
19651966

19661967
/// Remove nodes from the graph.
19671968
///
19681969
/// If a node index in the list is not present in the graph it will be
19691970
/// ignored.
19701971
///
1971-
/// :param list index_list: A list of node indicies to remove from the
1972-
/// the graph.
1972+
/// :param iterable index_list: An iterable of node indices to remove from the
1973+
/// graph.
19731974
#[pyo3(text_signature = "(self, index_list, /)")]
1974-
pub fn remove_nodes_from(&mut self, index_list: Vec<usize>) -> PyResult<()> {
1975-
for node in index_list {
1975+
pub fn remove_nodes_from(&mut self, index_list: Bound<'_, PyAny>) -> PyResult<()> {
1976+
for py_obj in index_list.iter()? {
1977+
let node = py_obj?.extract::<usize>()?;
19761978
self.remove_node(node)?;
19771979
}
19781980
Ok(())

0 commit comments

Comments
 (0)