Skip to main content

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}