4. Funktioner

Programmeringsteknik

(Skillnad mellan versioner)
Hoppa till: navigering, sök
Versionen från 29 juni 2007 kl. 14.19 (redigera)
KTH.SE:u1w7eri0 (Diskussion | bidrag)
(Funktioner)
← Gå till föregående ändring
Versionen från 29 juni 2007 kl. 14.24 (redigera) (ogör)
KTH.SE:u1w7eri0 (Diskussion | bidrag)
(Funktioner)
Gå till nästa ändring →
Rad 1: Rad 1:
-==Funktioner== 
- 
- 
Det här kommer du att lära dig i detta avsnitt: Det här kommer du att lära dig i detta avsnitt:
Rad 10: Rad 7:
* Skicka indata till en funktion (parametrar) * Skicka indata till en funktion (parametrar)
* Rekursion * Rekursion
 +
 +Stora program är besvärligare att konstruera än små.
 +Men man kan göra det enklare för sig genom att dela
 +upp programmet i funktioner. En funktion är som ett
 +litet delprogram i programmet.
 +
 +[FOTO PÅ EN RIKTIG SNIGELTÄVLING]
 +
 +När man anordnar tävlingar med riktiga sniglar tar det mycket
 +lång tid innan deltagarna kommer i mål. Vi ska istället skriva
 +ett litet program som simulerar en tävling mellan två sniglar:
 +
 +[SKÄRMBILD ======================================================
 +
 + ---------------------------------------------------
 + | VEM HAR DEN SNABBASTE SNIGELN? |
 + | |
 + | Här får din snigel tävla mot en vältränad |
 + | racersnigel. Skriv in namnet på din snigel |
 + | så sätter tävlingen igång! |
 + ---------------------------------------------------
 +Vad heter din snigel? Ebba
 +Klara...färdiga...gå!
 +
 + Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @
 +Racersnigeln: - - - - - - - - - - - - - - - - - - - - - - - - - - - @
 +
 +
 +Det här loppet tog en oväntad vändning, Ebba vann!
 +======================================================]
 +
 +
 +
 +
 +==Dela upp ett problem i funktioner==
 +
 +För att programmet ska bli enklare att skriva bestämmer vi
 +först vilka funktioner vi ska ha. Det är inte alls självklart
 +hur ett programmet ska delas upp i funktioner; samma problem
 +kan lösas på många olika sätt! Vi vill se till att varje
 +funktion har ett enda uppdrag.
 +
 +Här är ett förslag på uppdelning:
 +
 +1. Skriv ut informationsrutan och läs in namnet på snigeln
 +2. Simulera tävlingen och rita upp banorna
 +3. Skriv ut vem som vann
 +
 +Ettan och tvåan består av flera olika moment. Vi gör det
 +ännu enklare för oss genom att dela upp i ännu mindre delar:
 +
 +1. Skriv ut informationsrutan
 +2. Läs in namnet på en snigel
 +3. Simulera tävlingen
 +4. Rita upp en snigels bana
 +5. Skriv ut vem som vann
 +
 +Vi börjar med att skriva in dessa moment som kommentarer i
 +filen snigel.py, för att vi ska hålla reda på vad som
 +är kvar att göra. Då får vi också en kommentar för varje
 +funktion, så att det syns vad som händer var.
 +
 +
 +
 +==Definiera en funktion==
 +
 +Först kommer funktionshuvudet (första raden i funktionen). Exempel:
 +
 +<pre>
 +def visaInformation():
 +</pre>
 +
 +Funktionshuvudet består av fyra delar.
 +* Ordet def (talar om att en funktion ska definieras här)
 +* Funktionens namn, gärna ett verb som beskriver vad funktionen gör
 +* Ett par parenteser ()
 +* Ett kolon
 +
 +Under funktionshuvudet kommer funktionskroppen, som helt enkelt är
 +en följd av indenterade satser som funktionen ska utföra.
 +Så här ser hela funktionen ut:
 +
 +<pre>
 +# Skriver ut informationsrutan
 +def visaInformation():
 + print """
 + ---------------------------------------------------
 + | VEM HAR DEN SNABBASTE SNIGELN? |
 + | |
 + | Här får din snigel tävla mot en vältränad |
 + | racersnigel. Skriv in namnet på din snigel |
 + | så sätter tävlingen igång! |
 + ---------------------------------------------------"""
 +</pre>
 +
 +
 +
 +Lägg till koden ovan i din fil snigel.py och provkör.
 +Men vad nu - programmet skriver inte ut något alls?
 +Gå vidare till nästa avsnitt för att få reda på varför!
 +
 +
 +==Anropa en funktion==
 +
 +Satserna i funktionskroppen utförs inte om inte funktionen
 +anropas. Anropet ska stå längst ner i programmet, under
 +funktionsdefinitionerna. Den delen av programmet kallar vi
 +huvudprogrammet.
 +
 +Anropet består bara av funktionens namn följt av ett parentespar.
 +Om du lägger till anropet sist i ditt program ska alltihop se ut så här:
 +
 +<pre>
 +# Skriver ut informationsrutan
 +def visaInformation():
 + print """
 + ---------------------------------------------------
 + | VEM HAR DEN SNABBASTE SNIGELN? |
 + | |
 + | Här får din snigel tävla mot en vältränad |
 + | racersnigel. Skriv in namnet på din snigel |
 + | så sätter tävlingen igång! |
 + ---------------------------------------------------"""
 +
 +# Läs in namnet på en snigel
 +# Simulera tävlingen
 +# Rita upp en snigels bana
 +# Skriv ut vem som vann
 +
 +visaInformation()
 +</pre>
 +
 +Spara i filen snigel.py och provkör!
 +
 +
 +==Skicka utdata från en funktion (returvärden)==
 +
 +
 +Då ger vi oss på nästa funktion, som ska läsa in namnet på en snigel.
 +Namnet på snigeln är utdata från funktionen. Den som kör programmet
 +och knappar in namnet tycker förstås att det är indata, men
 +ur funktionens synvinkel är det nåt som ska skickas ut.
 +
 +För att skicka ut ett värde ur funktionen skriver vi ordet
 +return följt av den variabel vi vill skicka ut värdet på.
 +Funktionen som läser in namnet kommer att se ut så här:
 +
 +<pre>
 +def lasNamn():
 + namn = raw_input("Vad heter din snigel? ")
 + return namn
 +</pre>
 +
 +
 +När vi ska anropa funktionen måste vi ta hänsyn till att den
 +returnerar ett värde. Därför skriver vi anropet i en tilldelningssats,
 +med en variabel till vänster som får ta emot det returnerade värdet.
 +Variabeln behöver inte ha samma namn som variabeln i return-satsen
 +inuti funktionen.
 +
 +<pre>
 +dinSnigelsNamn = lasNamn()
 +</pre>
 +
 +När den här satsen utförs av Python kommer följande att hända. Först
 +anropas funktionen lasNamn, som läser in namnet och returnerar det.
 +Sen kommer det returnerade värdet att lagras i variabel dinSnigelsNamn.
 +
 +Vi lägger in den nya i filen snigel.py och provkör.
 +
 +<pre>
 +# Skriver ut informationsrutan
 +def visaInformation():
 + print """
 + ---------------------------------------------------
 + | VEM HAR DEN SNABBASTE SNIGELN? |
 + | |
 + | Här får din snigel tävla mot en vältränad |
 + | racersnigel. Skriv in namnet på din snigel |
 + | så sätter tävlingen igång! |
 + ---------------------------------------------------"""
 +
 +# Läs in namnet på en snigel
 +def lasNamn():
 + namn = raw_input("Vad heter din snigel? ")
 + return namn
 +
 +# Simulera tävlingen
 +# Rita upp en snigels bana
 +# Skriv ut vem som vann
 +
 +visaInformation()
 +dinSnigelsNamn = lasNamn()
 +</pre>
 +
 +
 +Men hur ska vi veta om det fungerade eller inte?
 +Jo, vi lägger in en kontrollutskrift
 +<pre>
 +print "Din snigel heter alltså", dinSnigelsNamn
 +</pre>
 +allra sist i programmet. Kontrollutskrifter är en enkelt sätt
 +att se om programmet fungerar som det ska, och är till stor
 +nytta när man försöker ta reda på varför programmet inte gör
 +som det ska.
 +
 +Provkör igen. När programmet fungerar kan du ta bort kontrollutskriften!
 +
 +Det här är viktigt, så vi tar ett exempel till, den här gången med
 +två returvärden. Nästa funktion ska simulera tävlingen, på det
 +här viset:
 +* Sniglarna startar bägge på startstrecket, dvs vid 0
 +* Vi skriver ut Klara...färdiga...gå
 +* Och startar själva tävlingen, som representeras av en while-slinga
 +* I varje varv i slingan låter vi sniglarna ta varsitt skutt av slumpmässig längd
 +* När någon av sniglarna (eller bägge) passerar mållinjen så avbryter vi
 +* Sist returnerar vi bägge sniglarnas slutpositioner
 +
 +<pre>
 +def tavling():
 + snigelbana1 = 0
 + snigelbana2 = 0
 + print "Klara...färdiga...gå! \n"
 + while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
 + snigelbana1 += random.randrange(5)
 + snigelbana2 += random.randrange(5)
 + return snigelbana1, snigelbana2
 +</pre>
 +
 +I return-satsen allra sist kan vi se att två värden
 +returneras, åtskilda med kommatecken. Givetvis måste vi då också
 +ta emot två värden i huvudprogrammet.
 +
 +Den distans (i centimeter) som sniglarna ska tillryggalägga
 +representeras av konstanten DISTANS. En variabel som ska
 +ha ett konstant värde under hela programmet brukar man
 +skriva med stora bokstäver, VERSALER. Python bryr sig inte om det, men
 +andra människor som läser programmet kan se vad man menar.
 +Vi definierar DISTANS överst i huvudprogrammet, och frågar
 +oss om den kommer att synas inuti funktionen?
 +
 +Lägg in den nya funktionen i snigel.py, enligt nedan. Glöm inte
 +att lägga in kontrollutskrifter av snigelbana1 och snigelbana2
 +innan du provkör!
 +
 +<pre>
 +import random
 +
 +# Skriver ut informationsrutan
 +def visaInformation():
 + print """
 + ---------------------------------------------------
 + | VEM HAR DEN SNABBASTE SNIGELN? |
 + | |
 + | Här får din snigel tävla mot en vältränad |
 + | racersnigel. Skriv in namnet på din snigel |
 + | så sätter tävlingen igång! |
 + ---------------------------------------------------"""
 +
 +# Läser in namnet på användarens snigel
 +def lasNamn():
 + namn = raw_input("Vad heter din snigel? ")
 + return namn
 +
 +# Simulerar en tävling mellan två sniglar
 +def tavling():
 + snigelbana1 = 0
 + snigelbana2 = 0
 + print "Klara...färdiga...gå! \n"
 + while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
 + snigelbana1 += random.randrange(5)
 + snigelbana2 += random.randrange(5)
 + return snigelbana1, snigelbana2
 +
 +DISTANS = 30
 +visaInformation()
 +dinSnigelsNamn = lasNamn()
 +snigelbana1, snigelbana2 = tavling()
 +</pre>
 +
 +När du fått det här att fungera så ska vi ta en ny titt på funktionen
 +tavling. Ser du att vi gör precis samma sak med variablerna snigelbana1
 +och snigelbana2? Först sätts variablerna till noll, sedan jämför vi
 +bägge med DISTANS i while-slingans villkor, och inuti while-slingan
 +ökas bägge med ett slumpat värde. Sist returneras bägge värdena.
 +Det här kallas för <em>kodupprepning</em> och det ska man försöka
 +undvika, av följande skäl:
 +* Det blir mer kod att skriva, vilket ger fler felkällor
 +* Programmet blir svårare att underhålla - när man ska införa ändringar måste man ändra för bägge variablerna
 +* Det ser fult ut
 +
 +==Skicka indata till en funktion (parametrar)==
 +
 +I det här avsnittet ska vi sona vårt stilbrott genom att skriva
 +en generell funktion för utskrift av en snigels bana. Funktionen ska gå
 +att använda för vilken snigel som helst.
 +
 +Utskriften av en snigelbana kan se ut så här:
 +<code>
 + Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @
 +</code>
 +Vad vill vi kunna variera från en snigel till en annan? Två saker - snigelns
 +namn och banans längd. Säg att vår funktion heter <code>ritaBanan</code>.
 +Här följer tre olika exempel på hur en sån funktion skulle kunna anropas:
 +<pre>
 +ritaBanan("Ebba", 31)
 +ritaBanan("Racersnigeln", 28)
 +ritaBanan(dinSnigelsNamn, snigelBana1)
 +</pre>
 +
 +De två värden eller variabler som står innanför parenteserna i anropet
 +kallas för <em>parametrar</em>. När vi definierar funktionen måste vi
 +namnge alla parametrarna. Vi kallar dom för <code>snigelnamn</code>
 +respektive <code>langd</code>. Så här ser funktionen ut.
 +<pre>
 +def ritaBanan(snigelnamn,langd):
 + print snigelnamn.rjust(12) + ":",
 + for i in range(1,langd):
 + print "-", # slemspåret
 + print "@" # snigeln
 +</pre>
 +
 +Du har nog inte sett rjust förut? Det är en strängmetod som
 +högerjusterar strängen i givet antal positioner. Namnen kommer
 +då att sluta i samma position (12), så att våra snigelbanor kan
 +starta i samma läge.
 +
 +==Rekursion==
==Test== ==Test==

Versionen från 29 juni 2007 kl. 14.24

Det här kommer du att lära dig i detta avsnitt:

  • Dela upp ett problem i funktioner
  • Definiera en funktion
  • Anropa en funktion
  • Skicka utdata från en funktion (returvärden)
  • Skicka indata till en funktion (parametrar)
  • Rekursion

Stora program är besvärligare att konstruera än små. Men man kan göra det enklare för sig genom att dela upp programmet i funktioner. En funktion är som ett litet delprogram i programmet.

[FOTO PÅ EN RIKTIG SNIGELTÄVLING]

När man anordnar tävlingar med riktiga sniglar tar det mycket lång tid innan deltagarna kommer i mål. Vi ska istället skriva ett litet program som simulerar en tävling mellan två sniglar:

[SKÄRMBILD ======================================================

            ---------------------------------------------------
            |          VEM HAR DEN SNABBASTE SNIGELN?         |
            |                                                 |
            |  Här får din snigel tävla mot en vältränad      |
            |  racersnigel. Skriv in namnet på din snigel     |
            |  så sätter tävlingen igång!                     |
            ---------------------------------------------------

Vad heter din snigel? Ebba Klara...färdiga...gå!

       Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @

Racersnigeln: - - - - - - - - - - - - - - - - - - - - - - - - - - - @


Det här loppet tog en oväntad vändning, Ebba vann! ======================================================]



Innehåll

Dela upp ett problem i funktioner

För att programmet ska bli enklare att skriva bestämmer vi först vilka funktioner vi ska ha. Det är inte alls självklart hur ett programmet ska delas upp i funktioner; samma problem kan lösas på många olika sätt! Vi vill se till att varje funktion har ett enda uppdrag.

Här är ett förslag på uppdelning:

1. Skriv ut informationsrutan och läs in namnet på snigeln 2. Simulera tävlingen och rita upp banorna 3. Skriv ut vem som vann

Ettan och tvåan består av flera olika moment. Vi gör det ännu enklare för oss genom att dela upp i ännu mindre delar:

1. Skriv ut informationsrutan 2. Läs in namnet på en snigel 3. Simulera tävlingen 4. Rita upp en snigels bana 5. Skriv ut vem som vann

Vi börjar med att skriva in dessa moment som kommentarer i filen snigel.py, för att vi ska hålla reda på vad som är kvar att göra. Då får vi också en kommentar för varje funktion, så att det syns vad som händer var.


Definiera en funktion

Först kommer funktionshuvudet (första raden i funktionen). Exempel:

def visaInformation():

Funktionshuvudet består av fyra delar.

  • Ordet def (talar om att en funktion ska definieras här)
  • Funktionens namn, gärna ett verb som beskriver vad funktionen gör
  • Ett par parenteser ()
  • Ett kolon

Under funktionshuvudet kommer funktionskroppen, som helt enkelt är en följd av indenterade satser som funktionen ska utföra. Så här ser hela funktionen ut:

# Skriver ut informationsrutan
def visaInformation():
    print """
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------"""


Lägg till koden ovan i din fil snigel.py och provkör. Men vad nu - programmet skriver inte ut något alls? Gå vidare till nästa avsnitt för att få reda på varför!


Anropa en funktion

Satserna i funktionskroppen utförs inte om inte funktionen anropas. Anropet ska stå längst ner i programmet, under funktionsdefinitionerna. Den delen av programmet kallar vi huvudprogrammet.

Anropet består bara av funktionens namn följt av ett parentespar. Om du lägger till anropet sist i ditt program ska alltihop se ut så här:

# Skriver ut informationsrutan
def visaInformation():
    print """
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------"""

# Läs in namnet på en snigel
# Simulera tävlingen
# Rita upp en snigels bana
# Skriv ut vem som vann

visaInformation()

Spara i filen snigel.py och provkör!


Skicka utdata från en funktion (returvärden)

Då ger vi oss på nästa funktion, som ska läsa in namnet på en snigel. Namnet på snigeln är utdata från funktionen. Den som kör programmet och knappar in namnet tycker förstås att det är indata, men ur funktionens synvinkel är det nåt som ska skickas ut.

För att skicka ut ett värde ur funktionen skriver vi ordet return följt av den variabel vi vill skicka ut värdet på. Funktionen som läser in namnet kommer att se ut så här:

def lasNamn():
    namn = raw_input("Vad heter din snigel? ")
    return namn


När vi ska anropa funktionen måste vi ta hänsyn till att den returnerar ett värde. Därför skriver vi anropet i en tilldelningssats, med en variabel till vänster som får ta emot det returnerade värdet. Variabeln behöver inte ha samma namn som variabeln i return-satsen inuti funktionen.

dinSnigelsNamn = lasNamn()

När den här satsen utförs av Python kommer följande att hända. Först anropas funktionen lasNamn, som läser in namnet och returnerar det. Sen kommer det returnerade värdet att lagras i variabel dinSnigelsNamn.

Vi lägger in den nya i filen snigel.py och provkör.

# Skriver ut informationsrutan
def visaInformation():
    print """
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------"""

# Läs in namnet på en snigel
def lasNamn():
    namn = raw_input("Vad heter din snigel? ")
    return namn

# Simulera tävlingen
# Rita upp en snigels bana
# Skriv ut vem som vann

visaInformation()
dinSnigelsNamn = lasNamn()


Men hur ska vi veta om det fungerade eller inte? Jo, vi lägger in en kontrollutskrift

print "Din snigel heter alltså", dinSnigelsNamn

allra sist i programmet. Kontrollutskrifter är en enkelt sätt att se om programmet fungerar som det ska, och är till stor nytta när man försöker ta reda på varför programmet inte gör som det ska.

Provkör igen. När programmet fungerar kan du ta bort kontrollutskriften!

Det här är viktigt, så vi tar ett exempel till, den här gången med två returvärden. Nästa funktion ska simulera tävlingen, på det här viset:

  • Sniglarna startar bägge på startstrecket, dvs vid 0
  • Vi skriver ut Klara...färdiga...gå
  • Och startar själva tävlingen, som representeras av en while-slinga
  • I varje varv i slingan låter vi sniglarna ta varsitt skutt av slumpmässig längd
  • När någon av sniglarna (eller bägge) passerar mållinjen så avbryter vi
  • Sist returnerar vi bägge sniglarnas slutpositioner
def tavling():
    snigelbana1 = 0
    snigelbana2 = 0
    print "Klara...färdiga...gå! \n"
    while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
        snigelbana1 += random.randrange(5)
        snigelbana2 += random.randrange(5)
    return snigelbana1, snigelbana2

I return-satsen allra sist kan vi se att två värden returneras, åtskilda med kommatecken. Givetvis måste vi då också ta emot två värden i huvudprogrammet.

Den distans (i centimeter) som sniglarna ska tillryggalägga representeras av konstanten DISTANS. En variabel som ska ha ett konstant värde under hela programmet brukar man skriva med stora bokstäver, VERSALER. Python bryr sig inte om det, men andra människor som läser programmet kan se vad man menar. Vi definierar DISTANS överst i huvudprogrammet, och frågar oss om den kommer att synas inuti funktionen?

Lägg in den nya funktionen i snigel.py, enligt nedan. Glöm inte att lägga in kontrollutskrifter av snigelbana1 och snigelbana2 innan du provkör!

import random

# Skriver ut informationsrutan
def visaInformation():
    print """
             ---------------------------------------------------
             |          VEM HAR DEN SNABBASTE SNIGELN?         |
             |                                                 |
             |  Här får din snigel tävla mot en vältränad      |
             |  racersnigel. Skriv in namnet på din snigel     |
             |  så sätter tävlingen igång!                     |
             ---------------------------------------------------"""

# Läser in namnet på användarens snigel
def lasNamn():
    namn = raw_input("Vad heter din snigel? ")
    return namn

# Simulerar en tävling mellan två sniglar
def tavling():
    snigelbana1 = 0
    snigelbana2 = 0
    print "Klara...färdiga...gå! \n"
    while snigelbana1 < DISTANS and snigelbana2 < DISTANS:
        snigelbana1 += random.randrange(5)
        snigelbana2 += random.randrange(5)
    return snigelbana1, snigelbana2

DISTANS = 30
visaInformation()
dinSnigelsNamn = lasNamn()
snigelbana1, snigelbana2 = tavling()

När du fått det här att fungera så ska vi ta en ny titt på funktionen tavling. Ser du att vi gör precis samma sak med variablerna snigelbana1 och snigelbana2? Först sätts variablerna till noll, sedan jämför vi bägge med DISTANS i while-slingans villkor, och inuti while-slingan ökas bägge med ett slumpat värde. Sist returneras bägge värdena. Det här kallas för kodupprepning och det ska man försöka undvika, av följande skäl:

  • Det blir mer kod att skriva, vilket ger fler felkällor
  • Programmet blir svårare att underhålla - när man ska införa ändringar måste man ändra för bägge variablerna
  • Det ser fult ut

Skicka indata till en funktion (parametrar)

I det här avsnittet ska vi sona vårt stilbrott genom att skriva en generell funktion för utskrift av en snigels bana. Funktionen ska gå att använda för vilken snigel som helst.

Utskriften av en snigelbana kan se ut så här:

       Ebba: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @

Vad vill vi kunna variera från en snigel till en annan? Två saker - snigelns namn och banans längd. Säg att vår funktion heter ritaBanan. Här följer tre olika exempel på hur en sån funktion skulle kunna anropas:

ritaBanan("Ebba", 31)
ritaBanan("Racersnigeln", 28)
ritaBanan(dinSnigelsNamn, snigelBana1)

De två värden eller variabler som står innanför parenteserna i anropet kallas för parametrar. När vi definierar funktionen måste vi namnge alla parametrarna. Vi kallar dom för snigelnamn respektive langd. Så här ser funktionen ut.

def ritaBanan(snigelnamn,langd):
    print snigelnamn.rjust(12) + ":",
    for i in range(1,langd):
        print "-",                 # slemspåret
    print "@"                      # snigeln

Du har nog inte sett rjust förut? Det är en strängmetod som högerjusterar strängen i givet antal positioner. Namnen kommer då att sluta i samma position (12), så att våra snigelbanor kan starta i samma läge.

Rekursion

Test

TODO


Inlämningsuppgift 2

TODO

Personliga verktyg