#![warn(clippy::pedantic)]
#![deny(missing_docs)]
pub mod algorithms;
pub mod arc_storage;
#[cfg(feature = "datasets")]
pub mod datasets;
pub mod node_storage;
pub mod search;
mod arc_info;
use std::{
collections::{HashMap, HashSet},
fmt::{Display, Write},
hash::Hash,
marker::PhantomData,
ops::Index,
write,
};
pub use arc_storage::{ArcStorage, HashMapStorage};
use search::{BfsIter, DfsIter, Direction};
use crate::{
algorithms::{Cyclic, topological_sorting},
node_storage::NodeStorage,
search::PathIter,
};
pub use self::arc_info::ArcInfo;
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
pub struct Network<
I,
NodeAttr,
ArcAttr,
NodeStore = HashMap<I, NodeAttr>,
ArcStore = HashMapStorage<I, ArcAttr>,
> where
NodeStore: NodeStorage<I, NodeAttr>,
ArcStore: ArcStorage<I, ArcAttr>,
{
_phantom_aa: PhantomData<ArcAttr>,
_phantom_na: PhantomData<NodeAttr>,
_phantom_i: PhantomData<I>,
nodes: NodeStore,
arc_store: ArcStore,
}
impl<I, NodeAttr, ArcAttr, NodeStore, ArcStore> Clone
for Network<I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NodeAttr> + Clone,
ArcStore: ArcStorage<I, ArcAttr> + Clone,
ArcAttr: Clone,
NodeAttr: Clone,
{
fn clone(&self) -> Self {
Self {
_phantom_aa: self._phantom_aa,
_phantom_na: self._phantom_na,
_phantom_i: PhantomData,
nodes: self.nodes.clone(),
arc_store: self.arc_store.clone(),
}
}
}
impl<I, NodeAttr, ArcAttr, NodeStore, ArcStore> PartialEq
for Network<I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NodeAttr> + PartialEq,
ArcStore: ArcStorage<I, ArcAttr> + PartialEq,
ArcAttr: PartialEq,
NodeAttr: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.nodes == other.nodes && self.arc_store == other.arc_store
}
}
impl<I, NA, AA, NodeStore, ArcStore> Default for Network<I, NA, AA, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NA> + Default,
ArcStore: ArcStorage<I, AA> + Default,
{
fn default() -> Self {
Self {
_phantom_aa: PhantomData,
_phantom_na: PhantomData,
_phantom_i: PhantomData,
nodes: Default::default(),
arc_store: Default::default(),
}
}
}
impl<I, NA, AA, NodeStore, ArcStore> std::fmt::Debug for Network<I, NA, AA, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NA> + std::fmt::Debug,
ArcStore: ArcStorage<I, AA> + std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Network")
.field("nodes", &self.nodes)
.field("arc_store", &self.arc_store)
.finish()
}
}
impl<I, NA, AA, NodeStore, ArcStore> Network<I, NA, AA, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NA> + Default,
ArcStore: ArcStorage<I, AA> + Default,
{
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl<I, NodeAttr, ArcAttr, NodeStore, ArcStore> Network<I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NodeAttr>,
ArcStore: ArcStorage<I, ArcAttr>,
{
}
impl<I, NodeAttr, ArcAttr, NodeStore, ArcStore> Network<I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NodeAttr>,
ArcStore: ArcStorage<I, ArcAttr>,
{
#[must_use]
pub const fn arc_store(&self) -> &ArcStore {
&self.arc_store
}
pub fn n_nodes(&self) -> usize {
self.nodes.n_nodes()
}
pub fn add_node(&mut self, id: I, attributes: NodeAttr) -> Option<NodeAttr> {
self.nodes.add_node(id, attributes)
}
pub fn into_parts(self) -> (NodeStore, ArcStore) {
(self.nodes, self.arc_store)
}
pub fn from_parts(
nodes: NodeStore,
arc_store: ArcStore,
) -> Result<Self, IncompatableArcAndNodeStores> {
if arc_store
.arc_iter()
.all(|a| nodes.contains_node(&a.head) && nodes.contains_node(&a.tail))
{
Ok(Self {
_phantom_na: PhantomData,
_phantom_aa: PhantomData,
_phantom_i: PhantomData,
nodes,
arc_store,
})
} else {
Err(IncompatableArcAndNodeStores)
}
}
pub fn add_nodes<T: IntoIterator<Item = (I, NodeAttr)>>(&mut self, from: T) {
from.into_iter().for_each(|(id, attr)| {
self.add_node(id, attr);
});
}
pub fn remove_node(&mut self, id: &I) -> Option<NodeAttr> {
self.arc_store.remove_arcs_with_node(id);
self.nodes.remove_node(id)
}
pub fn contains_node(&self, id: &I) -> bool {
self.nodes.contains_node(id)
}
pub fn node(&self, id: &I) -> Option<&NodeAttr> {
self.nodes.node(id)
}
pub fn node_mut(&mut self, id: &I) -> Option<&mut NodeAttr> {
self.nodes.node_mut(id)
}
pub fn iter_nodes(&self) -> impl Iterator<Item = (&I, &NodeAttr)> {
self.nodes.iter_nodes()
}
pub fn bfs<'a>(
&'a self,
start_id: &'a I,
direction: Direction,
) -> BfsIter<'a, I, ArcAttr, ArcStore> {
BfsIter::new(&self.arc_store, start_id, direction)
}
pub fn dfs<'a>(
&'a self,
start_id: &'a I,
direction: Direction,
) -> DfsIter<'a, I, ArcAttr, ArcStore> {
DfsIter::new(&self.arc_store, start_id, direction)
}
pub fn paths<'a>(
&'a self,
start_id: &'a I,
direction: Direction,
) -> PathIter<'a, I, ArcAttr, ArcStore>
where
I: Eq + Hash,
{
PathIter::new(self.arc_store(), start_id, direction)
}
pub fn map_nodes<F, NewNodeAttr, NewNodeStore>(
self,
f: F,
) -> Result<
Network<I, NewNodeAttr, ArcAttr, NewNodeStore, ArcStore>,
IncompatableArcAndNodeStores,
>
where
F: Fn((I, NodeAttr)) -> (I, NewNodeAttr),
NewNodeStore: NodeStorage<I, NewNodeAttr> + std::iter::FromIterator<(I, NewNodeAttr)>,
NodeStore: IntoIterator<Item = (I, NodeAttr)>,
{
Network::from_parts(self.nodes.into_iter().map(f).collect(), self.arc_store)
}
pub fn map_arcs<F, NewArcAttr, NewArcStore>(
self,
f: F,
) -> Result<
Network<I, NodeAttr, NewArcAttr, NodeStore, NewArcStore>,
IncompatableArcAndNodeStores,
>
where
F: Fn(ArcInfo<I, ArcAttr>) -> ArcInfo<I, NewArcAttr>,
NewArcStore: ArcStorage<I, NewArcAttr> + FromIterator<ArcInfo<I, NewArcAttr>>,
ArcStore: IntoIterator<Item = ArcInfo<I, ArcAttr>>,
{
Network::from_parts(self.nodes, self.arc_store.into_iter().map(f).collect())
}
pub fn map_nodes_and_arcs<FN, FA, NewNodeAttr, NewArcAttr, NewNodeStore, NewArcStore>(
self,
f_nodes: FN,
f_arcs: FA,
) -> Result<
Network<I, NewNodeAttr, NewArcAttr, NewNodeStore, NewArcStore>,
IncompatableArcAndNodeStores,
>
where
FN: Fn((I, NodeAttr)) -> (I, NewNodeAttr),
FA: Fn(ArcInfo<I, ArcAttr>) -> ArcInfo<I, NewArcAttr>,
NodeStore: IntoIterator<Item = (I, NodeAttr)>,
ArcStore: IntoIterator<Item = ArcInfo<I, ArcAttr>>,
NewNodeStore: NodeStorage<I, NewNodeAttr> + std::iter::FromIterator<(I, NewNodeAttr)>,
NewArcStore: ArcStorage<I, NewArcAttr> + FromIterator<ArcInfo<I, NewArcAttr>>,
{
Network::from_parts(
self.nodes.into_iter().map(f_nodes).collect(),
self.arc_store.into_iter().map(f_arcs).collect(),
)
}
pub fn topological_sorting(&self) -> Result<Vec<I>, Cyclic>
where
I: Eq + Hash + Clone,
{
topological_sorting(self)
}
pub fn generations(&self) -> Result<Vec<HashSet<&I>>, Cyclic>
where
I: Eq + Hash,
{
let mut generations: Vec<HashSet<&I>> = vec![];
let mut indegrees = HashMap::new();
let mut zero_indegrees = HashSet::new();
self.nodes.iter_nodes().for_each(|(i, _attributes)| {
let indegree = self.reverse_arcs(i).count();
if indegree > 0 {
indegrees.insert(i, indegree);
} else {
zero_indegrees.insert(i);
}
});
while !zero_indegrees.is_empty() {
let this_generation = std::mem::take(&mut zero_indegrees);
for node in &this_generation {
self.forward_arcs(node).for_each(|arc| {
let child_indegrees = indegrees
.get_mut(arc.head())
.expect("Child should be in the indegrees map");
*child_indegrees -= 1;
if *child_indegrees == 0 {
zero_indegrees.insert(arc.head());
indegrees.remove(arc.head());
}
});
}
generations.push(this_generation);
}
if indegrees.is_empty() {
Ok(generations)
} else {
Err(Cyclic)
}
}
pub fn m_arcs(&self) -> usize {
self.arc_store.m_arcs()
}
pub fn add_arc<T: Into<ArcInfo<I, ArcAttr>>>(&mut self, arc: T) -> Result<(), NoSuchNode<I>> {
let arc = arc.into();
if !self.contains_node(&arc.tail) {
Err(NoSuchNode(arc.tail))
} else if !self.contains_node(&arc.head) {
Err(NoSuchNode(arc.head))
} else {
self.arc_store.add_arc(arc);
Ok(())
}
}
pub fn add_arcs<T: Into<ArcInfo<I, ArcAttr>>, It: IntoIterator<Item = T>>(
&mut self,
iter: It,
) -> Result<(), NoSuchNode<I>> {
for arc in iter {
self.add_arc(arc)?;
}
Ok(())
}
pub fn remove_arc(&mut self, tail: &I, head: &I) -> Option<ArcAttr> {
self.arc_store.remove_arc(tail, head)
}
pub fn remove_arcs<'a, It>(&'a mut self, iter: It)
where
It: IntoIterator<Item = (&'a I, &'a I)>,
{
for (tail, head) in iter {
self.arc_store.remove_arc(tail, head);
}
}
pub fn arc_iter<'a>(&'a self) -> impl Iterator<Item = &'a ArcInfo<I, ArcAttr>> + 'a
where
ArcAttr: 'a,
I: 'a,
{
self.arc_store.arc_iter()
}
pub fn arc_iter_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut ArcInfo<I, ArcAttr>> + 'a
where
ArcAttr: 'a,
I: 'a,
{
self.arc_store.arc_iter_mut()
}
pub fn forward_arcs<'a>(
&'a self,
node: &'a I,
) -> impl Iterator<Item = &'a ArcInfo<I, ArcAttr>> + 'a
where
ArcAttr: 'a,
I: 'a,
{
self.arc_store.forward_arcs(node)
}
pub fn reverse_arcs<'a>(
&'a self,
node: &'a I,
) -> impl Iterator<Item = &'a ArcInfo<I, ArcAttr>> + 'a
where
ArcAttr: 'a,
I: 'a,
{
self.arc_store.reverse_arcs(node)
}
pub fn arc(&self, tail: &I, head: &I) -> Option<&ArcAttr> {
self.arc_store.arc(tail, head)
}
pub fn arc_mut(&mut self, tail: &I, head: &I) -> Option<&mut ArcAttr> {
self.arc_store.arc_mut(tail, head)
}
pub fn contains_arc(&self, tail: &I, head: &I) -> bool {
self.arc_store.contains_arc(tail, head)
}
pub fn has_forward_arcs(&self, node: &I) -> bool {
self.arc_store.has_forward_arcs(node)
}
pub fn has_reverse_arcs(&self, node: &I) -> bool {
self.arc_store.has_reverse_arcs(node)
}
pub fn dot<'a>(&'a self) -> DotWriter<'a, I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
I: Display,
{
DotWriter::new(self)
}
}
pub struct DotWriter<'a, I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NodeAttr>,
ArcStore: ArcStorage<I, ArcAttr>,
I: Display,
{
network: &'a Network<I, NodeAttr, ArcAttr, NodeStore, ArcStore>,
node_attr_label: Box<dyn Fn(&NodeAttr) -> Option<String> + 'a>,
arc_attr_label: Box<dyn Fn(&ArcAttr) -> Option<String> + 'a>,
}
impl<'a, I, NodeAttr, ArcAttr, NodeStore, ArcStore>
DotWriter<'a, I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
NodeStore: NodeStorage<I, NodeAttr>,
ArcStore: ArcStorage<I, ArcAttr>,
I: Display,
{
fn new(network: &'a Network<I, NodeAttr, ArcAttr, NodeStore, ArcStore>) -> Self {
Self {
network,
node_attr_label: Box::new(|_| None),
arc_attr_label: Box::new(|_| None),
}
}
pub fn with_node_attr_display<F: Fn(&NodeAttr) -> Option<String> + 'a>(
self,
node_attr_label: F,
) -> Self {
Self {
node_attr_label: Box::new(node_attr_label),
..self
}
}
pub fn with_arc_attr_display<F: Fn(&ArcAttr) -> Option<String> + 'a>(
self,
arc_attr_label: F,
) -> Self {
Self {
arc_attr_label: Box::new(arc_attr_label),
..self
}
}
pub fn to_string(self) -> Result<String, std::fmt::Error> {
let mut out = String::new();
writeln!(&mut out, "digraph G {{")?;
writeln!(&mut out, " // Nodes")?;
for (node_id, node_attr) in self.network.iter_nodes() {
write!(&mut out, " {node_id}")?;
if let Some(label) = (self.node_attr_label)(node_attr) {
writeln!(&mut out, " [label=\"{node_id}\\n({label})\"];")?;
} else {
writeln!(&mut out, ";")?;
}
}
out.push_str("\n // Arcs\n");
for arc in self.network.arc_iter() {
write!(&mut out, " {} -> {}", arc.tail, arc.head,)?;
if let Some(label) = (self.arc_attr_label)(&arc.attributes) {
writeln!(&mut out, "[label=\"{label}\"];")?;
} else {
writeln!(&mut out, ";")?;
}
}
writeln!(&mut out, "}}")?;
Ok(out)
}
}
#[derive(Debug)]
pub struct NoSuchNode<I>(I);
impl<I> Display for NoSuchNode<I>
where
I: Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "No node corresponds to id '{0}'", self.0)
}
}
impl<I: Display + std::fmt::Debug> std::error::Error for NoSuchNode<I> {}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
pub struct IncompatableArcAndNodeStores;
impl Display for IncompatableArcAndNodeStores {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"The provided ArcStore constains nodes not in provided NodeStore"
)
}
}
impl std::error::Error for IncompatableArcAndNodeStores {}
pub struct NodeAccessor<'a, I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
ArcStore: ArcStorage<I, ArcAttr>,
NodeStore: NodeStorage<I, NodeAttr>,
{
network: &'a Network<I, NodeAttr, ArcAttr, NodeStore, ArcStore>,
}
impl<I, NodeAttr, ArcAttr, NodeStore, ArcStore> Index<&I>
for NodeAccessor<'_, I, NodeAttr, ArcAttr, NodeStore, ArcStore>
where
ArcStore: ArcStorage<I, ArcAttr>,
NodeStore: NodeStorage<I, NodeAttr>,
{
type Output = NodeAttr;
fn index(&self, index: &I) -> &Self::Output {
if let Some(value) = self.network.nodes.node(index) {
return value;
}
panic!("Index does not correspond to a node.");
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::{HashMapStorage, Network};
#[test]
fn into_and_from_parts() {
let mut net: Network<usize, char, &str, HashMap<usize, char>, HashMapStorage<usize, &str>> =
Network::new();
net.add_nodes([(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f')]);
net.add_arcs([
(0, 1, "ab"),
(1, 2, "bc"),
(2, 3, "cd"),
(3, 4, "de"),
(4, 5, "ef"),
])
.unwrap();
let expected_network = net.clone();
let (nodes, arcs): (HashMap<usize, char>, _) = net.into_parts();
let mut expected_nodes = HashMap::new();
expected_nodes.insert(0, 'a');
expected_nodes.insert(1, 'b');
expected_nodes.insert(2, 'c');
expected_nodes.insert(3, 'd');
expected_nodes.insert(4, 'e');
expected_nodes.insert(5, 'f');
assert_eq!(nodes, expected_nodes);
let arcs: HashMap<(usize, usize), &str> = arcs.into();
let expected_arcs = HashMap::from([
((0, 1), "ab"),
((1, 2), "bc"),
((2, 3), "cd"),
((3, 4), "de"),
((4, 5), "ef"),
]);
assert_eq!(arcs, expected_arcs);
let reconstructed_net = Network::from_parts(nodes, arcs.into()).unwrap();
assert_eq!(reconstructed_net, expected_network);
}
}