use crate::core::error::IgraphError;
use crate::core::{Graph, IgraphResult, VertexId};
pub fn contract_vertices(graph: &Graph, mapping: &[VertexId]) -> IgraphResult<Graph> {
let n = graph.vcount();
if mapping.len() != n as usize {
return Err(IgraphError::InvalidArgument(format!(
"mapping length {} does not match vertex count {n}",
mapping.len()
)));
}
let new_vcount = if n == 0 {
0u32
} else {
mapping.iter().copied().max().unwrap_or(0) + 1
};
let ecount = graph.ecount();
let directed = graph.is_directed();
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let eid_u32 = eid as u32;
let (src, tgt) = graph.edge(eid_u32)?;
let new_src = mapping[src as usize];
let new_tgt = mapping[tgt as usize];
edges.push((new_src, new_tgt));
}
let mut result = Graph::new(new_vcount, directed)?;
result.add_edges(edges)?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_contraction() {
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 cg = contract_vertices(&g, &[0, 0, 1, 1]).unwrap();
assert_eq!(cg.vcount(), 2);
assert_eq!(cg.ecount(), 3);
}
#[test]
fn test_identity_mapping() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let cg = contract_vertices(&g, &[0, 1, 2]).unwrap();
assert_eq!(cg.vcount(), 3);
assert_eq!(cg.ecount(), 2);
for eid in 0..2u32 {
assert_eq!(g.edge(eid).unwrap(), cg.edge(eid).unwrap());
}
}
#[test]
fn test_all_into_one() {
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();
g.add_edge(3, 0).unwrap();
let cg = contract_vertices(&g, &[0, 0, 0, 0]).unwrap();
assert_eq!(cg.vcount(), 1);
assert_eq!(cg.ecount(), 4); }
#[test]
fn test_directed() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let cg = contract_vertices(&g, &[0, 1, 0, 1]).unwrap();
assert!(cg.is_directed());
assert_eq!(cg.vcount(), 2);
assert_eq!(cg.ecount(), 2);
assert_eq!(cg.edge(0).unwrap(), (0, 1));
assert_eq!(cg.edge(1).unwrap(), (0, 1));
}
#[test]
fn test_empty_graph() {
let g = Graph::with_vertices(0);
let cg = contract_vertices(&g, &[]).unwrap();
assert_eq!(cg.vcount(), 0);
assert_eq!(cg.ecount(), 0);
}
#[test]
fn test_no_edges() {
let g = Graph::with_vertices(5);
let cg = contract_vertices(&g, &[0, 0, 1, 1, 2]).unwrap();
assert_eq!(cg.vcount(), 3);
assert_eq!(cg.ecount(), 0);
}
#[test]
fn test_wrong_length() {
let g = Graph::with_vertices(3);
let result = contract_vertices(&g, &[0, 1]);
assert!(result.is_err());
}
#[test]
fn test_self_loop_preserved() {
let mut g = Graph::with_vertices(3);
g.add_edge(1, 1).unwrap();
let cg = contract_vertices(&g, &[0, 0, 1]).unwrap();
assert_eq!(cg.vcount(), 2);
assert_eq!(cg.ecount(), 1);
assert_eq!(cg.edge(0).unwrap(), (0, 0));
}
#[test]
fn test_non_consecutive_mapping() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let cg = contract_vertices(&g, &[0, 5, 5]).unwrap();
assert_eq!(cg.vcount(), 6);
assert_eq!(cg.ecount(), 2);
}
}