use super::{ordered_insert, Cost, Path};
use std::collections::HashMap;
use std::hash::Hash;
pub fn dijkstra_search<Id: Copy + Eq + Hash, NeighborIter: Iterator<Item = (Id, Cost)>>(
get_all_neighbors: impl Fn(Id) -> NeighborIter,
is_walkable: impl Fn(Id) -> bool,
start: Id,
goals: &[Id],
) -> HashMap<Id, Path<Id>> {
let mut visited = ::std::collections::HashMap::new();
let mut next = vec![(start, 0)];
visited.insert(start, (0, start));
let mut remaining_goals = goals.to_vec();
let mut goal_costs = HashMap::with_capacity(goals.len());
while let Some((current_id, _)) = next.pop() {
let cost = visited[¤t_id].0;
let mut found_one = false;
for &goal_id in remaining_goals.iter() {
if current_id == goal_id {
goal_costs.insert(goal_id, cost);
found_one = true;
}
}
if found_one {
remaining_goals.retain(|&id| id != current_id);
if remaining_goals.is_empty() {
break;
}
}
for (other_id, delta_cost) in get_all_neighbors(current_id) {
let other_cost = cost + delta_cost;
if !is_walkable(other_id) {
let mut is_goal = false;
for &goal_id in remaining_goals.iter() {
if other_id == goal_id {
is_goal = true;
}
}
if !is_goal {
continue;
}
}
if let Some(&(prev_cost, _)) = visited.get(&other_id) {
if prev_cost > other_cost {
next.retain(|&(id, _)| id != other_id);
}
}
if !visited.contains_key(&other_id) || visited[&other_id].0 > other_cost {
ordered_insert(&mut next, (other_id, other_cost), |&(_, cost)| cost);
visited.insert(other_id, (other_cost, current_id));
}
}
}
let mut goal_data = HashMap::with_capacity(goal_costs.len());
for (&goal, &cost) in goal_costs.iter() {
let steps = {
let mut steps = vec![];
let mut current = goal;
while current != start {
steps.push(current);
let (_, prev) = visited[¤t];
current = prev;
}
steps.push(start);
steps.reverse();
steps
};
goal_data.insert(goal, Path::new(steps, cost));
}
goal_data
}