class Student: def __init__(self, namn, kthid, program, mobil, ): self.namn = namn self.kthid = kthid self.program = program def bytProgram(self, nytt): self.program = nytt def __str__(self): strang = self.namn + " " + self.program return strang s = Student("Emma Khama", "i56ef2g", "COPEN") print(s) s.bytProgram("CDATE") print(s)Metoden
__str__()
anropas automatiskt av satsen
print sAndra såna metoder är:
__add__
som anropas av operatorn +
__eq__
, __lt__
osv som används i villkor (anropas av operatorerna )
studiegrupp = [] studiegrupp.append(Student("Emma Khama", "i56ef2g", "CDATE")) studiegrupp.append(Student("Kim Robinson", "i56ag3g", "CMETE")) studiegrupp.append(Student("Nour Simson", "i56tr3c", "CLGYM")) for p in studiegrupp: print(p)
Här är en interaktiv listanimation av Y. Daniel Liang.
I Student-klassen ovan kan vi lägga till ett attribut next:
class Student: def __init__(self, namn, kthid, program, next = None): self.namn = namn self.kthid = kthid self.program = program self.next = next # Ska peka på nästa StudentDå kan vi skapa länkade listor av Student-objekt så här:
studiegrupp = None studiegrupp = Student("Emma Khama", "i56ef2g", "CDATE", None) studiegrupp.next = Student("Kim Robinson", "i56ag3g", "CMETE", None) studiegrupp.next.next = Student("Nour Simson", "i56tr3c", "CLGYM", None) p = studiegrupp while p != None: print(p) p = p.next
Vad är dåligt med att lägga till ett next-fält i Student-klassen?
Enklare vore ju om vi kunde skilja på tankarna om den
länkade listan och studentens data. Vi generaliserar och
kallar innehållet i noden för data.
Här är klassen Node
:
class Node: def __init__(self, x, next = None): self.data = x # Kan referera till värde av valfri typ self.next = next
När en nod skapas med n = Node(rad)
har n.next
värdet None
, dvs pekar inte på någonting.
Man kan med fördel göra en övergripande klass som håller ordning på noderna. T.ex. har en first- och en lastpekare.
Abstraktion innebär att dölja detaljer. En användare behöver inte känna till detaljer för att kunna utnyttja en datastruktur eller algoritm och en konstruktör behöver inte veta var informationen kommer ifrån eller vad resultaten ska utnyttjas till. Några fördelar med abstraktion:
Begreppet gränssnitt används i många sammanhang,
men det handlar alltid om någon form av "kontakt" eller "kommunikation".
Man talar tex om gränssnittet mellan olja och vatten som skiktat sig
i en behållare
och om grafiska användargränssnitt som underlättar kommunikationen
mellan användare och datorprogram.
Här gäller det gränssnitt inuti program; hur en viss del av koden
kommunicerar med resten av programmet.
Heltal, flyttal, textsträngar och vektorer är datorns datatyper. Verklighetens datatyper är många fler, till exempel pengar, temperaturer och datum. Det är frestande att låta pengar representeras av heltal, temperaturer av flyttal (grader Celsius) och datum av textsträngar ("2014-09-02"), alltså av konkreta datatyper, men det är inte så bra. Datorn kan inte lagra hur stora heltal som helst, så när man kommer upp i stora belopp beter sig inte programmet som man tänkt sig. Temperatur anges i Fahrenheit i USA, så där räknar programmet fel. Och misstaget att ha datum som konkret datatyp kostade hundratals miljarder i omprogrammering vid tusenårsskiftet.
En abstrakt datatyp
saldo = kronor() # Ett objekt av den abstrakta datatypen kronor saldo.set(2000) # Sätter nytt värde saldo.plus(1500) # Ändrar värdet print(saldo.get()) # Åtkomst av värdet - - - T = temperatur() # Ett objekt av den abstrakta datatypen temperatur T.setC(11.5) # Sätter nytt värde i grader Celsius print(T.getF()) # Åtkomst i grader Fahrenheit - - - d = datum() # Ett objekt av den abstrakta datatypen datum d.set(2012,10,31) # Sätter ett värde if d.helgdag(): # Användbara anrop finnsSpecifikationen av vilka anrop som finns kallas datatypens gränssnitt och är det enda användaren behöver känna till. Hur data representeras konkret och hur metoderna implementerats behöver användaren inte veta. Om implementationen ändras påverkar det inte gränssnittet, så ingen användarkod behöver ändras.
En kö fungerar som man förväntar sig, dvs det man stoppar in först är det som tas ut först.
För en abstrakt kö finns följande operationer: |
put(x)
        Stoppa in x sist i kön.
x = get()
      Plocka ut och returnera det som står först i kön.
isEmpty()
      Undersök om kön är tom.
|
I labb 2 ska ni använda en kö för att förbereda en kortkonst!
En stack fungerar som en trave tallrikar - det man lägger överst på stacken
är det som kommer att tas bort först.
För en abstrakt stack finns följande operationer: |
push(x)
      Lägg x överst på stacken.
x = pop()
    Plocka ut och returnera det som ligger överst.
isEmpty()
    Undersök om stacken är tom.
|
Syster Maud inser att vaccinet inte kommer att räcka till alla
och ser att många i början av kön är unga och friska. Hon tror
att dom som är i minst behov av vaccination hamnat först
(dom sprang snabbast).
Hon se till att personer som är 65 eller äldre
vaccineras först. Hur ska hon göra?
Skriv programmet MaudVaccinerar.py som läser filen
patienter.txt av typen
Usain 28 Henrik 72 Alexander 46 Linda 51 Elsa 82 Herman 104och skriver ut alla under 65 först.
Yngre måste tillfälligt läggas i ett förvaringsutrymme medan filen läses
igenom, till exempel en stack. Man lägger ett objekt på stacken med
anropet push(p) och man hämtar ett objekt från
stacken med p = pop() . Så här blir programmet:
|
from stack import Stack class Patient: def __init__(self, namn, ålder): self.namn = namn self.ålder = int(ålder) def __str__(self): return self.namn + " " + str(self.ålder) + " år" def main(): korridor = Stack() register = open("patienter.txt","r") for rad in register: lista = rad.split() namn = lista[0] ålder = lista[1] p = Patient(namn, ålder) if p.ålder < 65: korridor.push(p) else: print("Vaccinerar:", p) print("Om vi hinner tar vi också: ") while not korridor.isEmpty(): p = korridor.pop() print("\t", p) main() |
I vilken ordning kommer personerna ut?
stack.py
För en stack behövs
bara en referens top
till den översta noden, sedan kommer
man åt övriga noder genom att följa next
-pekarna.
class Stack: def __init__(self): self.top = None def push(self,x): """Lägger x överst på stacken """ ny = Node(x) ny.next = self.top self.top = ny def pop(self): """Plockar ut och returnerar det översta elementet """ x = self.top.data self.top = self.top.next return x def isEmpty(self): """Returnerar True om stacken är tom, False annars""" if self.top == None: return True else: return False class Node: def __init__(self, x, next = None): self.data = x self.next = next
if __name__ == "__main__": s = Stack() s.push(3) if s.pop() == 3: print ("klarar testet") else: print ("klarar inte testet")__name__ sätts till "__main__" enbart när man kör programmet från kommandoraden. Ett testfall hårdkodas med testdata och testas mot ett förväntat värde. Man kan med fördel skriva testerna först och bara ha tomma implementationer. När man kör testprogrammen så fallerar alla testerna men allt eftersom man implementerar funktionerna så passerar testerna. Att skriva testerna först och implementationen därefter kallas testdriven utveckling.
En kö kan implementeras likadant som länkad lista,
nu vill man ha en pekare i var ände på kön. Den som hette top
i stacken kallar vi first och så har vi last som
pekar på sista noden. Där ska nämligen nya noder stoppas in.
|
class Queue: def __init__(self): self.first = None self.last = None def put(self,x): """Stoppar in x sist i kön """ ny = Node(x) if self.first == None: # Om kön är tom blir det på ett sätt... - - - # ...som du får tänka ut själv. else: # Annars blir det på ett annat sätt.. - - - # ...som du också får lura ut själv. def get(self): """Plockar ut och returnerar det som står först i kön """ - - - def isEmpty(self): """Returnerar True om kön är tom, False annars """ - - - |