use std::collections::VecDeque;
use crate::{spec::Locale, text};
const MOST_NAMED: usize = 3;
const HUB_DEGREE: usize = 3;
const MOST_CUT_POINTS: usize = 2;
#[derive(Clone, Copy)]
pub(crate) struct Facts {
pub directed: bool,
}
#[derive(Debug, Default, PartialEq)]
pub(crate) struct Structure {
pub hubs: Vec<usize>,
pub degree: usize,
pub sources: Vec<usize>,
pub sinks: Vec<usize>,
pub loops: Vec<Vec<usize>>,
pub parts: usize,
pub cut_points: Vec<usize>,
}
impl Structure {
pub(crate) fn of(
nodes: usize,
edges: &[(usize, usize)],
marked: &[bool],
facts: Facts,
) -> Self {
let edges: Vec<(usize, usize)> = edges.iter().copied().filter(|(a, b)| a != b).collect();
if nodes < 2 || is_chain(nodes, &edges) {
return Self::default();
}
let mut incoming = vec![0; nodes];
let mut outgoing = vec![0; nodes];
for &(from, to) in &edges {
outgoing[from] += 1;
incoming[to] += 1;
}
let degrees: Vec<usize> = (0..nodes).map(|n| incoming[n] + outgoing[n]).collect();
let degree = degrees.iter().copied().max().unwrap_or(0);
let hubs: Vec<usize> = (0..nodes).filter(|&n| degrees[n] == degree).collect();
let hubs = if degree >= HUB_DEGREE && hubs.len() <= MOST_NAMED {
hubs
} else {
Vec::new()
};
let mut structure = Self {
hubs,
degree,
parts: parts(nodes, &edges),
cut_points: cut_points(nodes, &edges),
..Self::default()
};
if structure.cut_points.len() > MOST_CUT_POINTS {
structure.cut_points.clear();
}
if facts.directed {
let unmarked = |n: &usize| !marked.get(*n).copied().unwrap_or(false);
structure.sources = (0..nodes)
.filter(|&n| incoming[n] == 0 && outgoing[n] > 0)
.filter(unmarked)
.collect();
structure.sinks = (0..nodes)
.filter(|&n| outgoing[n] == 0 && incoming[n] > 0)
.filter(unmarked)
.collect();
structure.loops = loops(nodes, &edges);
for ends in [&mut structure.sources, &mut structure.sinks] {
if ends.len() > MOST_NAMED {
ends.clear();
}
}
}
structure
}
pub(crate) fn sentences(&self, locale: Locale, label: impl Fn(usize) -> String) -> String {
let names = |nodes: &[usize]| nodes.iter().map(|&n| label(n)).collect::<Vec<_>>();
let mut sentences = Vec::new();
if !self.hubs.is_empty() {
sentences.push(text::most_connected(
locale,
&names(&self.hubs),
self.degree,
));
}
if !self.sources.is_empty() {
sentences.push(text::sources(locale, &names(&self.sources)));
}
if !self.sinks.is_empty() {
sentences.push(text::sinks(locale, &names(&self.sinks)));
}
if !self.loops.is_empty() {
let loops: Vec<String> = self
.loops
.iter()
.map(|cycle| {
let mut steps = names(cycle);
steps.push(label(cycle[0]));
steps.join(" → ")
})
.collect();
sentences.push(text::loops(locale, &loops));
}
if self.parts > 1 {
sentences.push(text::unconnected_parts(locale, self.parts));
}
if !self.cut_points.is_empty() {
sentences.push(text::bottlenecks(locale, &names(&self.cut_points)));
}
sentences.join(" ")
}
}
fn is_chain(nodes: usize, edges: &[(usize, usize)]) -> bool {
if edges.len() != nodes - 1 {
return false;
}
let mut incoming = vec![0; nodes];
let mut outgoing = vec![0; nodes];
for &(from, to) in edges {
outgoing[from] += 1;
incoming[to] += 1;
}
incoming.iter().all(|&count| count <= 1)
&& outgoing.iter().all(|&count| count <= 1)
&& parts(nodes, edges) == 1
}
pub(crate) fn neighbours(nodes: usize, edges: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut neighbours = vec![Vec::new(); nodes];
for &(from, to) in edges {
neighbours[from].push(to);
neighbours[to].push(from);
}
neighbours
}
fn parts(nodes: usize, edges: &[(usize, usize)]) -> usize {
let neighbours = neighbours(nodes, edges);
let mut seen = vec![false; nodes];
let mut count = 0;
for start in 0..nodes {
if seen[start] {
continue;
}
count += 1;
seen[start] = true;
let mut stack = vec![start];
while let Some(node) = stack.pop() {
for &next in &neighbours[node] {
if !seen[next] {
seen[next] = true;
stack.push(next);
}
}
}
}
count
}
fn cut_points(nodes: usize, edges: &[(usize, usize)]) -> Vec<usize> {
let neighbours = neighbours(nodes, edges);
(0..nodes)
.filter(|&cut| {
let mut seen = vec![false; nodes];
seen[cut] = true;
let mut large = 0;
for &start in &neighbours[cut] {
if seen[start] {
continue;
}
seen[start] = true;
let mut size = 1;
let mut stack = vec![start];
while let Some(node) = stack.pop() {
for &next in &neighbours[node] {
if !seen[next] {
seen[next] = true;
size += 1;
stack.push(next);
}
}
}
if size > 1 {
large += 1;
}
}
large >= 2
})
.collect()
}
fn loops(nodes: usize, edges: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut forward = vec![Vec::new(); nodes];
for &(from, to) in edges {
forward[from].push(to);
}
let reach = |start: usize, within: &dyn Fn(usize) -> bool| -> Vec<Option<usize>> {
let mut from = vec![None; nodes];
let mut queue = VecDeque::from([start]);
let mut seen = vec![false; nodes];
seen[start] = true;
while let Some(node) = queue.pop_front() {
for &next in &forward[node] {
if within(next) && !seen[next] {
seen[next] = true;
from[next] = Some(node);
queue.push_back(next);
}
}
}
from
};
let mut backward = vec![Vec::new(); nodes];
for &(from, to) in edges {
backward[to].push(from);
}
let mut part = vec![usize::MAX; nodes];
let mut found = Vec::new();
for start in 0..nodes {
if part[start] != usize::MAX || found.len() == MOST_NAMED {
continue;
}
let ahead = reach(start, &|_| true);
let mut behind = vec![false; nodes];
behind[start] = true;
let mut stack = vec![start];
while let Some(node) = stack.pop() {
for &previous in &backward[node] {
if !behind[previous] {
behind[previous] = true;
stack.push(previous);
}
}
}
let members: Vec<usize> = (0..nodes)
.filter(|&n| behind[n] && (n == start || ahead[n].is_some()))
.collect();
for &member in &members {
part[member] = start;
}
if members.len() < 2 {
continue;
}
let inside = |n: usize| part[n] == start;
let way = reach(start, &inside);
let closing = edges
.iter()
.filter(|(from, to)| *to == start && inside(*from))
.map(|(from, _)| *from)
.min_by_key(|&last| {
let mut length = 0;
let mut node = last;
while let Some(previous) = way[node] {
length += 1;
node = previous;
}
(length, last)
})
.expect("a part of two nodes has an edge back to its first");
let mut cycle = vec![closing];
while let Some(previous) = way[*cycle.last().expect("not empty")] {
cycle.push(previous);
}
cycle.reverse();
found.push(cycle);
}
found
}
#[cfg(test)]
mod tests {
use super::{Facts, Structure};
const DIRECTED: Facts = Facts { directed: true };
fn of(nodes: usize, edges: &[(usize, usize)]) -> Structure {
Structure::of(nodes, edges, &[], DIRECTED)
}
#[test]
fn a_chain_has_nothing_to_tell() {
assert_eq!(of(4, &[(0, 1), (1, 2), (2, 3)]), Structure::default());
}
#[test]
fn a_loop_runs_from_its_first_node_back_to_it() {
let structure = of(4, &[(0, 1), (1, 2), (2, 3), (3, 1)]);
assert_eq!(structure.loops, vec![vec![1, 2, 3]]);
assert_eq!(structure.sources, vec![0]);
assert!(structure.sinks.is_empty(), "{:?}", structure.sinks);
}
#[test]
fn the_shortest_way_back_wins() {
let structure = of(4, &[(0, 1), (1, 2), (2, 0), (0, 3), (3, 0)]);
assert_eq!(structure.loops, vec![vec![0, 3]]);
}
#[test]
fn unconnected_parts_are_counted() {
let structure = of(5, &[(0, 1), (0, 2), (3, 4)]);
assert_eq!(structure.parts, 2);
}
#[test]
fn hubs_tie_in_the_order_of_the_specification() {
let structure = of(5, &[(0, 1), (1, 2), (1, 3), (2, 3), (2, 4)]);
assert_eq!(structure.hubs, vec![1, 2]);
assert_eq!(structure.degree, 3);
}
#[test]
fn a_bottleneck_joins_two_halves() {
let edges = [(0, 1), (1, 2), (2, 0), (2, 3), (3, 4), (4, 2)];
assert_eq!(of(5, &edges).cut_points, vec![2]);
}
#[test]
fn marked_ends_are_not_named_again() {
let structure = Structure::of(
4,
&[(0, 1), (1, 2), (1, 3)],
&[true, false, false, false],
DIRECTED,
);
assert!(structure.sources.is_empty(), "{:?}", structure.sources);
assert_eq!(structure.sinks, vec![2, 3]);
}
#[test]
fn undirected_facts_leave_out_direction() {
let structure = Structure::of(3, &[(0, 1), (1, 2), (2, 0)], &[], Facts { directed: false });
assert!(structure.loops.is_empty() && structure.sources.is_empty());
}
}