Skip to content

Commit

Permalink
feat: Implemented generic Dijkstra path finder from Graphs, created p…
Browse files Browse the repository at this point in the history
…ath_finding_test_case_a, it works.
  • Loading branch information
jortrr committed Jul 20, 2024
1 parent 38b206d commit ea0974d
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 12 deletions.
22 changes: 22 additions & 0 deletions docs/path_finding_test_case_a.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
digraph path_finding_test_case_a {
label = "PathFinding Test Case A";
node [shape = circle;];

a [label = "a: 0";];
b [label = "b: 3";];
c [label = "c: 5";];
d [label = "d: 7";];
e [label = "e: 9";];
f [label = "f: 8";];
g [label = "g: 13";];

a -> b [label = "3";];
b -> c [label = "2";];
b -> d [label = "4";];
a -> d [label = "8";];
c -> e [label = "4";];
e -> b [label = "10";];
d -> f [label = "1";];
f -> g [label = "5";];
g -> f [label = "2";];
}
75 changes: 63 additions & 12 deletions src/path_finding.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// This module contains a generic implementation of the Dijkstra pathfinding algorithm,
// because these kind of problems will often occurs in Advent of Code

use crate::{debug, test};
use std::{cell::RefCell, cmp::min, fmt::Debug, ops::Deref, rc::Rc};

/// Custom types
Expand All @@ -12,9 +13,6 @@ type Edges<T> = Vec<Edge<T>>;
/// Reference counted mutable Node<T> (shared_ptr): https://doc.rust-lang.org/book/ch15-04-rc.html
type NodeRef<T> = Rc<RefCell<Node<T>>>;

/// Shorthand trait
trait Debuggable: PartialEq + Copy + Clone + Debug {}

/// A `Node` with a `state` and an optional `distance` to some starting `Node`.
#[derive(Clone, Copy, Debug)]
struct Node<T> {
Expand Down Expand Up @@ -61,14 +59,14 @@ impl<T> Edge<T> {
}
}

struct Graph<T: Debuggable> {
struct Graph<T: PartialEq + Clone + Debug> {
visited_nodes: NodeRefs<T>,
unvisited_nodes: NodeRefs<T>,
edges: Edges<T>,
starting_node: NodeRef<T>,
}

impl<T: Debuggable> Graph<T> {
impl<T: PartialEq + Clone + Debug> Graph<T> {
fn new(starting_state: T) -> Graph<T> {
let starting_node: NodeRef<T> = Node::new_ref(starting_state, Some(0));
starting_node.borrow_mut().visited = true;
Expand Down Expand Up @@ -96,7 +94,7 @@ impl<T: Debuggable> Graph<T> {
/// if such a `Node` already exists.
/// Returns a `NodeRef<T>` to the new or existing `Node`.
fn insert_node(&mut self, state: T) -> NodeRef<T> {
let node_ref_option = self.get_node_ref(state);
let node_ref_option = self.get_node_ref(state.clone());
match node_ref_option {
Some(node_ref) => node_ref,
_ => {
Expand Down Expand Up @@ -126,7 +124,7 @@ impl<T: Debuggable> Graph<T> {
/// Visit a single Node with a specified state, and update all of it's unvisited neighbours with the shortest_distance to those Nodes.
/// Will panic if the Node cannot be visited.
fn visit(&mut self, state: &T) {
let current_node_ref_option: Option<NodeRef<T>> = self.get_node_ref(*state);
let current_node_ref_option: Option<NodeRef<T>> = self.get_node_ref(state.clone());
match current_node_ref_option {
Some(current_node_ref) => {
match current_node_ref.borrow().visited {
Expand Down Expand Up @@ -154,20 +152,22 @@ impl<T: Debuggable> Graph<T> {
/// Visit a `NodeRef<T>` that is assumed to be valid, meaning that it exists, is unvisited, and has a distance value.
/// Will update all unvisited neighbours of the `Node` with the shortest distance to those `Nodes`, or panic
fn visit_valid_node_ref(&mut self, node_ref: NodeRef<T>) {
debug!(true, "visit_valid_node_ref({:?})", node_ref);
// Remove current_node from unvisited_nodes, and add to visited_nodes.
self.unvisited_nodes
.retain(|node| *node.borrow() != *node_ref.borrow());
self.visited_nodes.push(Rc::clone(&node_ref));
node_ref.borrow_mut().visited = true;

// Update all unvisited neighbours with the shortest distance to that node
let edges_to_neighbours: Edges<T> = self.get_edges(node_ref.borrow().state);
let distance = node_ref.borrow().distance_option.unwrap();
let edges_to_neighbours: Edges<T> = self.get_edges(node_ref.borrow().state.clone());
let distance_to_current_node = node_ref.borrow().distance_option.unwrap();
for edge in edges_to_neighbours {
if !edge.second.borrow().visited {
let distance_to_neighbour = distance_to_current_node + edge.distance;
let shortest_distance: Distance = match edge.second.borrow().distance_option {
Some(other_distance) => min(distance, other_distance),
None => distance,
Some(previous_distance) => min(distance_to_neighbour, previous_distance),
None => distance_to_neighbour,
};
edge.second.borrow_mut().distance_option = Some(shortest_distance);
}
Expand Down Expand Up @@ -204,6 +204,57 @@ impl<T: Debuggable> Graph<T> {
/// Run (Dijkstra) pathfinding algorithm to find shortest distance from self.starting_node to all other Nodes.
pub fn run_pathfinding_algorithm(&mut self) {
self.visit_valid_node_ref(Rc::clone(&self.starting_node));
//TODO: Continue here
let mut next_node_option: Option<NodeRef<T>> = self.get_next_node_to_visit();
while let Some(next_node) = &next_node_option {
self.visit_valid_node_ref(Rc::clone(next_node));
next_node_option = self.get_next_node_to_visit();
}
// We have now visited all unvisited Nodes that were reachable
if !self.unvisited_nodes.is_empty() {
panic!(
"Not all Nodes have been visited, '{}' are unreachable, this should not occur.",
self.unvisited_nodes.len()
);
}
}

fn get_distance(&self, state: T) -> DistanceOption {
let node_ref_option: Option<NodeRef<T>> = self.get_node_ref(state);
match node_ref_option {
Some(node_ref) => node_ref.borrow().distance_option,
_ => None,
}
}
}

#[test]
fn test_case_a() {
let mut graph: Graph<&str> = Graph::new("a");
let edges = vec![
("a", "b", 3),
("b", "c", 2),
("b", "d", 4),
("c", "e", 4),
("e", "b", 10),
("a", "d", 8),
("d", "f", 1),
("f", "g", 5),
("g", "f", 2),
];
edges.iter().for_each(|e| graph.add_edge(e.0, e.1, e.2));
graph.run_pathfinding_algorithm();
dbg!(&graph.unvisited_nodes);
dbg!(&graph.visited_nodes);
let distances = vec![
("a", 0),
("b", 3),
("c", 5),
("d", 7),
("e", 9),
("f", 8),
("g", 13),
];
distances.iter().for_each(|t| {
test!(t.1, graph.get_distance(t.0).unwrap(), "{} == {}", t.0, t.1);
});
}

0 comments on commit ea0974d

Please sign in to comment.