TDA144 Exempellösningar för hemtenta 2020-03-17
Dokumentet innehåller exempellösningar samt korta beskrivningar av vanliga korrekta lösningar och vanliga misstag
Exempellösning:
public static int height(Robot r){
int h = 1;
// Den här loopen körs för varje rad i tomrummet while(true) {
// Flytta till västra ändan av raden while (tryMoving(r, Robot.WEST)) ;
// Flytta steg för steg österut while (true)
if (tryMoving(r, Robot.SOUTH)){
h++;
break; // Om vi kan gå söderut startar den yttre loopen om } else if (!tryMoving(r, Robot.EAST))
return h; // Om vi inte kan gå söder eller öster är vi klara }
}
// Försök gå ett steg i angiven riktning.
// Returnerar svaret på om förflyttning gick att genomföra public static boolean tryMoving(Robot r, int dir){
while (r.getDirection() != dir) r.turnLeft();
if (r.canMove()){
r.move();
return true;
} else
return false;
}
De vanligaste lösningarna använder en av två beskrivningar av ungefär samma algoritm:
• För varje rad "scannar" roboten från ena till andra sidan efter en möjlighet att gå söderut.
Hittar den ingen är den klar.
• Roboten följer en vägg tills den bara kan gå norrut.
Båda är lika acceptabla men mitt allmäna intryck är att lösningar av den första typen är tydligare i beskrivningen av algoritmen, kanske för att den är lättare att dela upp i tydliga steg.
Vanliga fel:
• Loopar som tydligt inte fungerar.
• Missar att gå tillbaka till början av varje ny rad.
• Kod som verkar utgå från att roboten inte kan röra sig höger eller vänster på nedersta raden.
• Missförstånd om hur hålrummet ser ut (att man aldrig behöver gå norr) Vanliga småfel:
• Att inte räkna raden roboten börjar på.
Uppgift 2:
Exempellösning:
import java.util.*;
public class Parameters {
// Övre och undre gräns för tillåtna värden i den här samlingen private double upperLimit;
private double lowerLimit;
// Nycklar är parameternamn, värden parametervärden
private Map<String, Double> values = new TreeMap<String, Double>();
// Default-värde för den här samlingen private double defaultValue = 0;
public Parameters(double upperLimit, double lowerLimit) { this.upperLimit = upperLimit;
this.lowerLimit = lowerLimit;
}
public void setValue(String parameter, double value) { if (value >= lowerLimit && value <= upperLimit)
// Skriver automatiskt över tidigare värde om det finns något values.put(parameter, value);
else
throw new RuntimeException("Value out of bounds: "+value);
}
public double getValue(String parameter) { // En vanlig lösning:
// Double v = values.get(parameter) // Viktigt att använda Double // if (v != null)
// return v;
// return defaultValue;
// Finns en metod i Map som gör exakt det vi vill här // metoden ger aldrig null
values.getOrDefault(parameter, defaultValue) }
public void setDefaultValue(double defaultValue) { this.defaultValue = defaultValue;
}
// Testkod
public static void main(String[] args) {
Parameters par = new Parameters(100.0, -100.0);
System.out.println(par.getValue("x"));
par.setValue("x", 10.0);
System.out.println(par.getValue("x"));
par.setDefaultValue(5.0);
System.out.println(par.getValue("y"));
try {
par.setValue("z", -101);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
} } }
• Tre doubles för övre gräns, nedre gräns och default-värde
• En datastruktur för själva samlingen
Korrekt lösning för metodsignaturer:
• Konstruktor som tar övre och undre gräns för värden:
public Parameters(double upperLimit, double lowerLimit)
• Andra konstruerare förekommer, till exempel en som tar även någon datastruktur
• public void setValue(String key, double value)
• public double getValue(String key)
• public void setDefaultValue(double defaultValue)
• Det här är I princip det korrekta sättet att göra metodsignaturerna, för att kunna skriva testprogrammet så som det anges
Vanliga korrekta lösningar för datastrukturen:
• Använda en Map<String, Double> och delegera nästan alla metoder till den. Det här är den enklaste korrekta lösningen.
• Använda två listor (en med parameternamn och en med värden på samma positioner).
Fungerar också, men blir lite mer invecklad med större risk för misstag.
• En lista med objekt av en ytterligare klass Parameter som är en enkel lagringsklass för par av namn och värden.
Vanliga misstag:
• Att enbart göra en klass som bara kan representera en parameter (ingen datastruktur).
• Att ha instansvariabler för namn och värde direkt i Parameters.
• Att göra någon instansvariabel statisk.
• Att spara värden men inga namn
• Att kräva att alla parametrar läggs till innan de får ett värde
• Att hårdkoda in gränserna så de är samma för alla parametersamlingar.
• Missa try-catch
• Helt missa test-koden
Uppgift 3:
Knight:
import java.util.*;
public class Knight extends ChessPiece {
public Knight(Position pos, boolean isWhite) { super(pos, isWhite);
}
public List<Position> possibleMoves(ChessBoard board) { List<Position> res = new ArrayList<>();
// Pjäsens nuvarande rad och kolumn int row = getPosition().getRow();
int col = getPosition().getCol();
// En array med de åtta tänkbara positionerna (kan vara ogiltiga) // Man kan generera de här positionerna programmatiskt med loopar, // men fördelen är diskutabel.
Position[] basic = {
new Position(row+2,col+1), new Position(row+2,col-1), new Position(row-2,col+1), new Position(row-2,col-1), new Position(row+1,col+2), new Position(row+1,col-2), new Position(row-1,col+2), new Position(row-1,col-2) };
// Lägg till de tänkbara positioner som är giltiga och inte har en // allierad pjäs på sig
for(Position p : basic) {
if (ChessBoard.isValid(p)) {
// Kunde vara utanför if-satsen också ChessPiece piece = board.getPiece(p);
if(piece == null || piece.isWhite() != isWhite()) res.add(p);
} }
return res;
} }
Vanliga korrekta lösningar:
• Generera först alla åtta tänkbara positioner (endera med loopar eller genom åtta satser), loopa sedan igenom dem och lägg de tillåtna dragen i en lista (visad här).
• Lägg alla tänkbara drag direkt i en lista, filtrera sedan bort de ogiltiga dragen.
import java.util.*;
public class Bishop extends ChessPiece {
public Bishop(Position pos, boolean isWhite) { super(pos, isWhite);
}
public List<Position> possibleMoves(ChessBoard b){
List<Position> res = new ArrayList<>();
// Lägg till alla drag för de fyra tillåtna riktningarna addMoves(res, b, 1, 1 );
addMoves(res, b, 1, -1);
addMoves(res, b, -1, 1 );
addMoves(res, b, -1, -1);
return res;
}
// Lägg till i alla drag i en riktning till listan
// Riktningen anges genom förändring i kolumn/rad för varje steg // Till exempel (1,0) för att gå rakt neråt eller (-1,-1) upp vänster // Slutar när den når en pjäs eller en ogiltig position
private void addMoves(List<Position> res, ChessBoard b, int deltaRow, int deltaCol) {
// Offset är antalet steg den tar i den givna riktningen for(int offset = 1; true; offset++) {
// Positionen pjäsen skulle hamna på Position p = new Position(
getPosition().getRow()+offset*deltaRow, getPosition().getCol()+offset*deltaCol );
// Pjäsen på p (eller null om p är tom) ChessPiece other = b.getPiece(p);
if (!ChessBoard.isValid(p))
return; // Avbryt metoden om p är ogiltig else if (other!=null) {
// p innehåller en pjäs, metoden avbryts
// Är pjäsen en motståndarpjäs läggs p till listan if (other.isWhite() != this.isWhite())
res.add(p);
return;
}
// p är giltig och tom, lägg till i listan res.add(p);
} } }
Vanliga korrekta lösningar:
* Fyra loopar för de fyra riktningarna, som avbryts vid en pjäs eller ogiltig ruta.
Vanliga misstag:
• Inte kontrollera om resultatet av getPiece() är null innan det används.
• Att använda instansvariabler för att lagra listor med drag (oftast utan att återskapa dem vid varje anrop)
• Att inte få den grundläggande infrastrukturen rätt (skapa, fylla och returnera en lista) till exempel att skapa positioner men aldrig lägga till dem i listor.
• Att låta löparen hoppa över pjäser. Ofta genom att använda en loop för alla diagonaler och alltså inte kunna avbryta den när en pjäs påträffas.
• Att göra något helt annat än possibleMoves, till exempel en move-metod eller en metod som avgör om ett specifikt drag är giltigt.
Vanliga småfel:
• Inkorrekta eller väldigt invecklade sätt att avgöra om en pjäs är en motståndarpjäs.
• Att försöka tilldela metodanrop, typ p.getRow()++
Överbetygsuppgift:
• Flera metoder skulle behövas, till exempel ett sätt att avgöra vilken pjäs som är kung för varje spelare.
• Några har identifierat att det vore praktiskt om man kunde simulera drag utan att uföra dem. Då kunde man avgöra matt genom att simulera varje möjligt drag och se om schackläget består för varje möjligt drag för alla pjäser.
• Att ha någon slags samlad datastruktur för nåbara rutor på brädet för varje spelare är troligtvis en bra tanke.
• Många identifierade korrekt olika problem som kunde uppstå, till exempel att det inte räcker med att se om någon pjäs kan ställa sig mellan en hotad kung och den hotande pjäsen eftersom att varje förflyttning kan leda till att kungen hotas på något annat sätt.
• Många identifierade korrekt att det kan bli ganska beräkningsintensivt att beräkna matt.