use std::collections::BTreeSet;
use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphResult, VertexId};
pub fn complementer(graph: &Graph, loops: bool) -> IgraphResult<Graph> {
let n = graph.vcount();
let directed = graph.is_directed();
let mut g = Graph::new(n, directed)?;
if n == 0 {
return Ok(g);
}
let m = u32::try_from(graph.ecount())
.map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
let mut existing: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
for e in 0..m {
existing.insert(graph.edge(e as EdgeId)?);
}
let mut new_edges: Vec<(VertexId, VertexId)> = Vec::new();
if directed {
for u in 0..n {
for v in 0..n {
if u == v && !loops {
continue;
}
if !existing.contains(&(u, v)) {
new_edges.push((u, v));
}
}
}
} else {
for u in 0..n {
let lo = if loops { u } else { u + 1 };
for v in lo..n {
if !existing.contains(&(u, v)) {
new_edges.push((u, v));
}
}
}
}
g.add_edges(new_edges)?;
Ok(g)
}
#[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_graph_yields_empty() {
let g = Graph::with_vertices(0);
let c = complementer(&g, false).unwrap();
assert_eq!(c.vcount(), 0);
assert_eq!(c.ecount(), 0);
}
#[test]
fn no_edges_no_loops_is_complete_graph() {
let g = Graph::with_vertices(3);
let c = complementer(&g, false).unwrap();
assert_eq!(sorted_edges(&c), vec![(0, 1), (0, 2), (1, 2)]);
}
#[test]
fn no_edges_with_loops_adds_self_loops() {
let g = Graph::with_vertices(2);
let c = complementer(&g, true).unwrap();
assert_eq!(sorted_edges(&c), vec![(0, 0), (0, 1), (1, 1)]);
}
#[test]
fn complete_graph_complementer_is_empty() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 2).unwrap();
let c = complementer(&g, false).unwrap();
assert_eq!(c.ecount(), 0);
}
#[test]
fn path_complementer_is_single_chord() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let c = complementer(&g, false).unwrap();
assert_eq!(c.ecount(), 1);
assert_eq!(c.edge(0).unwrap(), (0, 2));
}
#[test]
fn directed_complementer_includes_reverses() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
let c = complementer(&g, false).unwrap();
assert!(c.is_directed());
assert_eq!(
sorted_edges(&c),
vec![(0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
);
}
#[test]
fn directed_with_loops_adds_self_loops() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
let c = complementer(&g, true).unwrap();
assert_eq!(sorted_edges(&c), vec![(0, 0), (1, 0), (1, 1)]);
}
#[test]
fn double_complement_recovers_original_for_simple_undirected() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let c = complementer(&g, false).unwrap();
let cc = complementer(&c, false).unwrap();
assert_eq!(sorted_edges(&cc), sorted_edges(&g));
}
#[test]
fn singleton_with_loops() {
let g = Graph::with_vertices(1);
let c = complementer(&g, true).unwrap();
assert_eq!(sorted_edges(&c), vec![(0, 0)]);
}
#[test]
fn singleton_without_loops() {
let g = Graph::with_vertices(1);
let c = complementer(&g, false).unwrap();
assert_eq!(c.ecount(), 0);
}
#[test]
fn parallel_edges_in_input_collapse() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let c = complementer(&g, false).unwrap();
assert_eq!(sorted_edges(&c), vec![(0, 2), (1, 2)]);
}
}