rust_igraph/core/builder.rs
1//! Fluent graph construction via the builder pattern.
2
3use super::error::{IgraphError, IgraphResult};
4use super::graph::{Graph, VertexId};
5
6/// A fluent builder for constructing [`Graph`] instances.
7///
8/// `GraphBuilder` accumulates vertices and edges, then produces a
9/// [`Graph`] in a single `build()` call. It validates structure only at
10/// build time, so intermediate states need not be valid graphs.
11///
12/// # Examples
13///
14/// ```
15/// use rust_igraph::GraphBuilder;
16///
17/// // Build a directed triangle.
18/// let g = GraphBuilder::directed()
19/// .vertices(3)
20/// .edge(0, 1)
21/// .edge(1, 2)
22/// .edge(2, 0)
23/// .build()
24/// .unwrap();
25/// assert_eq!(g.vcount(), 3);
26/// assert_eq!(g.ecount(), 3);
27/// assert!(g.is_directed());
28/// ```
29///
30/// ```
31/// use rust_igraph::GraphBuilder;
32///
33/// // Build from an edge list (vertex count inferred).
34/// let g = GraphBuilder::undirected()
35/// .edges(&[(0, 1), (1, 2), (2, 3), (3, 0)])
36/// .build()
37/// .unwrap();
38/// assert_eq!(g.vcount(), 4);
39/// assert_eq!(g.ecount(), 4);
40/// ```
41#[derive(Debug, Clone)]
42pub struct GraphBuilder {
43 directed: bool,
44 n: Option<u32>,
45 edge_list: Vec<(VertexId, VertexId)>,
46}
47
48impl GraphBuilder {
49 /// Start building a directed graph.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// use rust_igraph::GraphBuilder;
55 ///
56 /// let g = GraphBuilder::directed()
57 /// .vertices(2)
58 /// .edge(0, 1)
59 /// .build()
60 /// .unwrap();
61 /// assert!(g.is_directed());
62 /// ```
63 #[must_use]
64 pub fn directed() -> Self {
65 Self {
66 directed: true,
67 n: None,
68 edge_list: Vec::new(),
69 }
70 }
71
72 /// Start building an undirected graph.
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// use rust_igraph::GraphBuilder;
78 ///
79 /// let g = GraphBuilder::undirected()
80 /// .vertices(5)
81 /// .build()
82 /// .unwrap();
83 /// assert!(!g.is_directed());
84 /// assert_eq!(g.vcount(), 5);
85 /// ```
86 #[must_use]
87 pub fn undirected() -> Self {
88 Self {
89 directed: false,
90 n: None,
91 edge_list: Vec::new(),
92 }
93 }
94
95 /// Set the vertex count explicitly.
96 ///
97 /// If not called, vertex count is inferred from the maximum vertex id
98 /// in any added edge (plus one). Calling this with a value *smaller*
99 /// than the inferred count causes `build()` to return an error.
100 #[must_use]
101 pub fn vertices(mut self, n: u32) -> Self {
102 self.n = Some(n);
103 self
104 }
105
106 /// Add a single edge.
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// use rust_igraph::GraphBuilder;
112 ///
113 /// let g = GraphBuilder::undirected()
114 /// .edge(0, 1)
115 /// .edge(1, 2)
116 /// .build()
117 /// .unwrap();
118 /// assert_eq!(g.ecount(), 2);
119 /// ```
120 #[must_use]
121 pub fn edge(mut self, from: VertexId, to: VertexId) -> Self {
122 self.edge_list.push((from, to));
123 self
124 }
125
126 /// Add multiple edges from a slice of `(from, to)` pairs.
127 ///
128 /// # Examples
129 ///
130 /// ```
131 /// use rust_igraph::GraphBuilder;
132 ///
133 /// let g = GraphBuilder::undirected()
134 /// .edges(&[(0, 1), (1, 2), (2, 0)])
135 /// .build()
136 /// .unwrap();
137 /// assert_eq!(g.ecount(), 3);
138 /// ```
139 #[must_use]
140 pub fn edges(mut self, pairs: &[(VertexId, VertexId)]) -> Self {
141 self.edge_list.extend_from_slice(pairs);
142 self
143 }
144
145 /// Add a chain of edges forming a path: `v0 -> v1 -> v2 -> ... -> vk`.
146 ///
147 /// # Examples
148 ///
149 /// ```
150 /// use rust_igraph::GraphBuilder;
151 ///
152 /// let g = GraphBuilder::undirected()
153 /// .path(&[0, 1, 2, 3, 4])
154 /// .build()
155 /// .unwrap();
156 /// assert_eq!(g.ecount(), 4);
157 /// ```
158 #[must_use]
159 pub fn path(mut self, vertices: &[VertexId]) -> Self {
160 for w in vertices.windows(2) {
161 self.edge_list.push((w[0], w[1]));
162 }
163 self
164 }
165
166 /// Add edges forming a cycle: `v0 -> v1 -> ... -> vk -> v0`.
167 ///
168 /// # Examples
169 ///
170 /// ```
171 /// use rust_igraph::GraphBuilder;
172 ///
173 /// let g = GraphBuilder::undirected()
174 /// .cycle(&[0, 1, 2, 3])
175 /// .build()
176 /// .unwrap();
177 /// assert_eq!(g.ecount(), 4);
178 /// assert!(g.has_edge(3, 0));
179 /// ```
180 #[must_use]
181 pub fn cycle(mut self, vertices: &[VertexId]) -> Self {
182 for w in vertices.windows(2) {
183 self.edge_list.push((w[0], w[1]));
184 }
185 if vertices.len() >= 2 {
186 self.edge_list
187 .push((vertices[vertices.len() - 1], vertices[0]));
188 }
189 self
190 }
191
192 /// Add edges forming a complete subgraph (clique) on the given vertices.
193 ///
194 /// # Examples
195 ///
196 /// ```
197 /// use rust_igraph::GraphBuilder;
198 ///
199 /// let g = GraphBuilder::undirected()
200 /// .clique(&[0, 1, 2, 3])
201 /// .build()
202 /// .unwrap();
203 /// // K_4 has 4*3/2 = 6 edges.
204 /// assert_eq!(g.ecount(), 6);
205 /// ```
206 #[must_use]
207 pub fn clique(mut self, vertices: &[VertexId]) -> Self {
208 for (i, &u) in vertices.iter().enumerate() {
209 for &v in &vertices[i + 1..] {
210 self.edge_list.push((u, v));
211 }
212 }
213 self
214 }
215
216 /// Add edges connecting a center vertex to all given leaf vertices (star).
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// use rust_igraph::GraphBuilder;
222 ///
223 /// let g = GraphBuilder::undirected()
224 /// .star(0, &[1, 2, 3, 4])
225 /// .build()
226 /// .unwrap();
227 /// assert_eq!(g.ecount(), 4);
228 /// ```
229 #[must_use]
230 pub fn star(mut self, center: VertexId, leaves: &[VertexId]) -> Self {
231 for &leaf in leaves {
232 self.edge_list.push((center, leaf));
233 }
234 self
235 }
236
237 /// Consume the builder and produce a [`Graph`].
238 ///
239 /// # Errors
240 ///
241 /// Returns an error if the explicit vertex count is smaller than
242 /// required by the edges.
243 ///
244 /// # Examples
245 ///
246 /// ```
247 /// use rust_igraph::GraphBuilder;
248 ///
249 /// // Error: vertex count too small for edge (0, 5).
250 /// let r = GraphBuilder::undirected()
251 /// .vertices(3)
252 /// .edge(0, 5)
253 /// .build();
254 /// assert!(r.is_err());
255 /// ```
256 pub fn build(self) -> IgraphResult<Graph> {
257 let inferred = match self.edge_list.iter().flat_map(|&(u, v)| [u, v]).max() {
258 Some(m) => m
259 .checked_add(1)
260 .ok_or_else(|| IgraphError::InvalidArgument("vertex id overflow".to_owned()))?,
261 None => 0,
262 };
263
264 let n = match self.n {
265 Some(explicit) => {
266 if explicit < inferred {
267 return Err(IgraphError::InvalidArgument(format!(
268 "vertex count {explicit} is too small for edges referencing vertex {}",
269 inferred - 1
270 )));
271 }
272 explicit
273 }
274 None => inferred,
275 };
276
277 let mut g = Graph::new(n, self.directed)?;
278 g.add_edges(self.edge_list)?;
279 Ok(g)
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn directed_triangle() {
289 let g = GraphBuilder::directed()
290 .vertices(3)
291 .edge(0, 1)
292 .edge(1, 2)
293 .edge(2, 0)
294 .build()
295 .expect("build");
296 assert!(g.is_directed());
297 assert_eq!(g.vcount(), 3);
298 assert_eq!(g.ecount(), 3);
299 }
300
301 #[test]
302 fn undirected_inferred_vertices() {
303 let g = GraphBuilder::undirected()
304 .edges(&[(0, 3), (1, 4)])
305 .build()
306 .expect("build");
307 assert_eq!(g.vcount(), 5);
308 assert_eq!(g.ecount(), 2);
309 }
310
311 #[test]
312 fn empty_graph() {
313 let g = GraphBuilder::undirected()
314 .vertices(10)
315 .build()
316 .expect("build");
317 assert_eq!(g.vcount(), 10);
318 assert_eq!(g.ecount(), 0);
319 }
320
321 #[test]
322 fn zero_vertices_no_edges() {
323 let g = GraphBuilder::undirected().build().expect("build");
324 assert_eq!(g.vcount(), 0);
325 assert_eq!(g.ecount(), 0);
326 }
327
328 #[test]
329 fn path_helper() {
330 let g = GraphBuilder::undirected()
331 .path(&[0, 1, 2, 3])
332 .build()
333 .expect("build");
334 assert_eq!(g.ecount(), 3);
335 assert!(g.has_edge(0, 1));
336 assert!(g.has_edge(2, 3));
337 }
338
339 #[test]
340 fn cycle_helper() {
341 let g = GraphBuilder::undirected()
342 .cycle(&[0, 1, 2, 3, 4])
343 .build()
344 .expect("build");
345 assert_eq!(g.ecount(), 5);
346 assert!(g.has_edge(4, 0));
347 }
348
349 #[test]
350 fn clique_helper() {
351 let g = GraphBuilder::undirected()
352 .clique(&[0, 1, 2, 3])
353 .build()
354 .expect("build");
355 assert_eq!(g.ecount(), 6);
356 }
357
358 #[test]
359 fn star_helper() {
360 let g = GraphBuilder::undirected()
361 .star(0, &[1, 2, 3])
362 .build()
363 .expect("build");
364 assert_eq!(g.ecount(), 3);
365 assert!(g.has_edge(0, 1));
366 assert!(g.has_edge(0, 3));
367 }
368
369 #[test]
370 fn vertex_count_too_small_errors() {
371 let r = GraphBuilder::undirected().vertices(2).edge(0, 5).build();
372 assert!(r.is_err());
373 }
374
375 #[test]
376 fn chaining_multiple_patterns() {
377 let g = GraphBuilder::undirected()
378 .clique(&[0, 1, 2])
379 .star(3, &[0, 1, 2])
380 .build()
381 .expect("build");
382 assert_eq!(g.vcount(), 4);
383 assert_eq!(g.ecount(), 6);
384 }
385}