use crate::Snapshot;
const SKEW: usize = 32;
#[must_use]
pub fn triangle_count(g: &Snapshot) -> u64 {
let n = g.nodes() as usize;
if n < 3 {
return 0;
}
let (at, up) = upward(g);
let mut found = 0u64;
for node in 0..n {
let mine = &up[at[node] as usize..at[node + 1] as usize];
for other in mine {
let theirs = &up[at[*other as usize] as usize..at[*other as usize + 1] as usize];
found += common(mine, theirs);
}
}
found
}
fn upward(g: &Snapshot) -> (Vec<u64>, Vec<u32>) {
let n = g.nodes() as usize;
let mut order: Vec<u32> = (0..n as u32).collect();
order.sort_unstable_by_key(|node| (g.out_degree(*node) + g.in_degree(*node), *node));
let mut rank = vec![0u32; n];
for (at, node) in order.iter().enumerate() {
rank[*node as usize] = at as u32;
}
let mut at = vec![0u64; n + 1];
let mut up: Vec<u32> = Vec::new();
let mut mine: Vec<u32> = Vec::new();
for (r, node) in order.iter().enumerate() {
mine.clear();
for side in [g.out(*node), g.into_(*node)] {
for other in side {
let other = rank[*other as usize];
if other > r as u32 {
mine.push(other);
}
}
}
mine.sort_unstable();
mine.dedup();
up.extend_from_slice(&mine);
at[r + 1] = up.len() as u64;
}
(at, up)
}
fn common(a: &[u32], b: &[u32]) -> u64 {
if a.len() > b.len() * SKEW {
return search(b, a);
}
if b.len() > a.len() * SKEW {
return search(a, b);
}
let (mut i, mut j, mut found) = (0, 0, 0);
while i < a.len() && j < b.len() {
match a[i].cmp(&b[j]) {
std::cmp::Ordering::Less => i += 1,
std::cmp::Ordering::Greater => j += 1,
std::cmp::Ordering::Equal => {
found += 1;
i += 1;
j += 1;
}
}
}
found
}
fn search(short: &[u32], long: &[u32]) -> u64 {
let (mut from, mut found) = (0, 0);
for want in short {
match long[from..].binary_search(want) {
Ok(at) => {
found += 1;
from += at + 1;
}
Err(at) => from += at,
}
if from >= long.len() {
break;
}
}
found
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::NO_PROPS;
use crate::{Graph, Snapshot};
use std::collections::BTreeSet;
use yo_common::Rng;
fn reference(g: &Snapshot) -> u64 {
let n = g.nodes() as usize;
let mut near: Vec<BTreeSet<u32>> = vec![BTreeSet::new(); n];
for node in 0..n as u32 {
for other in g.out(node).iter().chain(g.into_(node)) {
if *other != node {
near[node as usize].insert(*other);
near[*other as usize].insert(node);
}
}
}
let mut found = 0;
for a in 0..n as u32 {
for b in a + 1..n as u32 {
if !near[a as usize].contains(&b) {
continue;
}
for c in b + 1..n as u32 {
if near[a as usize].contains(&c) && near[b as usize].contains(&c) {
found += 1;
}
}
}
}
found
}
fn linked(edges: &[(u64, u64)]) -> Graph {
let mut g = Graph::new();
for (from, to) in edges {
g.link(*from, *to, 1, NO_PROPS).expect("an edge");
}
g
}
#[test]
fn three_nodes_joined_up_are_one_triangle() {
let s = Snapshot::of(&linked(&[(1, 2), (2, 3), (3, 1)]));
assert_eq!(triangle_count(&s), 1);
}
#[test]
fn a_chain_has_none() {
let s = Snapshot::of(&linked(&[(1, 2), (2, 3), (3, 4), (4, 5)]));
assert_eq!(triangle_count(&s), 0);
}
#[test]
fn a_complete_graph_has_all_of_them() {
let mut edges = Vec::new();
for a in 0..5u64 {
for b in a + 1..5 {
edges.push((a, b));
}
}
assert_eq!(triangle_count(&Snapshot::of(&linked(&edges))), 10);
}
#[test]
fn which_way_the_edges_point_makes_no_difference() {
let one = Snapshot::of(&linked(&[(1, 2), (2, 3), (3, 1)]));
let other = Snapshot::of(&linked(&[(1, 2), (1, 3), (2, 3)]));
assert_eq!(triangle_count(&one), triangle_count(&other));
}
#[test]
fn a_self_loop_and_a_second_edge_are_not_a_triangle() {
let s = Snapshot::of(&linked(&[(1, 1), (1, 2), (2, 1), (2, 2)]));
assert_eq!(triangle_count(&s), 0);
let s = Snapshot::of(&linked(&[(1, 2), (2, 1), (2, 3), (3, 1), (3, 3)]));
assert_eq!(triangle_count(&s), 1);
}
#[test]
fn too_few_nodes_to_have_one() {
assert_eq!(triangle_count(&Snapshot::default()), 0);
assert_eq!(triangle_count(&Snapshot::of(&linked(&[(1, 2)]))), 0);
}
#[test]
fn a_hub_over_a_ring() {
let size = if cfg!(miri) { 30u64 } else { 2000 };
let mut edges: Vec<(u64, u64)> = (0..size).map(|i| (i, (i + 1) % size)).collect();
edges.extend((0..size).map(|i| (size + 1, i)));
let s = Snapshot::of(&linked(&edges));
assert_eq!(triangle_count(&s), u64::from(size as u32));
}
#[test]
fn it_agrees_with_the_slow_one() {
let mut rng = Rng::new(0x7a13);
let (cases, spread) = if cfg!(miri) { (3, 8) } else { (60, 40) };
for case in 0..cases {
let nodes = 3 + rng.next_u64() % spread;
let edges: Vec<(u64, u64)> = (0..nodes * 4)
.map(|_| (rng.next_u64() % nodes, rng.next_u64() % nodes))
.collect();
let s = Snapshot::of(&linked(&edges));
assert_eq!(triangle_count(&s), reference(&s), "case {case}");
}
}
}