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}