graph_core/adjacency_list.rs
1use crate::{Graph, GraphError, NodeId};
2use std::slice;
3
4/// A sparse graph representation backed by an adjacency list.
5///
6/// Node data is stored in a `Vec<N>`. Outgoing edges for each node are stored
7/// in a `Vec<Vec<(NodeId, W)>>` indexed by node position.
8///
9/// # Directed vs Undirected
10///
11/// Use [`AdjacencyList::directed`] or [`AdjacencyList::undirected`].
12/// For an undirected graph, `add_edge(u, v, w)` inserts both `v` into `u`'s
13/// list and `u` into `v`'s list automatically.
14///
15/// # Complexity
16///
17/// | Operation | Time |
18/// |-----------------|-----------------|
19/// | `add_node` | O(1) amortised |
20/// | `add_edge` | O(1) amortised |
21/// | `neighbors` | O(out-degree) |
22/// | `contains_edge` | O(out-degree) |
23/// | `node_count` | O(1) |
24/// | `edge_count` | O(1) |
25///
26/// # Examples
27///
28/// ```
29/// use graph_core::{AdjacencyList, Graph};
30///
31/// let mut g: AdjacencyList<&str, f64> = AdjacencyList::directed();
32/// let a = g.add_node("A");
33/// let b = g.add_node("B");
34/// let c = g.add_node("C");
35///
36/// g.add_edge(a, b, 1.0).unwrap();
37/// g.add_edge(b, c, 2.0).unwrap();
38///
39/// assert_eq!(g.node_count(), 3);
40/// assert_eq!(g.edge_count(), 2);
41/// assert!(g.contains_edge(a, b));
42/// assert!(!g.contains_edge(c, a));
43/// ```
44#[derive(Debug, Clone)]
45pub struct AdjacencyList<N, W = f64> {
46 nodes: Vec<N>,
47 /// `adjacency[i]` holds `(target, weight)` for every outgoing edge from node `i`.
48 adjacency: Vec<Vec<(NodeId, W)>>,
49 edge_count: usize,
50 directed: bool,
51}
52
53// ── Construction ──────────────────────────────────────────────────────────────
54
55impl<N, W> AdjacencyList<N, W> {
56 /// Creates an empty **directed** adjacency-list graph.
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// use graph_core::{AdjacencyList, Graph};
62 ///
63 /// let g: AdjacencyList<()> = AdjacencyList::directed();
64 /// assert!(g.is_empty());
65 /// ```
66 pub fn directed() -> Self {
67 Self {
68 nodes: Vec::new(),
69 adjacency: Vec::new(),
70 edge_count: 0,
71 directed: true,
72 }
73 }
74
75 /// Creates an empty **undirected** adjacency-list graph.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use graph_core::{AdjacencyList, Graph};
81 ///
82 /// let mut g: AdjacencyList<()> = AdjacencyList::undirected();
83 /// let u = g.add_node(());
84 /// let v = g.add_node(());
85 /// g.add_edge(u, v, 1.0).unwrap();
86 ///
87 /// // Both directions exist.
88 /// assert!(g.contains_edge(u, v));
89 /// assert!(g.contains_edge(v, u));
90 /// ```
91 pub fn undirected() -> Self {
92 Self {
93 nodes: Vec::new(),
94 adjacency: Vec::new(),
95 edge_count: 0,
96 directed: false,
97 }
98 }
99
100 /// Returns `true` if this graph is directed.
101 ///
102 /// # Examples
103 ///
104 /// ```
105 /// use graph_core::AdjacencyList;
106 ///
107 /// let d: AdjacencyList<()> = AdjacencyList::directed();
108 /// assert!(d.is_directed());
109 ///
110 /// let u: AdjacencyList<()> = AdjacencyList::undirected();
111 /// assert!(!u.is_directed());
112 /// ```
113 #[must_use]
114 #[inline]
115 pub fn is_directed(&self) -> bool {
116 self.directed
117 }
118}
119
120// ── Node data access ──────────────────────────────────────────────────────────
121
122impl<N, W> AdjacencyList<N, W> {
123 /// Returns a reference to the data stored at `id`, or `None` if not found.
124 ///
125 /// # Examples
126 ///
127 /// ```
128 /// use graph_core::{AdjacencyList, Graph};
129 ///
130 /// let mut g: AdjacencyList<&str> = AdjacencyList::directed();
131 /// let id = g.add_node("hello");
132 /// assert_eq!(g.node_data(id), Some(&"hello"));
133 /// ```
134 #[must_use]
135 #[inline]
136 pub fn node_data(&self, id: NodeId) -> Option<&N> {
137 self.nodes.get(id.index())
138 }
139
140 /// Returns a mutable reference to the data stored at `id`, or `None` if not found.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// use graph_core::{AdjacencyList, Graph};
146 ///
147 /// let mut g: AdjacencyList<u32> = AdjacencyList::directed();
148 /// let id = g.add_node(1);
149 /// *g.node_data_mut(id).unwrap() = 42;
150 /// assert_eq!(g.node_data(id), Some(&42));
151 /// ```
152 #[inline]
153 pub fn node_data_mut(&mut self, id: NodeId) -> Option<&mut N> {
154 self.nodes.get_mut(id.index())
155 }
156}
157
158// ── Graph trait ───────────────────────────────────────────────────────────────
159
160/// Iterator over all `NodeId`s in an [`AdjacencyList`].
161pub struct NodeIter {
162 current: usize,
163 total: usize,
164}
165
166impl Iterator for NodeIter {
167 type Item = NodeId;
168
169 #[inline]
170 fn next(&mut self) -> Option<NodeId> {
171 if self.current < self.total {
172 let id = NodeId::new(self.current);
173 self.current += 1;
174 Some(id)
175 } else {
176 None
177 }
178 }
179
180 #[inline]
181 fn size_hint(&self) -> (usize, Option<usize>) {
182 let remaining = self.total - self.current;
183 (remaining, Some(remaining))
184 }
185}
186
187impl ExactSizeIterator for NodeIter {}
188
189/// Iterator over `(NodeId, &W)` neighbour pairs for one node.
190pub struct NeighborIter<'a, W> {
191 inner: slice::Iter<'a, (NodeId, W)>,
192}
193
194impl<'a, W> Iterator for NeighborIter<'a, W> {
195 type Item = (NodeId, &'a W);
196
197 #[inline]
198 fn next(&mut self) -> Option<(NodeId, &'a W)> {
199 self.inner.next().map(|(id, w)| (*id, w))
200 }
201
202 #[inline]
203 fn size_hint(&self) -> (usize, Option<usize>) {
204 self.inner.size_hint()
205 }
206}
207
208impl<N, W: Clone> Graph for AdjacencyList<N, W> {
209 type NodeData = N;
210 type Weight = W;
211 type NodeIter<'a>
212 = NodeIter
213 where
214 Self: 'a;
215 type NeighborIter<'a>
216 = NeighborIter<'a, W>
217 where
218 Self: 'a;
219
220 fn add_node(&mut self, data: N) -> NodeId {
221 let id = NodeId::new(self.nodes.len());
222 self.nodes.push(data);
223 self.adjacency.push(Vec::new());
224 id
225 }
226
227 fn add_edge(&mut self, from: NodeId, to: NodeId, weight: W) -> Result<(), GraphError> {
228 if !self.contains_node(from) {
229 return Err(GraphError::NodeNotFound(from));
230 }
231 if !self.contains_node(to) {
232 return Err(GraphError::NodeNotFound(to));
233 }
234 self.adjacency[from.index()].push((to, weight.clone()));
235 if !self.directed && from != to {
236 self.adjacency[to.index()].push((from, weight));
237 }
238 self.edge_count += 1;
239 Ok(())
240 }
241
242 fn remove_node(&mut self, id: NodeId) -> Option<N> {
243 let idx = id.index();
244 if idx >= self.nodes.len() {
245 return None;
246 }
247 // Remove edges pointing to this node from all adjacency lists.
248 for adj in &mut self.adjacency {
249 adj.retain(|(target, _)| *target != id);
250 }
251 // Count outgoing edges being removed.
252 let outgoing = self.adjacency[idx].len();
253 self.edge_count = self.edge_count.saturating_sub(outgoing);
254
255 // We mark the slot as invalid by leaving a hole (swap-remove would
256 // renumber later nodes, breaking existing NodeIds). Instead we remove
257 // by replacing with a sentinel. For simplicity in this educational
258 // implementation we use swap_remove and accept that NodeIds beyond
259 // the removed index are invalidated — callers should rebuild if needed.
260 self.adjacency.remove(idx);
261 Some(self.nodes.remove(idx))
262 }
263
264 #[inline]
265 fn node_count(&self) -> usize {
266 self.nodes.len()
267 }
268
269 #[inline]
270 fn edge_count(&self) -> usize {
271 self.edge_count
272 }
273
274 #[inline]
275 fn contains_node(&self, id: NodeId) -> bool {
276 id.index() < self.nodes.len()
277 }
278
279 fn contains_edge(&self, from: NodeId, to: NodeId) -> bool {
280 if !self.contains_node(from) {
281 return false;
282 }
283 self.adjacency[from.index()]
284 .iter()
285 .any(|(target, _)| *target == to)
286 }
287
288 fn degree(&self, id: NodeId) -> usize {
289 self.adjacency[id.index()].len()
290 }
291
292 fn nodes(&self) -> NodeIter {
293 NodeIter {
294 current: 0,
295 total: self.nodes.len(),
296 }
297 }
298
299 fn neighbors(&self, id: NodeId) -> NeighborIter<'_, W> {
300 NeighborIter {
301 inner: self.adjacency[id.index()].iter(),
302 }
303 }
304}