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 *iter_l.next().expect("peek matched").1
109 } else {
110 0
111 };
112 let cr = if take_r {
113 *iter_r.next().expect("peek matched").1
114 } else {
115 0
116 };
117 let m = std::cmp::max(cl, cr);
118 for _ in 0..m {
119 edges.push(k);
120 }
121 }
122
123 let mut out = Graph::new(n, directed)?;
124 out.add_edges(edges)?;
125 Ok(out)
126}
127
128pub fn union_many(graphs: &[&Graph]) -> IgraphResult<Graph> {
158 if graphs.is_empty() {
159 return Graph::new(0, true);
160 }
161
162 let directed = graphs[0].is_directed();
163 for g in &graphs[1..] {
164 if g.is_directed() != directed {
165 return Err(IgraphError::InvalidArgument(
166 "union_many: cannot mix directed and undirected graphs".to_string(),
167 ));
168 }
169 }
170
171 let n = graphs.iter().map(|g| g.vcount()).max().unwrap_or(0);
172
173 let canon = |u: VertexId, v: VertexId| -> (VertexId, VertexId) {
174 if directed || u <= v { (u, v) } else { (v, u) }
175 };
176
177 let mut max_mult: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
179
180 for g in graphs {
181 let mut counts: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
182 let m = g.ecount();
183 for eid in 0..m {
184 #[allow(clippy::cast_possible_truncation)]
185 let eid_u32 = eid as u32;
186 let (u, v) = g.edge(eid_u32)?;
187 *counts.entry(canon(u, v)).or_insert(0) += 1;
188 }
189 for (pair, cnt) in counts {
190 let entry = max_mult.entry(pair).or_insert(0);
191 if cnt > *entry {
192 *entry = cnt;
193 }
194 }
195 }
196
197 let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
198 for (pair, mult) in &max_mult {
199 for _ in 0..*mult {
200 edges.push(*pair);
201 }
202 }
203
204 let mut out = Graph::new(n, directed)?;
205 out.add_edges(edges)?;
206 Ok(out)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
214 let m = u32::try_from(g.ecount()).unwrap();
215 let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
216 v.sort_unstable();
217 v
218 }
219
220 #[test]
221 fn empty_union_empty() {
222 let a = Graph::with_vertices(0);
223 let b = Graph::with_vertices(0);
224 let u = union(&a, &b).unwrap();
225 assert_eq!(u.vcount(), 0);
226 assert_eq!(u.ecount(), 0);
227 assert!(!u.is_directed());
228 }
229
230 #[test]
231 fn vcount_is_max_of_inputs() {
232 let a = Graph::with_vertices(3);
234 let b = Graph::with_vertices(6);
235 let u = union(&a, &b).unwrap();
236 assert_eq!(u.vcount(), 6);
237 assert_eq!(u.ecount(), 0);
238 }
239
240 #[test]
241 fn triangle_union_path_doc_example() {
242 let mut a = Graph::with_vertices(3);
243 a.add_edge(0, 1).unwrap();
244 a.add_edge(1, 2).unwrap();
245 a.add_edge(2, 0).unwrap();
246 let mut b = Graph::with_vertices(4);
247 b.add_edge(0, 1).unwrap();
248 b.add_edge(1, 3).unwrap();
249 let u = union(&a, &b).unwrap();
250 assert_eq!(u.vcount(), 4);
251 assert_eq!(u.ecount(), 4);
252 assert_eq!(sorted_edges(&u), vec![(0, 1), (0, 2), (1, 2), (1, 3)]);
253 }
254
255 #[test]
256 fn max_multiplicity_when_left_has_more() {
257 let mut a = Graph::with_vertices(2);
259 a.add_edge(0, 1).unwrap();
260 a.add_edge(0, 1).unwrap();
261 a.add_edge(0, 1).unwrap();
262 let mut b = Graph::with_vertices(2);
263 b.add_edge(0, 1).unwrap();
264 let u = union(&a, &b).unwrap();
265 assert_eq!(u.ecount(), 3);
266 }
267
268 #[test]
269 fn max_multiplicity_when_right_has_more() {
270 let mut a = Graph::with_vertices(2);
272 a.add_edge(0, 1).unwrap();
273 let mut b = Graph::with_vertices(2);
274 for _ in 0..5 {
275 b.add_edge(0, 1).unwrap();
276 }
277 let u = union(&a, &b).unwrap();
278 assert_eq!(u.ecount(), 5);
279 }
280
281 #[test]
282 fn disjoint_edge_sets_become_simple_union() {
283 let mut a = Graph::with_vertices(4);
285 a.add_edge(0, 1).unwrap();
286 let mut b = Graph::with_vertices(4);
287 b.add_edge(2, 3).unwrap();
288 let u = union(&a, &b).unwrap();
289 assert_eq!(sorted_edges(&u), vec![(0, 1), (2, 3)]);
290 }
291
292 #[test]
293 fn idempotent_with_self() {
294 let mut a = Graph::with_vertices(4);
296 a.add_edge(0, 1).unwrap();
297 a.add_edge(1, 2).unwrap();
298 a.add_edge(0, 2).unwrap();
299 a.add_edge(0, 2).unwrap(); let u = union(&a, &a).unwrap();
301 assert_eq!(u.vcount(), a.vcount());
302 assert_eq!(u.ecount(), a.ecount());
303 assert_eq!(sorted_edges(&u), sorted_edges(&a));
304 }
305
306 #[test]
307 fn directed_keeps_orientation_separate() {
308 let mut a = Graph::new(2, true).unwrap();
310 a.add_edge(0, 1).unwrap();
311 let mut b = Graph::new(2, true).unwrap();
312 b.add_edge(1, 0).unwrap();
313 let u = union(&a, &b).unwrap();
314 assert!(u.is_directed());
315 assert_eq!(u.ecount(), 2);
316 assert_eq!(sorted_edges(&u), vec![(0, 1), (1, 0)]);
317 }
318
319 #[test]
320 fn directed_max_multiplicity_per_orientation() {
321 let mut a = Graph::new(2, true).unwrap();
323 a.add_edge(0, 1).unwrap();
324 a.add_edge(0, 1).unwrap();
325 let mut b = Graph::new(2, true).unwrap();
326 for _ in 0..3 {
327 b.add_edge(1, 0).unwrap();
328 }
329 let u = union(&a, &b).unwrap();
330 assert_eq!(u.ecount(), 5);
331 let s = sorted_edges(&u);
332 assert_eq!(s.iter().filter(|&&p| p == (0, 1)).count(), 2);
333 assert_eq!(s.iter().filter(|&&p| p == (1, 0)).count(), 3);
334 }
335
336 #[test]
337 fn loops_are_preserved() {
338 let mut a = Graph::with_vertices(1);
340 a.add_edge(0, 0).unwrap();
341 a.add_edge(0, 0).unwrap();
342 let mut b = Graph::with_vertices(1);
343 b.add_edge(0, 0).unwrap();
344 let u = union(&a, &b).unwrap();
345 assert_eq!(u.ecount(), 2);
346 assert!(u.edge(0).unwrap() == (0, 0));
347 }
348
349 #[test]
350 fn unaligned_vertex_sizes_use_max() {
351 let mut a = Graph::with_vertices(2);
353 a.add_edge(0, 1).unwrap();
354 let mut b = Graph::with_vertices(5);
355 b.add_edge(3, 4).unwrap();
356 let u = union(&a, &b).unwrap();
357 assert_eq!(u.vcount(), 5);
358 assert_eq!(sorted_edges(&u), vec![(0, 1), (3, 4)]);
359 }
360
361 #[test]
362 fn mixed_directedness_errors() {
363 let a = Graph::with_vertices(2);
364 let b = Graph::new(2, true).unwrap();
365 assert!(union(&a, &b).is_err());
366 }
367
368 #[test]
369 fn undirected_canonicalises_swapped_endpoints() {
370 let mut a = Graph::with_vertices(2);
372 a.add_edge(1, 0).unwrap();
373 let mut b = Graph::with_vertices(2);
374 b.add_edge(0, 1).unwrap();
375 let u = union(&a, &b).unwrap();
376 assert_eq!(u.ecount(), 1);
378 let endpoints = u.edge(0).unwrap();
379 assert!(endpoints == (0, 1) || endpoints == (1, 0));
380 }
381
382 #[test]
383 fn larger_left_vertex_count_preserved() {
384 let mut a = Graph::with_vertices(5);
386 a.add_edge(3, 4).unwrap();
387 let mut b = Graph::with_vertices(2);
388 b.add_edge(0, 1).unwrap();
389 let u = union(&a, &b).unwrap();
390 assert_eq!(u.vcount(), 5);
391 assert_eq!(sorted_edges(&u), vec![(0, 1), (3, 4)]);
392 }
393
394 #[test]
397 fn union_many_empty_list() {
398 let u = union_many(&[]).unwrap();
399 assert_eq!(u.vcount(), 0);
400 assert!(u.is_directed());
401 }
402
403 #[test]
404 fn union_many_single_graph() {
405 let mut a = Graph::with_vertices(3);
406 a.add_edge(0, 1).unwrap();
407 a.add_edge(1, 2).unwrap();
408 let u = union_many(&[&a]).unwrap();
409 assert_eq!(u.vcount(), 3);
410 assert_eq!(u.ecount(), 2);
411 assert_eq!(sorted_edges(&u), sorted_edges(&a));
412 }
413
414 #[test]
415 fn union_many_three_graphs() {
416 let mut a = Graph::with_vertices(3);
417 a.add_edge(0, 1).unwrap();
418 let mut b = Graph::with_vertices(4);
419 b.add_edge(1, 2).unwrap();
420 let mut c = Graph::with_vertices(5);
421 c.add_edge(3, 4).unwrap();
422
423 let u = union_many(&[&a, &b, &c]).unwrap();
424 assert_eq!(u.vcount(), 5);
425 assert_eq!(u.ecount(), 3);
426 assert_eq!(sorted_edges(&u), vec![(0, 1), (1, 2), (3, 4)]);
427 }
428
429 #[test]
430 fn union_many_max_multiplicity() {
431 let mut a = Graph::with_vertices(2);
432 a.add_edge(0, 1).unwrap();
433 a.add_edge(0, 1).unwrap();
434 let mut b = Graph::with_vertices(2);
435 b.add_edge(0, 1).unwrap();
436 b.add_edge(0, 1).unwrap();
437 b.add_edge(0, 1).unwrap();
438 let mut c = Graph::with_vertices(2);
439 c.add_edge(0, 1).unwrap();
440
441 let u = union_many(&[&a, &b, &c]).unwrap();
442 assert_eq!(u.ecount(), 3); }
444
445 #[test]
446 fn union_many_mixed_directedness_fails() {
447 let a = Graph::with_vertices(2);
448 let b = Graph::new(2, true).unwrap();
449 assert!(union_many(&[&a, &b]).is_err());
450 }
451
452 #[test]
453 fn union_many_directed() {
454 let mut a = Graph::new(3, true).unwrap();
455 a.add_edge(0, 1).unwrap();
456 let mut b = Graph::new(3, true).unwrap();
457 b.add_edge(1, 0).unwrap();
458 let mut c = Graph::new(3, true).unwrap();
459 c.add_edge(1, 2).unwrap();
460
461 let u = union_many(&[&a, &b, &c]).unwrap();
462 assert!(u.is_directed());
463 assert_eq!(u.ecount(), 3);
464 assert_eq!(sorted_edges(&u), vec![(0, 1), (1, 0), (1, 2)]);
465 }
466}