diff --git a/Cargo.lock b/Cargo.lock index 1830323..60924ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ dependencies = [ "geo 0.12.0 (git+https://github.com/FliegendeWurst/geo)", "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pathfinding 1.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "rstar 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "svg 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -81,6 +82,11 @@ dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "failure" version = "0.1.5" @@ -101,6 +107,11 @@ dependencies = [ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fixedbitset" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "float-cmp" version = "0.4.0" @@ -146,6 +157,19 @@ dependencies = [ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "indexmap" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lazy_static" version = "1.3.0" @@ -185,6 +209,17 @@ dependencies = [ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pathfinding" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pdqselect" version = "0.1.0" @@ -412,19 +447,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum float-cmp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "134a8fa843d80a51a5b77d36d42bc2def9edcb0262c914861d08129fd1926600" "checksum geo 0.12.0 (git+https://github.com/FliegendeWurst/geo)" = "" "checksum geo-types 0.4.1 (git+https://github.com/FliegendeWurst/geo)" = "" "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" +"checksum pathfinding 1.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "37691aaf6640549d85ed79575cb159843b07380d420aac9e891b627e7cc3f1f3" "checksum pdqselect 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ec91767ecc0a0bbe558ce8c9da33c068066c57ecc8bb8477ef8c1ad3ef77c27" "checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" diff --git a/Cargo.toml b/Cargo.toml index 4ca8209..0df73d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ svgdom = "0.16.1" rstar = "0.2.0" lazy_static = "1.3.0" hashbrown = "0.1.8" +pathfinding = "1.1.12" diff --git a/src/main.rs b/src/main.rs index 11d54b8..25ea7f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,15 @@ use chrono::{Duration, NaiveTime}; use geo::prelude::*; use hashbrown::HashMap; use lazy_static::lazy_static; +use pathfinding::directed::dijkstra::dijkstra; use rstar::{RTree, RTreeObject}; use structopt::StructOpt; use std::cmp; -use std::collections::BinaryHeap; use std::f32; +use std::hash::{Hash, Hasher}; use std::process::exit; use std::sync::RwLock; -use std::time::{self, Instant}; mod display; mod input; @@ -26,10 +26,10 @@ type Polygon = geo::Polygon; struct Opt { /// Debug-Modus aktivieren #[structopt(short = "d", long = "debug")] - debug: bool, + _debug: bool, /// Zwischenlösungen speichern #[structopt(short = "s", long = "save")] - save_intermediates: bool, + _save_intermediates: bool, /// Beste Lösung als tkz-Code ausgeben #[structopt(short = "t", long = "tkz")] tkz: bool, @@ -42,7 +42,7 @@ struct Opt { } /// Möglicher Zustand von Lisas Rennen -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] struct RunState { /// Theoretisch mögliche Startposition des Busses bus: f32, @@ -52,15 +52,16 @@ struct RunState { total_distance: f32, } /// Trait-Implementierung, damit die Struktur in BinaryHeaps passt -impl cmp::Eq for RunState {} -impl cmp::Ord for RunState { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.bus.partial_cmp(&other.bus).unwrap() +impl PartialEq for RunState { + fn eq(&self, other: &Self) -> bool { + self.pos.x() == other.pos.x() && self.pos.y() == other.pos.y() } } -impl cmp::PartialOrd for RunState { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl cmp::Eq for RunState {} +impl Hash for RunState { + fn hash(&self, state: &mut H) { + (self.pos.x() as u32).hash(state); + (self.pos.y() as u32).hash(state); } } @@ -130,43 +131,24 @@ fn main() { calc_time(distance(meeting_point, house), meeting_point.y()) ); - // Zustände in Max-Heap sortieren - let mut states = BinaryHeap::new(); - states.push(vec![start]); + let mut best = dijkstra( + &vec![start], + |state| { + let last = &state[0]; + // neue Zustände + let mut all = vec![]; - // beste Lösung speichern - let mut best_bus = f32::NEG_INFINITY; - let mut best = vec![]; - // Zwischenlösungen speichern - let save_prefix = "route_"; - let mut save_counter = 0; - // Debug-Statistiken - let mut total_time = time::Duration::from_secs(0); - let mut iterations = 0; - - // weitersuchen, bis kein besserer Zustand mehr vorhanden ist - while states.peek().map(|x| x[0].bus > best_bus) == Some(true) { - let start = Instant::now(); - // besten Zustand einlesen - let state = states.pop().unwrap(); - let last = &state[0]; - - // neue Zustände - let mut all = vec![]; - - // versuche, zu jeder anderen Ecke zu gehen - for &next in points - .iter() - // Ecke sollte nicht schon in Route sein - .filter(|&&next| !state.iter().any(|p| p.pos == next)) - // Weg zu dieser Ecke sollte kein Polygon schneiden - .filter(|&&next| none_intersect(Line::new(last.pos, next))) - { - // Lisa könnte zu dieser Ecke rennen - let total_distance = last.total_distance + distance(last.pos, next); - let bus = best_bus_pos(&opt, total_distance, next); - // theoretisch möglicher Startzeitpunkt ist nach bestem gefundenem Startzeitpunkt - if bus > best_bus { + // versuche, zu jeder anderen Ecke zu gehen + for &next in points + .iter() + // Ecke sollte nicht schon in Route sein + .filter(|&&next| !state.iter().any(|p| p.pos == next)) + // Weg zu dieser Ecke sollte kein Polygon schneiden + .filter(|&&next| none_intersect(Line::new(last.pos, next))) + { + // Lisa könnte zu dieser Ecke rennen + let total_distance = last.total_distance + distance(last.pos, next); + let bus = best_bus_pos(&opt, total_distance, next); let mut route = state.clone(); route.insert( 0, @@ -176,23 +158,21 @@ fn main() { total_distance, }, ); - all.push(route); + assert!(last.bus >= bus); + all.push((route, ((last.bus - bus) * 1000.0) as u32)); } - } - // versuche, direkt zum Bus zu gehen - // Lisa trifft Bus in einem bestimmten Winkel (meist 60°) - let next = Point::new( - 0.0, - last.pos.y() + (opt.lisa / opt.bus).asin().tan() * last.pos.x(), - ); - let line = Line::new(last.pos, next); - // freier Weg? - if none_intersect(line) { - let line_length = line.end_point().euclidean_distance(&line.start_point()); - let total_distance = last.total_distance + line_length; - let bus = best_bus_pos(&opt, total_distance, line.end_point()); - if bus > best_bus { - // neue beste Wartezeit + // versuche, direkt zum Bus zu gehen + // Lisa trifft Bus in einem bestimmten Winkel (meist 60°) + let next = Point::new( + 0.0, + last.pos.y() + (opt.lisa / opt.bus).asin().tan() * last.pos.x(), + ); + let line = Line::new(last.pos, next); + // freier Weg? + if none_intersect(line) { + let line_length = line.end_point().euclidean_distance(&line.start_point()); + let total_distance = last.total_distance + line_length; + let bus = best_bus_pos(&opt, total_distance, line.end_point()); let mut route = state.clone(); route.insert( 0, @@ -202,43 +182,18 @@ fn main() { total_distance, }, ); - // Verbesserung anzeigen - eprintln!( - "Verbesserung: {:?} ({:?}+ Möglichkeiten verbleiben)", - calc_time(total_distance, line.end.y), - states.len() - ); - best = route; - best.reverse(); - best_bus = bus; - if opt.save_intermediates { - // Zwischenlösung als SVG speichern - display::save_svg( - opt, - &format!("{}{}.svg", save_prefix, save_counter), - house, - &polys, - &best, - ); - save_counter += 1; - } + all.push((route, 0)); } - } - // neu gefundene Routen speichern - // -> automatisch im BinaryHeap einsortiert - states.extend(all); - let end = Instant::now(); - total_time += end - start; - iterations += 1; - } - if opt.debug { - // Performanz-Schätzung anzeigen - eprintln!( - "DEBUG: {}us/Iteration mit {} Iterationen", - total_time.as_micros() / iterations, - iterations - ); - } + // neu gefundene Routen speichern + all + }, + |node| node[0].pos.x() == 0.0, + ) + .map(|x| (x.0).last().cloned()) + .unwrap() + .unwrap(); + best.reverse(); + // beste Lösung ausgeben eprintln!("Finale Lösung:"); let route = best;