use crate::core::{Graph, IgraphResult, VertexId};
pub fn is_mutual(graph: &Graph, loops: bool) -> IgraphResult<Vec<bool>> {
let ecount = graph.ecount();
if !graph.is_directed() {
return Ok(vec![true; ecount]);
}
let n = graph.vcount() as usize;
let mut out_adj: Vec<Vec<VertexId>> = vec![Vec::new(); n];
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (from, to) = graph.edge(eid as u32)?;
out_adj[from as usize].push(to);
}
for adj in &mut out_adj {
adj.sort_unstable();
}
let mut result = Vec::with_capacity(ecount);
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (from, to) = graph.edge(eid as u32)?;
if from == to {
result.push(loops);
} else {
let has_reverse = out_adj[to as usize].binary_search(&from).is_ok();
result.push(has_reverse);
}
}
Ok(result)
}
pub fn has_mutual(graph: &Graph, loops: bool) -> IgraphResult<bool> {
let ecount = graph.ecount();
if !graph.is_directed() {
return Ok(ecount > 0);
}
let n = graph.vcount() as usize;
let mut out_adj: Vec<Vec<VertexId>> = vec![Vec::new(); n];
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (from, to) = graph.edge(eid as u32)?;
out_adj[from as usize].push(to);
}
for adj in &mut out_adj {
adj.sort_unstable();
}
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (from, to) = graph.edge(eid as u32)?;
if from == to {
if loops {
return Ok(true);
}
} else if out_adj[to as usize].binary_search(&from).is_ok() {
return Ok(true);
}
}
Ok(false)
}
pub fn count_mutual(graph: &Graph, loops: bool) -> IgraphResult<usize> {
let ecount = graph.ecount();
if !graph.is_directed() {
return Ok(ecount);
}
let n = graph.vcount() as usize;
let mut out_adj: Vec<Vec<VertexId>> = vec![Vec::new(); n];
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (from, to) = graph.edge(eid as u32)?;
out_adj[from as usize].push(to);
}
for adj in &mut out_adj {
adj.sort_unstable();
}
let mut count: usize = 0;
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (from, to) = graph.edge(eid as u32)?;
if from == to {
if loops {
count += 1;
}
} else if from < to && out_adj[to as usize].binary_search(&from).is_ok() {
count += 1;
}
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_mutual_undirected() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let result = is_mutual(&g, true).unwrap();
assert_eq!(result, vec![true, true]);
}
#[test]
fn is_mutual_directed_reciprocal() {
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 result = is_mutual(&g, true).unwrap();
assert_eq!(result, vec![true, true, false]);
}
#[test]
fn is_mutual_self_loop_true() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let result = is_mutual(&g, true).unwrap();
assert_eq!(result, vec![true, false]);
}
#[test]
fn is_mutual_self_loop_false() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let result = is_mutual(&g, false).unwrap();
assert_eq!(result, vec![false, false]);
}
#[test]
fn is_mutual_empty() {
let g = Graph::new(3, true).unwrap();
let result = is_mutual(&g, true).unwrap();
assert!(result.is_empty());
}
#[test]
fn has_mutual_no_mutual() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert!(!has_mutual(&g, false).unwrap());
}
#[test]
fn has_mutual_with_mutual() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
assert!(has_mutual(&g, false).unwrap());
}
#[test]
fn has_mutual_undirected_nonempty() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert!(has_mutual(&g, false).unwrap());
}
#[test]
fn has_mutual_undirected_empty() {
let g = Graph::with_vertices(3);
assert!(!has_mutual(&g, false).unwrap());
}
#[test]
fn has_mutual_self_loop_counted() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
assert!(has_mutual(&g, true).unwrap());
assert!(!has_mutual(&g, false).unwrap());
}
#[test]
fn count_mutual_basic() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 1).unwrap();
g.add_edge(2, 3).unwrap();
assert_eq!(count_mutual(&g, false).unwrap(), 2);
}
#[test]
fn count_mutual_none() {
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();
assert_eq!(count_mutual(&g, false).unwrap(), 0);
}
#[test]
fn count_mutual_all() {
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();
g.add_edge(2, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(2, 0).unwrap();
assert_eq!(count_mutual(&g, false).unwrap(), 3);
}
#[test]
fn count_mutual_undirected() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(count_mutual(&g, false).unwrap(), 2);
}
#[test]
fn count_mutual_with_self_loops() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 0).unwrap();
assert_eq!(count_mutual(&g, true).unwrap(), 2); assert_eq!(count_mutual(&g, false).unwrap(), 1); }
}