Skip to content

Commit fba1275

Browse files
authored
Enhance docs and unpack tuples in tree.rs (#1408)
1 parent c683aed commit fba1275

File tree

1 file changed

+83
-38
lines changed

1 file changed

+83
-38
lines changed

src/tree.rs

Lines changed: 83 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,53 @@ use rayon::prelude::*;
2727

2828
use crate::iterators::WeightedEdgeList;
2929

30-
/// Find the edges in the minimum spanning tree or forest of a graph
30+
/// Find the edges in the minimum spanning tree or forest of an undirected graph
3131
/// using Kruskal's algorithm.
3232
///
33-
/// :param PyGraph graph: Undirected graph
34-
/// :param weight_fn: A callable object (function, lambda, etc) which
35-
/// will be passed the edge object and expected to return a ``float``. This
36-
/// tells rustworkx/rust how to extract a numerical weight as a ``float``
37-
/// for edge object. Some simple examples are::
33+
/// A Minimum Spanning Tree (MST) is a subset of the edges of a connected,
34+
/// undirected graph that connects all vertices together without cycles and
35+
/// with the minimum possible total edge weight.
3836
///
39-
/// minimum_spanning_edges(graph, weight_fn: lambda x: 1)
37+
/// This function computes the edges that form the MST or forest of an
38+
/// undirected graph. Kruskal's algorithm works by sorting all the edges in the
39+
/// graph by their weights and then adding them one by one to the MST, ensuring
40+
/// that no cycles are formed.
4041
///
41-
/// to return a weight of 1 for all edges. Also::
42+
/// >>> G = rx.PyGraph()
43+
/// >>> G.add_nodes_from(["A", "B", "C", "D"])
44+
/// NodeIndices[0, 1, 2, 3]
45+
/// >>> G.add_edges_from([(0, 1, 10), (0, 2, 6), (0, 3, 5), (1, 3, 15), (2, 3, 4)])
46+
/// EdgeIndices[0, 1, 2, 3, 4]
47+
/// >>> rx.minimum_spanning_edges(G, weight_fn=lambda x: x)
48+
/// WeightedEdgeList[(2, 3, 4), (0, 3, 5), (0, 1, 10)]
4249
///
43-
/// minimum_spanning_edges(graph, weight_fn: float)
50+
/// In this example, the edge `(0, 2, 6)` won't become part of the MST because
51+
/// in the moment it's considered, the two other edges connecting nodes
52+
/// 0-3-2 are already parts of MST because of their lower weight.
4453
///
45-
/// to cast the edge object as a float as the weight.
46-
/// :param float default_weight: If ``weight_fn`` isn't specified this optional
54+
/// To obtain the result as a graph, see :func:`~minimum_spanning_tree`.
55+
///
56+
/// :param PyGraph graph: An undirected graph
57+
/// :param weight_fn: A callable object (function, lambda, etc) that takes
58+
/// an edge object and returns a ``float``. This function is used to
59+
/// extract the numerical weight for each edge. For example:
60+
///
61+
/// rx.minimum_spanning_edges(G, weight_fn=lambda x: 1)
62+
///
63+
/// will assign a weight of 1 to all edges and thus ignore the real weights.
64+
///
65+
/// rx.minimum_spanning_edges(G, weight_fn=float)
66+
///
67+
/// will just cast the edge object to a ``float`` to determine its weight.
68+
/// :param float default_weight: If ``weight_fn`` isn't specified, this optional
4769
/// float value will be used for the weight/cost of each edge.
4870
///
49-
/// :returns: The :math:`N - |c|` edges of the Minimum Spanning Tree (or Forest, if :math:`|c| > 1`)
50-
/// where :math:`N` is the number of nodes and :math:`|c|` is the number of connected components of the graph
71+
/// :returns: The :math:`N - |c|` edges of the Minimum Spanning Tree (or Forest,
72+
/// if :math:`|c| > 1`) where :math:`N` is the number of nodes and
73+
/// :math:`|c|` is the number of connected components of the graph.
5174
/// :rtype: WeightedEdgeList
75+
///
76+
/// :raises ValueError: If a NaN value is found (or computed) as an edge weight.
5277
#[pyfunction]
5378
#[pyo3(signature=(graph, weight_fn=None, default_weight=1.0), text_signature = "(graph, weight_fn=None, default_weight=1.0)")]
5479
pub fn minimum_spanning_edges(
@@ -69,52 +94,72 @@ pub fn minimum_spanning_edges(
6994
edge_list.push((weight, edge));
7095
}
7196

72-
edge_list.par_sort_unstable_by(|a, b| {
73-
let weight_a = a.0;
74-
let weight_b = b.0;
75-
weight_a.partial_cmp(&weight_b).unwrap_or(Ordering::Less)
97+
edge_list.par_sort_unstable_by(|(weight_a, _), (weight_b, _)| {
98+
weight_a.partial_cmp(weight_b).unwrap_or(Ordering::Less)
7699
});
77100

78-
let mut answer: Vec<(usize, usize, PyObject)> = Vec::new();
79-
for float_edge_pair in edge_list.iter() {
80-
let edge = float_edge_pair.1;
101+
let mut mst_edges: Vec<(usize, usize, PyObject)> = Vec::new();
102+
for (_, edge) in edge_list.iter() {
81103
let u = edge.source().index();
82104
let v = edge.target().index();
83105
if subgraphs.union(u, v) {
84-
let w = edge.weight().clone_ref(py);
85-
answer.push((u, v, w));
106+
mst_edges.push((u, v, edge.weight().clone_ref(py)));
86107
}
87108
}
88109

89-
Ok(WeightedEdgeList { edges: answer })
110+
Ok(WeightedEdgeList { edges: mst_edges })
90111
}
91112

92-
/// Find the minimum spanning tree or forest of a graph
93-
/// using Kruskal's algorithm.
113+
/// Find the minimum spanning tree or forest of an undirected graph using
114+
/// Kruskal's algorithm.
94115
///
95-
/// :param PyGraph graph: Undirected graph
96-
/// :param weight_fn: A callable object (function, lambda, etc) which
97-
/// will be passed the edge object and expected to return a ``float``. This
98-
/// tells rustworkx/rust how to extract a numerical weight as a ``float``
99-
/// for edge object. Some simple examples are::
116+
/// A Minimum Spanning Tree (MST) is a subset of the edges of a connected,
117+
/// undirected graph that connects all vertices together without cycles and
118+
/// with the minimum possible total edge weight.
100119
///
101-
/// minimum_spanning_tree(graph, weight_fn: lambda x: 1)
120+
/// This function computes the edges that form the MST or forest of an
121+
/// undirected graph. Kruskal's algorithm works by sorting all the edges in the
122+
/// graph by their weights and then adding them one by one to the MST, ensuring
123+
/// that no cycles are formed.
102124
///
103-
/// to return a weight of 1 for all edges. Also::
125+
/// >>> G = rx.PyGraph()
126+
/// >>> G.add_nodes_from(["A", "B", "C", "D"])
127+
/// NodeIndices[0, 1, 2, 3]
128+
/// >>> G.add_edges_from([(0, 1, 10), (0, 2, 6), (0, 3, 5), (1, 3, 15), (2, 3, 4)])
129+
/// EdgeIndices[0, 1, 2, 3, 4]
130+
/// >>> mst_G = rx.minimum_spanning_tree(G, weight_fn=lambda x: x)
131+
/// >>> mst_G.weighted_edge_list()
132+
/// WeightedEdgeList[(2, 3, 4), (0, 3, 5), (0, 1, 10)]
104133
///
105-
/// minimum_spanning_tree(graph, weight_fn: float)
134+
/// In this example, the edge `(0, 2, 6)` won't become part of the MST because
135+
/// in the moment it's considered, the two other edges connecting nodes
136+
/// 0-3-2 are already parts of MST because of their lower weight.
106137
///
107-
/// to cast the edge object as a float as the weight.
108-
/// :param float default_weight: If ``weight_fn`` isn't specified this optional
138+
/// To obtain the result just as a list of edges, see :func:`~minimum_spanning_edges`.
139+
///
140+
/// :param PyGraph graph: An undirected graph
141+
/// :param weight_fn: A callable object (function, lambda, etc) that takes
142+
/// an edge object and returns a ``float``. This function is used to
143+
/// extract the numerical weight for each edge. For example:
144+
///
145+
/// rx.minimum_spanning_tree(G, weight_fn=lambda x: 1)
146+
///
147+
/// will assign a weight of 1 to all edges and thus ignore the real weights.
148+
///
149+
/// rx.minimum_spanning_tree(G, weight_fn=float)
150+
///
151+
/// will just cast the edge object to a ``float`` to determine its weight.
152+
/// :param float default_weight: If ``weight_fn`` isn't specified, this optional
109153
/// float value will be used for the weight/cost of each edge.
110154
///
111155
/// :returns: A Minimum Spanning Tree (or Forest, if the graph is not connected).
112-
///
113156
/// :rtype: PyGraph
114157
///
115158
/// .. note::
116159
///
117160
/// The new graph will keep the same node indices, but edge indices might differ.
161+
///
162+
/// :raises ValueError: If a NaN value is found (or computed) as an edge weight.
118163
#[pyfunction]
119164
#[pyo3(signature=(graph, weight_fn=None, default_weight=1.0), text_signature = "(graph, weight_fn=None, default_weight=1.0)")]
120165
pub fn minimum_spanning_tree(
@@ -126,11 +171,11 @@ pub fn minimum_spanning_tree(
126171
let mut spanning_tree = (*graph).clone();
127172
spanning_tree.graph.clear_edges();
128173

129-
for edge in minimum_spanning_edges(py, graph, weight_fn, default_weight)?
174+
for &(u, v, ref weight) in minimum_spanning_edges(py, graph, weight_fn, default_weight)?
130175
.edges
131176
.iter()
132177
{
133-
spanning_tree.add_edge(edge.0, edge.1, edge.2.clone_ref(py))?;
178+
spanning_tree.add_edge(u, v, weight.clone_ref(py))?;
134179
}
135180

136181
Ok(spanning_tree)

0 commit comments

Comments
 (0)