use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::algorithms;
use super::edge::Edge;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EdgeStore<E> {
edges: Vec<E>,
}
impl<E> Default for EdgeStore<E> {
fn default() -> Self {
Self { edges: Vec::new() }
}
}
impl<E> EdgeStore<E> {
pub fn new() -> Self {
Self::default()
}
pub(crate) fn add_edge(&mut self, edge: E) {
self.edges.push(edge);
}
pub fn retain<F: FnMut(&E) -> bool>(&mut self, f: F) {
self.edges.retain(f);
}
pub fn edges(&self) -> &[E] {
&self.edges
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
}
impl<E: Edge> EdgeStore<E> {
pub fn remove_node(&mut self, node_id: E::NodeId) {
self.edges.retain(|e| !e.involves(node_id));
}
pub fn archive_node(&mut self, node_id: E::NodeId) {
for edge in &mut self.edges {
if edge.involves(node_id) {
edge.archive();
}
}
}
pub fn unarchive_node(&mut self, node_id: E::NodeId) {
for edge in &mut self.edges {
if edge.involves(node_id) {
edge.unarchive();
}
}
}
pub fn remove_directed_edge(&mut self, source: E::NodeId, target: E::NodeId) -> bool {
let before = self.edges.len();
self.edges
.retain(|e| !(e.is_active() && e.source() == source && e.target() == target));
self.edges.len() < before
}
pub fn remove_undirected_edge(&mut self, a: E::NodeId, b: E::NodeId) -> bool {
let before = self.edges.len();
self.edges.retain(|e| {
!(e.is_active()
&& ((e.source() == a && e.target() == b) || (e.source() == b && e.target() == a)))
});
self.edges.len() < before
}
pub fn outgoing(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
self.edges.iter().filter(move |e| e.source() == node_id)
}
pub fn incoming(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
self.edges.iter().filter(move |e| e.target() == node_id)
}
pub fn outgoing_active(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
self.edges
.iter()
.filter(move |e| e.source() == node_id && e.is_active())
}
pub fn incoming_active(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
self.edges
.iter()
.filter(move |e| e.target() == node_id && e.is_active())
}
pub fn active_edges(&self) -> impl Iterator<Item = &E> {
self.edges.iter().filter(|e| e.is_active())
}
pub fn adjacency_list(&self) -> HashMap<E::NodeId, Vec<E::NodeId>> {
let mut adj_list: HashMap<E::NodeId, Vec<E::NodeId>> = HashMap::new();
for edge in self.active_edges() {
adj_list
.entry(edge.source())
.or_default()
.push(edge.target());
}
adj_list
}
pub fn active_edge_count(&self) -> usize {
self.edges.iter().filter(|e| e.is_active()).count()
}
pub fn would_create_cycle(&self, source: E::NodeId, target: E::NodeId) -> bool {
let adj_list = self.adjacency_list();
algorithms::would_create_cycle(&adj_list, source, target)
}
pub fn has_cycle(&self) -> bool {
let adj_list = self.adjacency_list();
algorithms::has_cycle(&adj_list)
}
pub fn reachable_from(&self, start: E::NodeId) -> std::collections::HashSet<E::NodeId> {
let adj_list = self.adjacency_list();
algorithms::reachable_from(&adj_list, start)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::edge::EdgeBase;
use uuid::Uuid;
fn base(source: Uuid, target: Uuid) -> EdgeBase {
EdgeBase::new(source, target)
}
#[test]
fn test_new_returns_empty_edge_store() {
let graph: EdgeStore<EdgeBase> = EdgeStore::new();
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_add_edge_increments_count() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let source = Uuid::new_v4();
let target = Uuid::new_v4();
graph.add_edge(base(source, target));
assert_eq!(graph.edge_count(), 1);
}
#[test]
fn test_remove_directed_edge_only_matches_exact_orientation() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
graph.add_edge(base(a, b));
assert!(!graph.remove_directed_edge(b, a), "wrong orientation");
assert_eq!(graph.edge_count(), 1);
assert!(graph.remove_directed_edge(a, b));
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_remove_undirected_edge_matches_either_orientation() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
graph.add_edge(base(a, b));
assert!(
graph.remove_undirected_edge(b, a),
"symmetric on either ordering"
);
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_remove_directed_edge_preserves_archived_record() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let mut archived = base(a, b);
archived.archive();
graph.add_edge(archived);
graph.add_edge(base(a, b));
assert!(graph.remove_directed_edge(a, b));
assert_eq!(
graph.edge_count(),
1,
"archived record must survive the active remove"
);
assert_eq!(graph.active_edge_count(), 0);
}
#[test]
fn test_remove_directed_edge_returns_false_when_only_archived_exists() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let mut archived = base(a, b);
archived.archive();
graph.add_edge(archived);
assert!(
!graph.remove_directed_edge(a, b),
"no active edge means remove is a no-op"
);
assert_eq!(graph.edge_count(), 1, "archived record untouched");
}
#[test]
fn test_remove_undirected_edge_preserves_archived_record() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let mut archived = base(a, b);
archived.archive();
graph.add_edge(archived);
graph.add_edge(base(b, a));
assert!(graph.remove_undirected_edge(a, b));
assert_eq!(graph.edge_count(), 1);
assert_eq!(graph.active_edge_count(), 0);
}
#[test]
fn test_remove_node_drops_every_incident_edge() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let node_a = Uuid::new_v4();
let node_b = Uuid::new_v4();
let node_c = Uuid::new_v4();
graph.add_edge(base(node_a, node_b));
graph.add_edge(base(node_b, node_c));
graph.add_edge(base(node_c, node_a));
graph.remove_node(node_b);
assert_eq!(graph.edge_count(), 1);
}
#[test]
fn test_archive_node_hides_incident_edges_from_active_count() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let node_a = Uuid::new_v4();
let node_b = Uuid::new_v4();
let node_c = Uuid::new_v4();
graph.add_edge(base(node_a, node_b));
graph.add_edge(base(node_b, node_c));
assert_eq!(graph.active_edge_count(), 2);
graph.archive_node(node_b);
assert_eq!(graph.edge_count(), 2);
assert_eq!(graph.active_edge_count(), 0);
}
#[test]
fn test_unarchive_node_restores_active_count() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let node_a = Uuid::new_v4();
let node_b = Uuid::new_v4();
graph.add_edge(base(node_a, node_b));
graph.archive_node(node_a);
assert_eq!(graph.active_edge_count(), 0);
graph.unarchive_node(node_a);
assert_eq!(graph.active_edge_count(), 1);
}
#[test]
fn test_outgoing_and_incoming_partition_by_endpoint_role() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let node_a = Uuid::new_v4();
let node_b = Uuid::new_v4();
let node_c = Uuid::new_v4();
graph.add_edge(base(node_a, node_b));
graph.add_edge(base(node_a, node_c));
graph.add_edge(base(node_c, node_a));
assert_eq!(graph.outgoing(node_a).count(), 2);
assert_eq!(graph.incoming(node_a).count(), 1);
assert_eq!(graph.outgoing(node_b).count(), 0);
assert_eq!(graph.incoming(node_b).count(), 1);
}
#[test]
fn test_would_create_cycle_detects_directed_path_closing() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let node_a = Uuid::new_v4();
let node_b = Uuid::new_v4();
let node_c = Uuid::new_v4();
graph.add_edge(base(node_a, node_b));
graph.add_edge(base(node_b, node_c));
assert!(graph.would_create_cycle(node_c, node_a));
assert!(graph.would_create_cycle(node_c, node_b));
}
#[test]
fn test_adjacency_list_counts_active_outgoing_per_node() {
let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
let node_a = Uuid::new_v4();
let node_b = Uuid::new_v4();
let node_c = Uuid::new_v4();
graph.add_edge(base(node_a, node_b));
graph.add_edge(base(node_b, node_c));
let adj_list = graph.adjacency_list();
assert_eq!(adj_list.len(), 2);
assert_eq!(adj_list.get(&node_a).unwrap().len(), 1);
assert_eq!(adj_list.get(&node_b).unwrap().len(), 1);
}
}