Skip to content

Commit fe4b681

Browse files
eliotheinrichIvanIsCoding
authored andcommitted
Has path (#952)
* Added has_path for PyGraph and PyDiGraph * Fixed linting errors and added release notes * Fixed doc type * Fixed cargo fmt problem * Fixed clippy problem * Typo in release notes * Apply suggestions from code review * Remove whitespace --------- Co-authored-by: Ivan Carvalho <[email protected]>
1 parent 3ab2d17 commit fe4b681

File tree

6 files changed

+142
-0
lines changed

6 files changed

+142
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
features:
3+
- |
4+
Added :func:`~rustworkx.has_path` which accepts as arguments a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph` and checks if there is a path from source to destination
5+
6+
.. jupyter-execute::
7+
8+
from rustworkx import PyDiGraph, has_path
9+
10+
graph = PyDiGraph()
11+
a = graph.add_node("A")
12+
b = graph.add_node("B")
13+
c = graph.add_node("C")
14+
edge_list = [(a, b, 1), (b, c, 1)]
15+
graph.add_edges_from(edge_list)
16+
17+
path_exists = has_path(graph, a, c)
18+
assert(path_exists == True)
19+
20+
path_exists = has_path(graph, c, a)
21+
assert(path_exists == False)

rustworkx/__init__.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,39 @@ def _graph_dijkstra_shortest_path(graph, source, target=None, weight_fn=None, de
587587
)
588588

589589

590+
@functools.singledispatch
591+
def has_path(
592+
graph,
593+
source,
594+
target,
595+
as_undirected=False,
596+
):
597+
"""Checks if a path exists between a source and target node
598+
599+
:param graph: The input graph to use. Can either be a
600+
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
601+
:param int source: The node index to find paths from
602+
:param int target: The index of the target node
603+
:param bool as_undirected: If set to true the graph will be treated as
604+
undirected for finding existence of a path. This only works with a
605+
:class:`~rustworkx.PyDiGraph` input for ``graph``
606+
607+
:return: True if a path exists, False if not
608+
:rtype: bool
609+
"""
610+
raise TypeError("Invalid Input Type %s for graph" % type(graph))
611+
612+
613+
@has_path.register(PyDiGraph)
614+
def _digraph_has_path(graph, source, target, as_undirected=False):
615+
return digraph_has_path(graph, source, target=target, as_undirected=as_undirected)
616+
617+
618+
@has_path.register(PyGraph)
619+
def _graph_has_path(graph, source, target):
620+
return graph_has_path(graph, source, target=target)
621+
622+
590623
@functools.singledispatch
591624
def all_pairs_dijkstra_shortest_paths(graph, edge_cost_fn):
592625
"""For each node in the graph, finds the shortest paths to all others.

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,8 @@ fn rustworkx(py: Python<'_>, m: &PyModule) -> PyResult<()> {
400400
m.add_wrapped(wrap_pyfunction!(digraph_all_simple_paths))?;
401401
m.add_wrapped(wrap_pyfunction!(graph_dijkstra_shortest_paths))?;
402402
m.add_wrapped(wrap_pyfunction!(digraph_dijkstra_shortest_paths))?;
403+
m.add_wrapped(wrap_pyfunction!(graph_has_path))?;
404+
m.add_wrapped(wrap_pyfunction!(digraph_has_path))?;
403405
m.add_wrapped(wrap_pyfunction!(graph_dijkstra_shortest_path_lengths))?;
404406
m.add_wrapped(wrap_pyfunction!(digraph_dijkstra_shortest_path_lengths))?;
405407
m.add_wrapped(wrap_pyfunction!(graph_bellman_ford_shortest_paths))?;

src/shortest_path/mod.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,32 @@ pub fn graph_dijkstra_shortest_paths(
109109
})
110110
}
111111

112+
/// Check if a graph has a path between source and target nodes
113+
///
114+
/// :param PyGraph graph:
115+
/// :param int source: The node index to find paths from
116+
/// :param int target: The index of the target node
117+
///
118+
/// :return: True if a path exists, False if not.
119+
/// :rtype: bool
120+
/// :raises ValueError: when an edge weight with NaN or negative value
121+
/// is provided.
122+
#[pyfunction]
123+
#[pyo3(
124+
signature=(graph, source, target),
125+
text_signature = "(graph, source, target)"
126+
)]
127+
pub fn graph_has_path(
128+
py: Python,
129+
graph: &graph::PyGraph,
130+
source: usize,
131+
target: usize,
132+
) -> PyResult<bool> {
133+
let path_mapping = graph_dijkstra_shortest_paths(py, graph, source, Some(target), None, 1.0)?;
134+
135+
Ok(!path_mapping.paths.is_empty())
136+
}
137+
112138
/// Find the shortest path from a node
113139
///
114140
/// This function will generate the shortest path from a source node using
@@ -184,6 +210,36 @@ pub fn digraph_dijkstra_shortest_paths(
184210
})
185211
}
186212

213+
/// Check if a digraph has a path between source and target nodes
214+
///
215+
/// :param PyDiGraph graph:
216+
/// :param int source: The node index to find paths from
217+
/// :param int target: The index of the target node
218+
/// :param bool as_undirected: If set to true the graph will be treated as
219+
/// undirected for finding a path
220+
///
221+
/// :return: True if a path exists, False if not.
222+
/// :rtype: bool
223+
/// :raises ValueError: when an edge weight with NaN or negative value
224+
/// is provided.
225+
#[pyfunction]
226+
#[pyo3(
227+
signature=(graph, source, target, as_undirected=false),
228+
text_signature = "(graph, source, target, /, as_undirected=false)"
229+
)]
230+
pub fn digraph_has_path(
231+
py: Python,
232+
graph: &digraph::PyDiGraph,
233+
source: usize,
234+
target: usize,
235+
as_undirected: bool,
236+
) -> PyResult<bool> {
237+
let path_mapping =
238+
digraph_dijkstra_shortest_paths(py, graph, source, Some(target), None, 1.0, as_undirected)?;
239+
240+
Ok(!path_mapping.paths.is_empty())
241+
}
242+
187243
/// Compute the lengths of the shortest paths for a PyGraph object using
188244
/// Dijkstra's algorithm
189245
///

tests/rustworkx_tests/digraph/test_dijkstra.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,21 @@ def test_dijkstra_path(self):
7070
}
7171
self.assertEqual(expected, paths)
7272

73+
def test_dijkstra_has_path(self):
74+
g = rustworkx.PyDiGraph()
75+
a = g.add_node("A")
76+
b = g.add_node("B")
77+
c = g.add_node("C")
78+
79+
edge_list = [
80+
(a, b, 7),
81+
(c, b, 9),
82+
(c, b, 10),
83+
]
84+
g.add_edges_from(edge_list)
85+
86+
self.assertFalse(rustworkx.digraph_has_path(g, a, c))
87+
7388
def test_dijkstra_path_with_weight_fn(self):
7489
paths = rustworkx.digraph_dijkstra_shortest_paths(self.graph, self.a, weight_fn=lambda x: x)
7590
expected = {

tests/rustworkx_tests/graph/test_dijkstra.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ def test_dijkstra_path(self):
5050
expected = {4: [self.a, self.c, self.d, self.e]}
5151
self.assertEqual(expected, path)
5252

53+
def test_dijkstra_has_path(self):
54+
g = rustworkx.PyGraph()
55+
a = g.add_node("A")
56+
b = g.add_node("B")
57+
c = g.add_node("C")
58+
59+
edge_list = [
60+
(a, b, 7),
61+
(c, b, 9),
62+
(c, b, 10),
63+
]
64+
g.add_edges_from(edge_list)
65+
66+
self.assertTrue(rustworkx.graph_has_path(g, a, c))
67+
5368
def test_dijkstra_with_no_goal_set(self):
5469
path = rustworkx.graph_dijkstra_shortest_path_lengths(self.graph, self.a, lambda x: 1)
5570
expected = {1: 1.0, 2: 1.0, 3: 1.0, 4: 2.0, 5: 2.0}

0 commit comments

Comments
 (0)