use crate::core::{Graph, IgraphResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToUndirectedMode {
Each,
Collapse,
Mutual,
}
pub fn to_undirected(graph: &Graph, mode: ToUndirectedMode) -> IgraphResult<Graph> {
let n = graph.vcount();
if !graph.is_directed() {
let mut result = Graph::with_vertices(n);
let ecount = graph.ecount();
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (src, tgt) = graph.edge(eid as u32)?;
result.add_edge(src, tgt)?;
}
return Ok(result);
}
let mut result = Graph::with_vertices(n);
let ecount = graph.ecount();
match mode {
ToUndirectedMode::Each => {
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (src, tgt) = graph.edge(eid as u32)?;
result.add_edge(src, tgt)?;
}
}
ToUndirectedMode::Collapse => {
let mut seen = std::collections::HashSet::with_capacity(ecount);
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (src, tgt) = graph.edge(eid as u32)?;
let key = if src <= tgt { (src, tgt) } else { (tgt, src) };
if seen.insert(key) {
result.add_edge(key.0, key.1)?;
}
}
}
ToUndirectedMode::Mutual => {
let mut forward = std::collections::HashSet::with_capacity(ecount);
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (src, tgt) = graph.edge(eid as u32)?;
forward.insert((src, tgt));
}
let mut added = std::collections::HashSet::new();
for &(src, tgt) in &forward {
if src != tgt && forward.contains(&(tgt, src)) {
let key = if src <= tgt { (src, tgt) } else { (tgt, src) };
if added.insert(key) {
result.add_edge(key.0, key.1)?;
}
}
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_already_undirected() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
assert!(!u.is_directed());
assert_eq!(u.vcount(), 3);
assert_eq!(u.ecount(), 2);
}
#[test]
fn test_each_mode_mutual_pair() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
assert!(!u.is_directed());
assert_eq!(u.ecount(), 2); }
#[test]
fn test_each_mode_single_edge() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
assert_eq!(u.ecount(), 2);
}
#[test]
fn test_collapse_mode_mutual() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
assert_eq!(u.ecount(), 2); }
#[test]
fn test_collapse_mode_parallel() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
assert_eq!(u.ecount(), 1); }
#[test]
fn test_mutual_mode_basic() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
assert_eq!(u.ecount(), 1); }
#[test]
fn test_mutual_mode_no_mutual() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
assert_eq!(u.ecount(), 0); }
#[test]
fn test_mutual_mode_all_mutual() {
let mut g = Graph::new(3, true).unwrap();
for i in 0..3u32 {
for j in 0..3u32 {
if i != j {
g.add_edge(i, j).unwrap();
}
}
}
let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
assert_eq!(u.ecount(), 3); }
#[test]
fn test_empty_graph() {
let g = Graph::new(0, true).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
assert!(!u.is_directed());
assert_eq!(u.vcount(), 0);
assert_eq!(u.ecount(), 0);
}
#[test]
fn test_no_edges() {
let g = Graph::new(5, true).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
assert_eq!(u.vcount(), 5);
assert_eq!(u.ecount(), 0);
}
#[test]
fn test_self_loops_each() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Each).unwrap();
assert_eq!(u.ecount(), 2);
}
#[test]
fn test_self_loops_mutual() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Mutual).unwrap();
assert_eq!(u.ecount(), 0);
}
#[test]
fn test_vcount_preserved() {
let mut g = Graph::new(10, true).unwrap();
g.add_edge(0, 5).unwrap();
g.add_edge(5, 0).unwrap();
let u = to_undirected(&g, ToUndirectedMode::Collapse).unwrap();
assert_eq!(u.vcount(), 10);
}
}