rust_igraph/algorithms/operators/
union.rs1use 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
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
133 let m = u32::try_from(g.ecount()).unwrap();
134 let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
135 v.sort_unstable();
136 v
137 }
138
139 #[test]
140 fn empty_union_empty() {
141 let a = Graph::with_vertices(0);
142 let b = Graph::with_vertices(0);
143 let u = union(&a, &b).unwrap();
144 assert_eq!(u.vcount(), 0);
145 assert_eq!(u.ecount(), 0);
146 assert!(!u.is_directed());
147 }
148
149 #[test]
150 fn vcount_is_max_of_inputs() {
151 let a = Graph::with_vertices(3);
153 let b = Graph::with_vertices(6);
154 let u = union(&a, &b).unwrap();
155 assert_eq!(u.vcount(), 6);
156 assert_eq!(u.ecount(), 0);
157 }
158
159 #[test]
160 fn triangle_union_path_doc_example() {
161 let mut a = Graph::with_vertices(3);
162 a.add_edge(0, 1).unwrap();
163 a.add_edge(1, 2).unwrap();
164 a.add_edge(2, 0).unwrap();
165 let mut b = Graph::with_vertices(4);
166 b.add_edge(0, 1).unwrap();
167 b.add_edge(1, 3).unwrap();
168 let u = union(&a, &b).unwrap();
169 assert_eq!(u.vcount(), 4);
170 assert_eq!(u.ecount(), 4);
171 assert_eq!(sorted_edges(&u), vec![(0, 1), (0, 2), (1, 2), (1, 3)]);
172 }
173
174 #[test]
175 fn max_multiplicity_when_left_has_more() {
176 let mut a = Graph::with_vertices(2);
178 a.add_edge(0, 1).unwrap();
179 a.add_edge(0, 1).unwrap();
180 a.add_edge(0, 1).unwrap();
181 let mut b = Graph::with_vertices(2);
182 b.add_edge(0, 1).unwrap();
183 let u = union(&a, &b).unwrap();
184 assert_eq!(u.ecount(), 3);
185 }
186
187 #[test]
188 fn max_multiplicity_when_right_has_more() {
189 let mut a = Graph::with_vertices(2);
191 a.add_edge(0, 1).unwrap();
192 let mut b = Graph::with_vertices(2);
193 for _ in 0..5 {
194 b.add_edge(0, 1).unwrap();
195 }
196 let u = union(&a, &b).unwrap();
197 assert_eq!(u.ecount(), 5);
198 }
199
200 #[test]
201 fn disjoint_edge_sets_become_simple_union() {
202 let mut a = Graph::with_vertices(4);
204 a.add_edge(0, 1).unwrap();
205 let mut b = Graph::with_vertices(4);
206 b.add_edge(2, 3).unwrap();
207 let u = union(&a, &b).unwrap();
208 assert_eq!(sorted_edges(&u), vec![(0, 1), (2, 3)]);
209 }
210
211 #[test]
212 fn idempotent_with_self() {
213 let mut a = Graph::with_vertices(4);
215 a.add_edge(0, 1).unwrap();
216 a.add_edge(1, 2).unwrap();
217 a.add_edge(0, 2).unwrap();
218 a.add_edge(0, 2).unwrap(); let u = union(&a, &a).unwrap();
220 assert_eq!(u.vcount(), a.vcount());
221 assert_eq!(u.ecount(), a.ecount());
222 assert_eq!(sorted_edges(&u), sorted_edges(&a));
223 }
224
225 #[test]
226 fn directed_keeps_orientation_separate() {
227 let mut a = Graph::new(2, true).unwrap();
229 a.add_edge(0, 1).unwrap();
230 let mut b = Graph::new(2, true).unwrap();
231 b.add_edge(1, 0).unwrap();
232 let u = union(&a, &b).unwrap();
233 assert!(u.is_directed());
234 assert_eq!(u.ecount(), 2);
235 assert_eq!(sorted_edges(&u), vec![(0, 1), (1, 0)]);
236 }
237
238 #[test]
239 fn directed_max_multiplicity_per_orientation() {
240 let mut a = Graph::new(2, true).unwrap();
242 a.add_edge(0, 1).unwrap();
243 a.add_edge(0, 1).unwrap();
244 let mut b = Graph::new(2, true).unwrap();
245 for _ in 0..3 {
246 b.add_edge(1, 0).unwrap();
247 }
248 let u = union(&a, &b).unwrap();
249 assert_eq!(u.ecount(), 5);
250 let s = sorted_edges(&u);
251 assert_eq!(s.iter().filter(|&&p| p == (0, 1)).count(), 2);
252 assert_eq!(s.iter().filter(|&&p| p == (1, 0)).count(), 3);
253 }
254
255 #[test]
256 fn loops_are_preserved() {
257 let mut a = Graph::with_vertices(1);
259 a.add_edge(0, 0).unwrap();
260 a.add_edge(0, 0).unwrap();
261 let mut b = Graph::with_vertices(1);
262 b.add_edge(0, 0).unwrap();
263 let u = union(&a, &b).unwrap();
264 assert_eq!(u.ecount(), 2);
265 assert!(u.edge(0).unwrap() == (0, 0));
266 }
267
268 #[test]
269 fn unaligned_vertex_sizes_use_max() {
270 let mut a = Graph::with_vertices(2);
272 a.add_edge(0, 1).unwrap();
273 let mut b = Graph::with_vertices(5);
274 b.add_edge(3, 4).unwrap();
275 let u = union(&a, &b).unwrap();
276 assert_eq!(u.vcount(), 5);
277 assert_eq!(sorted_edges(&u), vec![(0, 1), (3, 4)]);
278 }
279
280 #[test]
281 fn mixed_directedness_errors() {
282 let a = Graph::with_vertices(2);
283 let b = Graph::new(2, true).unwrap();
284 assert!(union(&a, &b).is_err());
285 }
286
287 #[test]
288 fn undirected_canonicalises_swapped_endpoints() {
289 let mut a = Graph::with_vertices(2);
291 a.add_edge(1, 0).unwrap();
292 let mut b = Graph::with_vertices(2);
293 b.add_edge(0, 1).unwrap();
294 let u = union(&a, &b).unwrap();
295 assert_eq!(u.ecount(), 1);
297 let endpoints = u.edge(0).unwrap();
298 assert!(endpoints == (0, 1) || endpoints == (1, 0));
299 }
300
301 #[test]
302 fn larger_left_vertex_count_preserved() {
303 let mut a = Graph::with_vertices(5);
305 a.add_edge(3, 4).unwrap();
306 let mut b = Graph::with_vertices(2);
307 b.add_edge(0, 1).unwrap();
308 let u = union(&a, &b).unwrap();
309 assert_eq!(u.vcount(), 5);
310 assert_eq!(sorted_edges(&u), vec![(0, 1), (3, 4)]);
311 }
312}