use crate::core::view::{EdgeId, GraphView, Link, NodeId};
#[derive(Debug, Clone)]
pub struct CsrView<W> {
nodes: Vec<NodeId>,
offsets: Vec<usize>,
links: Vec<Link<W>>,
edge_endpoints: Vec<Option<(NodeId, NodeId)>>,
row_of_slot: Vec<Option<usize>>,
directed: bool,
edge_count: usize,
}
impl<W: Copy> CsrView<W> {
pub fn from_view<G>(source: &G) -> Self
where
G: GraphView<Weight = W> + ?Sized,
{
let nodes: Vec<NodeId> = source.node_ids().collect();
let highest_slot = nodes.iter().map(|id| id.index()).max();
let mut row_of_slot: Vec<Option<usize>> =
vec![None; highest_slot.map_or(0, |slot| slot + 1)];
for (row, id) in nodes.iter().enumerate() {
row_of_slot[id.index()] = Some(row);
}
let admits = |candidate: NodeId| {
row_of_slot
.get(candidate.index())
.copied()
.flatten()
.is_some_and(|row| nodes[row] == candidate)
};
let mut offsets = Vec::with_capacity(nodes.len() + 1);
let mut links = Vec::new();
let mut highest_edge = 0usize;
for &node in &nodes {
offsets.push(links.len());
for link in source.links(node) {
highest_edge = highest_edge.max(link.edge.index());
if admits(link.target) {
links.push(link);
}
}
}
offsets.push(links.len());
let mut edge_endpoints = vec![
None;
if links.is_empty() {
0
} else {
highest_edge + 1
}
];
for link in &links {
if let Some(slot) = edge_endpoints.get_mut(link.edge.index()) {
*slot = source.edge_endpoints(link.edge);
}
}
Self {
nodes,
offsets,
links,
edge_endpoints,
row_of_slot,
directed: source.is_directed(),
edge_count: source.edge_count(),
}
}
fn row_of(&self, node: NodeId) -> Option<usize> {
let row = self.row_of_slot.get(node.index()).copied().flatten()?;
(self.nodes[row] == node).then_some(row)
}
}
pub struct CsrNodeIds<'a> {
inner: std::slice::Iter<'a, NodeId>,
}
impl<'a> Iterator for CsrNodeIds<'a> {
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().copied()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
pub struct CsrLinks<'a, W> {
inner: std::slice::Iter<'a, Link<W>>,
}
impl<'a, W: Copy> Iterator for CsrLinks<'a, W> {
type Item = Link<W>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().copied()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<W: Copy> GraphView for CsrView<W> {
type Weight = W;
type NodeIds<'a>
= CsrNodeIds<'a>
where
Self: 'a;
type Links<'a>
= CsrLinks<'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.edge_count
}
fn contains_node(&self, node: NodeId) -> bool {
self.row_of(node).is_some()
}
fn node_ids(&self) -> Self::NodeIds<'_> {
CsrNodeIds {
inner: self.nodes.iter(),
}
}
fn links(&self, node: NodeId) -> Self::Links<'_> {
let range = match self.row_of(node) {
Some(row) => self.offsets[row]..self.offsets[row + 1],
None => 0..0,
};
CsrLinks {
inner: self.links[range].iter(),
}
}
fn edge_endpoints(&self, edge: EdgeId) -> Option<(NodeId, NodeId)> {
self.edge_endpoints.get(edge.index()).copied().flatten()
}
fn degree(&self, node: NodeId) -> usize {
match self.row_of(node) {
Some(row) => self.offsets[row + 1] - self.offsets[row],
None => 0,
}
}
}
#[cfg(test)]
#[cfg(feature = "graph")]
mod tests {
use super::*;
use crate::core::{Bridges, Connectivity, EdgeFiltered, ShortestPath, Traversal};
use crate::graph::Graph;
fn sample() -> (Graph<u32, u32, &'static str>, Vec<NodeId>) {
let mut graph = Graph::<u32, u32, &str>::new(false);
let nodes: Vec<_> = (0..6).map(|i| graph.add_node(i)).collect();
for &(a, b, w) in &[(0, 1, 1), (1, 2, 2), (2, 0, 3), (2, 3, 4), (3, 4, 5)] {
graph.add_edge(nodes[a], nodes[b], w, "e").unwrap();
}
graph.add_node(99);
(graph, nodes)
}
#[test]
fn test_the_projection_describes_the_same_graph() {
let (graph, nodes) = sample();
let frozen = CsrView::from_view(&graph);
assert_eq!(frozen.node_count(), graph.node_count());
assert_eq!(frozen.edge_count(), graph.edge_count());
assert_eq!(frozen.is_directed(), graph.is_directed());
assert_eq!(frozen.node_id_vec(), graph.node_id_vec());
for &node in &nodes {
assert_eq!(frozen.degree(node), graph.degree(node));
let mut from_csr: Vec<_> = frozen.links(node).map(|l| (l.target, l.edge)).collect();
let mut from_graph: Vec<_> = graph.links(node).map(|l| (l.target, l.edge)).collect();
from_csr.sort_unstable();
from_graph.sort_unstable();
assert_eq!(from_csr, from_graph);
}
}
#[test]
fn test_algorithms_agree_across_the_two_layouts() {
let (graph, nodes) = sample();
let frozen = CsrView::from_view(&graph);
assert_eq!(
frozen.dijkstra(nodes[0]).unwrap(),
graph.dijkstra(nodes[0]).unwrap()
);
assert_eq!(frozen.bridges().unwrap(), graph.bridges().unwrap());
assert_eq!(
frozen.weakly_connected_components().unwrap(),
graph.weakly_connected_components().unwrap()
);
assert_eq!(frozen.has_cycle().unwrap(), graph.has_cycle().unwrap());
assert_eq!(
frozen.reachable_from(nodes[0]).unwrap(),
graph.reachable_from(nodes[0]).unwrap()
);
}
#[test]
fn test_an_unknown_identifier_has_no_links() {
let (graph, _) = sample();
let frozen = CsrView::from_view(&graph);
let absent = NodeId::from_index(999);
assert!(!frozen.contains_node(absent));
assert_eq!(frozen.links(absent).count(), 0);
assert_eq!(frozen.degree(absent), 0);
}
#[test]
fn test_sparse_identifiers_are_preserved() {
let mut graph = Graph::<u32, u32, ()>::new(false);
let a = graph.add_node(0);
let gap = graph.add_node(1);
let b = graph.add_node(2);
graph.remove_node(gap).unwrap();
graph.add_edge(a, b, 7, ()).unwrap();
let frozen = CsrView::from_view(&graph);
assert_eq!(frozen.node_id_vec(), vec![a, b]);
assert!(!frozen.contains_node(gap));
assert_eq!(frozen.dijkstra(a).unwrap()[&b], 7);
}
#[test]
fn test_a_filtered_view_can_be_frozen() {
let (graph, nodes) = sample();
let cheap = EdgeFiltered::new(&graph, |edge| edge.index() < 2);
let frozen = CsrView::from_view(&cheap);
assert_eq!(frozen.node_count(), graph.node_count());
assert_eq!(frozen.links(nodes[3]).count(), 0);
assert_eq!(frozen.degree(nodes[1]), 2);
}
#[test]
fn test_an_empty_graph_projects_cleanly() {
let graph = Graph::<u32, u32, ()>::new(true);
let frozen = CsrView::from_view(&graph);
assert_eq!(frozen.node_count(), 0);
assert_eq!(frozen.edge_count(), 0);
assert!(frozen.node_id_vec().is_empty());
}
}