rust_igraph/algorithms/operators/
disjoint_union.rs1use crate::core::graph::EdgeId;
15use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
16
17pub fn disjoint_union(left: &Graph, right: &Graph) -> IgraphResult<Graph> {
47 disjoint_union_many(&[left, right])
48}
49
50pub fn disjoint_union_many(graphs: &[&Graph]) -> IgraphResult<Graph> {
82 if graphs.is_empty() {
83 return Graph::new(0, false);
84 }
85 let directed = graphs[0].is_directed();
86 for g in &graphs[1..] {
87 if g.is_directed() != directed {
88 return Err(IgraphError::InvalidArgument(
89 "disjoint_union_many: cannot mix directed and undirected graphs".to_string(),
90 ));
91 }
92 }
93
94 let mut shifts: Vec<u32> = Vec::with_capacity(graphs.len());
97 let mut total_v: u32 = 0;
98 let mut total_e: usize = 0;
99 for g in graphs {
100 shifts.push(total_v);
101 total_v = total_v
102 .checked_add(g.vcount())
103 .ok_or(IgraphError::Internal("vertex count overflow"))?;
104 total_e = total_e
105 .checked_add(g.ecount())
106 .ok_or(IgraphError::Internal("edge count overflow"))?;
107 }
108
109 let mut out = Graph::new(total_v, directed)?;
110 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(total_e);
111 for (i, g) in graphs.iter().enumerate() {
112 let shift = shifts[i];
113 let m = u32::try_from(g.ecount())
114 .map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
115 for e in 0..m {
116 let (u, v) = g.edge(e as EdgeId)?;
117 edges.push((u + shift, v + shift));
118 }
119 }
120 out.add_edges(edges)?;
121 Ok(out)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
129 let m = u32::try_from(g.ecount()).unwrap();
130 let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
131 v.sort_unstable();
132 v
133 }
134
135 #[test]
136 fn two_triangles_undirected() {
137 let mut a = Graph::with_vertices(3);
138 a.add_edge(0, 1).unwrap();
139 a.add_edge(1, 2).unwrap();
140 a.add_edge(2, 0).unwrap();
141 let b = a.clone();
142 let u = disjoint_union(&a, &b).unwrap();
143 assert_eq!(u.vcount(), 6);
144 assert_eq!(u.ecount(), 6);
145 assert_eq!(
146 sorted_edges(&u),
147 vec![(0, 1), (0, 2), (1, 2), (3, 4), (3, 5), (4, 5)]
148 );
149 }
150
151 #[test]
152 fn empty_left() {
153 let a = Graph::with_vertices(0);
154 let mut b = Graph::with_vertices(2);
155 b.add_edge(0, 1).unwrap();
156 let u = disjoint_union(&a, &b).unwrap();
157 assert_eq!(u.vcount(), 2);
158 assert_eq!(u.ecount(), 1);
159 assert_eq!(u.edge(0).unwrap(), (0, 1));
160 }
161
162 #[test]
163 fn empty_right() {
164 let mut a = Graph::with_vertices(2);
165 a.add_edge(0, 1).unwrap();
166 let b = Graph::with_vertices(0);
167 let u = disjoint_union(&a, &b).unwrap();
168 assert_eq!(u.vcount(), 2);
169 assert_eq!(u.ecount(), 1);
170 }
171
172 #[test]
173 fn both_empty() {
174 let a = Graph::with_vertices(0);
175 let b = Graph::with_vertices(0);
176 let u = disjoint_union(&a, &b).unwrap();
177 assert_eq!(u.vcount(), 0);
178 assert_eq!(u.ecount(), 0);
179 }
180
181 #[test]
182 fn isolated_plus_edge() {
183 let mut a = Graph::with_vertices(2);
185 a.add_edge(0, 1).unwrap();
186 let b = Graph::with_vertices(3);
187 let u = disjoint_union(&a, &b).unwrap();
188 assert_eq!(u.vcount(), 5);
189 assert_eq!(u.ecount(), 1);
190 }
191
192 #[test]
193 fn directed_directed_succeeds() {
194 let mut a = Graph::new(2, true).unwrap();
195 a.add_edge(0, 1).unwrap();
196 let mut b = Graph::new(2, true).unwrap();
197 b.add_edge(1, 0).unwrap();
198 let u = disjoint_union(&a, &b).unwrap();
199 assert!(u.is_directed());
200 assert_eq!(u.vcount(), 4);
201 assert_eq!(u.ecount(), 2);
202 assert_eq!(u.edge(0).unwrap(), (0, 1));
203 assert_eq!(u.edge(1).unwrap(), (3, 2));
204 }
205
206 #[test]
207 fn mixed_directedness_errors() {
208 let a = Graph::with_vertices(2);
209 let b = Graph::new(2, true).unwrap();
210 assert!(disjoint_union(&a, &b).is_err());
211 }
212
213 #[test]
214 fn vertex_count_preserved_for_isolated_left() {
215 let a = Graph::with_vertices(5);
216 let mut b = Graph::with_vertices(2);
217 b.add_edge(0, 1).unwrap();
218 let u = disjoint_union(&a, &b).unwrap();
219 assert_eq!(u.vcount(), 7);
220 assert_eq!(u.edge(0).unwrap(), (5, 6));
221 }
222
223 #[test]
224 fn idempotent_with_self_when_relabelled() {
225 let mut a = Graph::with_vertices(4);
227 a.add_edge(0, 1).unwrap();
228 a.add_edge(1, 2).unwrap();
229 a.add_edge(2, 3).unwrap();
230 let u = disjoint_union(&a, &a).unwrap();
231 assert_eq!(u.vcount(), 8);
232 assert_eq!(u.ecount(), 6);
233 }
234
235 #[test]
238 fn many_empty_slice_yields_null_graph() {
239 let u = disjoint_union_many(&[]).unwrap();
240 assert_eq!(u.vcount(), 0);
241 assert_eq!(u.ecount(), 0);
242 assert!(!u.is_directed());
243 }
244
245 #[test]
246 fn many_single_graph_yields_clone() {
247 let mut a = Graph::with_vertices(3);
248 a.add_edge(0, 1).unwrap();
249 a.add_edge(1, 2).unwrap();
250 let u = disjoint_union_many(&[&a]).unwrap();
251 assert_eq!(u.vcount(), a.vcount());
252 assert_eq!(u.ecount(), a.ecount());
253 assert_eq!(sorted_edges(&u), sorted_edges(&a));
254 }
255
256 #[test]
257 fn many_three_triangles_shifts_correctly() {
258 let mut t = Graph::with_vertices(3);
259 t.add_edge(0, 1).unwrap();
260 t.add_edge(1, 2).unwrap();
261 t.add_edge(2, 0).unwrap();
262 let u = disjoint_union_many(&[&t, &t, &t]).unwrap();
263 assert_eq!(u.vcount(), 9);
264 assert_eq!(u.ecount(), 9);
265 assert_eq!(u.edge(0).unwrap(), (0, 1));
267 assert_eq!(u.edge(3).unwrap(), (3, 4));
268 assert_eq!(u.edge(6).unwrap(), (6, 7));
269 }
270
271 #[test]
272 fn many_matches_pairwise_left_to_right() {
273 let mut a = Graph::with_vertices(2);
275 a.add_edge(0, 1).unwrap();
276 let mut b = Graph::with_vertices(3);
277 b.add_edge(0, 1).unwrap();
278 b.add_edge(1, 2).unwrap();
279 let mut c = Graph::with_vertices(2);
280 c.add_edge(0, 1).unwrap();
281 let u = disjoint_union_many(&[&a, &b, &c]).unwrap();
282 let pairwise = disjoint_union(&disjoint_union(&a, &b).unwrap(), &c).unwrap();
283 assert_eq!(u.vcount(), pairwise.vcount());
284 assert_eq!(u.ecount(), pairwise.ecount());
285 assert_eq!(sorted_edges(&u), sorted_edges(&pairwise));
286 }
287
288 #[test]
289 fn many_mixed_directedness_errors() {
290 let a = Graph::with_vertices(2);
291 let b = Graph::with_vertices(2);
292 let c = Graph::new(2, true).unwrap();
293 assert!(disjoint_union_many(&[&a, &b, &c]).is_err());
294 }
295
296 #[test]
297 fn many_directed_preserves_orientation() {
298 let mut a = Graph::new(2, true).unwrap();
299 a.add_edge(0, 1).unwrap();
300 let mut b = Graph::new(2, true).unwrap();
301 b.add_edge(1, 0).unwrap();
302 let u = disjoint_union_many(&[&a, &b]).unwrap();
303 assert!(u.is_directed());
304 assert_eq!(u.vcount(), 4);
305 assert_eq!(u.ecount(), 2);
306 assert_eq!(u.edge(0).unwrap(), (0, 1));
307 assert_eq!(u.edge(1).unwrap(), (3, 2));
308 }
309}