use std::collections::BTreeMap;
use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn difference(orig: &Graph, sub: &Graph) -> IgraphResult<Graph> {
if orig.is_directed() != sub.is_directed() {
return Err(IgraphError::InvalidArgument(
"difference: cannot mix directed and undirected graphs".to_string(),
));
}
let directed = orig.is_directed();
let n = orig.vcount();
let canon = |u: VertexId, v: VertexId| -> (VertexId, VertexId) {
if directed || u <= v { (u, v) } else { (v, u) }
};
let mut count_orig: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
let mut count_sub: BTreeMap<(VertexId, VertexId), u32> = BTreeMap::new();
let m_o = u32::try_from(orig.ecount())
.map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
for e in 0..m_o {
let (u, v) = orig.edge(e as EdgeId)?;
*count_orig.entry(canon(u, v)).or_insert(0) += 1;
}
let m_s = u32::try_from(sub.ecount())
.map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
for e in 0..m_s {
let (u, v) = sub.edge(e as EdgeId)?;
*count_sub.entry(canon(u, v)).or_insert(0) += 1;
}
let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
for (k, &co) in &count_orig {
let cs = count_sub.get(k).copied().unwrap_or(0);
let m = co.saturating_sub(cs);
for _ in 0..m {
edges.push(*k);
}
}
let mut out = Graph::new(n, directed)?;
out.add_edges(edges)?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
let m = u32::try_from(g.ecount()).unwrap();
let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
v.sort_unstable();
v
}
#[test]
fn empty_minus_empty() {
let a = Graph::with_vertices(0);
let b = Graph::with_vertices(0);
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 0);
assert_eq!(d.ecount(), 0);
assert!(!d.is_directed());
}
#[test]
fn vcount_is_orig_only() {
let a = Graph::with_vertices(3);
let b = Graph::with_vertices(7);
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 3);
assert_eq!(d.ecount(), 0);
}
#[test]
fn vcount_when_orig_larger() {
let a = Graph::with_vertices(7);
let b = Graph::with_vertices(3);
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 7);
}
#[test]
fn triangle_minus_path_doc_example() {
let mut a = Graph::with_vertices(3);
a.add_edge(0, 1).unwrap();
a.add_edge(1, 2).unwrap();
a.add_edge(2, 0).unwrap();
let mut b = Graph::with_vertices(3);
b.add_edge(0, 1).unwrap();
b.add_edge(1, 2).unwrap();
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 3);
assert_eq!(d.ecount(), 1);
assert_eq!(sorted_edges(&d), vec![(0, 2)]);
}
#[test]
fn self_difference_is_empty() {
let mut a = Graph::with_vertices(4);
a.add_edge(0, 1).unwrap();
a.add_edge(1, 2).unwrap();
a.add_edge(0, 2).unwrap();
a.add_edge(0, 2).unwrap(); let d = difference(&a, &a).unwrap();
assert_eq!(d.vcount(), 4);
assert_eq!(d.ecount(), 0);
}
#[test]
fn difference_with_empty_is_identity_edges() {
let mut a = Graph::with_vertices(3);
a.add_edge(0, 1).unwrap();
a.add_edge(1, 2).unwrap();
let b = Graph::with_vertices(3);
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 3);
assert_eq!(sorted_edges(&d), vec![(0, 1), (1, 2)]);
}
#[test]
fn empty_minus_anything_is_empty() {
let a = Graph::with_vertices(3);
let mut b = Graph::with_vertices(3);
b.add_edge(0, 1).unwrap();
b.add_edge(1, 2).unwrap();
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 3);
assert_eq!(d.ecount(), 0);
}
#[test]
fn pair_unique_to_sub_does_not_appear() {
let mut a = Graph::with_vertices(4);
a.add_edge(0, 1).unwrap();
let mut b = Graph::with_vertices(4);
b.add_edge(2, 3).unwrap();
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 4);
assert_eq!(sorted_edges(&d), vec![(0, 1)]);
}
#[test]
fn multiplicity_clamps_to_zero() {
let mut a = Graph::with_vertices(2);
a.add_edge(0, 1).unwrap();
a.add_edge(0, 1).unwrap();
let mut b = Graph::with_vertices(2);
for _ in 0..5 {
b.add_edge(0, 1).unwrap();
}
let d = difference(&a, &b).unwrap();
assert_eq!(d.ecount(), 0);
}
#[test]
fn multiplicity_partial_subtraction() {
let mut a = Graph::with_vertices(2);
for _ in 0..5 {
a.add_edge(0, 1).unwrap();
}
let mut b = Graph::with_vertices(2);
b.add_edge(0, 1).unwrap();
b.add_edge(0, 1).unwrap();
let d = difference(&a, &b).unwrap();
assert_eq!(d.ecount(), 3);
assert!(sorted_edges(&d).iter().all(|&p| p == (0, 1)));
}
#[test]
fn directed_orientations_are_independent() {
let mut a = Graph::new(2, true).unwrap();
a.add_edge(0, 1).unwrap();
a.add_edge(1, 0).unwrap();
let mut b = Graph::new(2, true).unwrap();
b.add_edge(0, 1).unwrap();
let d = difference(&a, &b).unwrap();
assert!(d.is_directed());
assert_eq!(d.ecount(), 1);
assert_eq!(d.edge(0).unwrap(), (1, 0));
}
#[test]
fn directed_per_orientation_multiplicity() {
let mut a = Graph::new(2, true).unwrap();
for _ in 0..4 {
a.add_edge(0, 1).unwrap();
}
for _ in 0..2 {
a.add_edge(1, 0).unwrap();
}
let mut b = Graph::new(2, true).unwrap();
b.add_edge(0, 1).unwrap();
for _ in 0..3 {
b.add_edge(1, 0).unwrap();
}
let d = difference(&a, &b).unwrap();
assert_eq!(d.ecount(), 3);
assert!(sorted_edges(&d).iter().all(|&p| p == (0, 1)));
}
#[test]
fn loops_subtract_correctly() {
let mut a = Graph::with_vertices(1);
for _ in 0..3 {
a.add_edge(0, 0).unwrap();
}
let mut b = Graph::with_vertices(1);
b.add_edge(0, 0).unwrap();
let d = difference(&a, &b).unwrap();
assert_eq!(d.ecount(), 2);
assert!(sorted_edges(&d).iter().all(|&p| p == (0, 0)));
}
#[test]
fn mixed_directedness_errors() {
let a = Graph::with_vertices(2);
let b = Graph::new(2, true).unwrap();
assert!(difference(&a, &b).is_err());
}
#[test]
fn undirected_canonicalises_swapped_endpoints() {
let mut a = Graph::with_vertices(2);
a.add_edge(1, 0).unwrap();
let mut b = Graph::with_vertices(2);
b.add_edge(0, 1).unwrap();
let d = difference(&a, &b).unwrap();
assert_eq!(d.ecount(), 0);
}
#[test]
fn sub_high_index_vertex_ignored() {
let mut a = Graph::with_vertices(3);
a.add_edge(0, 1).unwrap();
a.add_edge(1, 2).unwrap();
let mut b = Graph::with_vertices(5);
b.add_edge(0, 1).unwrap();
b.add_edge(3, 4).unwrap();
let d = difference(&a, &b).unwrap();
assert_eq!(d.vcount(), 3);
assert_eq!(sorted_edges(&d), vec![(1, 2)]);
}
}