use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CsrIndex {
offsets: Vec<u64>,
neighbors: Vec<u64>,
}
impl CsrIndex {
pub fn neighbors(&self, vertex_id: u64) -> &[u64] {
let v = vertex_id as usize;
if v >= self.offsets.len() - 1 {
return &[];
}
let start = self.offsets[v] as usize;
let end = self.offsets[v + 1] as usize;
&self.neighbors[start..end]
}
pub fn num_edges(&self) -> u64 {
self.neighbors.len() as u64
}
}
#[derive(Debug)]
pub struct CsrIndexBuilder {
edges: Vec<(u64, u64)>,
num_vertices: Option<u64>,
}
impl CsrIndexBuilder {
pub fn new() -> Self {
Self {
edges: Vec::new(),
num_vertices: None,
}
}
pub fn with_num_vertices(mut self, n: u64) -> Self {
self.num_vertices = Some(n);
self
}
pub fn add_edge(mut self, src: u64, dst: u64) -> Self {
self.edges.push((src, dst));
self
}
pub fn build(mut self) -> CsrIndex {
let num_vertices = self.num_vertices.unwrap_or_else(|| {
self.edges
.iter()
.flat_map(|&(s, d)| [s, d])
.max()
.map(|m| m + 1)
.unwrap_or(0)
});
self.edges.sort_unstable_by_key(|&(src, _)| src);
let mut offsets = vec![0u64; num_vertices as usize + 1];
let mut neighbors = Vec::with_capacity(self.edges.len());
let mut degree_map: HashMap<u64, u64> = HashMap::new();
for &(src, _) in &self.edges {
*degree_map.entry(src).or_insert(0) += 1;
}
let mut running = 0u64;
for v in 0..num_vertices {
offsets[v as usize] = running;
running += degree_map.get(&v).copied().unwrap_or(0);
}
offsets[num_vertices as usize] = running;
for &(_, dst) in &self.edges {
neighbors.push(dst);
}
CsrIndex { offsets, neighbors }
}
}
impl Default for CsrIndexBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_index() -> CsrIndex {
CsrIndexBuilder::new()
.with_num_vertices(4)
.add_edge(0, 1)
.add_edge(0, 2)
.add_edge(0, 3)
.add_edge(1, 2)
.add_edge(2, 3)
.build()
}
#[test]
fn test_basic_neighbor_lookup() {
let idx = sample_index();
assert_eq!(idx.neighbors(0), &[1, 2, 3]);
assert_eq!(idx.neighbors(1), &[2]);
assert_eq!(idx.neighbors(2), &[3]);
assert_eq!(idx.neighbors(3), &[] as &[u64]);
}
#[test]
fn test_metadata() {
let idx = sample_index();
assert_eq!(idx.num_edges(), 5);
}
#[test]
fn test_out_of_range_vertex() {
let idx = sample_index();
assert_eq!(idx.neighbors(99), &[] as &[u64]);
}
#[test]
fn test_empty_graph() {
let idx = CsrIndexBuilder::new().with_num_vertices(0).build();
assert_eq!(idx.num_edges(), 0);
assert_eq!(idx.neighbors(0), &[] as &[u64]);
}
#[test]
fn test_isolated_vertices() {
let idx = CsrIndexBuilder::new()
.with_num_vertices(5)
.add_edge(1, 3)
.build();
assert_eq!(idx.neighbors(0), &[] as &[u64]);
assert_eq!(idx.neighbors(1), &[3]);
assert_eq!(idx.neighbors(2), &[] as &[u64]);
assert_eq!(idx.neighbors(3), &[] as &[u64]);
assert_eq!(idx.neighbors(4), &[] as &[u64]);
}
#[test]
fn test_auto_inferred_num_vertices() {
let idx = CsrIndexBuilder::new().add_edge(0, 5).add_edge(3, 7).build();
assert_eq!(idx.neighbors(7), &[] as &[u64]);
assert_eq!(idx.neighbors(0), &[5]);
assert_eq!(idx.neighbors(3), &[7]);
}
#[test]
fn test_self_loops() {
let idx = CsrIndexBuilder::new()
.with_num_vertices(3)
.add_edge(0, 0)
.add_edge(1, 1)
.add_edge(0, 1)
.build();
assert_eq!(idx.neighbors(0), &[0, 1]);
assert_eq!(idx.neighbors(1), &[1]);
}
#[test]
fn test_parallel_edges() {
let idx = CsrIndexBuilder::new()
.with_num_vertices(2)
.add_edge(0, 1)
.add_edge(0, 1)
.add_edge(0, 1)
.build();
assert_eq!(idx.neighbors(0), &[1, 1, 1]);
}
}