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);
fs::write(
filename,
@ -103,9 +109,7 @@ fn gen_params(
Point,
) {
let last = route.last().unwrap();
let points = vec![
(house, "red"),
];
let points = vec![(house, "red")];
let lines = route
.iter()
.map(|x| x.pos)
@ -115,7 +119,9 @@ fn gen_params(
.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)
}
@ -127,8 +133,25 @@ pub(crate) fn generate_svg(
route2_start: Point,
) -> String {
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 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 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 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()
// view box at (x,y), (w,h)
.set(
@ -137,8 +160,8 @@ pub(crate) fn generate_svg(
-dot_radius,
-upper_y - dot_radius,
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");

View File

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

View File

@ -7,6 +7,7 @@ use structopt::StructOpt;
use std::cmp;
use std::collections::BinaryHeap;
use std::f64;
use std::process::exit;
mod display;
mod input;
@ -16,39 +17,42 @@ type Line = geo::Line<f64>;
type LineString = geo::LineString<f64>;
type Polygon = geo::Polygon<f64>;
/// Datenstruktur für Kommandozeilen-Argumente
#[derive(StructOpt, Clone, Copy)]
#[structopt(name = "aufgabe1", about = "Implementierung für Aufgabe 1")]
struct Opt {
/// Debug-Modus aktivieren
#[structopt(short = "d", long = "debug")]
debug: bool,
/// Debug-Modus aktivieren
#[structopt(short = "d", long = "debug")]
debug: bool,
/// Zwischenlösungen speichern
#[structopt(short = "s", long = "save")]
save_intermediates: bool,
/// Beste Lösung als tkz-Code ausgeben
#[structopt(short = "t", long = "tkz")]
tkz: bool,
/// Geschwindigkeit von Lisa (in km/h)
#[structopt(short = "l", long = "lisa", default_value = "15")]
lisa: f64,
/// Geschwindigkeit von Lisa (in km/h)
#[structopt(short = "l", long = "lisa", default_value = "15")]
lisa: f64,
/// Geschwindigkeit vom Bus (in km/h)
#[structopt(short = "b", long = "bus", default_value = "30")]
bus: f64,
#[structopt(short = "b", long = "bus", default_value = "30")]
bus: f64,
}
/// Möglicher Zustand von Lisas Rennen
#[derive(Debug, Clone, Copy, PartialEq)]
struct RunState {
/// Theoretisch mögliche Wartezeit
delay: f64,
/// Theoretisch mögliche Startposition des Busses
bus: f64,
/// Lisas Position
pos: Point,
// Bisher zurückgelegte Strecke
total_distance: f64,
}
/// 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.delay.partial_cmp(&other.delay).unwrap()
self.bus.partial_cmp(&other.bus).unwrap()
}
}
impl cmp::PartialOrd for RunState {
@ -60,13 +64,30 @@ impl cmp::PartialOrd for RunState {
fn main() {
// Kommandozeilen-Argumente einlesen
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
let bus_speed = opt.bus / 3.6;
let lisa_speed = opt.lisa / 3.6;
// Startzeitpunkt berechnen
let calc_time = |total_distance, y| {
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
let calc_end_time = |total_distance, y| {
@ -88,12 +109,15 @@ fn main() {
// Startzustand: Lisa ist an ihrem Haus, zurückgelegte Distanz ist Null
let start = RunState {
pos: house,
delay: max_possible_delay(&opt, 0.0, house),
bus: best_bus_pos(&opt, 0.0, house),
total_distance: 0.0,
};
// 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!(
"Theoretisches Maximum: {:?}",
calc_time(distance(meeting_point, house), meeting_point.y())
@ -104,14 +128,14 @@ fn main() {
states.push(vec![start]);
// beste Lösung speichern
let mut best_delay = f64::NEG_INFINITY;
let mut best_bus = f64::NEG_INFINITY;
let mut best = vec![];
// Zwischenlösungen speichern
let save_prefix = "tmp_";
let save_prefix = "route_";
let mut save_counter = 0;
// 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
let state = states.pop().unwrap();
let last = &state[0];
@ -132,14 +156,15 @@ fn main() {
{
// Lisa könnte zu dieser Ecke rennen
let total_distance = last.total_distance + distance(last.pos, next);
let delay = max_possible_delay(&opt, total_distance, next);
if delay > best_delay {
let bus = best_bus_pos(&opt, total_distance, next);
// theoretisch möglicher Startzeitpunkt ist nach bestem gefundenem Startzeitpunkt
if bus > best_bus {
let mut route = state.clone();
route.insert(
0,
RunState {
pos: next,
delay,
bus,
total_distance,
},
);
@ -147,29 +172,29 @@ fn main() {
}
}
// 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(
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);
// freier Weg?
if none_intersect(&polys, &line) {
let line_length = line.end_point().euclidean_distance(&line.start_point());
let total_distance = last.total_distance + line_length;
let delay = max_possible_delay(&opt, total_distance, line.end_point());
if delay > best_delay {
let bus = best_bus_pos(&opt, total_distance, line.end_point());
if bus > best_bus {
// neue beste Wartezeit
let mut route = state.clone();
route.insert(
0,
RunState {
pos: next,
delay,
bus,
total_distance,
},
);
// Verbesserung anzeigen und speichern
// Verbesserung anzeigen und evtl. speichern
eprintln!(
"Verbesserung: {:?} ({:?}+ Möglichkeiten verbleiben)",
calc_time(total_distance, line.end.y),
@ -177,7 +202,7 @@ fn main() {
);
best = route;
best.reverse();
best_delay = delay;
best_bus = bus;
if opt.save_intermediates {
display::save_svg(
opt,
@ -233,12 +258,15 @@ fn main() {
}
}
/// Schneidet die Linie keines der Polygone?
/// (Berührungen erlaubt)
fn none_intersect(polys: &[Polygon], line: &Line) -> bool {
let mut middle = line.start;
middle.x += line.dx() / 2.0;
middle.y += line.dy() / 2.0;
let middle: Point = middle.into();
'poly: for p in polys {
// Alle Seitenlinien des Polygons
for l in p.exterior().lines().chain(vec![Line::new(
p.exterior().0[0],
*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)
|| (l.start == line.end && l.end == line.start)
{
// point is on polygon border
// Linie liegt auf Kante des Polygons
continue 'poly;
}
if l.start == line.start
@ -254,25 +282,43 @@ fn none_intersect(polys: &[Polygon], line: &Line) -> bool {
|| l.start == line.end
|| l.end == line.start
{
continue; // would always intersect with itself
continue; // Linie schneidet sich immer selbst
}
if l.intersects(line) {
return false;
}
}
// falls Mittelpunkt in Polygon:
if p.euclidean_distance(&middle) == 0.0 {
// schneidet Polygon
return false;
}
// (dieser Fall tritt auf, wenn die Linie von einer Polygon-Ecke zu einer anderen Ecke verläuft)
}
true
}
/// Euklidische Distanz zweier Punkte
fn distance(a: Point, b: Point) -> f64 {
((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 {
let x_l = lisa.x();
let y_l = lisa.y();
y_l - (((opt.bus/opt.lisa).powi(2) - 1.0) * x_l.powi(2)).sqrt() - total_distance * (opt.bus/opt.lisa)
/// Mit dieser Funktion kann man bestimmen, wo der Bus zu Lisas Startzeit war.
/// Die Funktion nimmt an, dass Lisa, von ihrer aktuellen Position aus, den Bus ohne Umwege erreichen kann.
/// Sehr hilfreich als Heuristik, da sie nie schlechter als das eigentliche Ergebnis sein kann.
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)
}