Skip to main content

pathfinding_indexed/
indexed_graph.rs

1//! Indexed graph storage and algorithms.
2
3use num_traits::{Bounded, Signed, Zero};
4use rustc_hash::{FxHashMap, FxHashSet};
5use std::collections::VecDeque;
6use std::hash::Hash;
7use thiserror::Error;
8
9/// Errors returned by indexed input helper constructors.
10#[derive(Clone, Debug, Eq, Error, PartialEq)]
11pub enum IndexedInputError {
12    /// Matrix rows do not all have the same length.
13    #[error("matrix rows must all have the same length")]
14    RaggedMatrix,
15    /// Adjacency matrices must be square.
16    #[error("adjacency matrix must be square")]
17    NonSquareAdjacencyMatrix,
18}
19
20/// A directed graph stored as dense `usize` indices with weighted adjacency lists.
21#[derive(Clone, Debug)]
22pub struct IndexedGraph<C> {
23    adjacency: Vec<Vec<(usize, C)>>,
24}
25
26impl<C> IndexedGraph<C> {
27    /// Build a directed indexed graph from an adjacency list.
28    #[must_use]
29    pub const fn from_adjacency(adjacency: Vec<Vec<(usize, C)>>) -> Self {
30        Self { adjacency }
31    }
32
33    /// Return the number of nodes in the graph.
34    #[must_use]
35    pub const fn node_count(&self) -> usize {
36        self.adjacency.len()
37    }
38
39    /// Return the number of nodes in the graph.
40    #[must_use]
41    pub const fn len(&self) -> usize {
42        self.adjacency.len()
43    }
44
45    /// Return true if the graph contains no nodes.
46    #[must_use]
47    pub const fn is_empty(&self) -> bool {
48        self.adjacency.is_empty()
49    }
50
51    /// Return the adjacency list for `node`.
52    #[must_use]
53    pub fn successors(&self, node: usize) -> &[(usize, C)] {
54        &self.adjacency[node]
55    }
56
57    /// Return all adjacency lists.
58    #[must_use]
59    pub fn adjacency(&self) -> &[Vec<(usize, C)>] {
60        &self.adjacency
61    }
62
63    /// Run A* from `start` to a node satisfying `success`.
64    pub fn astar<FH, FS>(
65        &self,
66        start: usize,
67        mut heuristic: FH,
68        mut success: FS,
69    ) -> Option<(Vec<usize>, C)>
70    where
71        C: Zero + Ord + Copy,
72        FH: FnMut(usize) -> C,
73        FS: FnMut(usize) -> bool,
74    {
75        crate::directed::astar::astar(
76            &start,
77            |node| self.successors(*node).iter().copied(),
78            |node| heuristic(*node),
79            |node| success(*node),
80        )
81    }
82
83    /// Run A* and return all shortest paths as an iterator.
84    pub fn astar_bag<FH, FS>(
85        &self,
86        start: usize,
87        mut heuristic: FH,
88        mut success: FS,
89    ) -> Option<(impl Iterator<Item = Vec<usize>>, C)>
90    where
91        C: Zero + Ord + Copy,
92        FH: FnMut(usize) -> C,
93        FS: FnMut(usize) -> bool,
94    {
95        crate::directed::astar::astar_bag(
96            &start,
97            |node| self.successors(*node).iter().copied(),
98            |node| heuristic(*node),
99            |node| success(*node),
100        )
101    }
102
103    /// Run A* and collect all shortest paths into a vector.
104    pub fn astar_bag_collect<FH, FS>(
105        &self,
106        start: usize,
107        mut heuristic: FH,
108        mut success: FS,
109    ) -> Option<(Vec<Vec<usize>>, C)>
110    where
111        C: Zero + Ord + Copy,
112        FH: FnMut(usize) -> C,
113        FS: FnMut(usize) -> bool,
114    {
115        crate::directed::astar::astar_bag_collect(
116            &start,
117            |node| self.successors(*node).iter().copied(),
118            |node| heuristic(*node),
119            |node| success(*node),
120        )
121    }
122
123    /// Run BFS from `start` to a node satisfying `success`.
124    pub fn bfs<FS>(&self, start: usize, mut success: FS) -> Option<Vec<usize>>
125    where
126        FS: FnMut(usize) -> bool,
127    {
128        crate::directed::bfs::bfs(
129            &start,
130            |node| self.successors(*node).iter().map(|edge| edge.0),
131            |node| success(*node),
132        )
133    }
134
135    /// Run BFS and return the shortest loop starting and ending at `start`.
136    #[must_use]
137    pub fn bfs_loop(&self, start: usize) -> Option<Vec<usize>> {
138        crate::directed::bfs::bfs_loop(&start, |node| {
139            self.successors(*node).iter().map(|edge| edge.0)
140        })
141    }
142
143    /// Run bidirectional BFS from `start` to `end`.
144    #[must_use]
145    pub fn bfs_bidirectional(&self, start: usize, end: usize) -> Option<Vec<usize>> {
146        let mut predecessors = vec![Vec::new(); self.node_count()];
147        for (from, edges) in self.adjacency.iter().enumerate() {
148            for &(to, _) in edges {
149                predecessors[to].push(from);
150            }
151        }
152
153        crate::directed::bfs::bfs_bidirectional(
154            &start,
155            &end,
156            |node| {
157                self.successors(*node)
158                    .iter()
159                    .map(|edge| edge.0)
160                    .collect::<Vec<_>>()
161            },
162            |node| predecessors[*node].clone(),
163        )
164    }
165
166    /// Iterate over nodes reachable from `start` using BFS order.
167    pub fn bfs_reach(&self, start: usize) -> impl Iterator<Item = usize> + '_ {
168        crate::directed::bfs::bfs_reach(start, |node| {
169            self.successors(*node).iter().map(|edge| edge.0)
170        })
171    }
172
173    /// Run DFS from `start` to a node satisfying `success`.
174    pub fn dfs<FS>(&self, start: usize, mut success: FS) -> Option<Vec<usize>>
175    where
176        FS: FnMut(usize) -> bool,
177    {
178        crate::directed::dfs::dfs(
179            start,
180            |node| self.successors(*node).iter().map(|edge| edge.0),
181            |node| success(*node),
182        )
183    }
184
185    /// Iterate over nodes reachable from `start` using DFS order.
186    pub fn dfs_reach(&self, start: usize) -> impl Iterator<Item = usize> + '_ {
187        crate::directed::dfs::dfs_reach(start, |node| {
188            self.successors(*node).iter().map(|edge| edge.0)
189        })
190    }
191
192    /// Run IDDFS from `start` to a node satisfying `success`.
193    pub fn iddfs<FS>(&self, start: usize, mut success: FS) -> Option<Vec<usize>>
194    where
195        FS: FnMut(usize) -> bool,
196    {
197        crate::directed::iddfs::iddfs(
198            start,
199            |node| self.successors(*node).iter().map(|edge| edge.0),
200            |node| success(*node),
201        )
202    }
203
204    /// Run IDA* from `start` to a node satisfying `success`.
205    pub fn idastar<FH, FS>(
206        &self,
207        start: usize,
208        mut heuristic: FH,
209        mut success: FS,
210    ) -> Option<(Vec<usize>, C)>
211    where
212        C: Zero + Ord + Copy,
213        FH: FnMut(usize) -> C,
214        FS: FnMut(usize) -> bool,
215    {
216        crate::directed::idastar::idastar(
217            &start,
218            |node| self.successors(*node).iter().copied(),
219            |node| heuristic(*node),
220            |node| success(*node),
221        )
222    }
223
224    /// Run Dijkstra from `start` to a node satisfying `success`.
225    pub fn dijkstra<FS>(&self, start: usize, mut success: FS) -> Option<(Vec<usize>, C)>
226    where
227        C: Zero + Ord + Copy,
228        FS: FnMut(usize) -> bool,
229    {
230        crate::directed::dijkstra::dijkstra(
231            &start,
232            |node| self.successors(*node).iter().copied(),
233            |node| success(*node),
234        )
235    }
236
237    /// Compute shortest paths to all reachable nodes from `start`.
238    pub fn dijkstra_all(&self, start: usize) -> Vec<Option<(usize, C)>>
239    where
240        C: Zero + Ord + Copy,
241    {
242        let parents = crate::directed::dijkstra::dijkstra_all(&start, |node| {
243            self.successors(*node).iter().copied()
244        });
245        let mut out = vec![None; self.node_count()];
246        for (node, (parent, cost)) in parents {
247            out[node] = Some((parent, cost));
248        }
249        out
250    }
251
252    /// Compute shortest paths until `stop` returns true.
253    pub fn dijkstra_partial<FS>(
254        &self,
255        start: usize,
256        mut stop: FS,
257    ) -> (Vec<Option<(usize, C)>>, Option<usize>)
258    where
259        C: Zero + Ord + Copy,
260        FS: FnMut(usize) -> bool,
261    {
262        let (parents, reached) = crate::directed::dijkstra::dijkstra_partial(
263            &start,
264            |node| self.successors(*node).iter().copied(),
265            |node| stop(*node),
266        );
267        let mut out = vec![None; self.node_count()];
268        for (node, (parent, cost)) in parents {
269            out[node] = Some((parent, cost));
270        }
271        (out, reached)
272    }
273
274    /// Iterate over nodes reached by Dijkstra, yielding the node, parent, and total cost.
275    pub fn dijkstra_reach(
276        &self,
277        start: usize,
278    ) -> impl Iterator<Item = (usize, Option<usize>, C)> + '_
279    where
280        C: Zero + Ord + Copy + Hash,
281    {
282        crate::directed::dijkstra::dijkstra_reach(&start, |node| {
283            self.successors(*node).iter().copied()
284        })
285        .map(|item| (item.node, item.parent, item.total_cost))
286    }
287
288    /// Run the BMSSP-based SSSP algorithm from `start`.
289    pub fn bmssp<FS>(&self, start: usize, success: FS) -> Option<(Vec<usize>, C)>
290    where
291        C: Zero + Ord + Copy,
292        FS: FnMut(usize) -> bool,
293    {
294        crate::directed::bmssp::bmssp_indexed(
295            start,
296            |node| self.successors(node).iter().copied(),
297            success,
298            self.node_count(),
299        )
300    }
301
302    /// Compute BMSSP parents and costs for all reachable nodes from `start`.
303    #[must_use]
304    pub fn bmssp_all(&self, start: usize) -> Vec<Option<(usize, C)>>
305    where
306        C: Zero + Ord + Copy,
307    {
308        crate::directed::bmssp::bmssp_all_indexed(
309            start,
310            |node| self.successors(node).iter().copied(),
311            self.node_count(),
312        )
313    }
314
315    /// Run Fringe search from `start` to a node satisfying `success`.
316    pub fn fringe<FH, FS>(
317        &self,
318        start: usize,
319        mut heuristic: FH,
320        mut success: FS,
321    ) -> Option<(Vec<usize>, C)>
322    where
323        C: Bounded + Zero + Ord + Copy,
324        FH: FnMut(usize) -> C,
325        FS: FnMut(usize) -> bool,
326    {
327        crate::directed::fringe::fringe(
328            &start,
329            |node| self.successors(*node).iter().copied(),
330            |node| heuristic(*node),
331            |node| success(*node),
332        )
333    }
334
335    /// Compute a topological ordering of all nodes.
336    ///
337    /// # Errors
338    ///
339    /// Returns `Err(node)` when a cycle is detected.
340    pub fn topological_sort(&self) -> Result<Vec<usize>, usize> {
341        let nodes = (0..self.node_count()).collect::<Vec<_>>();
342        crate::directed::topological_sort::topological_sort(&nodes, |node| {
343            self.successors(*node).iter().map(|edge| edge.0)
344        })
345    }
346
347    /// Compute strongly connected components for all nodes.
348    #[must_use]
349    pub fn strongly_connected_components(&self) -> Vec<Vec<usize>> {
350        let nodes = (0..self.node_count()).collect::<Vec<_>>();
351        crate::directed::strongly_connected_components::strongly_connected_components(
352            &nodes,
353            |node| self.successors(*node).iter().map(|edge| edge.0),
354        )
355    }
356
357    /// Compute strongly connected components reachable from `start`.
358    #[must_use]
359    pub fn strongly_connected_components_from(&self, start: usize) -> Vec<Vec<usize>> {
360        crate::directed::strongly_connected_components::strongly_connected_components_from(
361            &start,
362            |node| self.successors(*node).iter().map(|edge| edge.0),
363        )
364    }
365
366    /// Compute the strongly connected component containing `node`.
367    #[must_use]
368    pub fn strongly_connected_component(&self, node: usize) -> Vec<usize> {
369        crate::directed::strongly_connected_components::strongly_connected_component(&node, |n| {
370            self.successors(*n).iter().map(|edge| edge.0)
371        })
372    }
373
374    /// Count the number of paths from `start` to nodes satisfying `success`.
375    pub fn count_paths<FS>(&self, start: usize, mut success: FS) -> usize
376    where
377        FS: FnMut(usize) -> bool,
378    {
379        crate::directed::count_paths::count_paths(
380            start,
381            |node| self.successors(*node).iter().map(|edge| edge.0),
382            |node| success(*node),
383        )
384    }
385
386    /// Compute k-shortest paths using Yen's algorithm.
387    pub fn yen<FS>(&self, start: usize, mut success: FS, k: usize) -> Vec<(Vec<usize>, C)>
388    where
389        C: Zero + Ord + Copy,
390        FS: FnMut(usize) -> bool,
391    {
392        crate::directed::yen::yen(
393            &start,
394            |node| self.successors(*node).iter().copied(),
395            |node| success(*node),
396            k,
397        )
398    }
399
400    /// Compute the maximum flow and minimum cut using Edmonds-Karp.
401    ///
402    /// # Panics
403    ///
404    /// Panics if `source` or `sink` are out of bounds.
405    #[expect(clippy::too_many_lines)]
406    #[expect(clippy::type_complexity)]
407    #[must_use]
408    pub fn edmonds_karp(
409        &self,
410        source: usize,
411        sink: usize,
412    ) -> (Vec<((usize, usize), C)>, C, Vec<((usize, usize), C)>)
413    where
414        C: Copy + Zero + Signed + Ord + Bounded,
415    {
416        let node_count = self.node_count();
417        if node_count == 0 || source == sink {
418            return (Vec::new(), Zero::zero(), Vec::new());
419        }
420        assert!(source < node_count, "source out of bounds");
421        assert!(sink < node_count, "sink out of bounds");
422
423        let mut capacity = vec![vec![C::zero(); node_count]; node_count];
424        for (from, edges) in self.adjacency.iter().enumerate() {
425            for &(to, weight) in edges {
426                capacity[from][to] = capacity[from][to] + weight;
427            }
428        }
429
430        let mut predecessors = vec![Vec::new(); node_count];
431        for (from, edges) in self.adjacency.iter().enumerate() {
432            for &(to, _) in edges {
433                predecessors[to].push(from);
434            }
435        }
436
437        let mut flow = vec![vec![C::zero(); node_count]; node_count];
438        let mut total = C::zero();
439
440        loop {
441            let mut parent = vec![usize::MAX; node_count];
442            let mut direction = vec![true; node_count];
443            let mut path_capacity = vec![C::zero(); node_count];
444            let mut queue = VecDeque::new();
445
446            parent[source] = source;
447            path_capacity[source] = C::max_value();
448            queue.push_back(source);
449
450            while let Some(node) = queue.pop_front() {
451                let capacity_so_far = path_capacity[node];
452                for &(next, _) in &self.adjacency[node] {
453                    if parent[next] != usize::MAX || next == source {
454                        continue;
455                    }
456                    let residual = capacity[node][next] - flow[node][next];
457                    if residual <= Zero::zero() {
458                        continue;
459                    }
460                    parent[next] = node;
461                    direction[next] = true;
462                    path_capacity[next] = if capacity_so_far < residual {
463                        capacity_so_far
464                    } else {
465                        residual
466                    };
467                    if next == sink {
468                        break;
469                    }
470                    queue.push_back(next);
471                }
472                if parent[sink] != usize::MAX {
473                    break;
474                }
475                for &prev in &predecessors[node] {
476                    if parent[prev] != usize::MAX || prev == source {
477                        continue;
478                    }
479                    let residual = flow[prev][node];
480                    if residual <= Zero::zero() {
481                        continue;
482                    }
483                    parent[prev] = node;
484                    direction[prev] = false;
485                    path_capacity[prev] = if capacity_so_far < residual {
486                        capacity_so_far
487                    } else {
488                        residual
489                    };
490                    if prev == sink {
491                        break;
492                    }
493                    queue.push_back(prev);
494                }
495                if parent[sink] != usize::MAX {
496                    break;
497                }
498            }
499
500            if parent[sink] == usize::MAX {
501                break;
502            }
503
504            let augment = path_capacity[sink];
505            let mut node = sink;
506            while node != source {
507                let prev = parent[node];
508                if direction[node] {
509                    flow[prev][node] = flow[prev][node] + augment;
510                } else {
511                    flow[node][prev] = flow[node][prev] - augment;
512                }
513                node = prev;
514            }
515            total = total + augment;
516        }
517
518        let mut reachable = vec![false; node_count];
519        let mut queue = VecDeque::new();
520        reachable[source] = true;
521        queue.push_back(source);
522        while let Some(node) = queue.pop_front() {
523            for &(next, _) in &self.adjacency[node] {
524                if reachable[next] {
525                    continue;
526                }
527                let residual = capacity[node][next] - flow[node][next];
528                if residual > Zero::zero() {
529                    reachable[next] = true;
530                    queue.push_back(next);
531                }
532            }
533            for &prev in &predecessors[node] {
534                if reachable[prev] {
535                    continue;
536                }
537                if flow[prev][node] > Zero::zero() {
538                    reachable[prev] = true;
539                    queue.push_back(prev);
540                }
541            }
542        }
543
544        let mut flows = Vec::new();
545        for (from, edges) in self.adjacency.iter().enumerate() {
546            for &(to, _) in edges {
547                let value = flow[from][to];
548                if value > Zero::zero() {
549                    flows.push(((from, to), value));
550                }
551            }
552        }
553
554        let mut cuts = Vec::new();
555        for (from, edges) in self.adjacency.iter().enumerate() {
556            if !reachable[from] {
557                continue;
558            }
559            for &(to, _) in edges {
560                if reachable[to] {
561                    continue;
562                }
563                let cap = capacity[from][to];
564                if cap > Zero::zero() {
565                    cuts.push(((from, to), cap));
566                }
567            }
568        }
569
570        (flows, total, cuts)
571    }
572}
573
574impl<C: Copy> IndexedGraph<C> {
575    /// Build a directed indexed graph from a square adjacency matrix.
576    ///
577    /// Every `Some(weight)` cell becomes a directed edge from the row index to the
578    /// column index. `None` cells produce no edge.
579    ///
580    /// # Errors
581    ///
582    /// Returns [`IndexedInputError::RaggedMatrix`] if rows have different lengths and
583    /// [`IndexedInputError::NonSquareAdjacencyMatrix`] if the matrix is not square.
584    ///
585    /// # Example
586    ///
587    /// ```
588    /// use pathfinding_indexed::IndexedGraph;
589    ///
590    /// let graph = IndexedGraph::from_adjacency_matrix(&[
591    ///     vec![None, Some(3), None],
592    ///     vec![None, None, Some(2)],
593    ///     vec![Some(1), None, None],
594    /// ])
595    /// .unwrap();
596    ///
597    /// assert_eq!(graph.successors(0), &[(1, 3)]);
598    /// assert_eq!(graph.successors(1), &[(2, 2)]);
599    /// assert_eq!(graph.successors(2), &[(0, 1)]);
600    /// ```
601    pub fn from_adjacency_matrix(matrix: &[Vec<Option<C>>]) -> Result<Self, IndexedInputError> {
602        let rows = validate_rectangular_matrix(matrix)?;
603        if rows != matrix.first().map_or(0, Vec::len) {
604            return Err(IndexedInputError::NonSquareAdjacencyMatrix);
605        }
606
607        let adjacency = matrix
608            .iter()
609            .map(|row| {
610                row.iter()
611                    .enumerate()
612                    .filter_map(|(column, weight)| weight.map(|cost| (column, cost)))
613                    .collect()
614            })
615            .collect();
616        Ok(Self::from_adjacency(adjacency))
617    }
618}
619
620/// An undirected graph stored as dense `usize` indices with weighted adjacency lists.
621#[derive(Clone, Debug)]
622pub struct IndexedUndirectedGraph<C> {
623    adjacency: Vec<Vec<(usize, C)>>,
624    edges: Vec<(usize, usize, C)>,
625}
626
627impl<C> IndexedUndirectedGraph<C> {
628    /// Build an undirected indexed graph from a list of edges.
629    #[must_use]
630    pub fn from_edges(node_count: usize, edges: Vec<(usize, usize, C)>) -> Self
631    where
632        C: Clone,
633    {
634        let mut adjacency = vec![Vec::new(); node_count];
635        for (u, v, w) in &edges {
636            debug_assert!(*u < node_count, "edge start out of bounds");
637            debug_assert!(*v < node_count, "edge end out of bounds");
638            adjacency[*u].push((*v, w.clone()));
639            adjacency[*v].push((*u, w.clone()));
640        }
641        Self { adjacency, edges }
642    }
643
644    /// Return the number of nodes in the graph.
645    #[must_use]
646    pub const fn node_count(&self) -> usize {
647        self.adjacency.len()
648    }
649
650    /// Return the adjacency list for `node`.
651    #[must_use]
652    pub fn successors(&self, node: usize) -> &[(usize, C)] {
653        &self.adjacency[node]
654    }
655
656    /// Return the canonical edge list (each edge appears once).
657    #[must_use]
658    pub fn edges(&self) -> &[(usize, usize, C)] {
659        &self.edges
660    }
661
662    /// Return all adjacency lists.
663    #[must_use]
664    pub fn adjacency(&self) -> &[Vec<(usize, C)>] {
665        &self.adjacency
666    }
667
668    /// Compute connected components of the graph.
669    #[must_use]
670    pub fn connected_components(&self) -> Vec<Vec<usize>> {
671        let nodes = (0..self.node_count()).collect::<Vec<_>>();
672        let components =
673            crate::undirected::connected_components::connected_components(&nodes, |n| {
674                self.successors(*n).iter().map(|edge| edge.0)
675            });
676        components
677            .into_iter()
678            .map(|set| set.into_iter().collect())
679            .collect()
680    }
681
682    /// Return the component index for each node.
683    #[must_use]
684    pub fn component_index(&self) -> Vec<usize> {
685        let components = self.connected_components();
686        let mut index = vec![usize::MAX; self.node_count()];
687        for (component_idx, component) in components.iter().enumerate() {
688            for &node in component {
689                index[node] = component_idx;
690            }
691        }
692        index
693    }
694
695    /// Return connected components (alias of [`Self::connected_components`]).
696    #[must_use]
697    pub fn components(&self) -> Vec<Vec<usize>> {
698        self.connected_components()
699    }
700
701    /// Separate connected components using neighbor groups derived from adjacency.
702    #[must_use]
703    pub fn separate_components(&self) -> (Vec<usize>, Vec<usize>) {
704        let groups = (0..self.node_count())
705            .map(|node| {
706                let mut group = Vec::with_capacity(self.adjacency[node].len() + 1);
707                group.push(node);
708                group.extend(self.adjacency[node].iter().map(|edge| edge.0));
709                group
710            })
711            .collect::<Vec<_>>();
712        let (mapping, group_indices) =
713            crate::undirected::connected_components::separate_components(&groups);
714        let mut node_components = vec![usize::MAX; self.node_count()];
715        for (node, component) in mapping {
716            node_components[*node] = component;
717        }
718        (node_components, group_indices)
719    }
720
721    /// Enumerate all maximal cliques and collect them into a vector.
722    #[must_use]
723    pub fn maximal_cliques_collect(&self) -> Vec<Vec<usize>> {
724        let adjacency_sets = self.adjacency_sets();
725        let mut connected = |a: &usize, b: &usize| adjacency_sets[*a].contains(b);
726        crate::undirected::cliques::maximal_cliques_collect(0..self.node_count(), &mut connected)
727            .into_iter()
728            .map(|set| set.into_iter().collect())
729            .collect()
730    }
731
732    /// Enumerate all maximal cliques and send them to `consumer`.
733    pub fn maximal_cliques<CO>(&self, mut consumer: CO)
734    where
735        CO: FnMut(&Vec<usize>),
736    {
737        let adjacency_sets = self.adjacency_sets();
738        let mut connected = |a: &usize, b: &usize| adjacency_sets[*a].contains(b);
739        let mut adapter = |clique: &std::collections::HashSet<usize>| {
740            let clique_vec = clique.iter().copied().collect::<Vec<_>>();
741            consumer(&clique_vec);
742        };
743        crate::undirected::cliques::maximal_cliques(
744            0..self.node_count(),
745            &mut connected,
746            &mut adapter,
747        );
748    }
749
750    /// Compute a minimum spanning tree using Kruskal's algorithm (collected).
751    #[must_use]
752    pub fn kruskal(&self) -> Vec<(usize, usize, C)>
753    where
754        C: Clone + Ord,
755    {
756        self.kruskal_indices().collect()
757    }
758
759    /// Compute a minimum spanning tree using Kruskal's algorithm (iterator).
760    pub fn kruskal_indices(&self) -> impl Iterator<Item = (usize, usize, C)>
761    where
762        C: Clone + Ord,
763    {
764        crate::undirected::kruskal::kruskal_indices(self.node_count(), self.edges())
765    }
766
767    /// Compute a minimum spanning tree using Prim's algorithm.
768    #[must_use]
769    pub fn prim(&self) -> Vec<(usize, usize, C)>
770    where
771        C: Clone + Ord,
772    {
773        crate::undirected::prim::prim(self.edges())
774            .into_iter()
775            .map(|(a, b, c)| (*a, *b, c))
776            .collect()
777    }
778
779    fn adjacency_sets(&self) -> Vec<FxHashSet<usize>> {
780        self.adjacency
781            .iter()
782            .map(|edges| edges.iter().map(|edge| edge.0).collect())
783            .collect()
784    }
785}
786
787/// Helper that maps external node values to dense indices and builds an indexed graph.
788#[derive(Clone, Debug)]
789pub struct IndexedGraphMap<N, C> {
790    graph: IndexedGraph<C>,
791    nodes: Vec<N>,
792    index: FxHashMap<N, usize>,
793}
794
795impl<N, C> IndexedGraphMap<N, C>
796where
797    N: Eq + Hash + Clone,
798{
799    /// Build a mapped graph from seed nodes and a successor function.
800    pub fn from_nodes_and_successors<I, FN, IN>(nodes: I, mut successors: FN) -> Self
801    where
802        I: IntoIterator<Item = N>,
803        FN: FnMut(&N) -> IN,
804        IN: IntoIterator<Item = (N, C)>,
805    {
806        let mut mapped = Self {
807            graph: IndexedGraph::from_adjacency(Vec::new()),
808            nodes: Vec::new(),
809            index: FxHashMap::default(),
810        };
811
812        for node in nodes {
813            mapped.ensure_index(node);
814        }
815
816        let mut cursor = 0usize;
817        while cursor < mapped.nodes.len() {
818            let node = mapped.nodes[cursor].clone();
819            let mut edges = Vec::new();
820            for (neighbor, cost) in successors(&node) {
821                let neighbor_idx = mapped.ensure_index(neighbor);
822                edges.push((neighbor_idx, cost));
823            }
824            if cursor >= mapped.graph.adjacency.len() {
825                mapped.graph.adjacency.push(edges);
826            } else {
827                mapped.graph.adjacency[cursor] = edges;
828            }
829            cursor += 1;
830        }
831
832        mapped
833    }
834
835    /// Return the indexed graph.
836    #[must_use]
837    pub const fn graph(&self) -> &IndexedGraph<C> {
838        &self.graph
839    }
840
841    /// Return the indexed graph, consuming the mapping helper.
842    #[must_use]
843    pub fn into_graph(self) -> IndexedGraph<C> {
844        self.graph
845    }
846
847    /// Return the number of nodes in the mapped graph.
848    #[must_use]
849    pub const fn node_count(&self) -> usize {
850        self.graph.node_count()
851    }
852
853    /// Return the index assigned to `node`.
854    #[must_use]
855    pub fn index_of(&self, node: &N) -> Option<usize> {
856        self.index.get(node).copied()
857    }
858
859    /// Return the external node value for a given index.
860    #[must_use]
861    pub fn node(&self, index: usize) -> Option<&N> {
862        self.nodes.get(index)
863    }
864
865    /// Return the mapped node values in index order.
866    #[must_use]
867    pub fn nodes(&self) -> &[N] {
868        &self.nodes
869    }
870
871    fn ensure_index(&mut self, node: N) -> usize {
872        if let Some(&idx) = self.index.get(&node) {
873            return idx;
874        }
875        let idx = self.nodes.len();
876        self.nodes.push(node.clone());
877        self.index.insert(node, idx);
878        self.graph.adjacency.push(Vec::new());
879        idx
880    }
881}
882
883impl IndexedGraphMap<(usize, usize), usize> {
884    /// Build a mapped graph from a boolean matrix with 4-neighbor connectivity.
885    ///
886    /// `true` cells are walkable and become nodes labeled by `(row, column)`.
887    ///
888    /// # Errors
889    ///
890    /// Returns [`IndexedInputError::RaggedMatrix`] if rows have different lengths.
891    ///
892    /// # Example
893    ///
894    /// ```
895    /// use pathfinding_indexed::IndexedGraphMap;
896    ///
897    /// let mapped = IndexedGraphMap::from_walkable_matrix_4(&[
898    ///     vec![true, true, false],
899    ///     vec![false, true, true],
900    /// ])
901    /// .unwrap();
902    ///
903    /// let start = mapped.index_of(&(0, 0)).unwrap();
904    /// let goal = mapped.index_of(&(1, 2)).unwrap();
905    /// let result = mapped.graph().dijkstra(start, |node| node == goal);
906    /// assert_eq!(result.map(|(_, cost)| cost), Some(3));
907    /// ```
908    pub fn from_walkable_matrix_4(matrix: &[Vec<bool>]) -> Result<Self, IndexedInputError> {
909        Self::from_walkable_matrix(matrix, false)
910    }
911
912    /// Build a mapped graph from a boolean matrix with 8-neighbor connectivity.
913    ///
914    /// `true` cells are walkable and become nodes labeled by `(row, column)`.
915    ///
916    /// # Errors
917    ///
918    /// Returns [`IndexedInputError::RaggedMatrix`] if rows have different lengths.
919    pub fn from_walkable_matrix_8(matrix: &[Vec<bool>]) -> Result<Self, IndexedInputError> {
920        Self::from_walkable_matrix(matrix, true)
921    }
922
923    /// Build a mapped graph from grid dimensions and a walkability predicate using
924    /// 4-neighbor connectivity.
925    pub fn from_walkable_cells_4<F>(rows: usize, columns: usize, walkable: F) -> Self
926    where
927        F: FnMut((usize, usize)) -> bool,
928    {
929        Self::from_walkable_cells(rows, columns, false, walkable)
930    }
931
932    /// Build a mapped graph from grid dimensions and a walkability predicate using
933    /// 8-neighbor connectivity.
934    pub fn from_walkable_cells_8<F>(rows: usize, columns: usize, walkable: F) -> Self
935    where
936        F: FnMut((usize, usize)) -> bool,
937    {
938        Self::from_walkable_cells(rows, columns, true, walkable)
939    }
940
941    fn from_walkable_matrix(
942        matrix: &[Vec<bool>],
943        diagonal_mode: bool,
944    ) -> Result<Self, IndexedInputError> {
945        let rows = validate_rectangular_matrix(matrix)?;
946        let columns = matrix.first().map_or(0, Vec::len);
947        Ok(Self::from_walkable_cells(
948            rows,
949            columns,
950            diagonal_mode,
951            |(row, column)| matrix[row][column],
952        ))
953    }
954
955    fn from_walkable_cells<F>(
956        rows: usize,
957        columns: usize,
958        diagonal_mode: bool,
959        mut walkable: F,
960    ) -> Self
961    where
962        F: FnMut((usize, usize)) -> bool,
963    {
964        let mut nodes = Vec::new();
965        let mut index = FxHashMap::default();
966        let mut cell_index = vec![vec![None; columns]; rows];
967
968        for (row, row_indices) in cell_index.iter_mut().enumerate() {
969            for (column, slot) in row_indices.iter_mut().enumerate() {
970                let cell = (row, column);
971                if !walkable(cell) {
972                    continue;
973                }
974                let node_index = nodes.len();
975                nodes.push(cell);
976                index.insert(cell, node_index);
977                *slot = Some(node_index);
978            }
979        }
980
981        let mut adjacency = vec![Vec::new(); nodes.len()];
982        for (row, row_indices) in cell_index.iter().enumerate() {
983            for (column, &node_index) in row_indices.iter().enumerate() {
984                let Some(node_index) = node_index else {
985                    continue;
986                };
987                append_neighbor(&mut adjacency[node_index], &cell_index, row, column, -1, 0);
988                append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 1, 0);
989                append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 0, -1);
990                append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 0, 1);
991                if diagonal_mode {
992                    append_neighbor(&mut adjacency[node_index], &cell_index, row, column, -1, -1);
993                    append_neighbor(&mut adjacency[node_index], &cell_index, row, column, -1, 1);
994                    append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 1, -1);
995                    append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 1, 1);
996                }
997            }
998        }
999
1000        Self {
1001            graph: IndexedGraph::from_adjacency(adjacency),
1002            nodes,
1003            index,
1004        }
1005    }
1006}
1007
1008fn validate_rectangular_matrix<T>(matrix: &[Vec<T>]) -> Result<usize, IndexedInputError> {
1009    let expected = matrix.first().map_or(0, Vec::len);
1010    if matrix.iter().any(|row| row.len() != expected) {
1011        return Err(IndexedInputError::RaggedMatrix);
1012    }
1013    Ok(matrix.len())
1014}
1015
1016fn append_neighbor(
1017    edges: &mut Vec<(usize, usize)>,
1018    cell_index: &[Vec<Option<usize>>],
1019    row: usize,
1020    column: usize,
1021    row_delta: isize,
1022    column_delta: isize,
1023) {
1024    let Some(next_row) = row.checked_add_signed(row_delta) else {
1025        return;
1026    };
1027    let Some(next_column) = column.checked_add_signed(column_delta) else {
1028        return;
1029    };
1030    let Some(target_row) = cell_index.get(next_row) else {
1031        return;
1032    };
1033    let Some(Some(target)) = target_row.get(next_column) else {
1034        return;
1035    };
1036    edges.push((*target, 1));
1037}
1038
1039#[cfg(test)]
1040mod tests {
1041    use super::{IndexedGraph, IndexedGraphMap, IndexedInputError};
1042
1043    #[test]
1044    fn adjacency_matrix_builds_expected_edges() {
1045        let graph = IndexedGraph::from_adjacency_matrix(&[
1046            vec![None, Some(4), None],
1047            vec![Some(2), None, Some(7)],
1048            vec![None, None, None],
1049        ])
1050        .unwrap();
1051
1052        assert_eq!(graph.successors(0), &[(1, 4)]);
1053        assert_eq!(graph.successors(1), &[(0, 2), (2, 7)]);
1054        assert_eq!(graph.successors(2), &[]);
1055    }
1056
1057    #[test]
1058    fn adjacency_matrix_rejects_non_square_input() {
1059        let err = IndexedGraph::from_adjacency_matrix(&[vec![None, Some(1)]]).unwrap_err();
1060        assert_eq!(err, IndexedInputError::NonSquareAdjacencyMatrix);
1061    }
1062
1063    #[test]
1064    fn walkable_matrix_4_maps_coordinates() {
1065        let mapped = IndexedGraphMap::from_walkable_matrix_4(&[
1066            vec![true, true, false],
1067            vec![false, true, true],
1068        ])
1069        .unwrap();
1070
1071        assert_eq!(mapped.node_count(), 4);
1072        let start = mapped.index_of(&(0, 0)).unwrap();
1073        let goal = mapped.index_of(&(1, 2)).unwrap();
1074        let result = mapped.graph().dijkstra(start, |node| node == goal);
1075        assert_eq!(result.map(|(_, cost)| cost), Some(3));
1076    }
1077
1078    #[test]
1079    fn walkable_matrix_8_adds_diagonal_edges() {
1080        let mapped =
1081            IndexedGraphMap::from_walkable_matrix_8(&[vec![true, false], vec![false, true]])
1082                .unwrap();
1083
1084        let start = mapped.index_of(&(0, 0)).unwrap();
1085        let goal = mapped.index_of(&(1, 1)).unwrap();
1086        assert_eq!(mapped.graph().successors(start), &[(goal, 1)]);
1087    }
1088
1089    #[test]
1090    fn walkable_matrix_rejects_ragged_input() {
1091        let err =
1092            IndexedGraphMap::from_walkable_matrix_4(&[vec![true, true], vec![true]]).unwrap_err();
1093        assert_eq!(err, IndexedInputError::RaggedMatrix);
1094    }
1095
1096    #[test]
1097    fn walkable_cells_helper_uses_predicate() {
1098        let mapped = IndexedGraphMap::from_walkable_cells_4(2, 3, |cell| {
1099            matches!(cell, (0, 0) | (0, 1) | (1, 1))
1100        });
1101
1102        assert_eq!(mapped.node_count(), 3);
1103        assert!(mapped.index_of(&(1, 2)).is_none());
1104    }
1105}