use crate::core::filter::EdgeFiltered;
use crate::core::io::dot::{DotAttributes, DotStyle};
use crate::core::observer::MutableGraph;
use crate::core::view::{EdgeId, Generations, GraphView, Link, NodeId};
use crate::hgraph::h_edge::EdgeType;
use crate::hgraph::h_graph::HeterogeneousGraph;
use crate::hgraph::h_node::{HNode, NodeType};
use std::fmt::Debug;
pub struct HGraphNodeIds<'a, W, N: NodeType> {
inner: slab::Iter<'a, HNode<W, N>>,
generations: &'a Generations,
}
impl<'a, W, N: NodeType> Iterator for HGraphNodeIds<'a, W, N> {
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|(index, _)| NodeId::new(index as u32, self.generations.generation(index)))
}
}
pub struct HGraphLinks<'a, W> {
neighbors: std::slice::Iter<'a, (NodeId, Vec<(EdgeId, W)>)>,
current: Option<(NodeId, std::slice::Iter<'a, (EdgeId, W)>)>,
}
impl<'a, W: Copy> Iterator for HGraphLinks<'a, W> {
type Item = Link<W>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some((target, edges)) = self.current.as_mut() {
if let Some(&(edge, weight)) = edges.next() {
return Some(Link::new(*target, edge, weight));
}
}
let (target, edges) = self.neighbors.next()?;
self.current = Some((*target, edges.iter()));
}
}
}
impl<W, N, E> GraphView for HeterogeneousGraph<W, N, E>
where
W: Copy + PartialEq + Debug,
N: NodeType,
E: EdgeType,
{
type Weight = W;
type NodeIds<'a>
= HGraphNodeIds<'a, W, N>
where
Self: 'a;
type Links<'a>
= HGraphLinks<'a, W>
where
Self: 'a;
fn is_directed(&self) -> bool {
self.directed
}
fn node_count(&self) -> usize {
self.nodes.len()
}
fn edge_count(&self) -> usize {
self.edges.len()
}
fn contains_node(&self, node: NodeId) -> bool {
self.node(node).is_some()
}
fn node_ids(&self) -> Self::NodeIds<'_> {
HGraphNodeIds {
inner: self.nodes.iter(),
generations: self.node_generations(),
}
}
fn links(&self, node: NodeId) -> Self::Links<'_> {
HGraphLinks {
neighbors: match self.node(node) {
Some(node) => node.neighbors.iter(),
None => [].iter(),
},
current: None,
}
}
fn edge_endpoints(&self, edge: EdgeId) -> Option<(NodeId, NodeId)> {
self.edge(edge).map(|e| (e.from, e.to))
}
}
impl<W, N, E> MutableGraph for HeterogeneousGraph<W, N, E>
where
W: Copy + Default + PartialEq + Debug,
N: NodeType,
E: EdgeType + Default,
{
type NodeData = N;
type EdgeData = E;
type EdgeWeight = W;
type Error = crate::hgraph::h_graph::HGraphError;
fn add_node(&mut self, data: N) -> NodeId {
HeterogeneousGraph::add_node(self, data)
}
fn add_edge(
&mut self,
from: NodeId,
to: NodeId,
weight: W,
data: E,
) -> Result<EdgeId, Self::Error> {
HeterogeneousGraph::add_edge(self, from, to, weight, data)
}
fn remove_node(&mut self, node: NodeId) -> Result<bool, Self::Error> {
HeterogeneousGraph::remove_node(self, node)
}
fn remove_edge_by_id(&mut self, edge: EdgeId) -> Result<(), Self::Error> {
HeterogeneousGraph::remove_edge(self, edge)
}
}
impl<W, N, E> DotStyle for HeterogeneousGraph<W, N, E>
where
W: Copy + PartialEq + Debug + std::fmt::Display,
N: NodeType,
E: EdgeType,
{
fn node_attributes(&self, node: NodeId) -> DotAttributes {
let mut attributes = match self.node(node) {
Some(stored) => DotAttributes::new().label(stored.data.as_string()),
None => DotAttributes::new().label(node.to_string()),
};
if let Some(stored) = self.node(node) {
for (key, value) in &stored.attributes {
attributes = attributes.set(key.clone(), value.clone());
}
}
attributes
}
fn edge_attributes(&self, edge: EdgeId) -> DotAttributes {
let Some(stored) = self.edge(edge) else {
return DotAttributes::new();
};
let mut attributes =
DotAttributes::new().label(format!("{} ({})", stored.data.as_string(), stored.weight));
for (key, value) in &stored.attributes {
attributes = attributes.set(key.clone(), value.clone());
}
attributes
}
}
impl<W, N, E> HeterogeneousGraph<W, N, E>
where
W: Copy + PartialEq + Debug,
N: NodeType,
E: EdgeType,
{
pub fn edge_kind(&self, edge: EdgeId) -> Option<String> {
self.edges.get(edge.index()).map(|e| e.data.as_string())
}
pub fn of_edge_kinds<'a>(
&'a self,
kinds: &'a [&'a str],
) -> EdgeFiltered<&'a Self, impl Fn(EdgeId) -> bool + 'a> {
EdgeFiltered::new(self, move |edge| {
self.edge_kind(edge)
.is_some_and(|kind| kinds.contains(&kind.as_str()))
})
}
pub fn of_edge_data<'a>(
&'a self,
allowed: &'a [E],
) -> EdgeFiltered<&'a Self, impl Fn(EdgeId) -> bool + 'a>
where
E: PartialEq,
{
EdgeFiltered::new(self, move |edge| {
self.edge(edge)
.is_some_and(|e| allowed.is_empty() || allowed.contains(&e.data))
})
}
pub fn filter_edges<'a, F>(
&'a self,
predicate: F,
) -> EdgeFiltered<&'a Self, impl Fn(EdgeId) -> bool + 'a>
where
F: Fn(&crate::hgraph::h_edge::HEdge<W, E>) -> bool + 'a,
{
EdgeFiltered::new(self, move |edge| self.edge(edge).is_some_and(&predicate))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct City(String);
impl NodeType for City {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[derive(Clone, Debug, Default)]
struct Route(String);
impl EdgeType for Route {
fn as_string(&self) -> String {
self.0.clone()
}
}
#[test]
fn test_links_flatten_the_two_level_adjacency() {
let mut graph = HeterogeneousGraph::<f64, City, Route>::new(false);
let a = graph.add_node(City("A".into()));
let b = graph.add_node(City("B".into()));
let c = graph.add_node(City("C".into()));
let rail = graph.add_edge(a, b, 1.0, Route("rail".into())).unwrap();
let road = graph.add_edge(a, b, 2.0, Route("road".into())).unwrap();
let ferry = graph.add_edge(a, c, 3.0, Route("ferry".into())).unwrap();
let mut seen: Vec<_> = graph
.links(a)
.map(|link| (link.target, link.edge))
.collect();
seen.sort_unstable();
assert_eq!(seen, vec![(b, rail), (b, road), (c, ferry)]);
assert_eq!(graph.degree(a), 3);
assert_eq!(graph.node_id_vec(), vec![a, b, c]);
}
}