Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 96 additions & 18 deletions src/directed/dijkstra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use num_traits::Zero;
use rustc_hash::{FxHashMap, FxHashSet};
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
use std::convert::Infallible;
use std::hash::Hash;

/// Compute a shortest path using the [Dijkstra search
Expand Down Expand Up @@ -79,29 +80,56 @@ where
FN: FnMut(&N) -> IN,
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> bool,
{
match dijkstra_internal::<_, _, _, _, _, Infallible>(
start,
&mut |node| Ok(successors(node)),
&mut |node| Ok(success(node)),
) {
Ok(result) => result,
}
}

/// Compute a shortest path using the [Dijkstra search
/// algorithm](https://en.wikipedia.org/wiki/Dijkstra's_algorithm).
///
/// # Errors
/// This is the fallible version of [`dijkstra`].
/// `Err(err)` is returned immediately if and only if `successors` or `success` returns `Err(err)`.
pub fn try_dijkstra<N, C, FN, IN, FS, E>(
start: &N,
mut successors: FN,
mut success: FS,
) -> Result<Option<(Vec<N>, C)>, E>
where
N: Eq + Hash + Clone,
C: Zero + Ord + Copy,
FN: FnMut(&N) -> Result<IN, E>,
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> Result<bool, E>,
{
dijkstra_internal(start, &mut successors, &mut success)
}

pub(crate) fn dijkstra_internal<N, C, FN, IN, FS>(
pub(crate) fn dijkstra_internal<N, C, FN, IN, FS, E>(
start: &N,
successors: &mut FN,
success: &mut FS,
) -> Option<(Vec<N>, C)>
) -> Result<Option<(Vec<N>, C)>, E>
where
N: Eq + Hash + Clone,
C: Zero + Ord + Copy,
FN: FnMut(&N) -> IN,
FN: FnMut(&N) -> Result<IN, E>,
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> bool,
FS: FnMut(&N) -> Result<bool, E>,
{
let (parents, reached) = run_dijkstra(start, successors, success);
reached.map(|target| {
let (parents, reached) = run_dijkstra(start, successors, success)?;
Ok(reached.map(|target| {
(
reverse_path(&parents, |&(p, _)| p, target),
parents.get_index(target).unwrap().1.1,
)
})
}))
}

/// Determine all reachable nodes from a starting point as well as the
Expand Down Expand Up @@ -152,6 +180,24 @@ where
dijkstra_partial(start, successors, |_| false).0
}

/// Determine all reachable nodes from a starting point as well as the
/// minimum cost to reach them and a possible optimal parent node
/// using the [Dijkstra search
/// algorithm](https://en.wikipedia.org/wiki/Dijkstra's_algorithm).
///
/// # Errors
/// This is the fallible version of [`dijkstra_all`].
/// `Err(err)` is returned immediately if and only if `successors` or `success` returns `Err(err)`.
pub fn try_dijkstra_all<N, C, FN, IN, E>(start: &N, successors: FN) -> Result<HashMap<N, (N, C)>, E>
where
N: Eq + Hash + Clone,
C: Zero + Ord + Copy,
FN: FnMut(&N) -> Result<IN, E>,
IN: IntoIterator<Item = (N, C)>,
{
try_dijkstra_partial(start, successors, |_| Ok(false)).map(|(map, _)| map)
}

/// Determine some reachable nodes from a starting point as well as the minimum cost to
/// reach them and a possible optimal parent node
/// using the [Dijkstra search algorithm](https://en.wikipedia.org/wiki/Dijkstra's_algorithm).
Expand All @@ -168,7 +214,6 @@ where
///
/// The [`build_path`] function can be used to build a full path from the starting point to one
/// of the reachable targets.
#[expect(clippy::missing_panics_doc)]
pub fn dijkstra_partial<N, C, FN, IN, FS>(
start: &N,
mut successors: FN,
Expand All @@ -181,28 +226,61 @@ where
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> bool,
{
let (parents, reached) = run_dijkstra(start, &mut successors, &mut stop);
(
match try_dijkstra_partial::<_, _, _, _, _, Infallible>(
start,
|node| Ok(successors(node)),
|node| Ok(stop(node)),
) {
Ok(result) => result,
}
}

type PartialResult<N, C> = (HashMap<N, (N, C)>, Option<N>);

/// Determine some reachable nodes from a starting point as well as the minimum cost to
/// reach them and a possible optimal parent node
/// using the [Dijkstra search algorithm](https://en.wikipedia.org/wiki/Dijkstra's_algorithm).
///
/// # Errors
/// This is the fallible version of [`dijkstra_partial`].
/// `Err(err)` is returned immediately if and only if `successors` or `success` returns `Err(err)`.
#[expect(clippy::missing_panics_doc)]
pub fn try_dijkstra_partial<N, C, FN, IN, FS, E>(
start: &N,
mut successors: FN,
mut stop: FS,
) -> Result<PartialResult<N, C>, E>
where
N: Eq + Hash + Clone,
C: Zero + Ord + Copy,
FN: FnMut(&N) -> Result<IN, E>,
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> Result<bool, E>,
{
let (parents, reached) = run_dijkstra(start, &mut successors, &mut stop)?;
Ok((
parents
.iter()
.skip(1)
.map(|(n, (p, c))| (n.clone(), (parents.get_index(*p).unwrap().0.clone(), *c))) // unwrap() cannot fail
.collect(),
reached.map(|i| parents.get_index(i).unwrap().0.clone()),
)
))
}

fn run_dijkstra<N, C, FN, IN, FS>(
type RunResult<N, C> = (FxIndexMap<N, (usize, C)>, Option<usize>);

fn run_dijkstra<N, C, FN, IN, FS, E>(
start: &N,
successors: &mut FN,
stop: &mut FS,
) -> (FxIndexMap<N, (usize, C)>, Option<usize>)
) -> Result<RunResult<N, C>, E>
where
N: Eq + Hash + Clone,
C: Zero + Ord + Copy,
FN: FnMut(&N) -> IN,
FN: FnMut(&N) -> Result<IN, E>,
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> bool,
FS: FnMut(&N) -> Result<bool, E>,
{
let mut to_see = BinaryHeap::new();
to_see.push(SmallestHolder {
Expand All @@ -215,7 +293,7 @@ where
while let Some(SmallestHolder { cost, index }) = to_see.pop() {
let successors = {
let (node, &(_, c)) = parents.get_index(index).unwrap();
if stop(node) {
if stop(node)? {
target_reached = Some(index);
break;
}
Expand All @@ -225,7 +303,7 @@ where
if cost > c {
continue;
}
successors(node)
successors(node)?
};
for (successor, move_cost) in successors {
let new_cost = cost + move_cost;
Expand All @@ -251,7 +329,7 @@ where
});
}
}
(parents, target_reached)
Ok((parents, target_reached))
}

/// Build a path leading to a target according to a parents map, which must
Expand Down
64 changes: 48 additions & 16 deletions src/directed/yen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::cmp::Ordering;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::collections::HashSet;
use std::convert::Infallible;
use std::hash::Hash;

use super::dijkstra::dijkstra_internal;
Expand Down Expand Up @@ -42,6 +43,7 @@ where
}
}
}

/// Compute the k-shortest paths using the [Yen's search
/// algorithm](https://en.wikipedia.org/wiki/Yen%27s_algorithm).
///
Expand Down Expand Up @@ -108,8 +110,37 @@ where
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> bool,
{
let Some((n, c)) = dijkstra_internal(start, &mut successors, &mut success) else {
return vec![];
match try_yen::<_, _, _, _, _, Infallible>(
start,
|node| Ok(successors(node)),
|node| Ok(success(node)),
k,
) {
Ok(v) => v,
}
}

/// Compute the k-shortest paths using the [Yen's search
/// algorithm](https://en.wikipedia.org/wiki/Yen%27s_algorithm).
///
/// # Errors
/// This is the fallible version of [`yen`].
/// `Err(err)` is returned immediately if and only if `successors` or `success` returns `Err(err)`.
pub fn try_yen<N, C, FN, IN, FS, E>(
start: &N,
mut successors: FN,
mut success: FS,
k: usize,
) -> Result<Vec<(Vec<N>, C)>, E>
where
N: Eq + Hash + Clone,
C: Zero + Ord + Copy,
FN: FnMut(&N) -> Result<IN, E>,
IN: IntoIterator<Item = (N, C)>,
FS: FnMut(&N) -> Result<bool, E>,
{
let Some((n, c)) = dijkstra_internal(start, &mut successors, &mut success)? else {
return Ok(vec![]);
};

let mut visited = HashSet::new();
Expand Down Expand Up @@ -142,23 +173,24 @@ where
// We are creating a new successor function that will not return the
// filtered edges and nodes that routes already used.
let mut filtered_successor = |n: &N| {
successors(n)
.into_iter()
.filter(|(n2, _)| {
!filtered_nodes.contains(&n2) && !filtered_edges.contains(&(n, n2))
})
.collect::<Vec<_>>()
successors(n).map(|list| {
list.into_iter()
.filter(|(n2, _)| {
!filtered_nodes.contains(&n2) && !filtered_edges.contains(&(n, n2))
})
.collect::<Vec<_>>()
})
};

// Let us find the spur path from the spur node to the sink using.
if let Some((spur_path, _)) =
dijkstra_internal(spur_node, &mut filtered_successor, &mut success)
dijkstra_internal(spur_node, &mut filtered_successor, &mut success)?
{
let nodes: Vec<N> = root_path.iter().cloned().chain(spur_path).collect();
// If we have found the same path before, we will not add it.
if !visited.contains(&nodes) {
// Since we don't know the root_path cost, we need to recalculate.
let cost = make_cost(&nodes, &mut successors);
let cost = make_cost(&nodes, &mut successors)?;
let path = Path { nodes, cost };
// Mark as visited
visited.insert(path.nodes.clone());
Expand Down Expand Up @@ -190,26 +222,26 @@ where
}

routes.sort_unstable();
routes
Ok(routes
.into_iter()
.map(|Path { nodes, cost }| (nodes, cost))
.collect()
.collect())
}

fn make_cost<N, FN, IN, C>(nodes: &[N], successors: &mut FN) -> C
fn make_cost<N, FN, IN, C, E>(nodes: &[N], successors: &mut FN) -> Result<C, E>
where
N: Eq,
C: Zero,
FN: FnMut(&N) -> IN,
FN: FnMut(&N) -> Result<IN, E>,
IN: IntoIterator<Item = (N, C)>,
{
let mut cost = C::zero();
for edge in nodes.windows(2) {
for (n, c) in successors(&edge[0]) {
for (n, c) in successors(&edge[0])? {
if n == edge[1] {
cost = cost + c;
}
}
}
cost
Ok(cost)
}
Loading