#[derive(Debug, Clone)]
pub struct CsrAdjacency {
offsets: Vec<u32>,
targets: Vec<u32>,
edge_data: Option<Vec<u32>>,
}
impl CsrAdjacency {
#[must_use]
pub fn from_sorted_edges(num_nodes: usize, edges: &[(u32, u32)]) -> Self {
assert!(
edges.windows(2).all(|w| w[0].0 <= w[1].0),
"edges must be sorted by source"
);
let mut offsets = vec![0u32; num_nodes + 1];
for &(src, _) in edges {
offsets[src as usize + 1] += 1;
}
for i in 1..offsets.len() {
offsets[i] += offsets[i - 1];
}
let targets: Vec<u32> = edges.iter().map(|&(_, dst)| dst).collect();
Self {
offsets,
targets,
edge_data: None,
}
}
pub fn set_edge_data(&mut self, data: Vec<u32>) {
assert_eq!(
data.len(),
self.targets.len(),
"edge_data length must equal targets length"
);
self.edge_data = Some(data);
}
#[must_use]
pub fn has_edge_data(&self) -> bool {
self.edge_data.is_some()
}
#[must_use]
pub fn edge_data_at(&self, position: usize) -> Option<u32> {
self.edge_data.as_ref()?.get(position).copied()
}
#[must_use]
pub fn num_nodes(&self) -> usize {
self.offsets.len().saturating_sub(1)
}
#[must_use]
pub fn num_edges(&self) -> usize {
self.targets.len()
}
#[inline]
#[must_use]
pub fn neighbors(&self, node_offset: u32) -> &[u32] {
let i = node_offset as usize;
if i + 1 >= self.offsets.len() {
return &[];
}
let start = self.offsets[i] as usize;
let end = self.offsets[i + 1] as usize;
&self.targets[start..end]
}
#[inline]
#[must_use]
pub fn degree(&self, node_offset: u32) -> usize {
self.neighbors(node_offset).len()
}
#[must_use]
pub fn source_for_position(&self, position: u32) -> Option<u32> {
if position as usize >= self.targets.len() {
return None;
}
let num_nodes = self.num_nodes();
let mut lo = 0usize;
let mut hi = num_nodes;
while lo < hi {
let mid = lo + (hi - lo) / 2;
if self.offsets[mid + 1] <= position {
lo = mid + 1;
} else {
hi = mid;
}
}
Some(lo as u32)
}
#[inline]
#[must_use]
pub fn offset_of(&self, node_offset: u32) -> u32 {
let i = node_offset as usize;
if i >= self.offsets.len() {
return 0;
}
self.offsets[i]
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.offsets.len() * std::mem::size_of::<u32>()
+ self.targets.len() * std::mem::size_of::<u32>()
+ self
.edge_data
.as_ref()
.map_or(0, |d| d.len() * std::mem::size_of::<u32>())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_csr() {
let edges = vec![(0u32, 1u32), (0, 2), (1, 2)];
let csr = CsrAdjacency::from_sorted_edges(3, &edges);
assert_eq!(csr.num_nodes(), 3);
assert_eq!(csr.num_edges(), 3);
assert_eq!(csr.neighbors(0), &[1, 2]);
assert_eq!(csr.degree(0), 2);
assert_eq!(csr.neighbors(1), &[2]);
assert_eq!(csr.degree(1), 1);
assert_eq!(csr.neighbors(2), &[] as &[u32]);
assert_eq!(csr.degree(2), 0);
}
#[test]
fn test_source_for_position() {
let edges = vec![(0u32, 1u32), (0, 2), (1, 2)];
let csr = CsrAdjacency::from_sorted_edges(3, &edges);
assert_eq!(csr.source_for_position(0), Some(0));
assert_eq!(csr.source_for_position(1), Some(0));
assert_eq!(csr.source_for_position(2), Some(1));
assert_eq!(csr.source_for_position(3), None);
assert_eq!(csr.source_for_position(100), None);
}
#[test]
fn test_empty_graph() {
let csr = CsrAdjacency::from_sorted_edges(0, &[]);
assert_eq!(csr.num_nodes(), 0);
assert_eq!(csr.num_edges(), 0);
assert_eq!(csr.source_for_position(0), None);
assert_eq!(csr.memory_bytes(), 4); }
}