Skip to content

Commit 53868f8

Browse files
authored
Move bfs_successors and bfs_predecessors to rustworkx-core (#1209)
This commit adds an implementation of the bfs_successors and bfs_predecessors functions to the rustworkx-core crate exposing the functions to rust users. The existing implementation in the rustworkx crate is removed and it is updated to call the rustworkx-core functions. The rustwork-core functions return an iterator of nodes indices and the Python crate side consumes the iterator to build the bfs view these functions previously returned.
1 parent 4327583 commit 53868f8

File tree

3 files changed

+215
-33
lines changed

3 files changed

+215
-33
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
features:
3+
- |
4+
Added a new function ``bfs_predecessors()`` to the
5+
``rustworkx_core::traversal`` module. That is a generic Rust implementation
6+
for the core rust library that provides the
7+
:func:`.bfs_predecessors` function to Rust users.
8+
- |
9+
Added a new function ``bfs_successors()`` to the
10+
``rustworkx_core::traversal`` module. That is a generic Rust implementation
11+
for the core rust library that provides the
12+
:func:`.bfs_successors` function to Rust users.

rustworkx-core/src/traversal/mod.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,177 @@ where
140140
}
141141
}
142142

143+
struct BFSAncestryWalker<G, N, VM> {
144+
graph: G,
145+
walker: Bfs<N, VM>,
146+
}
147+
148+
impl<
149+
G: GraphRef + Visitable + IntoNeighborsDirected<NodeId = N>,
150+
N: Copy + Clone + PartialEq,
151+
VM: VisitMap<N>,
152+
> Iterator for BFSAncestryWalker<G, N, VM>
153+
{
154+
type Item = (N, Vec<N>);
155+
156+
fn next(&mut self) -> Option<Self::Item> {
157+
self.walker.next(self.graph).map(|nx| {
158+
(
159+
nx,
160+
self.graph
161+
.neighbors_directed(nx, petgraph::Direction::Outgoing)
162+
.collect(),
163+
)
164+
})
165+
}
166+
}
167+
168+
/// Return the successors in a breadth-first-search from a source node
169+
///
170+
/// Each iterator step returns the node indices in a bfs order from
171+
/// the specified node in the form:
172+
///
173+
/// `(Parent Node, vec![children nodes])`
174+
///
175+
/// # Arguments:
176+
///
177+
/// * `graph` - The graph to search
178+
/// * `node` - The node to search from
179+
///
180+
/// # Returns
181+
///
182+
/// An iterator of nodes in BFS order where each item in the iterator
183+
/// is a tuple of the `NodeId` for the parent and a `Vec` of node ids
184+
/// it's successors. If a node in the bfs traversal doesn't have any
185+
/// successors it will still be present but contain an empty vec.
186+
///
187+
/// # Example
188+
///
189+
/// ```rust
190+
/// use rustworkx_core::traversal::bfs_successors;
191+
/// use rustworkx_core::petgraph::stable_graph::{StableDiGraph, NodeIndex};
192+
///
193+
/// let graph: StableDiGraph<(), ()> = StableDiGraph::from_edges(&[
194+
/// (0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)
195+
/// ]);
196+
/// let successors: Vec<(usize, Vec<usize>)> = bfs_successors(&graph, NodeIndex::new(3))
197+
/// .map(|(x, succ)| (x.index(), succ.iter().map(|y| y.index()).collect()))
198+
/// .collect();
199+
/// assert_eq!(vec![(3_usize, vec![4_usize]), (4, vec![5]), (5, vec![])], successors);
200+
/// ```
201+
pub fn bfs_successors<G>(
202+
graph: G,
203+
node: G::NodeId,
204+
) -> impl Iterator<Item = (G::NodeId, Vec<G::NodeId>)>
205+
where
206+
G: GraphRef + Visitable + IntoNeighborsDirected,
207+
{
208+
BFSAncestryWalker {
209+
graph,
210+
walker: Bfs::new(graph, node),
211+
}
212+
}
213+
214+
/// Return the predecessor in a breadth-first-search from a source node
215+
///
216+
/// Each iterator step returns the node indices in a bfs order from
217+
/// the specified node in the form:
218+
///
219+
/// `(Child Node, vec![parent nodes])`
220+
///
221+
/// # Arguments:
222+
///
223+
/// * `graph` - The graph to search
224+
/// * `node` - The node to search from
225+
///
226+
/// # Returns
227+
///
228+
/// An iterator of nodes in BFS order where each item in the iterator
229+
/// is a tuple of the `NodeId` for the child and a `Vec` of node ids
230+
/// it's predecessors. If a node in the bfs traversal doesn't have any
231+
/// predecessors it will still be present but contain an empty vec.
232+
///
233+
/// # Example
234+
///
235+
/// ```rust
236+
/// use rustworkx_core::traversal::bfs_predecessors;
237+
/// use rustworkx_core::petgraph::stable_graph::{StableDiGraph, NodeIndex};
238+
///
239+
/// let graph: StableDiGraph<(), ()> = StableDiGraph::from_edges(&[
240+
/// (0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)
241+
/// ]);
242+
/// let predecessors: Vec<(usize, Vec<usize>)> = bfs_predecessors(&graph, NodeIndex::new(3))
243+
/// .map(|(x, succ)| (x.index(), succ.iter().map(|y| y.index()).collect()))
244+
/// .collect();
245+
/// assert_eq!(vec![(3_usize, vec![1_usize]), (1, vec![0]), (0, vec![])], predecessors);
246+
/// ```
247+
pub fn bfs_predecessors<G>(
248+
graph: G,
249+
node: G::NodeId,
250+
) -> impl Iterator<Item = (G::NodeId, Vec<G::NodeId>)>
251+
where
252+
G: GraphRef + Visitable + IntoNeighborsDirected,
253+
{
254+
let reversed = Reversed(graph);
255+
BFSAncestryWalker {
256+
graph: reversed,
257+
walker: Bfs::new(reversed, node),
258+
}
259+
}
260+
261+
#[cfg(test)]
262+
mod test_bfs_ancestry {
263+
use super::{bfs_predecessors, bfs_successors};
264+
use crate::petgraph::graph::DiGraph;
265+
use crate::petgraph::stable_graph::{NodeIndex, StableDiGraph};
266+
267+
#[test]
268+
fn test_bfs_predecessors_digraph() {
269+
let graph: DiGraph<(), ()> =
270+
DiGraph::from_edges(&[(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]);
271+
let predecessors: Vec<(usize, Vec<usize>)> = bfs_predecessors(&graph, NodeIndex::new(3))
272+
.map(|(x, succ)| (x.index(), succ.iter().map(|y| y.index()).collect()))
273+
.collect();
274+
assert_eq!(
275+
vec![(3_usize, vec![1_usize]), (1, vec![0]), (0, vec![])],
276+
predecessors
277+
);
278+
}
279+
280+
#[test]
281+
fn test_bfs_successors() {
282+
let graph: DiGraph<(), ()> =
283+
DiGraph::from_edges(&[(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]);
284+
let successors: Vec<(usize, Vec<usize>)> = bfs_successors(&graph, NodeIndex::new(3))
285+
.map(|(x, succ)| (x.index(), succ.iter().map(|y| y.index()).collect()))
286+
.collect();
287+
assert_eq!(
288+
vec![(3_usize, vec![4_usize]), (4, vec![5]), (5, vec![])],
289+
successors
290+
);
291+
}
292+
293+
#[test]
294+
fn test_no_predecessors() {
295+
let graph: StableDiGraph<(), ()> =
296+
StableDiGraph::from_edges(&[(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]);
297+
let predecessors: Vec<(usize, Vec<usize>)> = bfs_predecessors(&graph, NodeIndex::new(0))
298+
.map(|(x, succ)| (x.index(), succ.iter().map(|y| y.index()).collect()))
299+
.collect();
300+
assert_eq!(vec![(0_usize, vec![])], predecessors);
301+
}
302+
303+
#[test]
304+
fn test_no_successors() {
305+
let graph: StableDiGraph<(), ()> =
306+
StableDiGraph::from_edges(&[(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]);
307+
let successors: Vec<(usize, Vec<usize>)> = bfs_successors(&graph, NodeIndex::new(5))
308+
.map(|(x, succ)| (x.index(), succ.iter().map(|y| y.index()).collect()))
309+
.collect();
310+
assert_eq!(vec![(5_usize, vec![])], successors);
311+
}
312+
}
313+
143314
#[cfg(test)]
144315
mod test_ancestry {
145316
use super::{ancestors, descendants};

src/traversal/mod.rs

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ use dfs_visit::{dfs_handler, PyDfsVisitor};
1919
use dijkstra_visit::{dijkstra_handler, PyDijkstraVisitor};
2020

2121
use rustworkx_core::traversal::{
22-
ancestors as core_ancestors, breadth_first_search, depth_first_search,
22+
ancestors as core_ancestors, bfs_predecessors as core_bfs_predecessors,
23+
bfs_successors as core_bfs_successors, breadth_first_search, depth_first_search,
2324
descendants as core_descendants, dfs_edges, dijkstra_search,
2425
};
2526

@@ -34,7 +35,6 @@ use pyo3::prelude::*;
3435
use pyo3::Python;
3536

3637
use petgraph::graph::NodeIndex;
37-
use petgraph::visit::{Bfs, NodeCount, Reversed};
3838

3939
use crate::iterators::EdgeList;
4040

@@ -144,21 +144,21 @@ pub fn bfs_successors(
144144
node: usize,
145145
) -> iterators::BFSSuccessors {
146146
let index = NodeIndex::new(node);
147-
let mut bfs = Bfs::new(&graph.graph, index);
148-
let mut out_list: Vec<(PyObject, Vec<PyObject>)> = Vec::with_capacity(graph.node_count());
149-
while let Some(nx) = bfs.next(&graph.graph) {
150-
let successors: Vec<PyObject> = graph
151-
.graph
152-
.neighbors_directed(nx, petgraph::Direction::Outgoing)
153-
.map(|pred| graph.graph.node_weight(pred).unwrap().clone_ref(py))
154-
.collect();
155-
if !successors.is_empty() {
156-
out_list.push((
157-
graph.graph.node_weight(nx).unwrap().clone_ref(py),
158-
successors,
159-
));
160-
}
161-
}
147+
let out_list = core_bfs_successors(&graph.graph, index)
148+
.filter_map(|(nx, succ_list)| {
149+
if succ_list.is_empty() {
150+
None
151+
} else {
152+
Some((
153+
graph.graph.node_weight(nx).unwrap().clone_ref(py),
154+
succ_list
155+
.into_iter()
156+
.map(|pred| graph.graph.node_weight(pred).unwrap().clone_ref(py))
157+
.collect(),
158+
))
159+
}
160+
})
161+
.collect();
162162
iterators::BFSSuccessors {
163163
bfs_successors: out_list,
164164
}
@@ -185,22 +185,21 @@ pub fn bfs_predecessors(
185185
node: usize,
186186
) -> iterators::BFSPredecessors {
187187
let index = NodeIndex::new(node);
188-
let reverse_graph = Reversed(&graph.graph);
189-
let mut bfs = Bfs::new(reverse_graph, index);
190-
let mut out_list: Vec<(PyObject, Vec<PyObject>)> = Vec::with_capacity(graph.node_count());
191-
while let Some(nx) = bfs.next(reverse_graph) {
192-
let predecessors: Vec<PyObject> = graph
193-
.graph
194-
.neighbors_directed(nx, petgraph::Direction::Incoming)
195-
.map(|pred| graph.graph.node_weight(pred).unwrap().clone_ref(py))
196-
.collect();
197-
if !predecessors.is_empty() {
198-
out_list.push((
199-
graph.graph.node_weight(nx).unwrap().clone_ref(py),
200-
predecessors,
201-
));
202-
}
203-
}
188+
let out_list = core_bfs_predecessors(&graph.graph, index)
189+
.filter_map(|(nx, succ_list)| {
190+
if succ_list.is_empty() {
191+
None
192+
} else {
193+
Some((
194+
graph.graph.node_weight(nx).unwrap().clone_ref(py),
195+
succ_list
196+
.into_iter()
197+
.map(|pred| graph.graph.node_weight(pred).unwrap().clone_ref(py))
198+
.collect(),
199+
))
200+
}
201+
})
202+
.collect();
204203
iterators::BFSPredecessors {
205204
bfs_predecessors: out_list,
206205
}

0 commit comments

Comments
 (0)