rust_igraph/algorithms/operators/
difference.rs1use std::collections::BTreeMap;
21
22use crate::core::graph::EdgeId;
23use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
24
25pub fn difference(orig: &Graph, sub: &Graph) -> IgraphResult<Graph> {
62 if orig.is_directed() != sub.is_directed() {
63 return Err(IgraphError::InvalidArgument(
64 "difference: cannot mix directed and undirected graphs".to_string(),
65 ));
66 }
67 let directed = orig.is_directed();
68 let n = orig.vcount();
69
70 let canon = |u: VertexId, v: VertexId| -> (VertexId, VertexId) {
71 if directed || u <= v { (u, v) } else { (v, u) }
72 };
73
74 let mut count_orig: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
75 let mut count_sub: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
76
77 let m_o = u32::try_from(orig.ecount())
78 .map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
79 for e in 0..m_o {
80 let (u, v) = orig.edge(e as EdgeId)?;
81 *count_orig.entry(canon(u, v)).or_insert(0) += 1;
82 }
83 let m_s = u32::try_from(sub.ecount())
84 .map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
85 for e in 0..m_s {
86 let (u, v) = sub.edge(e as EdgeId)?;
87 *count_sub.entry(canon(u, v)).or_insert(0) += 1;
91 }
92
93 let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
97 for (k, &co) in &count_orig {
98 let cs = count_sub.get(k).copied().unwrap_or(0);
99 let m = co.saturating_sub(cs);
100 for _ in 0..m {
101 edges.push(*k);
102 }
103 }
104
105 let mut out = Graph::new(n, directed)?;
106 out.add_edges(edges)?;
107 Ok(out)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
115 let m = u32::try_from(g.ecount()).unwrap();
116 let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
117 v.sort_unstable();
118 v
119 }
120
121 #[test]
122 fn empty_minus_empty() {
123 let a = Graph::with_vertices(0);
124 let b = Graph::with_vertices(0);
125 let d = difference(&a, &b).unwrap();
126 assert_eq!(d.vcount(), 0);
127 assert_eq!(d.ecount(), 0);
128 assert!(!d.is_directed());
129 }
130
131 #[test]
132 fn vcount_is_orig_only() {
133 let a = Graph::with_vertices(3);
135 let b = Graph::with_vertices(7);
136 let d = difference(&a, &b).unwrap();
137 assert_eq!(d.vcount(), 3);
138 assert_eq!(d.ecount(), 0);
139 }
140
141 #[test]
142 fn vcount_when_orig_larger() {
143 let a = Graph::with_vertices(7);
144 let b = Graph::with_vertices(3);
145 let d = difference(&a, &b).unwrap();
146 assert_eq!(d.vcount(), 7);
147 }
148
149 #[test]
150 fn triangle_minus_path_doc_example() {
151 let mut a = Graph::with_vertices(3);
152 a.add_edge(0, 1).unwrap();
153 a.add_edge(1, 2).unwrap();
154 a.add_edge(2, 0).unwrap();
155 let mut b = Graph::with_vertices(3);
156 b.add_edge(0, 1).unwrap();
157 b.add_edge(1, 2).unwrap();
158 let d = difference(&a, &b).unwrap();
159 assert_eq!(d.vcount(), 3);
160 assert_eq!(d.ecount(), 1);
161 assert_eq!(sorted_edges(&d), vec![(0, 2)]);
162 }
163
164 #[test]
165 fn self_difference_is_empty() {
166 let mut a = Graph::with_vertices(4);
168 a.add_edge(0, 1).unwrap();
169 a.add_edge(1, 2).unwrap();
170 a.add_edge(0, 2).unwrap();
171 a.add_edge(0, 2).unwrap(); let d = difference(&a, &a).unwrap();
173 assert_eq!(d.vcount(), 4);
174 assert_eq!(d.ecount(), 0);
175 }
176
177 #[test]
178 fn difference_with_empty_is_identity_edges() {
179 let mut a = Graph::with_vertices(3);
180 a.add_edge(0, 1).unwrap();
181 a.add_edge(1, 2).unwrap();
182 let b = Graph::with_vertices(3);
183 let d = difference(&a, &b).unwrap();
184 assert_eq!(d.vcount(), 3);
185 assert_eq!(sorted_edges(&d), vec![(0, 1), (1, 2)]);
186 }
187
188 #[test]
189 fn empty_minus_anything_is_empty() {
190 let a = Graph::with_vertices(3);
191 let mut b = Graph::with_vertices(3);
192 b.add_edge(0, 1).unwrap();
193 b.add_edge(1, 2).unwrap();
194 let d = difference(&a, &b).unwrap();
195 assert_eq!(d.vcount(), 3);
196 assert_eq!(d.ecount(), 0);
197 }
198
199 #[test]
200 fn pair_unique_to_sub_does_not_appear() {
201 let mut a = Graph::with_vertices(4);
203 a.add_edge(0, 1).unwrap();
204 let mut b = Graph::with_vertices(4);
205 b.add_edge(2, 3).unwrap();
206 let d = difference(&a, &b).unwrap();
207 assert_eq!(d.vcount(), 4);
208 assert_eq!(sorted_edges(&d), vec![(0, 1)]);
209 }
210
211 #[test]
212 fn multiplicity_clamps_to_zero() {
213 let mut a = Graph::with_vertices(2);
215 a.add_edge(0, 1).unwrap();
216 a.add_edge(0, 1).unwrap();
217 let mut b = Graph::with_vertices(2);
218 for _ in 0..5 {
219 b.add_edge(0, 1).unwrap();
220 }
221 let d = difference(&a, &b).unwrap();
222 assert_eq!(d.ecount(), 0);
223 }
224
225 #[test]
226 fn multiplicity_partial_subtraction() {
227 let mut a = Graph::with_vertices(2);
229 for _ in 0..5 {
230 a.add_edge(0, 1).unwrap();
231 }
232 let mut b = Graph::with_vertices(2);
233 b.add_edge(0, 1).unwrap();
234 b.add_edge(0, 1).unwrap();
235 let d = difference(&a, &b).unwrap();
236 assert_eq!(d.ecount(), 3);
237 assert!(sorted_edges(&d).iter().all(|&p| p == (0, 1)));
238 }
239
240 #[test]
241 fn directed_orientations_are_independent() {
242 let mut a = Graph::new(2, true).unwrap();
244 a.add_edge(0, 1).unwrap();
245 a.add_edge(1, 0).unwrap();
246 let mut b = Graph::new(2, true).unwrap();
247 b.add_edge(0, 1).unwrap();
248 let d = difference(&a, &b).unwrap();
249 assert!(d.is_directed());
250 assert_eq!(d.ecount(), 1);
251 assert_eq!(d.edge(0).unwrap(), (1, 0));
252 }
253
254 #[test]
255 fn directed_per_orientation_multiplicity() {
256 let mut a = Graph::new(2, true).unwrap();
259 for _ in 0..4 {
260 a.add_edge(0, 1).unwrap();
261 }
262 for _ in 0..2 {
263 a.add_edge(1, 0).unwrap();
264 }
265 let mut b = Graph::new(2, true).unwrap();
266 b.add_edge(0, 1).unwrap();
267 for _ in 0..3 {
268 b.add_edge(1, 0).unwrap();
269 }
270 let d = difference(&a, &b).unwrap();
271 assert_eq!(d.ecount(), 3);
272 assert!(sorted_edges(&d).iter().all(|&p| p == (0, 1)));
273 }
274
275 #[test]
276 fn loops_subtract_correctly() {
277 let mut a = Graph::with_vertices(1);
279 for _ in 0..3 {
280 a.add_edge(0, 0).unwrap();
281 }
282 let mut b = Graph::with_vertices(1);
283 b.add_edge(0, 0).unwrap();
284 let d = difference(&a, &b).unwrap();
285 assert_eq!(d.ecount(), 2);
286 assert!(sorted_edges(&d).iter().all(|&p| p == (0, 0)));
287 }
288
289 #[test]
290 fn mixed_directedness_errors() {
291 let a = Graph::with_vertices(2);
292 let b = Graph::new(2, true).unwrap();
293 assert!(difference(&a, &b).is_err());
294 }
295
296 #[test]
297 fn undirected_canonicalises_swapped_endpoints() {
298 let mut a = Graph::with_vertices(2);
301 a.add_edge(1, 0).unwrap();
302 let mut b = Graph::with_vertices(2);
303 b.add_edge(0, 1).unwrap();
304 let d = difference(&a, &b).unwrap();
305 assert_eq!(d.ecount(), 0);
306 }
307
308 #[test]
309 fn sub_high_index_vertex_ignored() {
310 let mut a = Graph::with_vertices(3);
313 a.add_edge(0, 1).unwrap();
314 a.add_edge(1, 2).unwrap();
315 let mut b = Graph::with_vertices(5);
316 b.add_edge(0, 1).unwrap();
317 b.add_edge(3, 4).unwrap();
318 let d = difference(&a, &b).unwrap();
319 assert_eq!(d.vcount(), 3);
320 assert_eq!(sorted_edges(&d), vec![(1, 2)]);
321 }
322}