Abstrakta datatyper Laboration 2 GruDat, DD1344 Örjan Ekeberg (fritt från en idé av Henrik Eriksson) 27 oktober 2008 Målsättning Målet med denna laboration är att ge en inblick i hur man kan använda abstrakta datatyper. Du ska förstå hur abstraktion kan användas för att isolera användningen från implementationen. Efter laborationen ska du kunna implementera linjära datatyper med hjälp av sekvensiell samt länkad lagring. 1 Uppgiften Trollkarlen tar ut de tretton spaderna ur leken, håller dem som en kortlek med baksidan upp och lägger ut dem på följande sätt: Översta kortet stoppas underst, nästa kort läggs ut med framsidan upp, nästa kort stoppas underst, nästa kort läggs ut osv. Till publikens häpnad kommer korten upp i ordning ess, tvåa, trea... Utförande: Man ordnar i hemlighet korten enligt följande. Här bryter vi citatet ur Liberg: Trolleri för alla. I labbuppgiften ingår nämligen att ta reda på kortkonstens hemlighet! Som första steg ska du skriva en funktion trick som tar en lista med kort som parameter. Listan representerar kortleken och varje kort får representeras av ett tal. Funktionen ska returnera en lista med korten i den ordning de läggs ut till publigens häpnad. Om du skriver din funktion i filen cards.py ska du, när du är klar, kunna göra: >>> import cards >>> cards. t r i c k ( [ 3, 1, 5, 2, 4 ] ) [ 1, 2, 3, 4, 5 ] Det är naturligtvis möjligt att skriva funktionen trick så att den direkt manipulerar listor. Nackdelen med detta är att korttrickets logik blir ihopblandad med logiken kring listhanteringen. Vi ska istället utnyttja oss av att man i 1
korttricket hela tiden plockar ut kort i ena änden av leken och lägger till kort i andra änden. Detta passar perfekt för den abstrakta datatypen Kö. Tänk igenom hur funktionen trick borde fungera ifall det fanns en färdig datatyp för köer. Detta arbetssätt kallas ibland för wishful thinking och innebär att man inte låter sig hindras av detaljer som kommer att kunna fixas till senare. Skriv de delar av funktionen trick som du redan vet. Du vet t.ex. vad den ska heta och vilka parametrar den tar. Du bör också skriva en dokumentationssträng med körexempel för doctest, t.ex. plagierat från texten ovan. 2 Datatypen Kö Du ska nu implementera den abstrakta datatypen Kö. En abstrakt datatyp definieras av vilka operationer man kan göra, medan den verkliga lagringen får vara implementatörens ensak. I Python är det naturligt att använda klasser för att representera abstrakta datatyper. Operationerna implementeras då i form av metoder. En kö implementerad på detta sätt kan användas så här: >>> from queue import Queue >>> q = Queue ( ) >>> q. isempty ( ) True >>> q. enqueue (17) >>> q. enqueue (4711) >>> q. isempty ( ) False >>> q. dequeue ( ) 17 >>> q. dequeue ( ) 4711 >>> q. isempty ( ) True Skapa en ny fil queue.py där du definierar klassen Queue. Definiera följade metoder: init ( self ) Konstruktor, initierar kön isempty(self) Testar om kön är tom enqueue(self, ) Lagrar element sist i kön dequeue(self) Tar ut första elementet ur kön Implementera Queue Använd körexemplet ovan som textexempel för doctest. 2
Tips: I kursboken finns ett exempel på hur en kö kan implementeras med hjälp av en intern lista. Använd gärna detta exempel som en förlaga för din implementation. Tips: När doctest ska testa en klass är det lämpligt att skriva körexemplet i klassens dokumentationssträng. Skriv denna sträng som första rad i klassdefinitionen, före alla metoddefinitioner. Förvissa dig om att din datatyp Queue fungerar som den ska innan du går vidare. 3 Åter till korttricket Gå nu tillbaka till filen cards.py och skriv färdig funktionen trick. Funktionen ska alltså internt skapa en kö och lägga in alla elementen från listan i kön. Korten som läggs ut samlas förslagsvis i en lista som slutligen returneras. Provkör din funktion med doctest. Försök hitta hur 13 kort ska vara ordnade för att de ska komma ut i nummerordning. 3.1 Korttricket baklänges Egentligen vill vi ju inte bara simulera själva korttricket utan automatiskt räkna ut hur korten ska ordnas för att få det önskade resultatet. Ett sätt att åstadkomma detta är att göra korttricket baklänges. Du vill alltså kunna göra: >>> import cards >>> cards. r e v t r i c k ( [ 1, 2, 3, 4, 5 ] ) [ 3, 1, 5, 2, 4 ] >>> cards. t r i c k ( cards. r e v t r i c k ( [ 1, 2, 3, 4, 5 ] ) [ 1, 2, 3, 4, 5 ] >>> cards. t r i c k ( cards. r e v t r i c k ( range ( 1, 14)) [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1 3 ] Skriv funktionen revtrick enligt ovan. Tips: Använd gärna två köer: en för korten på bordet och en för korten i handen. Tänk på att vända köerna rätt (i vilken ände stoppar du in kort och i vilken plockar du ut när du gör tricket baklänges?) 3
4 Länkad lagring En av poängerna med dataabstraktion är att man kan byta representation utan att resten av programmet påverkas. Vi ska nu ersätta implementationen av typen Queue med en variant som internt använder länkad lagring. Utåt sett ska den nya Queue-klassen fungera som förut. Alla funktioner du skrivit i cards.py ska fortsätta att fungera, trots att korten nu inte ligger köade i listor. Länkad lagring bygger på att varje element i datastrukturen på något sätt lagras tillsammans med information om var nästa element finns. Ett snyggt sätt att åstadkomma detta är att för varje element utnyttja sig av objekt med två attribut, ofta kallade och. innehåller elementet själv medan innehåller nästa objekt. Det sista objektet har ett -attribut med det speciella värdet None. När man arbetar med länkade datastrukturer är det ofta bra att rita upp strukturen. Länkarna ritas då som pilar. Här är ett exempel: None Allan Beata Cecilia Detta representerar alltså en kö där Allan står först, Beata därefter, medan Cecilia står sist. Denna datastruktur kan man t.ex. bygga upp med följande pythonrader: class : def i n i t ( s e l f, ) : s e l f. = s e l f. = None a = ( Allan ) b = ( Beata ) c = ( C e c i l i a ) a. = b b. = c Lägg märke till att konstruktorn (metoden init ) tar en extraparameter,, som används som värde för attributet med samma namn. När objekten skapas sätts alltså -attributet rätt meddetsamma. -attributet sätts först till None men ändras senare så att länkarna pekar rätt. 4
4.1 Kö med länkad lagring För att implementera en kö med länkad lagring behöver du allså definiera två klasser: 1. Klassen Queue ska som tidigare definiera metoderna enqueue, dequeue och isempty (men de är naturligtvis annorlunda definierade). 2. Klassen används för att skapa de interna objekt som länkas ihop via -attributen. Båda dessa klasser bör ligga i samma fil, som förslagsvis kan heta linkedqueue.py. För att kunna ta ut och sätta in element i kön behöver vi hitta första respektive sista objektet på något sätt. Det bästa sättet att åstadkomma detta är att ha två attribut i själva Queue-objektet, kallade head och tail, som pekar ut början och slutet av kön: head Queue tail None Allan Beata Cecilia Skriv metoderna för den länkade varianten av Queue. Använd samma körexempel du gjorde iordning tidigare för kön för att med hjälp av doctest kontrollera att din nya kö baserad på länkad lagring fungerar. Tips: Rita upp hur strukturen ska se ut före och efter aktuell operation. Använd tilldelning för att lagra rätt referens på rätt plats. Tips: Du måste specialbehandla fallet när kön är tom. En naturlig representation av en tom kö är att låta både head och tail ha värdet Null. 5
5 Riffelblandning [Detta är en extrauppgift för högre betyg] Riffelblandning är enligt Nationalencyklopedin: Ett sedan slutet av 1800-talet vanligt sätt att blanda spelkorten i en kortlek. Två kortlekshalvor läggs vid sidan av varandra, varefter ett hörn eller en kortsida böjs upp av vardera tummen. Korten släpps, helst ett och ett, varannan gång från vänster och varannan gång från höger lekhalva. Korten skjuts så ihop till en lek. Metoden används ofta av trollkonstnärer när de utför skenblandningar. Det sägs att om man upprepar en perfekt gjord riffelblandning ett bestämt antal gånger så kommer korten att hamna i den ursprungliga ordningen igen. Testa om detta är sant och, isåfall, hur många gånger som krävs. Implementera en funktion riffel som utför ett stegs riffelblandning. Undersök om man verkligen kommer tillbaka till ursprungsordningen om man upprepat blandar en lek med 52 kort. Hur många gånger ska man blanda? Hur många gånger krävs om man har en kortlek med 62 kort? När du är klar med laborationen: redovisa för en handledare och se till att få en kvittens här. Ditt namn:................................................................... Handledarens signatur:....................................................... Dagens datum:............................................................... Extrauppgift genomförd:..................................................... GruDat 2008, Laboration 2 6