I denne oppgaven skal du implementere klasser og metoder for å representere (buss)ruter/turer og kunne estimere gjenværende tid for resten av turen. Du kan tenke deg at dette brukes av koden i en info.tavle, som informerer passasjerene om antatt ankomsttid, og en tjeneste for å få SMS en viss tid for ankomst (for de som skal hente passasjeren på desinasjonen). En strekning består av en sekvens av segmenter med hver sin angitte lengde (s) og (reise)tid (t). Fra lengden og tiden kan en beregne (gjennomsnitts)farten v = s/t. Tiden som knyttes til et segment kan være faktisk registrert reisetid, eller antatt reisetid (basert på historiske data), avhengig av hvilke data en velger å legge inn. Et eksempel med tre segmenter er vist i Figur 1 (s er km, mens t er sekunder): ![](/wiki/download/attachments/89170505/Figur1.png?version=1&modificationDate=1455187516000&api=v2) Figur 1
En antar at farten innen et segment er jevn, og passer på å dele opp en strekning i segmenter ut fra denne antakelsen. En strekning fra én by til en annen vil kanskje være delt i tre segmenter (se Figur 1) én for veien ut av den ene byen, en for delen mellom og en for veien inn til den andre byen, siden hver av disse segmentene har helt ulike kjøreforhold og dermed gjennomsnittsfart. Dersom en vei har varierende fartsgrense, så kan en dele opp i kortere segmenter. Hvis antakelsen om jevn fart innen et segment er riktig, så kan en estimere tid mellom steder underveis på en strekning. Du skal først implementere klasser for strekning og segment, som vi velger å kalle Path og Segment. Oppgave a)Skriv først Segment-klassen, med en konstruktør som tar inn lengde og tid for segmentet og nødvendige felt. Det skal ikke være mulig å endre egenskapene etter opprettelsen. Lag også én get-metode for hver av de tre egenskapene distance (lengde), duration (tid) og speed (fart). Expand |
---|
|
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
title | Segment |
---|
| public class Segment {
private final double duration, distance;
public Segment(double distance, double duration) {
this.distance = distance;
this.duration = duration;
}
public double getDuration(){
return duration;
}
public double getDistance()
{
return distance;
}
public double getSpeed() {
return distance / duration;
}
}
|
|
Oppgave b) Implementer Path-klassen, slik at den inneholder en sekvens av Segment-objekter. Det skal være mulig å initialisere Path-objekter med ingen, én eller flere segmenter. Skriv også metoder som gjør det mulig å legge til segmenter og gå gjennom dem basert på indeks. Velg selv fornuftige metode-navn og signaturer. Expand |
---|
| Her var poenget både å finne passende datastruktur og innkapslingsmetoder. En trenger bare én add-metode, selv om det er vist to har. For å gå gjennom segmentene trenger en både én metode for count/size (ellers vet en ikke når iterasjonen skal stoppe) og én for å hente ut et element. Det var veldig mange som lurte på formuleringen ”gå gjennom dem basert på indeks”, antageligvis fordi det ikke var sagt at dette var innkapsling for andre klasser, ikke til bruk internt i klassen. Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
title | Path LF |
---|
|
private final List<Segment> segments;
public Path(Segment... segments) {
this.segments = new ArrayList<Segment>(Arrays.asList(segments));
}
public int getSegmentCount() {
return segments.size();
}
public Segment getSegment(int i) {
return segments.get(i);
}
public void addSegment(Segment segment){
segments.add(segment);
}
public void addSegment(double distance, double duration){
addSegment(new Segment(distance, duration));
} |
|
Oppgave c) Tegn objektdiagram for strukturen av Path- og Segment-objekter tilsvarende strekningen vist i Figur 1. Expand |
---|
| Objektdiagram, ikke objekttilstandsdiagram eller klassediagram! PlantUML Macro |
---|
object "~#segment1: Segment" as segment1 {
distance = 20
duration = 1200
}
object "~#segment1: Segment" as segment2 {
distance = 80
duration = 3600
}
object "~#segment1: Segment" as segment3 {
distance = 10
duration = 550
}
object "~path: Path" as path {
}
path -> segment1: segments
path -> segment2: segments
path -> segment3: segments |
|
Se også Objektdiagrammer og Diagrammer Oppgave d) Lag følgende metoder (eksemplene er alle med utgangspunkt i Figur 1): - double getDuration() – returnerer (reise)tiden til hele strekningen. For tilstanden tilsvarende Figur 1 vil det være 1200 + 3600 + 550.
- double getDistance(Segment fromSegment, Segment uptoSegment) – returnerer lengden fra og med fromSegment til, men ikke med, uptoSegment. Hvis fromSegment er null, så beregnes lengden fra starten av strekningen. Hvis uptoSegment er null, så beregnes lengden av resten av strekningen. Utløs et passende unntak hvis en eller begge argumentene ikke er en del av denne strekningen. Her er noen eksempler på argumenter og forventet resultat:
fromSegment | uptoSegment | resultat | Segment 1 | Segment 3 | 20 + 80 | null | Segment 2 | 20 | Segment 2 | null | 80 + 10 | null | null | 20 + 80 + 10 |
- Segment getSegmentAt(double distance, boolean next) – returnerer segmentet som en er i etter at lengden distance er tilbakelagt (fra starten av strekningen). Dersom distance tilsvarer enden på et segment, så bestemmer next-argumentet om det er dette segmentet som skal returneres (next er false) eller evt. segmentet etter (next er true). Her er noen eksempler på argumenter og forventet resultat:
distance (s) | next | resultat | s = 0.0 | false | null | s = 0.0 | true | Segment 1 | s = s1 | false | Segment 1 | s = s1 | true | Segment 2 | s > 20 og s < 20 + 80 | | Segment 2 | s > 20 + 80 og s < 20 + 80 + 10 | | Segment 3 | = 20 + 80 + 10 | false | Segment 3 | = 20 + 80 + 10 | true | null |
Expand |
---|
|
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
title | getDuration getDistance getSegment LF |
---|
| public double getDuration(){
double duration = 0.0;
for (Segment segment : segments) {
duration += segment.getDuration();
}
return duration;
}
Her er det mange mulige varianter. Den
under går gjennom alle elementene og holder rede på i hvilket del av
gjennomgangen det skal summeres. En annen variant var å beregne start- og
slutt-indekser for gjennomgangen vha. segments.indexOf-kall.
public double getDistance(Segment fromSegment, Segment uptoSegment) {
if ((fromSegment == null || segments.contains(fromSegment)) &&
(uptoSegment == null || segments.contains(uptoSegment))) {
double distance = 0.0;
for (Segment segment : segments) {
if (segment == uptoSegment) {
return distance;
} else if (fromSegment == null || segment == fromSegment || distance > 0.0) {
distance += segment.getDistance();
}
}
return distance;
} else {
throw new IllegalArgumentException("Path doesn't contain both segments.");
}
}
Problemet med denne er håndteringen av next, når distance er tilbakelagt på en segment-grense. En må håndtere
tilfellene distance er 0.0, lik
total-distansen og lengre enn total-distansen.
public Segment getSegmentAt(double distance, boolean next) {
for (Segment segment : segments) {
if (distance == 0.0) {
return (next ? segment : null);
}
distance -= segment.getDistance();
if (distance < 0.0 || (distance == 0.0
&& (! next))) {
return segment;
}
}
return null;
}
|
|
Klassen Trip representerer en faktisk (kjøre)tur og brukes for å oppdatere infotavla på bussen med faktisk og estimert (reise)tid. Trip-klassen skal initialiseres med et Path-objekt som representerer forventet forløp, altså med segment-tider basert på historiske data. Trip skal også inneholde et Path-objekt med det faktiske forløpet, slik de registreres underveis på turen. Dette Path-objektet vil altså utvides med flere og flere Segment-objekter, etter hvert som segmentene passeres/tilbakelegges. Dette er illustrert i Figur 2: Første segment er registrert (med registerSegment-metoden, se under). Lengden er forventet lengde, men det har gått litt saktere. | ![](/wiki/download/thumbnails/89170505/figur%202.png?version=1&modificationDate=1455719043000&api=v2) | Andre segment er registrert (med registerSegment). Her er både lengde og tid som forventet. | ![](/wiki/download/attachments/89170505/figur22.png?version=1&modificationDate=1455719116000&api=v2) | Tredje segment er registrert (med registerSegment). Her har det gått litt saktere enn forventet. | ![](/wiki/download/attachments/89170505/firgur23.png?version=1&modificationDate=1455719275000&api=v2) |
Figur 2 Oppgave e)Implementer nødvendige felt og konstruktør for Trip. Implementer også registerSegment: - void registerSegment(double distance, double duration) – denne metoden kalles med en ny måling når et nytt segment er passert/tilbakelagt. Et nytt Segment-objekt skal legges inn med data tilsvarende målingen. Men merk at distance- og duration-argumentene er målt fra starten av hele turen, så de må regnes om før Segment-objektet opprettes. Hvis målt avstand ikke tilsvarer det en forventer ut fra forventet forløp eller det allerede er registrert nok segmenter, så skal det utløses et passende unntak.
Expand |
---|
| registerSegment-metoden kan skrives på mange måter, og kan forenkles en del hvis en antar at registreringen hittil har gått greit. Forslaget under er i så måte mer komplekst enn nødvendig. Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
title | registerSegment LF |
---|
| private final Path path;
public Trip(Path path) {
this.path = path;
}
private Path actualPath = new Path();
public void registerSegment(double distance, double duration) {
if (actualPath.getSegmentCount() >= path.getSegmentCount()) {
throw new IllegalArgumentException("Already registered enough segments");
}
Segment pathSegment = path.getSegmentAt(distance, true);
double expectedDistance = path.getDistance(null, pathSegment);
if (expectedDistance != distance) {
throw new IllegalArgumentException("Illegal distance, should have been " + expectedDistance + ", but was " + distance);
}
double segmentDistance = expectedDistance - path.getDistance(null, path.getSegmentAt(distance, false));
double segmentDuration = duration - actualPath.getDuration();
actualPath.addSegment(segmentDistance, segmentDuration);
} |
|
Oppgave f) Trip-klassen skal kunne brukes til å estimere tiden som gjenstår fra og med et punkt underveis, basert på forventet forløp (som ble angitt ved opprettelsen av Trip-objektet). Merk at dette punktet kan godt være midt i et segment. Koden under er et forsøk på implementasjon. De to argumentene er tilbakelagt lengde og tid brukt så langt. Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
title | estimateTime |
---|
| public double estimateTime(double distance, double duration) {
double remainingTime = 0.0;
for (Segment segment : this.getActualPath()) {
distance -= segment.getDistance();
if (distance < 0) {
// gjenværende tid for segmented en er kommet til
remainingTime = distance / segment.getSpeed();
} else if (remainingTime > 0.0) {
// legg til gjenværende tid for etterfølgende segment
remainingTime += segment.getDuration();
}
}
return remainingTime;
} |
Denne implementasjonen gjør et par antakelser om Trip- og Path-klassene, hvilke? Det er også plantet en liten logisk feil i koden, hvilken? Expand |
---|
| Koden bruker metoden getActualPath() fra Trip-klassen og den er ikke nevnt i oppgaven. Her står kallet på et sted hvor Java forventer en Iterable (eller en array). Typen må her være Iterable<Segment> sin løkkevariabelen er av typen Segment. Metode-navnet getActualPath() legger opp til at Path-klassen implementerer Iterable<Segment> og altså har en iterator()-metode som returnerer en Iterator<Segment>. Alternativt må getActualPath() returnere selv segment-lista, men det virker litt rart gitt navnet på metoden. Her er (til orientering) Path-koden: Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
title | estimateTime LF |
---|
| class Path implements Iterable<Segment> {
private final List<Segment> segments;
@Override
public Iterator<Segment> iterator() {
return segments.iterator();
}
}
|
Feilen som er plantet er at fortegnet på remainingTime-variabelen (i den første if-grenen) blir feil, når (vi vet at) distance er negativ. En må altså snu fortegnet med en minus i uttrykket på høyresida i tilordningen. Det er også en annen feil, nemlig navngiving av metoden getActualPath. Algoritmen er basert på at en går gjennom forventet path, dvs. path-feltet i Trip, ikke actualPath. Derfor burde metoden het getPath evt. getExpectedPath. Dette er opplagt forvirrende og noe vi tar hensyn til ved sensur. Poenget over med at Path må implementere Iterable er greit, men den logiske feilen ble vanskelig å finne pga. navnefeilen. På toppen av dette er det feil i test-logikken inni, så her er det mange riktige svar! For ordens skyld: Her er den helt riktige implementasjonen, også uten den ene feilen som var plantet: Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
title | estimeTime |
---|
| public double estimateTime(double distance, double duration) {
double remainingTime = 0.0;
for (Segment segment : this.getExpectedPath()) {
distance -= segment.getDistance();
// hvis vi har begynt å akkumulere
if (remainingTime > 0.0) {
// akkumuler
remainingTime += segment.getDuration();
} // hvis turen har kommet til dette segmentet
else if (distance < 0) {
// begynn å akkumulere
remainingTime = -distance / segment.getSpeed();
}
}
return remainingTime;
} |
|
Lenke til løsningskoden: Segment.java, Path.java og Trip.java |