Edge cases, more comments and formatting

This commit is contained in:
Arne Keller 2019-03-31 17:47:46 +02:00
parent 56a613fb88
commit 573788fbfc
3 changed files with 115 additions and 70 deletions

View File

@ -82,7 +82,13 @@ pub(crate) fn dump_route(opt: Opt, house: Point, polys: &[Polygon], route: &[Run
); );
} }
pub(crate) fn save_svg(opt: Opt, filename: &str, house: Point, polys: &[Polygon], route: &[RunState]) { pub(crate) fn save_svg(
opt: Opt,
filename: &str,
house: Point,
polys: &[Polygon],
route: &[RunState],
) {
let (points, lines, route1, route2_start) = gen_params(opt, house, route); let (points, lines, route1, route2_start) = gen_params(opt, house, route);
fs::write( fs::write(
filename, filename,
@ -103,9 +109,7 @@ fn gen_params(
Point, Point,
) { ) {
let last = route.last().unwrap(); let last = route.last().unwrap();
let points = vec![ let points = vec![(house, "red")];
(house, "red"),
];
let lines = route let lines = route
.iter() .iter()
.map(|x| x.pos) .map(|x| x.pos)
@ -115,7 +119,9 @@ fn gen_params(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let route1 = route.iter().map(|x| x.pos).collect::<Vec<_>>(); let route1 = route.iter().map(|x| x.pos).collect::<Vec<_>>();
let route2_start = last.pos.translate(0.0, -last.total_distance * (opt.bus/opt.lisa)); let route2_start = last
.pos
.translate(0.0, -last.total_distance * (opt.bus / opt.lisa));
(points, lines, route1, route2_start) (points, lines, route1, route2_start)
} }
@ -127,8 +133,25 @@ pub(crate) fn generate_svg(
route2_start: Point, route2_start: Point,
) -> String { ) -> String {
let dot_radius = 25.2; let dot_radius = 25.2;
let width = route1.iter().map(|p| p.x()).chain(polys.iter().map(|x| x.exterior().0.iter().map(|p| p.x)).flatten()).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap() + dot_radius * 2.0; let width = route1
let upper_y = polys.iter().map(|x| x.exterior().0.iter().map(|p| p.y)).flatten().chain(Some(route1.last().unwrap().y())).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap(); .iter()
.map(|p| p.x())
.chain(
polys
.iter()
.map(|x| x.exterior().0.iter().map(|p| p.x))
.flatten(),
)
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap()
+ dot_radius * 2.0;
let upper_y = polys
.iter()
.map(|x| x.exterior().0.iter().map(|p| p.y))
.flatten()
.chain(Some(route1.last().unwrap().y()))
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap();
let mut document = Document::new() let mut document = Document::new()
// view box at (x,y), (w,h) // view box at (x,y), (w,h)
.set( .set(
@ -137,8 +160,8 @@ pub(crate) fn generate_svg(
-dot_radius, -dot_radius,
-upper_y - dot_radius, -upper_y - dot_radius,
width, width,
-route2_start.y() + upper_y + dot_radius * 2.0 -route2_start.y() + upper_y + dot_radius * 2.0,
) ),
) )
.set("xmlns:xlink", "http://www.w3.org/1999/xlink"); .set("xmlns:xlink", "http://www.w3.org/1999/xlink");

View File

@ -60,43 +60,19 @@ fn read_stdin() -> InputData {
fn _custom_input() -> InputData { fn _custom_input() -> InputData {
let mut polys = vec![]; let mut polys = vec![];
polys.push(Polygon::new( polys.push(Polygon::new(
vec![ vec![[2.0, 0.0], [6.0, 0.0], [6.0, 6.0], [2.0, 5.0]].into(),
[2.0, 0.0],
[6.0, 0.0],
[6.0, 6.0],
[2.0, 5.0],
]
.into(),
vec![], vec![],
)); ));
polys.push(Polygon::new( polys.push(Polygon::new(
vec![ vec![[1.0, 20.0], [2.0, 20.0], [2.0, 23.0], [1.0, 23.0]].into(),
[1.0, 20.0],
[2.0, 20.0],
[2.0, 23.0],
[1.0, 23.0],
]
.into(),
vec![], vec![],
)); ));
polys.push(Polygon::new( polys.push(Polygon::new(
vec![ vec![[1.0, 25.0], [2.0, 25.0], [2.0, 28.0], [1.0, 28.0]].into(),
[1.0, 25.0],
[2.0, 25.0],
[2.0, 28.0],
[1.0, 28.0],
]
.into(),
vec![], vec![],
)); ));
let mut stick = Polygon::new( let mut stick = Polygon::new(
vec![ vec![[1.0, 115.0], [2.0, 115.0], [2.0, 130.0], [1.0, 130.0]].into(),
[1.0, 115.0],
[2.0, 115.0],
[2.0, 130.0],
[1.0, 130.0],
]
.into(),
vec![], vec![],
); );
polys.push(stick.clone()); polys.push(stick.clone());

View File

@ -7,6 +7,7 @@ use structopt::StructOpt;
use std::cmp; use std::cmp;
use std::collections::BinaryHeap; use std::collections::BinaryHeap;
use std::f64; use std::f64;
use std::process::exit;
mod display; mod display;
mod input; mod input;
@ -16,39 +17,42 @@ type Line = geo::Line<f64>;
type LineString = geo::LineString<f64>; type LineString = geo::LineString<f64>;
type Polygon = geo::Polygon<f64>; type Polygon = geo::Polygon<f64>;
/// Datenstruktur für Kommandozeilen-Argumente
#[derive(StructOpt, Clone, Copy)] #[derive(StructOpt, Clone, Copy)]
#[structopt(name = "aufgabe1", about = "Implementierung für Aufgabe 1")] #[structopt(name = "aufgabe1", about = "Implementierung für Aufgabe 1")]
struct Opt { struct Opt {
/// Debug-Modus aktivieren /// Debug-Modus aktivieren
#[structopt(short = "d", long = "debug")] #[structopt(short = "d", long = "debug")]
debug: bool, debug: bool,
/// Zwischenlösungen speichern /// Zwischenlösungen speichern
#[structopt(short = "s", long = "save")] #[structopt(short = "s", long = "save")]
save_intermediates: bool, save_intermediates: bool,
/// Beste Lösung als tkz-Code ausgeben /// Beste Lösung als tkz-Code ausgeben
#[structopt(short = "t", long = "tkz")] #[structopt(short = "t", long = "tkz")]
tkz: bool, tkz: bool,
/// Geschwindigkeit von Lisa (in km/h) /// Geschwindigkeit von Lisa (in km/h)
#[structopt(short = "l", long = "lisa", default_value = "15")] #[structopt(short = "l", long = "lisa", default_value = "15")]
lisa: f64, lisa: f64,
/// Geschwindigkeit vom Bus (in km/h) /// Geschwindigkeit vom Bus (in km/h)
#[structopt(short = "b", long = "bus", default_value = "30")] #[structopt(short = "b", long = "bus", default_value = "30")]
bus: f64, bus: f64,
} }
/// Möglicher Zustand von Lisas Rennen
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
struct RunState { struct RunState {
/// Theoretisch mögliche Wartezeit /// Theoretisch mögliche Startposition des Busses
delay: f64, bus: f64,
/// Lisas Position /// Lisas Position
pos: Point, pos: Point,
// Bisher zurückgelegte Strecke // Bisher zurückgelegte Strecke
total_distance: f64, total_distance: f64,
} }
/// Trait-Implementierung, damit die Struktur in BinaryHeaps passt
impl cmp::Eq for RunState {} impl cmp::Eq for RunState {}
impl cmp::Ord for RunState { impl cmp::Ord for RunState {
fn cmp(&self, other: &Self) -> cmp::Ordering { fn cmp(&self, other: &Self) -> cmp::Ordering {
self.delay.partial_cmp(&other.delay).unwrap() self.bus.partial_cmp(&other.bus).unwrap()
} }
} }
impl cmp::PartialOrd for RunState { impl cmp::PartialOrd for RunState {
@ -60,13 +64,30 @@ impl cmp::PartialOrd for RunState {
fn main() { fn main() {
// Kommandozeilen-Argumente einlesen // Kommandozeilen-Argumente einlesen
let opt = Opt::from_args(); let opt = Opt::from_args();
// (unwahrscheinlich)
if opt.lisa >= opt.bus {
eprintln!("Lisa sollte lieber zur Schule rennen, als den Bus zu nehmen.");
exit(1);
}
// (noch unwahrscheinlicher)
if opt.bus <= 0.0 {
if opt.lisa > 0.0 {
eprintln!("Lisa sollte lieber zur Schule rennen, da der Bus vermutlich eine Panne hat oder im Rückwärtsgang ist.");
} else {
eprintln!("Lisa kann nicht laufen.");
}
exit(1);
} else if opt.lisa <= 0.0 {
eprintln!("Lisa muss den Busfahrer bitten, zu ihrem Haus zu fahren.");
exit(1);
}
// Geschwindigkeiten in m/s umrechnen // Geschwindigkeiten in m/s umrechnen
let bus_speed = opt.bus / 3.6; let bus_speed = opt.bus / 3.6;
let lisa_speed = opt.lisa / 3.6; let lisa_speed = opt.lisa / 3.6;
// Startzeitpunkt berechnen // Startzeitpunkt berechnen
let calc_time = |total_distance, y| { let calc_time = |total_distance, y| {
NaiveTime::from_hms(7, 30, 0) NaiveTime::from_hms(7, 30, 0)
- Duration::seconds(((total_distance * (opt.bus/opt.lisa) - y) / bus_speed) as i64) - Duration::seconds(((total_distance * (opt.bus / opt.lisa) - y) / bus_speed) as i64)
}; };
// Ankuftszeit berechnen // Ankuftszeit berechnen
let calc_end_time = |total_distance, y| { let calc_end_time = |total_distance, y| {
@ -88,12 +109,15 @@ fn main() {
// Startzustand: Lisa ist an ihrem Haus, zurückgelegte Distanz ist Null // Startzustand: Lisa ist an ihrem Haus, zurückgelegte Distanz ist Null
let start = RunState { let start = RunState {
pos: house, pos: house,
delay: max_possible_delay(&opt, 0.0, house), bus: best_bus_pos(&opt, 0.0, house),
total_distance: 0.0, total_distance: 0.0,
}; };
// bestmöglicher Fall: Lisa geht direkt zum Bus // bestmöglicher Fall: Lisa geht direkt zum Bus
let meeting_point = Point::new(0.0, house.y() + (opt.lisa/opt.bus).asin().tan() * house.x()); let meeting_point = Point::new(
0.0,
house.y() + (opt.lisa / opt.bus).asin().tan() * house.x(),
);
eprintln!( eprintln!(
"Theoretisches Maximum: {:?}", "Theoretisches Maximum: {:?}",
calc_time(distance(meeting_point, house), meeting_point.y()) calc_time(distance(meeting_point, house), meeting_point.y())
@ -104,14 +128,14 @@ fn main() {
states.push(vec![start]); states.push(vec![start]);
// beste Lösung speichern // beste Lösung speichern
let mut best_delay = f64::NEG_INFINITY; let mut best_bus = f64::NEG_INFINITY;
let mut best = vec![]; let mut best = vec![];
// Zwischenlösungen speichern // Zwischenlösungen speichern
let save_prefix = "tmp_"; let save_prefix = "route_";
let mut save_counter = 0; let mut save_counter = 0;
// weitersuchen, bis kein besserer Zustand mehr vorhanden ist // weitersuchen, bis kein besserer Zustand mehr vorhanden ist
while states.peek().map(|x| x[0].delay > best_delay) == Some(true) { while states.peek().map(|x| x[0].bus > best_bus) == Some(true) {
// besten Zustand einlesen // besten Zustand einlesen
let state = states.pop().unwrap(); let state = states.pop().unwrap();
let last = &state[0]; let last = &state[0];
@ -132,14 +156,15 @@ fn main() {
{ {
// Lisa könnte zu dieser Ecke rennen // Lisa könnte zu dieser Ecke rennen
let total_distance = last.total_distance + distance(last.pos, next); let total_distance = last.total_distance + distance(last.pos, next);
let delay = max_possible_delay(&opt, total_distance, next); let bus = best_bus_pos(&opt, total_distance, next);
if delay > best_delay { // theoretisch möglicher Startzeitpunkt ist nach bestem gefundenem Startzeitpunkt
if bus > best_bus {
let mut route = state.clone(); let mut route = state.clone();
route.insert( route.insert(
0, 0,
RunState { RunState {
pos: next, pos: next,
delay, bus,
total_distance, total_distance,
}, },
); );
@ -147,29 +172,29 @@ fn main() {
} }
} }
// versuche, direkt zum Bus zu gehen // versuche, direkt zum Bus zu gehen
// Lisa trifft Bus mit 60°-Winkel // Lisa trifft Bus in einem bestimmten Winkel (meist 60°)
let next = Point::new( let next = Point::new(
0.0, 0.0,
last.pos.y() + (opt.lisa/opt.bus).asin().tan() * last.pos.x(), last.pos.y() + (opt.lisa / opt.bus).asin().tan() * last.pos.x(),
); );
let line = Line::new(last.pos, next); let line = Line::new(last.pos, next);
// freier Weg? // freier Weg?
if none_intersect(&polys, &line) { if none_intersect(&polys, &line) {
let line_length = line.end_point().euclidean_distance(&line.start_point()); let line_length = line.end_point().euclidean_distance(&line.start_point());
let total_distance = last.total_distance + line_length; let total_distance = last.total_distance + line_length;
let delay = max_possible_delay(&opt, total_distance, line.end_point()); let bus = best_bus_pos(&opt, total_distance, line.end_point());
if delay > best_delay { if bus > best_bus {
// neue beste Wartezeit // neue beste Wartezeit
let mut route = state.clone(); let mut route = state.clone();
route.insert( route.insert(
0, 0,
RunState { RunState {
pos: next, pos: next,
delay, bus,
total_distance, total_distance,
}, },
); );
// Verbesserung anzeigen und speichern // Verbesserung anzeigen und evtl. speichern
eprintln!( eprintln!(
"Verbesserung: {:?} ({:?}+ Möglichkeiten verbleiben)", "Verbesserung: {:?} ({:?}+ Möglichkeiten verbleiben)",
calc_time(total_distance, line.end.y), calc_time(total_distance, line.end.y),
@ -177,7 +202,7 @@ fn main() {
); );
best = route; best = route;
best.reverse(); best.reverse();
best_delay = delay; best_bus = bus;
if opt.save_intermediates { if opt.save_intermediates {
display::save_svg( display::save_svg(
opt, opt,
@ -233,12 +258,15 @@ fn main() {
} }
} }
/// Schneidet die Linie keines der Polygone?
/// (Berührungen erlaubt)
fn none_intersect(polys: &[Polygon], line: &Line) -> bool { fn none_intersect(polys: &[Polygon], line: &Line) -> bool {
let mut middle = line.start; let mut middle = line.start;
middle.x += line.dx() / 2.0; middle.x += line.dx() / 2.0;
middle.y += line.dy() / 2.0; middle.y += line.dy() / 2.0;
let middle: Point = middle.into(); let middle: Point = middle.into();
'poly: for p in polys { 'poly: for p in polys {
// Alle Seitenlinien des Polygons
for l in p.exterior().lines().chain(vec![Line::new( for l in p.exterior().lines().chain(vec![Line::new(
p.exterior().0[0], p.exterior().0[0],
*p.exterior().0.last().unwrap(), *p.exterior().0.last().unwrap(),
@ -246,7 +274,7 @@ fn none_intersect(polys: &[Polygon], line: &Line) -> bool {
if (l.start == line.start && l.end == line.end) if (l.start == line.start && l.end == line.end)
|| (l.start == line.end && l.end == line.start) || (l.start == line.end && l.end == line.start)
{ {
// point is on polygon border // Linie liegt auf Kante des Polygons
continue 'poly; continue 'poly;
} }
if l.start == line.start if l.start == line.start
@ -254,25 +282,43 @@ fn none_intersect(polys: &[Polygon], line: &Line) -> bool {
|| l.start == line.end || l.start == line.end
|| l.end == line.start || l.end == line.start
{ {
continue; // would always intersect with itself continue; // Linie schneidet sich immer selbst
} }
if l.intersects(line) { if l.intersects(line) {
return false; return false;
} }
} }
// falls Mittelpunkt in Polygon:
if p.euclidean_distance(&middle) == 0.0 { if p.euclidean_distance(&middle) == 0.0 {
// schneidet Polygon
return false; return false;
} }
// (dieser Fall tritt auf, wenn die Linie von einer Polygon-Ecke zu einer anderen Ecke verläuft)
} }
true true
} }
/// Euklidische Distanz zweier Punkte
fn distance(a: Point, b: Point) -> f64 { fn distance(a: Point, b: Point) -> f64 {
((a.x() - b.x()).powi(2) + (a.y() - b.y()).powi(2)).sqrt() ((a.x() - b.x()).powi(2) + (a.y() - b.y()).powi(2)).sqrt()
} }
fn max_possible_delay(opt: &Opt, total_distance: f64, lisa: Point) -> f64 { /// Mit dieser Funktion kann man bestimmen, wo der Bus zu Lisas Startzeit war.
let x_l = lisa.x(); /// Die Funktion nimmt an, dass Lisa, von ihrer aktuellen Position aus, den Bus ohne Umwege erreichen kann.
let y_l = lisa.y(); /// Sehr hilfreich als Heuristik, da sie nie schlechter als das eigentliche Ergebnis sein kann.
y_l - (((opt.bus/opt.lisa).powi(2) - 1.0) * x_l.powi(2)).sqrt() - total_distance * (opt.bus/opt.lisa) fn best_bus_pos(opt: &Opt, total_distance: f64, lisa: Point) -> f64 {
let x_l = lisa.x(); // a
let y_l = lisa.y(); // b
// y-Koordinate des Busses: d
// Länge, die Lisa noch laufen muss: l
// Verhältnis beider Geschwindigkeiten: x = bus / lisa
// l^2 = sqrt(a^2 + (b - xl - d)^2)
// die quadratische Gleichung hat zwei Lösungen (siehe Dok.)
// nach der ABC-Formel muss b^2 - 4ac = 0 sein, damit die Gleichung nur eine Lösung hat:
// (2xb-2xd)^2 - 4*((1-x^2)*(-a^2-b^2-d^2+2bd)) = 0
// (eine WolframAlpha-Eingabe später..)
// d = sqrt(a^2 * (x^2 - 1)) + b
// in dieser Berechnung wird auch die bereits zurückgelegte Strecke berücksichtigt:
y_l - (((opt.bus / opt.lisa).powi(2) - 1.0) * x_l.powi(2)).sqrt()
- total_distance * (opt.bus / opt.lisa)
} }