Kodprov 2017-12-18 – genomgång
Table of Contents
1 C-uppgiften
Denna C-uppgift var – som utannonserat – ganska lik den som gavs ut 2017-12-08 (följ länken för detaljer). Den implementation som förkom i föregående kodprov använde sig att pekare till pekare för att implementera urlänkning. I denna lista använder vi istället en sentinel, en extra tom länk först i listan så att det alltid går att ställa sig på föregående länk, även när man vill länka ut det logiska första elementetDet går att läsa mer här..
En eventuell svårighet med uppgiften var hur sentinelen
implementerades – genom att first
använde värdesemantik så att
all början av iteration startade med &first
till skillnad från
efterföljande steg som bara kunde göra ...->next
. I och med att
first
var en global variabel behövde den heller inte frigöras
för att undvika minnesläckage. Försök att frigöra &first
leder
till hårda kraschar.
Så här kunde en lösning till slut se ut.
static
obj_t *kalloc_inner_find(obj_t *mem, void *p)
{
do
{
if (kalloc_header(p) == mem->next) return mem;
mem = mem->next;
}
while mem;
return NULL;
}
Av 28 som skrev uppgiften blev 22 godkända och 6 underkända. Många har också blivit klara med uppgiften relativt snabbt. Inga problem med att förstå sentinel-listan här, men märkligt nog har många haft problem med att förstå exakt samma liststruktur i Java-uppgiften!
2 Java-uppgiften
Förra året lade flera studenter fram åsikten att kodproven i Java skulle ha flera enklare moment, istället för att testa någon mindre del djupare.
Kodprovet denna gång hade ett antal deluppgifter, som inte var namngivna i uppgiftstexten. Jag har tilldelat de olika deluppgifterna olika poäng:
Uppgift | Poäng | % Som klarade den |
---|---|---|
Subtypning | 5 | 84 |
Parametrisk polymorfism | 3 | 89 |
Konstruktorer | 3 | 84 |
Inkapsling | 2 | 82 |
Lägga till element | 5 | 64 |
Iteratorn | 5 | 68 |
equals() | 5 | 33 |
asArray() | 3 | 75 |
newEmptyCollection() | 2 | 64 |
Summa | 33 |
Uppenbart är att equals()
är det svåraste momentet – vilket
kanske förklarar den statistik på trasiga equals()
-metoder som
jag nämnde i samband med att vi gick igenom equals()
och varför
t.ex. dess signatur måste se ut som den gör.
2.1 Subtypning
Klassen LinkedSet
skall vara en subtyp av Collection
och
Set
. Eftersom inte Collection
implementerar Set
eller Set
ärver av Collection
är den enda möjligheten att möta
subtypskravet att göra LinkedSet
till en subklass av
Collection
och implementera interfacet Set
.
public class LinkedSet extends Collection implements Set
Man måste förstå ordet subtyp här. I övrigt skall det vara uppenbart att man endast kan subklassa den enda och implementera den andra.
2.2 Parametrisk polymorfism
Både Set
och Collection
är parameteriserade över typparametern
T
och instruktionerna efterfrågar typsäkerhet – att det skall
gå att skapa en mängd av element endast av en given typ. Lösningen
är att parameteriser LinkedSet
över T
vilket kräver att man
skickar vidare typparametern.
public class LinkedSet<A> extends Collection<A> implements Set<A>
Observera att man kan använda vilken bokstav man vill istället för A
.
Eftersom vi använt T
redan i koden i LinkedSet
är det rätt bokstav
att använda för att undvika sök-och-ersätt.
2.3 Konstruktorer
Superklassen Collection
har en konstruktor som kräver att en
maxkapacitet anges. Då vi ärver av Collection
måste vi se till
att samtliga konstruktorer i LinkedSet
anropar konstruktorn i
Collection
med maxkapaciteten. Så här kan vi t.ex. lösa det:
public LinkedSet(int maxCapacity) {
super(maxCapacity);
}
2.4 Inkapsling
Fälten first
och last
hade inga åtkomstmodifierare och
testerna klagar om de inte är private. Lösning:
private Link first = new First(null);
private Link last = first;
2.5 Lägga till element
När element skall läggas till skall vi kasta ett undantag om mängden är full. Exempelvis:
if (this.size() == this.maxCapacity()) throw new SetFullException();
Vidare skall vi se till att samma element bara förekommer i mängden en gång. Exempelvis:
if (this.contains(elem)) return false;
Att använda contains()
kräver att man förstår att dess
implementation av samma är det som efterfrågas i
instruktionerna. De som har implementerat en egen sådan kontroll i
add()
måste använda equals()
och inte ==
.
Ytterligare en svårighet med denna uppgift är att förstå hur inlänkning går till i en lista med en tom första länk. Exempelvis:
this.last.next = new Link(elem);
this.last = this.last.next;
Observera att det inte finns några specialfall! Det finns alltid
en länk vars next
vi kan skriva. Vi får heller inte glömma bort
att uppdatera this.last
i samband med detta.
2.6 Iteratorn
Implementation av iteraton kräver tre metoder. När man anropar
next()
skall man få ut nästa element (med start med det första,
förstås – vid första anropet), och vid nästa anrop nästa element,
etc. Metoden hasNext()
returnerar true
om nästa anrop till
next()
är valitt (dvs. vi passerar inte utanför listan). Till
sist remove som tar bort ett element ur listan.
Här är en fungerande implementation av iteratorn.
private class SetIterator implements Iterator<T> {
Link current;
Link prev = null;
public SetIterator(Link first) {
this.current = first;
}
public T next() {
this.prev = this.current;
this.current = this.current.next;
return this.current.element;
}
public boolean hasNext() {
return this.current.next != null;
}
public void remove() {
this.prev.next = this.current.next;
this.current = this.prev.next;
}
}
2.7 equals()
Att skriva en equals()
-metod kräver att man minns problemet med
overriding kontra overloading. Det är enkelt att förledas att tro
att man kan skriva en equals()
-metod på detta sätt:
public boolean equals(LinkedSet<T> o) {
for (T elem : this) {
if (o.contains(elem) == false) return false;
}
return this.size() == o.size();
}
Detta är korrekt, förutom att signaturen på metoden gör att vi
inte override:ar Object
:s equals()
-metod. Denna skall nämligen
ha följande signatur:
public boolean equals(Object o)
Som vi tog upp två gånger på föreläsningarna kan man använda sig av en kombination av båda implementationerna:
public boolean equals(Object o) {
if (o instanceof LinkedSet<T>
return this.equals((LinkedSet<T>) o);
} else {
return false;
}
}
Obs: på grund av Javas type erasure är lösningen ovan inte
typsäkerKompilatorn varnar men du får godkänt på kodprovet!
eftersom typomvandling inte kan kontrollera elementtyperna. Detta
kan man t.ex. lösa genom att implementera en egen contains som
opererar på Object
etc., eller helt enkelt ändra den existerande
på detta vis:
public boolean contains(Object elem) {
for (Object e : this) {
if (e.equals(elem)) return true;
}
return false;
}
En avancerad variant är att använda wildcards, som vi gick igenom
på föreläsningarna (här i kombination med contains()
-metoden ovan).
public boolean equals(Object o) {
if (o instanceof LinkedSet<?>
for (Object obj : (LinkedSet<?>) o) {
if (this.contains(obj) == false) return false;
}
return ((LinkedSet<?>) o).size() == this.size;
} else {
return false;
}
}
En anledning till varför signaturen på equals()
kräver att
inparameterns typ är Object
och inte något mer specifikt är för
att ekvivalens är definierat för alla typer, dvs. det skall gå –
dynamiskt – att jämföra två helt olika saker, och få ett svar på
skalan sant/falskt. Denna kod visar varför detta är vettigt:
public void someFunction(Object probablyALinkedSet) {
LinkedSet<T> set = new LinkedSet<T>(42);
if (set.equal(probablyALinkedSet)) {
...
}
}
/// In other method
someFunction(new LinkedSet<T>(42));
I koden ovan har vi tappat bort det faktum att
probablyALinkedSet
är ett LinkedSet
statiskt. Om vi inte
hade definierat equals()
som en metod vars inparameter hade
typen Object
hade vi inte tillåtits testa dessa två tomma
mängder för likhet.
2.8 asArray()
Denna metod tillhandahåller tjänsten att kunna få ut innehållet i mängden som en array. Detta borde vara straightline-kod att skriva för alla, inte minst med tanke på att liknande metoder delats ut i koden redan som gör samma typ av iteration:
public Object[] asArray() {
Object[] result = new Object[this.size()];
int index = 0;
for (T e : this) {
result[index++] = e;
}
return result;
}
2.9 newEmptyCollection()
Klassen Collection
tillhandahåller metoden
newEmptycollection()
som skapar en ny instans av klassen
Collection
att användas i t.ex. copy()
. Men om vi använder
copy()
för att kopiera ett LinkedSet
fungerar det inte
om inte newEmptycollection()
override:as i LinkedSet
för
att returerna ett LinkedSet
:
public LinkedSet<T> newEmptyCollection(int maxCapacity) {
return new LinkedSet<T>(maxCapacity);
}
Det går också bra att behålla returtypen om vi har löst subtypningsuppgiften korrekt:
public Collection<T> newEmptyCollection(int maxCapacity) {
return new LinkedSet<T>(maxCapacity);
}
2.10 (Själv)kritik av Java-uppgiften
Ett problem med detta kodprov har varit att många har haft svårt att få sin kod att kompilera. Om man inte begriper t.ex. subtypningen eller den parametriska polymorfismen, måste man kämpa hårt och göra många manuella förändringar för att få sin klass att fungera. Förmodligen har en bov i kompileringsdramat varit konstruktorerna där Javas kompilator ger ett ganska svårtytt felmeddelande. Vad felmeddelande försöker säga är i stort sett: "hej, det där anropet till superklassens konstruktor som vi normalt kompilerar in automagiskt kan inte stoppas in automatiskt eftersom det inte finns någon parameterlös defaultkonstuktorn."
Att inte ens kunna kompilera måste ha varit djupt demoraliserande, och det är jag ledsen för! Glädjande är att många, trots detta problem, lyckats få så många saker rätt. Det är särskilt imponerande eftersom det inte ens funnits kompilatorhjälp att få! Bra kämpat där!
I framtiden skall jag undvika att dela ut kod som inte fungerar, tror jag. Det begränsar visserligen de möjliga uppgifterna en smula, men jag tror att det ändå är bäst så.
2.11 Resultat
Av 70 som skrev har 32 blivit godkända, 16 är nära att bli godkända (18 poäng) och har därför fått rest och 22 är underkända. Vi förväntar oss därmed att knappt 48 av 70 blir godkända (65-69%).
3 Kodprovet 2018-01-05
En helt ny C-uppgift kommer att ges. Jag kommer att försöka göra
en variant på Java-uppgiften som inte har samma brister vad gäller
att få saker att kompilera, och där det är mindre rörigt vad som
skall görasJag gissar att många inte kunde ta ledning av
testen för detta eftersom koden inte körde…. Nästa Java-uppgft
kommer alltså att täcka samma saker, med en liknande uppgift:
subtypning, olika sorters polymorfism, konstruktorer, inkapsling,
vanlig hederlig Java-programmering, equals()
, samt overriding
kontra overloading.
IOOPM 2017