graph_core/adjacency_matrix.rs
1use crate::{Graph, GraphError, NodeId};
2
3/// A dense graph representation backed by a 2-D adjacency matrix.
4///
5/// Edge existence and weight are stored in a `Vec<Vec<Option<W>>>` matrix
6/// where `matrix[i][j]` is `Some(weight)` if there is an edge from `i` to
7/// `j`, or `None` otherwise.
8///
9/// # When to prefer this over [`AdjacencyList`](crate::AdjacencyList)
10///
11/// | Property | AdjacencyList | AdjacencyMatrix |
12/// |--------------|---------------|-----------------|
13/// | Space | O(V + E) | O(V²) |
14/// | `add_edge` | O(1) | O(1) |
15/// | `contains_edge` | O(deg) | **O(1)** |
16/// | `neighbors` | O(deg) | O(V) |
17///
18/// Use `AdjacencyMatrix` for dense graphs (many edges relative to V²) or
19/// when O(1) edge lookup is critical (e.g. Floyd-Warshall).
20///
21/// # Examples
22///
23/// ```
24/// use graph_core::{AdjacencyMatrix, Graph};
25///
26/// let mut g: AdjacencyMatrix<&str, u32> = AdjacencyMatrix::directed();
27/// let a = g.add_node("A");
28/// let b = g.add_node("B");
29/// g.add_edge(a, b, 5).unwrap();
30///
31/// assert!(g.contains_edge(a, b));
32/// assert!(!g.contains_edge(b, a));
33/// ```
34#[derive(Debug, Clone)]
35pub struct AdjacencyMatrix<N, W = f64> {
36 nodes: Vec<N>,
37 /// `matrix[i][j]` = `Some(weight)` for edge i→j, `None` for no edge.
38 matrix: Vec<Vec<Option<W>>>,
39 edge_count: usize,
40 directed: bool,
41}
42
43// ── Construction ──────────────────────────────────────────────────────────────
44
45impl<N, W> AdjacencyMatrix<N, W> {
46 /// Creates an empty **directed** adjacency-matrix graph.
47 ///
48 /// # Examples
49 ///
50 /// ```
51 /// use graph_core::{AdjacencyMatrix, Graph};
52 ///
53 /// let g: AdjacencyMatrix<()> = AdjacencyMatrix::directed();
54 /// assert!(g.is_empty());
55 /// ```
56 pub fn directed() -> Self {
57 Self {
58 nodes: Vec::new(),
59 matrix: Vec::new(),
60 edge_count: 0,
61 directed: true,
62 }
63 }
64
65 /// Creates an empty **undirected** adjacency-matrix graph.
66 ///
67 /// # Examples
68 ///
69 /// ```
70 /// use graph_core::{AdjacencyMatrix, Graph};
71 ///
72 /// let mut g: AdjacencyMatrix<()> = AdjacencyMatrix::undirected();
73 /// let u = g.add_node(());
74 /// let v = g.add_node(());
75 /// g.add_edge(u, v, 1.0).unwrap();
76 /// assert!(g.contains_edge(v, u)); // symmetric
77 /// ```
78 pub fn undirected() -> Self {
79 Self {
80 nodes: Vec::new(),
81 matrix: Vec::new(),
82 edge_count: 0,
83 directed: false,
84 }
85 }
86
87 /// Returns `true` if this graph is directed.
88 #[must_use]
89 #[inline]
90 pub fn is_directed(&self) -> bool {
91 self.directed
92 }
93
94 /// Returns a reference to the weight of edge `from → to`, or `None` if
95 /// no such edge exists.
96 ///
97 /// # Examples
98 ///
99 /// ```
100 /// use graph_core::{AdjacencyMatrix, Graph};
101 ///
102 /// let mut g: AdjacencyMatrix<()> = AdjacencyMatrix::directed();
103 /// let a = g.add_node(());
104 /// let b = g.add_node(());
105 /// g.add_edge(a, b, 3.0).unwrap();
106 /// assert_eq!(g.edge_weight(a, b), Some(&3.0));
107 /// assert_eq!(g.edge_weight(b, a), None);
108 /// ```
109 #[must_use]
110 #[inline]
111 pub fn edge_weight(&self, from: NodeId, to: NodeId) -> Option<&W> {
112 self.matrix.get(from.index())?.get(to.index())?.as_ref()
113 }
114}
115
116// ── Graph trait ───────────────────────────────────────────────────────────────
117
118/// Iterator over all `NodeId`s in an [`AdjacencyMatrix`].
119pub struct NodeIter {
120 current: usize,
121 total: usize,
122}
123
124impl Iterator for NodeIter {
125 type Item = NodeId;
126
127 #[inline]
128 fn next(&mut self) -> Option<NodeId> {
129 if self.current < self.total {
130 let id = NodeId::new(self.current);
131 self.current += 1;
132 Some(id)
133 } else {
134 None
135 }
136 }
137}
138
139/// Iterator over `(NodeId, &W)` neighbour pairs for one row of the matrix.
140pub struct NeighborIter<'a, W> {
141 row: &'a [Option<W>],
142 current: usize,
143}
144
145impl<'a, W> Iterator for NeighborIter<'a, W> {
146 type Item = (NodeId, &'a W);
147
148 fn next(&mut self) -> Option<(NodeId, &'a W)> {
149 while self.current < self.row.len() {
150 let idx = self.current;
151 self.current += 1;
152 if let Some(ref w) = self.row[idx] {
153 return Some((NodeId::new(idx), w));
154 }
155 }
156 None
157 }
158}
159
160impl<N, W: Clone> Graph for AdjacencyMatrix<N, W> {
161 type NodeData = N;
162 type Weight = W;
163 type NodeIter<'a>
164 = NodeIter
165 where
166 Self: 'a;
167 type NeighborIter<'a>
168 = NeighborIter<'a, W>
169 where
170 Self: 'a;
171
172 fn add_node(&mut self, data: N) -> NodeId {
173 let idx = self.nodes.len();
174 self.nodes.push(data);
175 // Extend existing rows with a new `None` column.
176 for row in &mut self.matrix {
177 row.push(None);
178 }
179 // Add a new row of all `None`.
180 self.matrix.push(vec![None; idx + 1]);
181 NodeId::new(idx)
182 }
183
184 fn add_edge(&mut self, from: NodeId, to: NodeId, weight: W) -> Result<(), GraphError> {
185 if !self.contains_node(from) {
186 return Err(GraphError::NodeNotFound(from));
187 }
188 if !self.contains_node(to) {
189 return Err(GraphError::NodeNotFound(to));
190 }
191 self.matrix[from.index()][to.index()] = Some(weight.clone());
192 if !self.directed && from != to {
193 self.matrix[to.index()][from.index()] = Some(weight);
194 }
195 self.edge_count += 1;
196 Ok(())
197 }
198
199 fn remove_node(&mut self, id: NodeId) -> Option<N> {
200 let idx = id.index();
201 if idx >= self.nodes.len() {
202 return None;
203 }
204 // Remove the row.
205 self.matrix.remove(idx);
206 // Remove the column from every remaining row.
207 for row in &mut self.matrix {
208 row.remove(idx);
209 }
210 // Recount edges (simple but correct).
211 self.edge_count = self
212 .matrix
213 .iter()
214 .flat_map(|row| row.iter())
215 .filter(|cell| cell.is_some())
216 .count();
217 Some(self.nodes.remove(idx))
218 }
219
220 #[inline]
221 fn node_count(&self) -> usize {
222 self.nodes.len()
223 }
224
225 #[inline]
226 fn edge_count(&self) -> usize {
227 self.edge_count
228 }
229
230 #[inline]
231 fn contains_node(&self, id: NodeId) -> bool {
232 id.index() < self.nodes.len()
233 }
234
235 #[inline]
236 fn contains_edge(&self, from: NodeId, to: NodeId) -> bool {
237 self.matrix
238 .get(from.index())
239 .and_then(|row| row.get(to.index()))
240 .map(|cell| cell.is_some())
241 .unwrap_or(false)
242 }
243
244 fn degree(&self, id: NodeId) -> usize {
245 self.matrix[id.index()]
246 .iter()
247 .filter(|cell| cell.is_some())
248 .count()
249 }
250
251 fn nodes(&self) -> NodeIter {
252 NodeIter {
253 current: 0,
254 total: self.nodes.len(),
255 }
256 }
257
258 fn neighbors(&self, id: NodeId) -> NeighborIter<'_, W> {
259 NeighborIter {
260 row: &self.matrix[id.index()],
261 current: 0,
262 }
263 }
264}