Skip to content

Commit d7b3f4f

Browse files
authored
Enhance docs and tests for pred/succ/anc/desc (#1405)
* Enhance docs and tests for pred/succ/anc/desc * Use match pattern in predecessors/successors
1 parent 4baef30 commit d7b3f4f

File tree

3 files changed

+93
-31
lines changed

3 files changed

+93
-31
lines changed

src/digraph.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use std::io::prelude::*;
2121
use std::io::{BufReader, BufWriter};
2222
use std::str;
2323

24+
use hashbrown::hash_set::Entry;
2425
use hashbrown::{HashMap, HashSet};
2526

2627
use rustworkx_core::dictmap::*;
@@ -644,13 +645,18 @@ impl PyDiGraph {
644645
/// >>> G.extend_from_edge_list([(0, 1), (1, 2), (1, 3), (1, 4)])
645646
/// >>> G.successors(1) # successors of the 'B' node
646647
/// ['E', 'D', 'C']
648+
/// >>> G.successors(10) # successors of an non-existing node
649+
/// []
647650
///
648-
/// To filter the successors by the attributes of the connecting edge,
649-
/// see :func:`~find_successors_by_edge`.
651+
/// .. seealso ::
652+
/// To filter the successors by the attributes of the connecting edge,
653+
/// see :func:`~find_successors_by_edge`.
650654
///
651-
/// See also :func:`~predecessors` and :func:`~neighbors`.
655+
/// See also :func:`~predecessors` and :func:`~neighbors`.
652656
///
653-
/// For undirected graphs, see :func:`~PyGraph.neighbors`.
657+
/// For undirected graphs, see :func:`~PyGraph.neighbors`.
658+
///
659+
/// To go beyond the nearest successors, see :func:`~rustworkx.descendants`.
654660
///
655661
/// :param int node: The index of the node to get the predecessors for
656662
///
@@ -665,8 +671,12 @@ impl PyDiGraph {
665671
let mut successors: Vec<&PyObject> = Vec::new();
666672
let mut used_indices: HashSet<NodeIndex> = HashSet::new();
667673
for succ in children {
668-
if used_indices.insert(succ) {
669-
successors.push(self.graph.node_weight(succ).unwrap());
674+
match used_indices.entry(succ) {
675+
Entry::Vacant(used_indices_entry) => {
676+
used_indices_entry.insert();
677+
successors.push(self.graph.node_weight(succ).unwrap());
678+
}
679+
Entry::Occupied(_) => {}
670680
}
671681
}
672682
successors
@@ -684,13 +694,18 @@ impl PyDiGraph {
684694
/// >>> G.extend_from_edge_list([(0, 3), (1, 3), (2, 3), (3, 4)])
685695
/// >>> G.predecessors(3) # predecessors of the 'D' node
686696
/// ['C', 'B', 'A']
697+
/// >>> G.predecessors(10) # predecessors of an non-existing node
698+
/// []
687699
///
688-
/// To filter the predecessors by the attributes of the connecting edge,
689-
/// see :func:`~find_predecessors_by_edge`.
700+
/// .. seealso ::
701+
/// To filter the predecessors by the attributes of the connecting edge,
702+
/// see :func:`~find_predecessors_by_edge`.
690703
///
691-
/// See also :func:`~successors`.
704+
/// See also :func:`~successors`.
692705
///
693-
/// For undirected graphs, see :func:`~PyGraph.neighbors`.
706+
/// For undirected graphs, see :func:`~PyGraph.neighbors`.
707+
///
708+
/// To get beyond the nearest predecessors, see :func:`~rustworkx.ancestors`.
694709
///
695710
/// :param int node: The index of the node to get the predecessors for
696711
///
@@ -705,9 +720,12 @@ impl PyDiGraph {
705720
let mut predec: Vec<&PyObject> = Vec::new();
706721
let mut used_indices: HashSet<NodeIndex> = HashSet::new();
707722
for pred in parents {
708-
if !used_indices.contains(&pred) {
709-
predec.push(self.graph.node_weight(pred).unwrap());
710-
used_indices.insert(pred);
723+
match used_indices.entry(pred) {
724+
Entry::Vacant(used_indices_entry) => {
725+
used_indices_entry.insert();
726+
predec.push(self.graph.node_weight(pred).unwrap());
727+
}
728+
Entry::Occupied(_) => {}
711729
}
712730
}
713731
predec

src/traversal/mod.rs

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -222,18 +222,33 @@ pub fn bfs_predecessors(
222222
}
223223
}
224224

225-
/// Return the ancestors of a node in a graph.
225+
/// Retrieve all ancestors of a specified node in a directed graph.
226226
///
227-
/// This differs from :meth:`PyDiGraph.predecessors` method in that
228-
/// ``predecessors`` returns only nodes with a direct edge into the provided
229-
/// node. While this function returns all nodes that have a path into the
230-
/// provided node.
227+
/// This function differs from the :meth:`PyDiGraph.predecessors` method,
228+
/// which only returns nodes that have a direct edge leading to the specified
229+
/// node. In contrast, this function returns all nodes that have a path
230+
/// leading to the specified node, regardless of the number of edges in
231+
/// between.
231232
///
232-
/// :param PyDiGraph graph: The graph to get the ancestors from.
233-
/// :param int node: The index of the graph node to get the ancestors for
233+
/// >>> G = rx.PyDiGraph()
234+
/// >>> G.add_nodes_from(range(5))
235+
/// NodeIndices[0, 1, 2, 3, 4]
236+
/// >>> G.add_edges_from_no_data([(0, 2), (1, 2), (2, 3), (3, 4)])
237+
/// [0, 1, 2, 3]
238+
/// >>> rx.ancestors(G, 3)
239+
/// {0, 1, 2}
234240
///
235-
/// :returns: A set of node indices of ancestors of provided node.
236-
/// :rtype: set
241+
/// .. seealso ::
242+
/// See also :func:`~predecessors`.
243+
///
244+
/// :param PyDiGraph graph: The directed graph from which to retrieve ancestors.
245+
/// :param int node: The index of the node for which to find ancestors.
246+
///
247+
/// :returns: A set containing the indices of all ancestor nodes of the
248+
/// specified node.
249+
/// :rtype: set[int]
250+
///
251+
/// :raises IndexError: If the specified node is not present in the directed graph.
237252
#[pyfunction]
238253
#[pyo3(text_signature = "(graph, node, /)")]
239254
pub fn ancestors(graph: &digraph::PyDiGraph, node: usize) -> PyResult<HashSet<usize>> {
@@ -250,18 +265,33 @@ pub fn ancestors(graph: &digraph::PyDiGraph, node: usize) -> PyResult<HashSet<us
250265
.collect())
251266
}
252267

253-
/// Return the descendants of a node in a graph.
268+
/// Retrieve all descendants of a specified node in a directed graph.
269+
///
270+
/// This function differs from the :meth:`PyDiGraph.successors` method,
271+
/// which only returns nodes that have a direct edge leading from the specified
272+
/// node. In contrast, this function returns all nodes that have a path
273+
/// leading from the specified node, regardless of the number of edges in
274+
/// between.
275+
///
276+
/// >>> G = rx.PyDiGraph()
277+
/// >>> G.add_nodes_from(range(5))
278+
/// NodeIndices[0, 1, 2, 3, 4]
279+
/// >>> G.add_edges_from_no_data([(0, 1), (1, 2), (2, 3), (2, 4)])
280+
/// [0, 1, 2, 3]
281+
/// >>> rx.descendants(G, 1)
282+
/// {2, 3, 4}
283+
///
284+
/// .. seealso ::
285+
/// See also :func:`~ancestors`.
254286
///
255-
/// This differs from :meth:`PyDiGraph.successors` method in that
256-
/// ``successors``` returns only nodes with a direct edge out of the provided
257-
/// node. While this function returns all nodes that have a path from the
258-
/// provided node.
287+
/// :param PyDiGraph graph: The directed graph from which to retrieve descendants.
288+
/// :param int node: The index of the node for which to find descendants.
259289
///
260-
/// :param PyDiGraph graph: The graph to get the descendants from
261-
/// :param int node: The index of the graph node to get the descendants for
290+
/// :returns: A set containing the indices of all descendant nodes of the
291+
/// specified node.
292+
/// :rtype: set[int]
262293
///
263-
/// :returns: A set of node indices of descendants of provided node.
264-
/// :rtype: set
294+
/// :raises IndexError: If the specified node is not present in the directed graph.
265295
#[pyfunction]
266296
#[pyo3(text_signature = "(graph, node, /)")]
267297
pub fn descendants(graph: &digraph::PyDiGraph, node: usize) -> PyResult<HashSet<usize>> {

tests/digraph/test_pred_succ.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ def test_many_parents(self):
5555
res,
5656
)
5757

58+
def test_missing_node_has_no_predecessors(self):
59+
G = rustworkx.PyDiGraph()
60+
G.add_nodes_from(range(5))
61+
G.add_edges_from_no_data([(0, 1), (2, 3)])
62+
res = G.predecessors(10)
63+
self.assertEqual([], res)
64+
5865

5966
class TestSuccessors(unittest.TestCase):
6067
def test_single_successor(self):
@@ -98,6 +105,13 @@ def test_many_children(self):
98105
res,
99106
)
100107

108+
def test_missing_node_has_no_successors(self):
109+
G = rustworkx.PyDiGraph()
110+
G.add_nodes_from(range(5))
111+
G.add_edges_from_no_data([(0, 1), (2, 3)])
112+
res = G.successors(10)
113+
self.assertEqual([], res)
114+
101115

102116
class TestFindPredecessorsByEdge(unittest.TestCase):
103117
def test_single_predecessor(self):

0 commit comments

Comments
 (0)