Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Kodeskjelettet til venstre er det samme for alle deloppgavene. Det er markert hvilke metoder og klasser som skal implementeres i hvilke deloppgaver. De kan også endres i andre deloppgaver basert på dine egne kodevalg.

Temaet for oppgaven er administrasjon av sykkelutleie. Sykkelutleietjenesten (BikeRental) tilbyr leie av sykler (Bike) som plukkes opp og settes tilbake på utvalgte stasjoner spredt rundt omkring. (Senere oppgaver legger opp til at en kan leie ut andre fremkomstmidler også). Syklene har GPS, slik at de hele tiden spores (GeoLocation). Den som skal leie sykkel (Person) bruker en app for å få oversikt over hvor (ved hvilke stasjoner) det er tilgjengelige sykler. Deretter er det bare å identifisere sykkelen, angi hvor lenge man ønsker å leie den, og en kan sykle av gårde. Når en er ferdig med å bruke sykkelen, må den settes tilbake på en stasjon (ikke nødvendigvis den samme som en tok den fra). Ved hjelp av appen angir man at leieforholdet er avsluttet. Leieprisen blir da beregnet og pengene trukket.
 
Prisen er basert på påbegynte timer, og merk at en ikke betaler for mer enn faktisk bruk. Hvis man altså angir at en ønsker å leie en sykkel i tre timer, men leverer den tilbake etter en halv time, så betaler en for én times bruk. Det er mulig å utvide leietiden underveis, noe som utløser et lite gebyr. Hvis en utvider leietiden eller leverer sykkelen etter at sykkelen skulle vært levert, så påløper det også et gebyr.
 
Del 1 av oppgaven fokuserer på administrasjon av leieforholdet. I første omgang beregnes pris kun fra tidsbruk, mens i del 2 utvides det med gebyrer og kvittering. Del 3 fokuserer på å gjøre det lettere å bytte måten prisen beregnes. Del 4 handler om mobil-appen og del 5 om testing.
 

Expand
titleDel 1 – BikeRental-, Bike-, og Location-klassene (35%)

BikeRental-klassen administrerer stasjonene, syklene og utleieforhold. Vi gjør det enkelt og identifiserer stasjonene med et geografisk punkt (GeoLocation, se skjelettet), altså lengde- og breddegrad. GeoLocation brukes også for lagring av posisjonen til syklene.

Oppgave 1 a) - GeoLocation

GeoLocation er kodet slik at instansene ikke kan endres. Hva er fordelen med dette?

Expand
titleLF
  • En stasjon kan ikke endre plassering.
  • ...
Code Block

Vanlige feil: ...

Oppgave b) - GeoLocation

Skriv ferdig double distance(GeoLocation)-metoden i GeoLocation.

Expand
titleLF

Her er poenget at en kan bruke en allerede eksisterende metode i GeoLocation til å hjelpe deg. Denne tar inn lengde- og breddegrader fra to GeoLocation, og returnerer avstanden. Koden til denne ekstra metoden ligger med under. I oppgave b behøvde man dog bare skrive metoden distance(GeoLocation), som dere også finner under.

Code Block
public class GeoLocation {

// Slettet gettere og settere etc.

// Kode for 1b
 public double distance(final GeoLocation other) {
  return distance(latitude, longitude, other.latitude, other.longitude);
 }


// Under følger kode for metoden dere kunne forvente at eksisterte.
 /*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 /*::                                                                         :*/
 /*::  This routine calculates the distance between two points (given the     :*/
 /*::  latitude/longitude of those points). It is being used to calculate     :*/
 /*::  the distance between two locations using GeoDataSource (TM) prodducts  :*/
 /*::                                                                         :*/
 /*::  Definitions:                                                           :*/
 /*::    South latitudes are negative, east longitudes are positive           :*/
 /*::                                                                         :*/
 /*::  Passed to function:                                                    :*/
 /*::    lat1, lon1 = Latitude and Longitude of point 1 (in decimal degrees)  :*/
 /*::    lat2, lon2 = Latitude and Longitude of point 2 (in decimal degrees)  :*/
 /*::  Worldwide cities and other features databases with latitude longitude  :*/
 /*::  are available at http://www.geodatasource.com                          :*/
 /*::                                                                         :*/
 /*::  For enquiries, please contact sales@geodatasource.com                  :*/
 /*::                                                                         :*/
 /*::  Official Web site: http://www.geodatasource.com                        :*/
 /*::                                                                         :*/
 /*::           GeoDataSource.com (C) All Rights Reserved 2015                :*/
 /*::                                                                         :*/
 /*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 public static double distance(final double lat1, final double lon1, final double lat2, final double lon2) {
  final double theta = lon1 - lon2;
  double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
  dist = Math.acos(dist);
  // convert to degrees
  dist = rad2deg(dist);
  dist = dist * 60 * 1.1515;
  // convert to meters
  dist = dist * 1609.344;
  return dist;
 }
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 /*:: This function converts decimal degrees to radians       :*/
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 private static double deg2rad(final double deg) {
  return (deg * Math.PI / 180.0);
 }
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 /*:: This function converts radians to decimal degrees       :*/
 /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
 private static double rad2deg(final double rad) {
  return (rad * 180 / Math.PI);
 }
}




Vanlige feil: Det var en del som ikke skjønte hva metodene skulle gjøre, selv om det var de samme metodene som på ordinær eksamen. De må imidlertid skrives annerledes, siden terningene er representert på en annen måte.

 

Oppgave c) - Bike

Bike-klassen skal ha assosiasjoner (koblinger) til Person- (se skjelettet) og GeoLocation-klassene iht. følgende klassediagram.

Image Added

(Location i bildet over skulle hett GeoLocation, men det var tydelig nok i forhold til teksten hva som er ment.)

Begge disse assosiasjonene er dynamiske, dvs. både location og renter (låntaker) kan endres. location-koblingen endres (automagisk, du kan bare forholde deg til at location hele tiden oppdateres) når sykkelen beveger seg, mens renter-koblingen endres når leieforhold innledes og avsluttes.

Skriv felt og metoder for location- og renter-assosiasjonene iht. Java sine kodingskonvensjoner.

Expand
titleLF

Her var det snakk om å velge en representasjonsmåte for en GeoLocation og en Person, samt lage gettere og settere. Skjule verdiene.

Code Block
public class Bike {
 @Override
 public String toString() {
  String s = "Bike [location=" + location + ", renter=" + renter+"\n";
  for (RentalInfo rentalInfo : rentals) {
   s += rentalInfo.toString()+"\n";
  }
  return s;
 }
 private GeoLocation location;
 private Person renter;
 public GeoLocation getLocation() {
  return location;
 }
 void setLocation(final GeoLocation location) {
  this.location = location;
 }
 public Person getRenter() {
  return renter;
 }
 void setRenter(final Person renter) {
  this.renter = renter;
 }

Gammel eksamen lagt inn som mal. Mer kommer!

Denne delen av oppgaven omhandler Dice-klassen, som brukes til å representere (verdien av) én eller flere terninger (engelsk: dice = terninger, die = terning). En slik klasse kan være nyttig i mange typer terningspill, for å representere terninger som nettopp er slått eller de av et sett terninger som en får poeng for.

Merk at poengene ikkeer en del av Dice-klassen, de håndteres av andre klasser som beskrives senere.

Dice-klassen er vist under, ??? erstatter kodefragmenter som det spørres om i oppgaveteksten., mens ... står for kode som er utelatt fordi det ikke er viktig. Vi kreverher at terningverdiene skal representeres på en bestemt måte, som et sett tellere for hver mulig terningverdi, og har derfor oppgitt deklarasjonen av valueCounters. Det skal ikkevære nødvendig å deklarere andre felt enn dette.

Eksempel: Hvis et Dice-objekt representerer de fire verdiene 1, 2, 2 og 4, så vil tellerverdiene i valueCountersvære 1, 2, 0, 1, 0 og 0, altså 1 ener, 2 toere, ingen treere, 1 firer, ingen femmere og ingen seksere. For getDieValue-metoden sin del, vil eneren være på indeks 0, de 2 toerne vil være på indeks 1 og 2, og fireren vil være på indeks 3.

Expand
titleDel 1 - Dice og DiceIterator
Expand
titleInnledning til del 1
Code Block
/**
 * Represents a set of die values.
 */
public class Dice implements Iterable<Integer> {
    /**
     * Counters for each possible die value.
     * The counter at index i (0-5) is the counter for the die value i+1 (1-6).
     * I.e. the value at index 2 is the counter for die value 3.
     */
    private final int[] valueCounters;
 
    /**
     * Initializes this Dice with the values in dieValues.
     * @param dieValues sequence of die values, not counter values
     */
    public Dice(Iterator<Integer> dieValues) {
        ???
    }

    /**
     * Initializes this Dice with the values in dieValues.
     * @param dieValues sequence of die values, not counter values
     */
    public Dice(Iterable<Integer> dieValues) {
        ???
    }
 
    @Override
    public Iterator<Integer> iterator() {
        return new DiceIterator(this);
    }
 
    /**
     * @return the number of die values
     */
    public int getDieCount() {
        ???
    }
 
    /**
     * Die values are considered ordered, with the smallest die values
     * at the lowest index. The value at a specific index
     * must be computed from the counters in valueCounters.
     * @param dieNum
     * @return the value of die number dieNum
     * @throws an appropriate exception, if dieNum is out of range
     */
    public int getDieValue(int dieNum) {
        ???
    }
 
    /**
     * @param value
     * @return the number of dice with the provided value
     */
    public int getValueCount(int value) {
        ???
    }

    /**
     * @param dice
     * @return true if all die values in the Dice argument appear in this Dice
     */
    public boolean contains(Dice dice) {
        ???
    }
 
    /**
     * @param dice
     * @return true if this Dice and the one provided have exactly the same die values
     */
    public boolean isSame(Dice dice) {
        ???
    }
 
    /**
     * @param dice a Dice object
     * @return a new Dice instance with the all the die values in
     * this Dice and the Dice argument combined
     */
    public Dice add(Dice dice) {
        ???
    }
 
    /**
     * @param dice
     * @return a new Dice instance with the die values from this Dice, but
     * without those from the Dice argument
     */
    public Dice remove(Dice dice) {
        ???
    }
}
Oppgave a) - Konstruktører (8 poeng)

Skriv kode for de to konstruktørene (merk forskjellen i argumenttype), slik at valueCounters blir riktig initialisert. Du SKAL IKKE innføre andre felt. Du kan lage hjelpemetoder om du ønsker. For begge konstruktørene er argumentet en sekvens med terningverdier (altså ikke tellere) som den nyinitialiserte instansen skal representere.

Merk at det er lov å lage ekstra konstruktører for å gjøre andre metoder, f.eks. add og remove, enklere å skrive.

Expand
titleLF

Her er poenget å skjønne hvordan tabeller virker (opprettes, leses fra og skrives til) og logikken bak valueCounters-feltet, samt hvordan iterere med Iterator (hasNext() og next()) og Iterable (iterator()).

Code Block
public Dice(Iterator<Integer> dieValues) {
	this.valueCounters = new int[6];
	while (dieValues.hasNext()) {
		int dieValue = dieValues.next();
		if (! (dieValue >= 1 && dieValue <= 6)) {
			throw new IllegalArgumentException("A die value must be in the range 1-6");
		}
		// add one to the corresponding counter
		this.valueCounters[dieValue - 1]++;
	}
}

public Dice(Iterable<Integer> dieValues) {
	this(dieValues.iterator());
}

Vanlige feil: En del skrev kode som om de skulle lagre enkeltverdiene, ikke tellerne for hver mulig verdi. Da blir løsningen som på den ordinære eksamen, og det gir ikke uttelling.

Oppgave b) - Terningverdier (8 poeng)

Skriv metodene getDieCountgetDieValue og getValueCount iht. forklaringen og koden gitt tidligere (API-beskrivelsen).

Expand
titleLF

Når en først har tellerne i valueCounters, så er getDieCount- og getValueCount-metodene trivielle. getDieValue er litt mer kinkig, da en må beregne hvilket tall som vil falle på en bestemt indeks, uten noen liste over alle terningverdiene.

Code Block
public int getDieCount() {
	int count = 0;
	for (int counter : valueCounters) {
		count += counter;
	}
	return count;
}

public int getDieValue(int dieNum) {
	if (dieNum < 0 || dieNum >= getDieCount()) {
		throw new IllegalArgumentException(dieNum + " is out of range");
	}
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		int counter = valueCounters[dieValue - 1];
		if (dieNum < counter) {
			return dieValue;
		}
		dieNum -= counter;
	}
	// should never come here
	throw new IllegalArgumentException();
}

public int getValueCount(final int value) {
	return valueCounters[value - 1];
}


Vanlige feil: Det var en del som ikke skjønte hva metodene skulle gjøre, selv om det var de samme metodene som på ordinær eksamen. De må imidlertid skrives annerledes, siden terningene er representert på en annen måte.

 

Oppgave c) - Flere metoder (8 poeng)

Skriv metodene containsisSameadd og remove, som alle tar et Dice-objekt som eneste argument. Merk at ingen av disse endrer på verken this-objektet eller argumentet.

Her er noen eksempler på bruken av disse metodene, hvor [t1, t2, t3, ... tn] brukes som pseudo-synaks for et Dice-objekt som representerer terningverdiene t1-tn:

[1, 2, 2, 4].contains([1, 4]) returnerer true, mens

[1, 2, 4].contains([2, 2]) returnerer false.

[1, 2, 2, 4].isSame([1, 2, 2, 4]) returnerer true, mens

[1, 2, 2, 4].isSame([1, 2, 4]) returnerer false

[1, 2].add([1, 4]) returnerer [1, 1, 2, 4]

[1, 1, 2].remove([1, 4]) returnerer [1, 2]

Merk at removei kke har samme logikk som Collectionsin removeAll-metode.

Expand
titleLF

Disse metodene kan forsåvidt skrives kun vha. getDieValue-metoden og konstruktørene (som de fleste gjorde på den ordinære eksamenen), men det er mye enklere å utnytte at vi representerer terningene med tellere. Her utnytter vi også at vi kan lage tomme Dice-instanser, og så etterpå justere tellerne i valueCounters.

Code Block
// Et Dice-objekt inneholder et annet hvis det er minst like mange av hver terning-verdi
public boolean contains(final Dice dice) {
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		if (this.valueCounters[dieValue - 1] < dice.getValueCount(dieValue)) {
			return false;
		}
	}
	return true;
}

// To Dice-objekter er like hvis det ene inneholder det andre og omvendt!
// Evt. kan kan sjekke at alle valueCounters-verdiene er like
public boolean isSame(final Dice dice) {
	return contains(dice) && dice.contains(this);
}

// To Dice-objekter kan slås sammen ved å legge sammen tellerne
public Dice add(final Dice dice) {
	// Lager et tomt Dice-objekt og justerer tellerne i løkka
	Dice result = new Dice(Arrays.asList());
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		result.valueCounters[dieValue - 1] = this.valueCounters[dieValue - 1] + dice.valueCounters[dieValue - 1];
	}
	return result;
}

// En kan trekke et Dice-objekt fra et annet ved å trekke den enes tellere fra den andres.
// En må huske å unngå negative tellere.
public Dice remove(final Dice dice) {
	// Lager et tomt Dice-objekt og justerer tellerne i løkka
	Dice result = new Dice(Arrays.asList());
	for (int dieValue = 1; dieValue <= 6; dieValue++) {
		result.valueCounters[dieValue - 1] = Math.max(0, this.valueCounters[dieValue - 1] - dice.getValueCount(dieValue));
	}
	return result;
}

Vanlige feil:

 

Oppgave d) - DiceIterator (6 poeng)

Lag en implementasjon av Iterator<Integer>kalt DiceIterator, slik at den kan brukes slik det er vist i Dice sin iterator()-metode. Du kan anta at valueCounters-feltet i Dice er synlig i DiceIterator-klassen.

Expand
titleLF

Trikset her er å skjønne at terningverdiene skal komme i samme rekkefølge som det en får fra getDieValue med stigende indekser. Her blir altså logikken helt uavhengig av hvordan terningsverdiene er representert internt i Dice-klassen.

Code Block
public class DiceIterator implements Iterator<Integer> {
	private final Dice dice;
	private int dieNum = 0;

	public DiceIterator(final Dice dice) {
		this.dice = dice;
	}

	@Override
	public boolean hasNext() {
		return dieNum < dice.getDieCount();
	}

	@Override
	public Integer next() {
		final int value = dice.getDieValue(dieNum);
		dieNum++;
		return value;
	}
}

Vanlige feil:

 


...