1use crate::ordinal::{Ordinal, to_usize};
9
10#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
12pub enum CsrError {
13 #[error("ordinal {ordinal} is out of range for {nodes} nodes")]
15 OutOfRange {
16 ordinal: Ordinal,
18 nodes: u32,
20 },
21 #[error("offsets are inconsistent with {targets} targets")]
23 Offsets {
24 targets: usize,
26 },
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Csr {
32 offsets: Vec<u32>,
33 targets: Vec<u32>,
34}
35
36impl Csr {
37 pub fn build(
46 nodes: u32,
47 edges: impl IntoIterator<Item = (Ordinal, Ordinal)>,
48 ) -> Result<Self, CsrError> {
49 let mut pairs: Vec<(u32, u32)> = Vec::new();
50 for (from, to) in edges {
51 for ordinal in [from, to] {
52 if ordinal.index() >= nodes {
53 return Err(CsrError::OutOfRange { ordinal, nodes });
54 }
55 }
56 pairs.push((from.index(), to.index()));
57 }
58 pairs.sort_unstable();
59 pairs.dedup();
60 let count = |targets: &Vec<u32>| {
61 u32::try_from(targets.len()).map_err(|_| CsrError::Offsets {
62 targets: targets.len(),
63 })
64 };
65 let mut offsets = Vec::with_capacity(to_usize(nodes).saturating_add(1));
66 let mut targets = Vec::with_capacity(pairs.len());
67 let mut cursor = 0_usize;
68 for node in 0..nodes {
69 offsets.push(count(&targets)?);
70 while let Some((from, to)) = pairs.get(cursor).copied() {
71 if from != node {
72 break;
73 }
74 targets.push(to);
75 cursor = cursor.saturating_add(1);
76 }
77 }
78 offsets.push(count(&targets)?);
79 Ok(Self { offsets, targets })
80 }
81
82 pub fn from_parts(offsets: Vec<u32>, targets: Vec<u32>) -> Result<Self, CsrError> {
89 let consistent = offsets.first() == Some(&0)
90 && offsets.windows(2).all(|w| w.first() <= w.get(1))
91 && offsets.last().copied().map(to_usize) == Some(targets.len());
92 if !consistent {
93 return Err(CsrError::Offsets {
94 targets: targets.len(),
95 });
96 }
97 Ok(Self { offsets, targets })
98 }
99
100 #[must_use]
102 pub fn nodes(&self) -> u32 {
103 u32::try_from(self.offsets.len().saturating_sub(1)).unwrap_or(u32::MAX)
104 }
105
106 #[must_use]
108 pub fn edges(&self) -> usize {
109 self.targets.len()
110 }
111
112 #[must_use]
114 pub fn neighbours(&self, node: Ordinal) -> &[u32] {
115 let start = self.offsets.get(node.as_usize()).copied();
116 let end = self.offsets.get(node.as_usize().saturating_add(1)).copied();
117 match (start, end) {
118 (Some(start), Some(end)) => self
119 .targets
120 .get(to_usize(start)..to_usize(end))
121 .unwrap_or(&[]),
122 _ => &[],
123 }
124 }
125
126 #[must_use]
128 pub fn offsets(&self) -> &[u32] {
129 &self.offsets
130 }
131
132 #[must_use]
134 pub fn targets(&self) -> &[u32] {
135 &self.targets
136 }
137
138 pub fn transpose(&self) -> Result<Self, CsrError> {
144 let nodes = self.nodes();
145 let reversed = (0..nodes).flat_map(|from| {
146 self.neighbours(Ordinal::new(from))
147 .iter()
148 .map(move |to| (Ordinal::new(*to), Ordinal::new(from)))
149 });
150 Self::build(nodes, reversed)
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::{Csr, CsrError};
157 use crate::ordinal::Ordinal;
158
159 fn o(i: u32) -> Ordinal {
160 Ordinal::new(i)
161 }
162
163 #[test]
164 fn rows_are_sorted_and_deduplicated() {
165 let csr = Csr::build(4, [(o(2), o(1)), (o(0), o(3)), (o(0), o(1)), (o(0), o(1))])
166 .expect("builds");
167 assert_eq!(csr.nodes(), 4);
168 assert_eq!(csr.edges(), 3);
169 assert_eq!(csr.neighbours(o(0)), &[1, 3]);
170 assert_eq!(csr.neighbours(o(1)), &[] as &[u32]);
171 assert_eq!(csr.neighbours(o(2)), &[1]);
172 assert_eq!(csr.neighbours(o(9)), &[] as &[u32]);
173 assert_eq!(csr.offsets(), &[0, 2, 2, 3, 3]);
174 }
175
176 #[test]
177 fn transpose_reverses_every_edge() {
178 let csr = Csr::build(3, [(o(0), o(1)), (o(0), o(2)), (o(1), o(2))]).expect("builds");
179 let back = csr.transpose().expect("transposes");
180 assert_eq!(back.neighbours(o(2)), &[0, 1]);
181 assert_eq!(back.neighbours(o(1)), &[0]);
182 assert_eq!(back.neighbours(o(0)), &[] as &[u32]);
183 assert_eq!(back.transpose().expect("transposes"), csr);
184 }
185
186 #[test]
187 fn out_of_range_and_inconsistent_parts_are_refused() {
188 assert_eq!(
189 Csr::build(2, [(o(0), o(2))]),
190 Err(CsrError::OutOfRange {
191 ordinal: o(2),
192 nodes: 2
193 })
194 );
195 assert!(Csr::from_parts(vec![0, 2, 1], vec![1]).is_err());
196 assert!(Csr::from_parts(vec![1, 1], Vec::new()).is_err());
197 assert!(Csr::from_parts(vec![0, 1], vec![0]).is_ok());
198 }
199}