Edge cases, more comments and formatting
This commit is contained in:
parent
56a613fb88
commit
573788fbfc
@ -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");
|
||||
|
||||
|
32
src/input.rs
32
src/input.rs
@ -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());
|
||||
|
92
src/main.rs
92
src/main.rs
@ -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,6 +17,7 @@ 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 {
|
||||
@ -36,19 +38,21 @@ struct Opt {
|
||||
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,6 +64,23 @@ 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;
|
||||
@ -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,7 +172,7 @@ 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(),
|
||||
@ -157,19 +182,19 @@ fn main() {
|
||||
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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user