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}