use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use super::graph_store::GraphStore;
#[derive(Debug, Clone)]
pub struct Path {
pub nodes: Vec<String>,
pub total_weight: f64,
pub edge_types: Vec<String>,
}
impl Path {
pub fn start(node: &str) -> Self {
Self {
nodes: vec![node.to_string()],
total_weight: 0.0,
edge_types: Vec::new(),
}
}
pub fn extend(&self, node: &str, edge_label: impl Into<String>, weight: f64) -> Self {
let mut nodes = self.nodes.clone();
nodes.push(node.to_string());
let mut edge_types = self.edge_types.clone();
edge_types.push(edge_label.into());
Self {
nodes,
total_weight: self.total_weight + weight,
edge_types,
}
}
pub fn len(&self) -> usize {
self.nodes.len().saturating_sub(1)
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn source(&self) -> Option<&str> {
self.nodes.first().map(|s| s.as_str())
}
pub fn target(&self) -> Option<&str> {
self.nodes.last().map(|s| s.as_str())
}
}
#[derive(Debug, Clone)]
pub struct ShortestPathResult {
pub path: Option<Path>,
pub nodes_visited: usize,
}
impl ShortestPathResult {
pub fn found(&self) -> bool {
self.path.is_some()
}
pub fn distance(&self) -> Option<usize> {
self.path.as_ref().map(|p| p.len())
}
pub fn total_weight(&self) -> Option<f64> {
self.path.as_ref().map(|p| p.total_weight)
}
}
#[derive(Debug, Clone)]
pub struct AllPathsResult {
pub paths: Vec<Path>,
pub nodes_visited: usize,
}
pub struct BFS;
mod bfs_impl;
pub struct DFS;
mod dfs_impl;
#[derive(Clone)]
struct DijkstraState {
node: String,
cost: f64,
path: Path,
}
impl Eq for DijkstraState {}
impl PartialEq for DijkstraState {
fn eq(&self, other: &Self) -> bool {
self.node == other.node && (self.cost - other.cost).abs() < f64::EPSILON
}
}
impl Ord for DijkstraState {
fn cmp(&self, other: &Self) -> Ordering {
other
.cost
.partial_cmp(&self.cost)
.unwrap_or(Ordering::Equal)
}
}
impl PartialOrd for DijkstraState {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub struct Dijkstra;
mod dijkstra_impl;
#[derive(Clone)]
struct AStarState {
node: String,
g_cost: f64, f_cost: f64, path: Path,
}
impl Eq for AStarState {}
impl PartialEq for AStarState {
fn eq(&self, other: &Self) -> bool {
self.node == other.node && (self.f_cost - other.f_cost).abs() < f64::EPSILON
}
}
impl Ord for AStarState {
fn cmp(&self, other: &Self) -> Ordering {
other
.f_cost
.partial_cmp(&self.f_cost)
.unwrap_or(Ordering::Equal)
}
}
impl PartialOrd for AStarState {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub struct AStar;
mod astar_impl;
#[derive(Debug, Clone)]
pub struct BellmanFordResult {
pub path: Option<Path>,
pub distances: HashMap<String, f64>,
pub has_negative_cycle: bool,
pub nodes_visited: usize,
}
pub struct BellmanFord;
mod bellman_ford_impl;
pub struct KShortestPaths;
mod k_shortest_impl;
struct PathCandidate {
path: Path,
}
impl Eq for PathCandidate {}
impl PartialEq for PathCandidate {
fn eq(&self, other: &Self) -> bool {
(self.path.total_weight - other.path.total_weight).abs() < f64::EPSILON
}
}
impl Ord for PathCandidate {
fn cmp(&self, other: &Self) -> Ordering {
other
.path
.total_weight
.partial_cmp(&self.path.total_weight)
.unwrap_or(Ordering::Equal)
}
}
impl PartialOrd for PathCandidate {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub struct AllShortestPaths;
mod all_shortest_impl;
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::query::test_support::{add_edge_or_panic, add_node_or_panic};
fn create_simple_graph() -> GraphStore {
let graph = GraphStore::new();
add_node_or_panic(&graph, "A", "Node A", "host");
add_node_or_panic(&graph, "B", "Node B", "host");
add_node_or_panic(&graph, "C", "Node C", "host");
add_node_or_panic(&graph, "D", "Node D", "host");
add_node_or_panic(&graph, "E", "Node E", "host");
add_edge_or_panic(&graph, "A", "B", "connects_to", 1.0);
add_edge_or_panic(&graph, "B", "C", "connects_to", 1.0);
add_edge_or_panic(&graph, "A", "D", "connects_to", 1.0);
add_edge_or_panic(&graph, "B", "E", "connects_to", 1.0);
add_edge_or_panic(&graph, "D", "E", "connects_to", 1.0);
graph
}
fn create_weighted_graph() -> GraphStore {
let graph = GraphStore::new();
add_node_or_panic(&graph, "A", "Node A", "host");
add_node_or_panic(&graph, "B", "Node B", "host");
add_node_or_panic(&graph, "C", "Node C", "host");
add_node_or_panic(&graph, "D", "Node D", "host");
add_node_or_panic(&graph, "E", "Node E", "host");
add_edge_or_panic(&graph, "A", "B", "connects_to", 1.0);
add_edge_or_panic(&graph, "A", "C", "connects_to", 4.0);
add_edge_or_panic(&graph, "B", "D", "connects_to", 2.0);
add_edge_or_panic(&graph, "C", "E", "connects_to", 1.0);
add_edge_or_panic(&graph, "E", "B", "connects_to", 1.0);
graph
}
#[test]
fn test_bfs_shortest_path() {
let graph = create_simple_graph();
let result = BFS::shortest_path(&graph, "A", "C");
assert!(result.found());
assert_eq!(result.distance(), Some(2)); }
#[test]
fn test_bfs_same_source_target() {
let graph = create_simple_graph();
let result = BFS::shortest_path(&graph, "A", "A");
assert!(result.found());
assert_eq!(result.distance(), Some(0));
}
#[test]
fn test_bfs_no_path() {
let graph = GraphStore::new();
add_node_or_panic(&graph, "A", "A", "host");
add_node_or_panic(&graph, "B", "B", "host");
let result = BFS::shortest_path(&graph, "A", "B");
assert!(!result.found());
}
#[test]
fn test_bfs_reachable() {
let graph = create_simple_graph();
let reachable = BFS::reachable(&graph, "A", 2);
assert!(reachable.iter().any(|(n, _)| n == "A"));
assert!(reachable.iter().any(|(n, _)| n == "B"));
assert!(reachable.iter().any(|(n, _)| n == "D"));
}
#[test]
fn test_dfs_find_path() {
let graph = create_simple_graph();
let result = DFS::find_path(&graph, "A", "E");
assert!(result.found());
}
#[test]
fn test_dfs_all_paths() {
let graph = create_simple_graph();
let result = DFS::all_paths(&graph, "A", "E", 3);
assert!(!result.paths.is_empty());
assert!(result.paths.len() >= 2);
}
#[test]
fn test_dfs_topological_sort() {
let graph = GraphStore::new();
add_node_or_panic(&graph, "A", "A", "host");
add_node_or_panic(&graph, "B", "B", "host");
add_node_or_panic(&graph, "C", "C", "host");
add_edge_or_panic(&graph, "A", "B", "connects_to", 1.0);
add_edge_or_panic(&graph, "B", "C", "connects_to", 1.0);
let result = DFS::topological_sort(&graph);
assert!(result.is_some());
let order = result.unwrap();
let a_pos = order.iter().position(|n| n == "A").unwrap();
let b_pos = order.iter().position(|n| n == "B").unwrap();
let c_pos = order.iter().position(|n| n == "C").unwrap();
assert!(a_pos < b_pos);
assert!(b_pos < c_pos);
}
#[test]
fn test_dijkstra_weighted() {
let graph = create_weighted_graph();
let result = Dijkstra::shortest_path(&graph, "A", "D");
assert!(result.found());
assert_eq!(result.total_weight(), Some(3.0));
}
#[test]
fn test_dijkstra_all_paths() {
let graph = create_weighted_graph();
let paths = Dijkstra::shortest_paths_from(&graph, "A");
assert!(paths.contains_key("A"));
assert!(paths.contains_key("B"));
assert!(paths.contains_key("D"));
}
#[test]
fn test_astar_zero_heuristic() {
let graph = create_weighted_graph();
let result = AStar::shortest_path_no_heuristic(&graph, "A", "D");
assert!(result.found());
assert_eq!(result.total_weight(), Some(3.0));
}
#[test]
fn test_bellman_ford_positive() {
let graph = create_weighted_graph();
let result = BellmanFord::shortest_path(&graph, "A", "D");
assert!(!result.has_negative_cycle);
assert!(result.path.is_some());
}
#[test]
fn test_k_shortest_paths() {
let graph = create_simple_graph();
let paths = KShortestPaths::find(&graph, "A", "E", 2);
assert!(!paths.is_empty());
}
#[test]
fn test_all_shortest_paths() {
let graph = create_simple_graph();
let result = AllShortestPaths::find(&graph, "A", "E");
assert!(result.paths.len() >= 2);
for path in &result.paths {
assert_eq!(path.len(), 2);
}
}
}