1use std::collections::BTreeMap;
18
19use crate::core::graph::EdgeId;
20use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
21
22pub fn union(left: &Graph, right: &Graph) -> IgraphResult<Graph> {
59 if left.is_directed() != right.is_directed() {
60 return Err(IgraphError::InvalidArgument(
61 "union: cannot mix directed and undirected graphs".to_string(),
62 ));
63 }
64 let directed = left.is_directed();
65 let n = std::cmp::max(left.vcount(), right.vcount());
66
67 let canon = |u: VertexId, v: VertexId| -> (VertexId, VertexId) {
68 if directed || u <= v { (u, v) } else { (v, u) }
69 };
70
71 let mut count_left: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
72 let mut count_right: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
73
74 let m_l = u32::try_from(left.ecount())
75 .map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
76 for e in 0..m_l {
77 let (u, v) = left.edge(e as EdgeId)?;
78 *count_left.entry(canon(u, v)).or_insert(0) += 1;
79 }
80 let m_r = u32::try_from(right.ecount())
81 .map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
82 for e in 0..m_r {
83 let (u, v) = right.edge(e as EdgeId)?;
84 *count_right.entry(canon(u, v)).or_insert(0) += 1;
85 }
86
87 let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
91 let mut iter_l = count_left.iter().peekable();
92 let mut iter_r = count_right.iter().peekable();
93 loop {
94 let next = match (iter_l.peek(), iter_r.peek()) {
95 (None, None) => break,
96 (Some((kl, _)), None) => Some((**kl, true, false)),
97 (None, Some((kr, _))) => Some((**kr, false, true)),
98 (Some((kl, _)), Some((kr, _))) => match kl.cmp(kr) {
99 std::cmp::Ordering::Less => Some((**kl, true, false)),
100 std::cmp::Ordering::Greater => Some((**kr, false, true)),
101 std::cmp::Ordering::Equal => Some((**kl, true, true)),
102 },
103 };
104 let Some((k, take_l, take_r)) = next else {
105 break;
106 };
107 let cl = if take_l {
108 match iter_l.next() {
109 Some((_, v)) => *v,
110 None => 0,
111 }
112 } else {
113 0
114 };
115 let cr = if take_r {
116 match iter_r.next() {
117 Some((_, v)) => *v,
118 None => 0,
119 }
120 } else {
121 0
122 };
123 let m = std::cmp::max(cl, cr);
124 for _ in 0..m {
125 edges.push(k);
126 }
127 }
128
129 let mut out = Graph::new(n, directed)?;
130 out.add_edges(edges)?;
131 Ok(out)
132}
133
134pub fn union_many(graphs: &[&Graph]) -> IgraphResult<Graph> {
164 if graphs.is_empty() {
165 return Graph::new(0, true);
166 }
167
168 let directed = graphs[0].is_directed();
169 for g in &graphs[1..] {
170 if g.is_directed() != directed {
171 return Err(IgraphError::InvalidArgument(
172 "union_many: cannot mix directed and undirected graphs".to_string(),
173 ));
174 }
175 }
176
177 let n = graphs.iter().map(|g| g.vcount()).max().unwrap_or(0);
178
179 let canon = |u: VertexId, v: VertexId| -> (VertexId, VertexId) {
180 if directed || u <= v { (u, v) } else { (v, u) }
181 };
182
183 let mut max_mult: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
185
186 for g in graphs {
187 let mut counts: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
188 let m = g.ecount();
189 for eid in 0..m {
190 #[allow(clippy::cast_possible_truncation)]
191 let eid_u32 = eid as u32;
192 let (u, v) = g.edge(eid_u32)?;
193 *counts.entry(canon(u, v)).or_insert(0) += 1;
194 }
195 for (pair, cnt) in counts {
196 let entry = max_mult.entry(pair).or_insert(0);
197 if cnt > *entry {
198 *entry = cnt;
199 }
200 }
201 }
202
203 let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
204 for (pair, mult) in &max_mult {
205 for _ in 0..*mult {
206 edges.push(*pair);
207 }
208 }
209
210 let mut out = Graph::new(n, directed)?;
211 out.add_edges(edges)?;
212 Ok(out)
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
220 let m = u32::try_from(g.ecount()).unwrap();
221 let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
222 v.sort_unstable();
223 v
224 }
225
226 #[test]
227 fn empty_union_empty() {
228 let a = Graph::with_vertices(0);
229 let b = Graph::with_vertices(0);
230 let u = union(&a, &b).unwrap();
231 assert_eq!(u.vcount(), 0);
232 assert_eq!(u.ecount(), 0);
233 assert!(!u.is_directed());
234 }
235
236 #[test]
237 fn vcount_is_max_of_inputs() {
238 let a = Graph::with_vertices(3);
240 let b = Graph::with_vertices(6);
241 let u = union(&a, &b).unwrap();
242 assert_eq!(u.vcount(), 6);
243 assert_eq!(u.ecount(), 0);
244 }
245
246 #[test]
247 fn triangle_union_path_doc_example() {
248 let mut a = Graph::with_vertices(3);
249 a.add_edge(0, 1).unwrap();
250 a.add_edge(1, 2).unwrap();
251 a.add_edge(2, 0).unwrap();
252 let mut b = Graph::with_vertices(4);
253 b.add_edge(0, 1).unwrap();
254 b.add_edge(1, 3).unwrap();
255 let u = union(&a, &b).unwrap();
256 assert_eq!(u.vcount(), 4);
257 assert_eq!(u.ecount(), 4);
258 assert_eq!(sorted_edges(&u), vec![(0, 1), (0, 2), (1, 2), (1, 3)]);
259 }
260
261 #[test]
262 fn max_multiplicity_when_left_has_more() {
263 let mut a = Graph::with_vertices(2);
265 a.add_edge(0, 1).unwrap();
266 a.add_edge(0, 1).unwrap();
267 a.add_edge(0, 1).unwrap();
268 let mut b = Graph::with_vertices(2);
269 b.add_edge(0, 1).unwrap();
270 let u = union(&a, &b).unwrap();
271 assert_eq!(u.ecount(), 3);
272 }
273
274 #[test]
275 fn max_multiplicity_when_right_has_more() {
276 let mut a = Graph::with_vertices(2);
278 a.add_edge(0, 1).unwrap();
279 let mut b = Graph::with_vertices(2);
280 for _ in 0..5 {
281 b.add_edge(0, 1).unwrap();
282 }
283 let u = union(&a, &b).unwrap();
284 assert_eq!(u.ecount(), 5);
285 }
286
287 #[test]
288 fn disjoint_edge_sets_become_simple_union() {
289 let mut a = Graph::with_vertices(4);
291 a.add_edge(0, 1).unwrap();
292 let mut b = Graph::with_vertices(4);
293 b.add_edge(2, 3).unwrap();
294 let u = union(&a, &b).unwrap();
295 assert_eq!(sorted_edges(&u), vec![(0, 1), (2, 3)]);
296 }
297
298 #[test]
299 fn idempotent_with_self() {
300 let mut a = Graph::with_vertices(4);
302 a.add_edge(0, 1).unwrap();
303 a.add_edge(1, 2).unwrap();
304 a.add_edge(0, 2).unwrap();
305 a.add_edge(0, 2).unwrap(); let u = union(&a, &a).unwrap();
307 assert_eq!(u.vcount(), a.vcount());
308 assert_eq!(u.ecount(), a.ecount());
309 assert_eq!(sorted_edges(&u), sorted_edges(&a));
310 }
311
312 #[test]
313 fn directed_keeps_orientation_separate() {
314 let mut a = Graph::new(2, true).unwrap();
316 a.add_edge(0, 1).unwrap();
317 let mut b = Graph::new(2, true).unwrap();
318 b.add_edge(1, 0).unwrap();
319 let u = union(&a, &b).unwrap();
320 assert!(u.is_directed());
321 assert_eq!(u.ecount(), 2);
322 assert_eq!(sorted_edges(&u), vec![(0, 1), (1, 0)]);
323 }
324
325 #[test]
326 fn directed_max_multiplicity_per_orientation() {
327 let mut a = Graph::new(2, true).unwrap();
329 a.add_edge(0, 1).unwrap();
330 a.add_edge(0, 1).unwrap();
331 let mut b = Graph::new(2, true).unwrap();
332 for _ in 0..3 {
333 b.add_edge(1, 0).unwrap();
334 }
335 let u = union(&a, &b).unwrap();
336 assert_eq!(u.ecount(), 5);
337 let s = sorted_edges(&u);
338 assert_eq!(s.iter().filter(|&&p| p == (0, 1)).count(), 2);
339 assert_eq!(s.iter().filter(|&&p| p == (1, 0)).count(), 3);
340 }
341
342 #[test]
343 fn loops_are_preserved() {
344 let mut a = Graph::with_vertices(1);
346 a.add_edge(0, 0).unwrap();
347 a.add_edge(0, 0).unwrap();
348 let mut b = Graph::with_vertices(1);
349 b.add_edge(0, 0).unwrap();
350 let u = union(&a, &b).unwrap();
351 assert_eq!(u.ecount(), 2);
352 assert!(u.edge(0).unwrap() == (0, 0));
353 }
354
355 #[test]
356 fn unaligned_vertex_sizes_use_max() {
357 let mut a = Graph::with_vertices(2);
359 a.add_edge(0, 1).unwrap();
360 let mut b = Graph::with_vertices(5);
361 b.add_edge(3, 4).unwrap();
362 let u = union(&a, &b).unwrap();
363 assert_eq!(u.vcount(), 5);
364 assert_eq!(sorted_edges(&u), vec![(0, 1), (3, 4)]);
365 }
366
367 #[test]
368 fn mixed_directedness_errors() {
369 let a = Graph::with_vertices(2);
370 let b = Graph::new(2, true).unwrap();
371 assert!(union(&a, &b).is_err());
372 }
373
374 #[test]
375 fn undirected_canonicalises_swapped_endpoints() {
376 let mut a = Graph::with_vertices(2);
378 a.add_edge(1, 0).unwrap();
379 let mut b = Graph::with_vertices(2);
380 b.add_edge(0, 1).unwrap();
381 let u = union(&a, &b).unwrap();
382 assert_eq!(u.ecount(), 1);
384 let endpoints = u.edge(0).unwrap();
385 assert!(endpoints == (0, 1) || endpoints == (1, 0));
386 }
387
388 #[test]
389 fn larger_left_vertex_count_preserved() {
390 let mut a = Graph::with_vertices(5);
392 a.add_edge(3, 4).unwrap();
393 let mut b = Graph::with_vertices(2);
394 b.add_edge(0, 1).unwrap();
395 let u = union(&a, &b).unwrap();
396 assert_eq!(u.vcount(), 5);
397 assert_eq!(sorted_edges(&u), vec![(0, 1), (3, 4)]);
398 }
399
400 #[test]
403 fn union_many_empty_list() {
404 let u = union_many(&[]).unwrap();
405 assert_eq!(u.vcount(), 0);
406 assert!(u.is_directed());
407 }
408
409 #[test]
410 fn union_many_single_graph() {
411 let mut a = Graph::with_vertices(3);
412 a.add_edge(0, 1).unwrap();
413 a.add_edge(1, 2).unwrap();
414 let u = union_many(&[&a]).unwrap();
415 assert_eq!(u.vcount(), 3);
416 assert_eq!(u.ecount(), 2);
417 assert_eq!(sorted_edges(&u), sorted_edges(&a));
418 }
419
420 #[test]
421 fn union_many_three_graphs() {
422 let mut a = Graph::with_vertices(3);
423 a.add_edge(0, 1).unwrap();
424 let mut b = Graph::with_vertices(4);
425 b.add_edge(1, 2).unwrap();
426 let mut c = Graph::with_vertices(5);
427 c.add_edge(3, 4).unwrap();
428
429 let u = union_many(&[&a, &b, &c]).unwrap();
430 assert_eq!(u.vcount(), 5);
431 assert_eq!(u.ecount(), 3);
432 assert_eq!(sorted_edges(&u), vec![(0, 1), (1, 2), (3, 4)]);
433 }
434
435 #[test]
436 fn union_many_max_multiplicity() {
437 let mut a = Graph::with_vertices(2);
438 a.add_edge(0, 1).unwrap();
439 a.add_edge(0, 1).unwrap();
440 let mut b = Graph::with_vertices(2);
441 b.add_edge(0, 1).unwrap();
442 b.add_edge(0, 1).unwrap();
443 b.add_edge(0, 1).unwrap();
444 let mut c = Graph::with_vertices(2);
445 c.add_edge(0, 1).unwrap();
446
447 let u = union_many(&[&a, &b, &c]).unwrap();
448 assert_eq!(u.ecount(), 3); }
450
451 #[test]
452 fn union_many_mixed_directedness_fails() {
453 let a = Graph::with_vertices(2);
454 let b = Graph::new(2, true).unwrap();
455 assert!(union_many(&[&a, &b]).is_err());
456 }
457
458 #[test]
459 fn union_many_directed() {
460 let mut a = Graph::new(3, true).unwrap();
461 a.add_edge(0, 1).unwrap();
462 let mut b = Graph::new(3, true).unwrap();
463 b.add_edge(1, 0).unwrap();
464 let mut c = Graph::new(3, true).unwrap();
465 c.add_edge(1, 2).unwrap();
466
467 let u = union_many(&[&a, &b, &c]).unwrap();
468 assert!(u.is_directed());
469 assert_eq!(u.ecount(), 3);
470 assert_eq!(sorted_edges(&u), vec![(0, 1), (1, 0), (1, 2)]);
471 }
472}