use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn disjoint_union(left: &Graph, right: &Graph) -> IgraphResult<Graph> {
disjoint_union_many(&[left, right])
}
pub fn disjoint_union_many(graphs: &[&Graph]) -> IgraphResult<Graph> {
if graphs.is_empty() {
return Graph::new(0, false);
}
let directed = graphs[0].is_directed();
for g in &graphs[1..] {
if g.is_directed() != directed {
return Err(IgraphError::InvalidArgument(
"disjoint_union_many: cannot mix directed and undirected graphs".to_string(),
));
}
}
let mut shifts: Vec<u32> = Vec::with_capacity(graphs.len());
let mut total_v: u32 = 0;
let mut total_e: usize = 0;
for g in graphs {
shifts.push(total_v);
total_v = total_v
.checked_add(g.vcount())
.ok_or(IgraphError::Internal("vertex count overflow"))?;
total_e = total_e
.checked_add(g.ecount())
.ok_or(IgraphError::Internal("edge count overflow"))?;
}
let mut out = Graph::new(total_v, directed)?;
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(total_e);
for (i, g) in graphs.iter().enumerate() {
let shift = shifts[i];
let m = u32::try_from(g.ecount())
.map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
for e in 0..m {
let (u, v) = g.edge(e as EdgeId)?;
edges.push((u + shift, v + shift));
}
}
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 two_triangles_undirected() {
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 b = a.clone();
let u = disjoint_union(&a, &b).unwrap();
assert_eq!(u.vcount(), 6);
assert_eq!(u.ecount(), 6);
assert_eq!(
sorted_edges(&u),
vec![(0, 1), (0, 2), (1, 2), (3, 4), (3, 5), (4, 5)]
);
}
#[test]
fn empty_left() {
let a = Graph::with_vertices(0);
let mut b = Graph::with_vertices(2);
b.add_edge(0, 1).unwrap();
let u = disjoint_union(&a, &b).unwrap();
assert_eq!(u.vcount(), 2);
assert_eq!(u.ecount(), 1);
assert_eq!(u.edge(0).unwrap(), (0, 1));
}
#[test]
fn empty_right() {
let mut a = Graph::with_vertices(2);
a.add_edge(0, 1).unwrap();
let b = Graph::with_vertices(0);
let u = disjoint_union(&a, &b).unwrap();
assert_eq!(u.vcount(), 2);
assert_eq!(u.ecount(), 1);
}
#[test]
fn both_empty() {
let a = Graph::with_vertices(0);
let b = Graph::with_vertices(0);
let u = disjoint_union(&a, &b).unwrap();
assert_eq!(u.vcount(), 0);
assert_eq!(u.ecount(), 0);
}
#[test]
fn isolated_plus_edge() {
let mut a = Graph::with_vertices(2);
a.add_edge(0, 1).unwrap();
let b = Graph::with_vertices(3);
let u = disjoint_union(&a, &b).unwrap();
assert_eq!(u.vcount(), 5);
assert_eq!(u.ecount(), 1);
}
#[test]
fn directed_directed_succeeds() {
let mut a = Graph::new(2, true).unwrap();
a.add_edge(0, 1).unwrap();
let mut b = Graph::new(2, true).unwrap();
b.add_edge(1, 0).unwrap();
let u = disjoint_union(&a, &b).unwrap();
assert!(u.is_directed());
assert_eq!(u.vcount(), 4);
assert_eq!(u.ecount(), 2);
assert_eq!(u.edge(0).unwrap(), (0, 1));
assert_eq!(u.edge(1).unwrap(), (3, 2));
}
#[test]
fn mixed_directedness_errors() {
let a = Graph::with_vertices(2);
let b = Graph::new(2, true).unwrap();
assert!(disjoint_union(&a, &b).is_err());
}
#[test]
fn vertex_count_preserved_for_isolated_left() {
let a = Graph::with_vertices(5);
let mut b = Graph::with_vertices(2);
b.add_edge(0, 1).unwrap();
let u = disjoint_union(&a, &b).unwrap();
assert_eq!(u.vcount(), 7);
assert_eq!(u.edge(0).unwrap(), (5, 6));
}
#[test]
fn idempotent_with_self_when_relabelled() {
let mut a = Graph::with_vertices(4);
a.add_edge(0, 1).unwrap();
a.add_edge(1, 2).unwrap();
a.add_edge(2, 3).unwrap();
let u = disjoint_union(&a, &a).unwrap();
assert_eq!(u.vcount(), 8);
assert_eq!(u.ecount(), 6);
}
#[test]
fn many_empty_slice_yields_null_graph() {
let u = disjoint_union_many(&[]).unwrap();
assert_eq!(u.vcount(), 0);
assert_eq!(u.ecount(), 0);
assert!(!u.is_directed());
}
#[test]
fn many_single_graph_yields_clone() {
let mut a = Graph::with_vertices(3);
a.add_edge(0, 1).unwrap();
a.add_edge(1, 2).unwrap();
let u = disjoint_union_many(&[&a]).unwrap();
assert_eq!(u.vcount(), a.vcount());
assert_eq!(u.ecount(), a.ecount());
assert_eq!(sorted_edges(&u), sorted_edges(&a));
}
#[test]
fn many_three_triangles_shifts_correctly() {
let mut t = Graph::with_vertices(3);
t.add_edge(0, 1).unwrap();
t.add_edge(1, 2).unwrap();
t.add_edge(2, 0).unwrap();
let u = disjoint_union_many(&[&t, &t, &t]).unwrap();
assert_eq!(u.vcount(), 9);
assert_eq!(u.ecount(), 9);
assert_eq!(u.edge(0).unwrap(), (0, 1));
assert_eq!(u.edge(3).unwrap(), (3, 4));
assert_eq!(u.edge(6).unwrap(), (6, 7));
}
#[test]
fn many_matches_pairwise_left_to_right() {
let mut a = Graph::with_vertices(2);
a.add_edge(0, 1).unwrap();
let mut b = Graph::with_vertices(3);
b.add_edge(0, 1).unwrap();
b.add_edge(1, 2).unwrap();
let mut c = Graph::with_vertices(2);
c.add_edge(0, 1).unwrap();
let u = disjoint_union_many(&[&a, &b, &c]).unwrap();
let pairwise = disjoint_union(&disjoint_union(&a, &b).unwrap(), &c).unwrap();
assert_eq!(u.vcount(), pairwise.vcount());
assert_eq!(u.ecount(), pairwise.ecount());
assert_eq!(sorted_edges(&u), sorted_edges(&pairwise));
}
#[test]
fn many_mixed_directedness_errors() {
let a = Graph::with_vertices(2);
let b = Graph::with_vertices(2);
let c = Graph::new(2, true).unwrap();
assert!(disjoint_union_many(&[&a, &b, &c]).is_err());
}
#[test]
fn many_directed_preserves_orientation() {
let mut a = Graph::new(2, true).unwrap();
a.add_edge(0, 1).unwrap();
let mut b = Graph::new(2, true).unwrap();
b.add_edge(1, 0).unwrap();
let u = disjoint_union_many(&[&a, &b]).unwrap();
assert!(u.is_directed());
assert_eq!(u.vcount(), 4);
assert_eq!(u.ecount(), 2);
assert_eq!(u.edge(0).unwrap(), (0, 1));
assert_eq!(u.edge(1).unwrap(), (3, 2));
}
}