diff --git a/Cargo.toml b/Cargo.toml index ad15569..d2a55b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xy_distance" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] clap = { version = "4.5.56", features = ["derive"] } diff --git a/src/algorithm.rs b/src/algorithm.rs new file mode 100644 index 0000000..e160e68 --- /dev/null +++ b/src/algorithm.rs @@ -0,0 +1,27 @@ +use std::collections::HashSet; + +fn held_karp() { + + +} + +#[derive(Eq, Hash, PartialEq)] +struct Point { + +} + +fn dp(subset: HashSet, endpoint: &Point) { + // Prüfen, ob Endpunkt Teil des Subsets ist + // Länge vom subset prüfen + // wenn Länge = 2 Base Case: return Distanz 0 bis Endpunkt + // wenn Länge > 2: + // bestimme nähesten Punkt (i) vom Endpunkt (j) + // return dp(subset 2, i) + Distant i zu j + // + +} + +fn contains(subset: HashSet, endpoint: &Point) -> bool { + subset.contains(endpoint) + +} \ No newline at end of file diff --git a/src/graph.rs b/src/graph.rs new file mode 100644 index 0000000..8abf101 --- /dev/null +++ b/src/graph.rs @@ -0,0 +1,34 @@ +// Graph representation using adjacency list +pub struct Graph { + pub adj: Vec>, // adjacency list: (neighbour, weight) + pub n: usize, // number of nodes +} + +impl Graph { + // Create new graph with n nodes + pub fn new(n: usize) -> Self { + Graph { + adj: vec![vec![]; n], + n, + } + } + + // Add undirected edge between u and v with weight w + pub fn add_edge(&mut self, u: usize, v: usize, w: f64) { + self.adj[u].push((v, w)); + self.adj[v].push((u, w)); + } + + // Create sample graph with 4 nodes and predefined edges + pub fn sample_graph() -> Self { + let mut graph = Graph::new(4); + // Based on original distances + graph.add_edge(0, 1, 5.0); + graph.add_edge(0, 2, 5.385164807134504); + graph.add_edge(0, 3, 5.385164807134504); + graph.add_edge(1, 2, 9.219544457292887); + graph.add_edge(1, 3, 6.324555320336759); + graph.add_edge(2, 3, 9.486832980505138); + graph + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7387785..51ac793 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,117 +1,39 @@ -// standard functionality for CLI args +// CLI argument parsing use clap::Parser; +mod path; +mod graph; -// A 2D point structure with floating-point xy-coordinates. -#[derive(Debug, Clone, Copy)] -struct Coordinate { - x: f64, - y: f64, -} - -// Implement methods for the Coordinate struct, including distance calculation -impl Coordinate { - fn distance_to(&self, other: &Coordinate) -> f64 { - let dx = self.x - other.x; - let dy = self.y - other.y; - (dx * dx + dy * dy).sqrt() - } -} - -// Sample set of coordinates for testing -fn sample_coordinates() -> Vec { - vec![ - Coordinate { x: 1.0, y: 2.0 }, - Coordinate { x: 4.0, y: 6.0 }, - Coordinate { x: -1.0, y: -3.0 }, - Coordinate { x: 6.0, y: 0.0 }, - ] -} - -// Parse the order string into a vector -fn parse_order(arg: &str) -> Result, String> { - - // Split the input string by non-digit characters and collect the digit chunks - let digit_chunks: Vec<&str> = arg - .split(|c: char | !c.is_ascii_digit()) - .filter(|s| !s.is_empty()) - .collect(); - - if digit_chunks.len() < 2 { - return Err("At least two indices are required.".into()); - } - - // Parse the digit chunks into 1-based indices - let one_based: Vec = digit_chunks - .iter() - .map(|s| s.parse::().map_err(|_| format! ("Not a number: \"{s}\""))) - .collect::, _>>() - .map_err(|_| "Failed to parse indices as usize.".to_string())?; - - // Convert 1-based indices to 0-based indices - let zero_based: Vec = one_based - .into_iter() - .map(|n| { - if n == 0 { - Err("Indices must be 1-based and greater than 0.".to_string()) - } else { - Ok(n - 1) - } - }) - .collect::, _>>()?; - -Ok(zero_based) -} - -// Calculate the total distance by summing distances between points in the given order -fn total_distance(coords: &[Coordinate], order: &[usize]) -> Result { - - if order.len() < 2 { - return Err("Order must contain at least two indices.".into()); - } - - let mut total = 0.0; - - for window in order.windows(2) { - let start_idx = window[0]; - let end_idx = window[1]; - - if start_idx >= coords.len() || end_idx >= coords.len() { - return Err(format!("Index out of bounds: {} or {}", start_idx, end_idx)); - } - - total += coords[start_idx].distance_to(&coords[end_idx]); - } - - Ok(total) -} - -// Define command-line arguments using clap +// Command-line arguments definition #[derive(Parser)] #[command(name = "xy_distance")] -#[command(about = "Calculate total distance for a sequence of xy-coordinates")] +#[command(about = "Find the shortest hamiltonian path through a set of coordinates")] + struct Args { - /// The order of coordinates to visit (e.g., "1 to 3 to 2 to 4") - order: String, + /// The starting node index (0-based) + start: usize, } -// Main function to parse arguments, compute the order, and calculate total distance +// Parse args, validate input, and compute shortest Hamiltonian path fn main() { let args = Args::parse(); - let order_str = &args.order; - let order = match parse_order(order_str) { - Ok(o) => o, - Err(e) => { - eprintln!("Error parsing order: {}", e); - std::process::exit(1); - } - }; - let coords = sample_coordinates(); - match total_distance(&coords, &order) { - Ok(distance) => println!("Total distance: {:.2}", distance), - Err(e) => { - eprintln!("Error calculating distance: {}", e); - std::process::exit(1); - } + let start = args.start; + let graph = graph::Graph::sample_graph(); + + // Validate start index + if start >= graph.n { + eprintln!("Error: Start index must be between 0 and {}", graph.n - 1); + std::process::exit(1); + } + + // Find and display result + let (path, cost) = path::find_hamiltonian_path(&graph, start); + + if cost.is_finite() { + let path_str = path.iter().map(|i| i.to_string()).collect::>().join(" -> "); + println!("Shortest Hamiltonian path: {} with total cost: {:.2}", path_str, cost); + } else { + eprintln!("No Hamiltonian path found."); + std::process::exit(1); } } \ No newline at end of file diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..3514d02 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,46 @@ +use crate::graph::Graph; + +// Find shortest Hamiltonian path starting from given node using DFS +pub fn find_hamiltonian_path(graph: &Graph, start: usize) -> (Vec,f64) { + let mut path = vec![start]; + let mut min_cost = f64::INFINITY; + let mut best_path = Vec::new(); + let mut visited = vec![false; graph.n]; + visited[start] = true; + + dfs(graph, start, &mut visited, &mut path, 0.0, &mut min_cost, &mut best_path); + + (best_path, min_cost) +} + +// Depth-first search to explore all possible paths +fn dfs( + graph: &Graph, + current: usize, + visited: &mut Vec, + path: &mut Vec, + cost: f64, + min_cost: &mut f64, + best_path: &mut Vec, +) { + // Update best path if complete path found + if path.len() == graph.n { + if cost < *min_cost { + *min_cost = cost; + *best_path = path.clone(); + } + return; + } + + // Explore unvisited neighbors + for &(neighbor, weight) in &graph.adj[current] { + if !visited[neighbor] { + visited[neighbor] = true; + path.push(neighbor); + dfs(graph, neighbor, visited, path, cost + weight, min_cost, best_path); + path.pop(); + visited[neighbor] = false; + } + } +} +