TDDE44 Programmering, grundkurs¶

Föreläsning 7¶

Johan Falkenjack, johan.falkenjack@liu.se¶

Föreläsningsöversikt¶

  • Information om datorsalstentan
  • Laboration 7
  • Kort om algoritmer och komplexitet
  • Skriva till fil
  • Ännu mer om testning av kod
  • Repetition

Datorsalstentan¶

logistik på plats, upplägg, inlämning

Datorsalstenta, 3 juni kl. 8.00-12.00¶

  • Anmälan via Lisam eller LiU-appen.
    • Anmälningsperiod: 4 maj-24 maj
  • Info på kurshemsidan under "Datorsalstenta"
  • Hybridrättning
  • Kort allmän info
    • https://www.ida.liu.se/edu/ugrad/datortenta/

Sal, inloggning, kontroll av bok¶

  • Information om vilken sal du ska gå till vid SU17/18
  • Inloggning
    • Står "tentainloggning"
    • Logga in med LiU-ID och ditt vanliga lösenord
    • Liknande datormiljö som vid labbande, men utan internetåtkomst
  • Kontroll av bok

Filer: tenta, datafiler, referens-PDF¶

  • Din hemkatalog under tentan nås via ~/
    • (Inte din vanliga hemkatalog, den har du inte tillgång till under tentan.)
  • Tentan som PDF samt eventuella filer som hör ihop med tentan finns i katalogen ~/given_files
  • Här finns också en PDF med dokumentationen för Python 3.10.
  • ~/given_files är skrivskyddad

Skriva tentan - rekommendation¶

  • Kopiera ev. datafiler från given_files till skrivbordet.
  • Spara dina filer till skrivbordet.
  • Skrivbordet via terminal: cd ~/Desktop
  • OBS! Glöm inte att spara dina filer, VSCode kommer inte spara automatiskt under tentan.
    • Du har inte heller tillgång till VSCodes Python extension under tentan, dvs. ingen code completion/IntelliSense och ingen "Play"-knapp för att köra din kod, du måste kunna köra via terminalen.

Frågor under tentan¶

  • Praktiska frågor: jour-assistenter och tentavakter
  • Frågor om tentauppgifter: använd studentklienten
  • Studentklienten: program som startas automatiskt vid inloggning och används för kommunikation med examinator samt inlämning av uppgifter

Tentaupplägg¶

  • Två delar, Del 1 och Del 2
  • Del 1: grundläggande uppgifter, samma storlek som enskilda uppgifter från Pythonuppgifter 1-3
  • Del 2: något mer omfattande uppgifter

  • Inga avdrag för avvikelser från PEP8 eller PEP257

Del 1¶

  • Krävs för att bli godkänd på tentan
  • Rättas live under tentatillfället och uppgifterna kan kompletteras i mån av tid.
  • Del 1 innehåller 5 uppgifter: 1.1, 1.2, etc. eventuellt med mer än en deluppgift.
  • Kom ihåg att lämna in en uppgift så fort ni är klara med den.
    • Om ni skickar in alla uppgifter 5 minuter innan tentans slut hinner ni inte få svar eller möjlighet att komplettera.
  • För att gå godkänt på Del 1 gäller att alla uppgifter måste vara godkända.

  • Målsättningen är att alla ska veta huruvida de blivit åtminstone godkända på tentan vid tentatidens slut.
    • (Några få gränsfall kan dock komma att godkännas i efterhand.)

Del 2¶

  • Rättas bara om Del 1 är godkänd och rättas efter tentan.
  • Består av några få större problemlösningsuppgifter.
  • En fungerande lösning på en uppgift ger minst 3 poäng.
  • Bonuspoäng upp till max 5 poäng per uppgift delas ut för bra lösningar.
  • Inkompletta och delvis felaktiga lösningar KAN ge 1-2 poäng.

  • Man kommer inte veta om man uppnått kraven för 4 eller 5 vid tentatidens slut.

Del 2 - Vad menas med "bra" lösning?¶

  • Lösningen är uppdelad i lämpliga delfunktioner med tydliga namn.
  • Lösningen använder tydliga variabelnamn.
  • Lösningen använder lämpliga datatyper.
  • Lösningen innehåller inte onödiga operationer, t.ex.
    • Duplicering av samma operationer på flera ställen.
    • Överflödiga typkonverteringar.
  • Lösningen är kommenterad på lämplig detaljnivå.

Betyg¶

  • Betyg 3: Godkänd på del 1.
  • Betyg 4: Godkänd på del 1 + ca 40 % på del 2.
  • Betyg 5: Godkänd på del 1 + ca 70 % på del 2.

Inlämning av uppgifter¶

  • En fil per uppgift
    • Eventuella deluppgifter (ex. 1.2.1, 1.2.2, etc.) lämnas in i samma fil.
  • Döp filen så att uppgiftsnummret finns med, t.ex. uppg12.py för ovanstående deluppgifter (står i tentan)
  • Filen skickas in via studentklienten:
      1. Tryck på knappen "Skicka in uppgift"
      1. Välj vilken uppgift och välj vilken fil som skickas in
      1. Bekräftelse på att filen är mottagen "Begäran mottagen"

Prova-på-tenta 21 maj 10.15-11.00 och 11.15-12.00¶

  • 10.15: Person 1 i varje labbpar i webreg
  • 11.15: Person 2 i varje labbpar i webreg (och ev. Person 3)
  • Lokal enligt vanliga schemat i TimeEdit

  • Möjlighet för både er och assistenterna att förbereda er inför riktiga tentan.
  • Se miljön, prova att ställa frågor i studentklienten, prova att skicka in uppgift och få feedback.

  • Utnyttja den här möjligheten! Vi vet av erfarenhet att många blir stressade första gången de ser tentamiljön och detta är ett sätt för er att slippa den stressen vid det riktiga tentatillfället.

På kurshemsidan¶

  • Exempeltenta och övningsuppgifter
  • PDF som ni kommer ha tillgänglig som referens
  • https://www.ida.liu.se/~TDDE44/kurslogistik/tenta/

Laboration 7¶

Stavningskontroll

Laboration 7¶

  • Uppgiften
    • Ordfrekvensdata
    • Redigeringsavstånd
    • Klasser
  • Förberedelser: UML-diagram
  • Problem som kan uppstå när man har mycket data

Laboration 7: Rättstavning¶

  • "Fortsättning" på laboration 2
  • Skript som använder ordfrekvensdata för att hitta okända ord i en text och föreslå alternativ till dessa ord med hjälp av redigeringsavstånd
    • T.ex. hoppssn → hoppsan
  • Rapport om okända ord + förslag sparas till fil

Laboration 7: filer¶

  • ordfrekvensfiler
    • innehåller antal förekomster av olika ord från diverse källor
    • mest förekommande först i filerna
  • några textfiler
    • exempel på texter som ni kan "rättstava"
    • använd gärna egna texter

Kortaste redigeringsavståndet¶

  • Algoritm som räknar ut antal redigeringsoperationer som behövs för $word_A$ → $word_B$
  • Levenshtein-avstånd: minsta antalet operationer av typerna lägg till bokstav, ta bort bokstav och ersätt bokstav som förändrar ett ord till ett annat.
    • 'katt' → 'hatt'; en operation (ersätt 'k' med 'h')
    • 'katt' → 'kalas'; tre operationer (ersätt första 't' med 'l', ersätt andra 't' med 'a', lägg till 's')
    • 'hall' → 'hal'; en operation (ta bort ett 'l')
  • Funktionen minimum_edit_distance(s1, s2) finns implementerad och redo att importera från filen med.py

Laboration 7: Klasser¶

  • Rita UML-diagram och skriv ert program utifrån det. Visa för labbassistent innan ni börjar koda.
  • Uppdatera UML-diagrammet under tiden ni programmerar. Visa slutgiltig version vid redovisning.
  • Minst följande två klasser:
    • SpellingWarning: innehåller information om upptäckta ev. stavfel (som t.ex. plats, ordet och förslag)
    • Report: innehåller SpellingWarning-objekt
  • För varje upptäckt fel skapar ni en instans av klassen SpellingWarning.
  • Utöver dessa klasser är det upp till er.
  • WordFrequencyData: innehåller ordfrekvensdata + metoder för att uppdatera/använda denna data
  • MainProgram

Förberedelser innan ni börjar implementera¶

  • Läs igenom instruktionerna på kurshemsidan
  • Diskutera med er labpartner och rita upp UML-diagram.
  • Visa upp ert UML-diagram och berätta hur ni tänkt för en assistent.
    • Tidigare år har många hoppat över det här steget och fått flera jobbiga kompletteringar när deras approach visat sig orimlig, undvik att göra samma misstag.

Tips¶

  • En rapport har många upptäckta fel → jmf. flera hus i en stad
  • Vid kontroll av flera filer → skapa flera rapporter (instanser)

  • Behövs flera klasser? Vad vill ni representera? När ska man använda en inbyggd datatyp, t.ex. list eller dictionary och när ska man skapa en klass?
  • Försök att se till så att en klass bara har ett ansvarsområde.
  • Kom ihåg att använda metoder för att interagera med datan som klassen representerar, bryt inte inkapslingen i onödan.

Konsekvenser av öka mängden data att bearbeta¶

Laboration 7¶

  • Körtid < 1 minut för minsta datafilen på en dator i labbsalen.
  • Går att köra <1 minut för största datafilen om man tillämpar vissa (rimliga) begränsningar.

Exempeluppgift¶

  • text.txt innehåller en text på 1000 ord
  • ordfrekvenser.tsv innehåller ordfrekvenser för 500k ord;
    • varje rad består av
    • ett ord
    • ett tab-tecken ('\t')
    • frekvensen som ordet förekommer i en viss text-korpus (samling av texter)
  • Vi ska skriva en algoritm som tar fram vilka ord i text.txt som finns med i ordfrekvenser.tsv

Loopar: Se upp för att göra om saker i onödan. Alt 1¶

    1. Kontrollera ett ord. För varje word_to_check i texten som ska kontrolleras (1 000 ord)
    • 1.1 Läs in data. läs in alla ordfrekevenser och spara dem som tupler av ord och frekvens i listan freq_data (500 000 ord)
    • 1.2 Finns ord med i data? Gå igenom alla inlästa ord och kontrollera om det är word_to_check (500 000 ord)

  • $\sim 1\ 000 \times (500\ 000 + 500\ 000) = 1 \times 10^9$ operationer
  • Om en operation tar 1 nanosekund ($1 \times 10^{-9}$) tar vår algoritm 1 sekund.

Loopar: Se upp för att göra om saker i onödan. Alt 2¶

    1. Läs in data. Läs in alla ordfrekevenser och spara dem som tupler av ord och frekvens i seprata listor för olika längd på orden, dvs en lista per ordlängd. (500 000 ord)
    1. Kontrollera ett ord. För varje ord word_to_check i texten som ska kontrolleras (1000 ord)
    • 2.1 Finns ord med i data? Gå igenom alla inlästa ord av rätt längd och kontrollera om det är word_to_check, avbryt så fort ordet hittats (25 000 ord, givet antagandet att vi har i snitt 50 000 ord per ordlängd och vi i snitt behöver söka igenom hälften av dessa för att hitta ordet vi söker)

  • $\sim 500\ 000 + (1000 \times 25\ 000) = 2,55 \times 10^7$ operationer
  • Om en operation tar 1 nanosekund ($1 \times 10^{-9}$) tar detta alternativ 0,0255 sekunder

Loopar: Se upp för att göra saker i onödan¶

  • Alt 1:
    • Läser in all ordfrekvensdata för varje ord som ska kontrolleras
    • Kontrollerar word_to_check mot alla ord, även de som inte har samma ordlängd.
  • Alt 2:
    • Läser in all ordfrekvensdata en gång.
    • Kontrollerar word_to_check mot de ord som har samma längd
    • Avbryter sökning när ordet har hittats

Kan vi snabba på detta ytterligare?¶

Loopar: Se upp för att göra saker i onödan¶

  • Alt 1:
    • Läser in all ordfrekvensdata för varje ord som ska kontrolleras
    • Kontrollerar word_to_check mot alla ord, även de som inte har samma ordlängd.
  • Alt 2:
    • Läser in all ordfrekvensdata en gång.
    • Kontrollerar word_to_check mot de ord som har samma längd
    • Avbryter sökning när ordet har hittats
  • Vi minskade antalet ord vi kontrollerade mot.
  • Kan vi minska antalet ord att kontrollera mot ytterligare?
  • Kan vi minska antalet ord att kontrollera mot... till ett?

Alternativ 1: Alla ord i en bucket¶

  • Vi säger att alla ord ligger i en bucket (hink) och att vi enkelt kan ta reda på vilken bucket ett visst ord måste ligga i.
  • Vi måste leta igenom alla ord i rätt bucket för att vara säkra på att ett ord är okänt.
  • Om alla ord ligger i samma bucket, så måste vi leta igenom alla ord.

  • Vi måste gå igenom alla ord i vårt lexikon varje gång vi vill testa om ett ord i texten är känt eller ej.

Alternativ 2: Ord av olika längd i olika buckets¶

  • Vi skapar en bucket per ordlängd (vi antar att antalet ord längre än 25 tecken är så litet att vi lägger alla dessa ord i samma bucket) och använder len(word) för att ta reda på vilken bucket vi ska leta igenom.

  • Längden av en sträng kan slås upp utan att behöva stega igenom strängen eftersom strängar är immutable, så att hitta rätt bucket går fort.

Alternativ 3: Massor med buckets¶

  • Vi skapar $m$ stycken buckets.
  • Vi använder någon hash-funktion $h:U \rightarrow {0, ..., m-1}$ sådan att $h(word) \in [0,m-1]$.
  • Väljer vi $h$ med omsorg och gör $m$ tillräckligt stort kan vi se till att nästan varje ord har en egen bucket.

  • Vi kan betrakta Alternativ 2 som ett specialfall av Alternativ 3, med ett litet $m$ och $h = $ len.
  • Vi kan lätt inse att len inte är en särskilt bra hash-funktion, eftersom de flesta orden

Det verkar knöligt, måste vi göra detta själva?¶

  • Nej.
    • (...inte i den här kursen.)
  • Pythons datatyper dict och set använder redan det här sättet att lagra data.
    • (...så det är lämpligt att använda dessa om och när det går.)

Komplexitet¶

Hur krävande är det att lösa en specifik uppgift med en specifik algoritm?

Vad är en algoritm?¶

  • Informellt: En väldefinierad beräkningsbar (computational) procedur som givet ett värde, eller en mängd värden, som input producerar ett värde, eller en mängd värden som output. (Cormen et. al. 2009. Introduction to algorithms.)
  • En algoritm sägs vara korrekt om den för varje korrekt indata, avslutar med korrekt utdata.
  • Vi kan förenklat säga att allt som kan uttryckas som en funktion är en algoritm.
  • Vad innebär beräkningsbarhet? Att en algoritm som givet ett ändligt antal operationer ger ett korrekt svar kan konstrueras. De flesta vardagsproblem är beräkningsbara.
  • https://en.wikipedia.org/wiki/List_of_undecidable_problems

En algoritms komplexitet¶

  • En algoritms komplexitet besvarar frågan:

Om vi ökar mängden indata, hur mycket ökar resursanvändningen?

  • En algoritms komplexitet uttrycks normalt som en funktion, $f(n)$, av storleken på algoritmens indata.
    • Ibland ser man $T(n)$ för tidskomplexitet och $S(n)$ för minneskomplexitet.

Olika typer av komplexitet¶

  • Tidskomplexitet (eng. time complexity): Hur många operationer krävs för att beräkna en algoritm?

  • Minneskomplexitet (eng. space complexity): Hur mycket utrymme i minnet krävs för att beräkna en algoritm?

  • Ibland också:

    • Kommunikationskomplexitet: Hur mycket kommunikation behövs mellan parterna för att beräkna en distribuerad algoritm. (Relevant för distribuerade system, realtidssystem, HPC, etc.)
    • Bitkomplexitet: En mer rigoröst definierad form av tidskomplexitet.
    • Aritmetisk komplexitet: När tal är begränsade i storlek så bortser vi ofta från det faktum att olika stora tal tar olika lång tid att beräkna, när tal kan vara godtyckligt stora måste vi dock ta hänsyn till denna aritmetiska komplexitet.
  • Idag är minne billigt och vi bryr vi oss främst om tidskomplexitet och offrar gärna minnesresurser för snabbare program.

Tidskomplexitet, hur många operationer krävs?¶

  • Hur mycket behöver datorn göra? Hur komplicerat är det att lösa ett givet problem med en viss algoritm?
  • Exempel, for-loop, leta efter största elementet
    • Hur många operationer behöver algoritmen?
In [ ]:
def get_max_value(values):
    max_value = values[0] # 1
    for value in values: # len(values) = n
        if value > max_value: # 1
            max_value = value # 1
    return max_value # 1
    

Tidskomplexitet, hur många operationer krävs?¶

  • Hur mycket behöver datorn göra? Hur komplicerat är det att lösa ett givet problem med en viss algoritm?
  • Exempel, for-loop, leta efter största elementet
    • Hur många operationer behöver algoritmen? $1 + n(1 + 1) + 1 = 2n + 2$ där $n = $ len(values)
In [ ]:
def get_max_value(values):
    max_value = values[0]
    for value in values:
        if value > max_value:
            max_value = value
    return max_value
    
def get_max_value(values):
    max_value = values[0] # 1
    for value in values: # len(values)
        if value > max_value: # 1
            max_value = value # 1
    return max_value # 1

Asymptotisk analys¶

  • Antag att vi har funktionerna $f$ och $g$ sådana att
$$ \begin{align} f(x) &= x^3+x^2+x \\ g(x) &= x^3 + c \end{align} $$
  • Då gäller att
$$ \lim_{x\to\infty} \frac{f(x)}{g(x)} = 1 $$
  • Vi säger att $f$ och $g$ är asymtotiskt ekvivalenta och uttrycker detta som
$$ f(x) \sim g(x) \text{ när } x\to\infty $$

Ordo-notation¶

  • Eng. Big O notation
  • Om det existerar några positiva heltal $M$ och $x_0$ sådana att
$$ |f(x)| \leq M|g(x)| \text{ för alla } x \geq x_0. $$
  • så säger vi att
$$ f(x) = O(g(x)) \text{ när } x\to\infty \\ $$
  • eller vanligtvis bara
$$ f(x) = O(g(x)) $$

Ordo-reglerna¶

  • Om $f(x)$ är en summa av flera termer så kan alla termer utom den med högst tillväxttakt utelämnas.
  • Om $f(x)$ är en produkt av flera faktorer så kan alla konstanter (faktorer som inte beror på $x$) utelämnas.
  • Alltså: Eftersom get_max_value tog $2n+2$ operationer att utföra så säger vi att dess tidskomplexitet är $O(n)$.
    • (Utläses "Ordo n" på svenska och "Order of n" på engelska.)
  • Har ni kommit till Maclaurin-utvecklingar i Analysen? Där används Ordo för att beskriva storleken på resttermen.

Exempel $O(n)$¶

In [90]:
import random

def sum_all(values):
    acc = 0
    for value in values:
        acc += value
    return acc
In [124]:
%%timeit

sum_all(random.sample(range(1_000_000), k=10))
2.98 μs ± 42.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [125]:
%%timeit

sum_all(random.sample(range(1_000_000), k=20))
5 μs ± 73.9 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [126]:
%%timeit

sum_all(random.sample(range(1_000_000), k=40))
8.17 μs ± 52.1 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [127]:
%%timeit

sum_all(random.sample(range(1_000_000), k=400))
71.3 μs ± 684 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [128]:
%%timeit

sum_all(random.sample(range(1_000_000), k=4000))
689 μs ± 4.7 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

Komplexitet, nästlade loopar¶

  • Antalet par-kombinationer man kan bilda givet $n$ värden
  • $\{ 1, 2, 3, ..., n \}$
  • $O(n^2)$ (kvadratisk komplexitet)
In [123]:
def make_pairs(values):
    # pre-allocate result list to avoid allocation artefacts 
    pairs = [None]*(values**2)
    current_index = 0
    for v1 in values:
        for v2 in values:
            pairs[current_index] = str(v1) + ":" + str(v2)
            current_index += 1
    return pairs

Exempel $O(n^2)$¶

In [131]:
def make_pairs(values):
    # pre-allocate result list to avoid allocation artefacts 
    pairs = [None]*(len(values)**2)
    current_index = 0
    for v1 in values:
        for v2 in values:
            pairs[current_index] = str(v1) + ":" + str(v2)
            current_index += 1
    return pairs
In [132]:
%%timeit

make_pairs(range(10))
10.7 μs ± 120 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [133]:
%%timeit

make_pairs(range(20))
42.4 μs ± 678 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [134]:
  %%timeit  

make_pairs(range(40))
169 μs ± 1.05 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
In [135]:
%%timeit

make_pairs(range(80))
671 μs ± 6.71 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
In [136]:
%%timeit

make_pairs(range(160))
2.72 ms ± 60 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Några komplexitetsklasser i stigande ordning¶

  • Sublinjär komplexitet (Woho!)
    • $O(1)$ - Konstant komplexitet: T.ex. indexering i en lista, uppslagning av en variabels värde, eller att byta plats på två värden i minnet.
    • $O(\log n)$ - logaritmisk komplexitet: Binärsökning (telefonkatalogsalgoritmen)
    • $O(\log^k n)$ - polylogaritmisk komplexitet: Uppslagning av ett värde i en dict eller set
  • Subkvadratisk komplexitet
  • Subexponentiell komplexitet
  • Enbart teoretiskt intressant komplexitet
  • Polylogaritmiska funktioner växer ofta mycket snabbare än linjärt i början, men rent asymtotiskt växer de alltid långsammare.

Några komplexitetsklasser i stigande ordning¶

  • Sublinjär komplexitet
  • Subkvadratisk komplexitet (Ok.)
    • $O(n)$ - linjär komplexitet: get_max_value, summering (under antagandet att addition har konstant komplexitet)
    • $O(n \log^k n)$ - kvasilinjär: quicksort, merge sort, heapsort, FFT (Fast Fourier transform)
  • Subexponentiell komplexitet
  • Enbart teoretiskt intressant komplexitet
  • Linjär komplexitet är så bra som det kan bli om hela input:en behöver behandlas i sekvens.

Några komplexitetsklasser i stigande ordning¶

  • Sublinjär komplexitet
  • Subkvadratisk komplexitet
  • Subexponentiell komplexitet (Eh...)
    • $O(n^2)$ - kvadratisk: redigeringsavstånd *host* *host*
    • $O(n^3)$ - kubisk: traditionell matrismultiplikation (men algoritmer så effektiva som $O(n^{2.376})$ existerar)
    • $O(n^c)$ - polynomiell av grad $c$ (för någon konstant $c$): aritmetiska operationer för godtyckligt stora tal
  • Enbart teoretiskt intressant komplexitet

Några komplexitetsklasser i stigande ordning¶

  • Sublinjär komplexitet
  • Subkvadratisk komplexitet
  • Subexponentiell komplexitet
  • Främst teoretiskt intressant komplexitet (Oh ****...)
    • $O(2^{n^c})$ - exponentiell komplexitet: brute-force lösenordsknäckning
    • $O(n!)$ - factorial complexity: situationer där alla permutationer av en sekvens måste genereras
    • $O(2^{2^{p(n)}})$ - dubbelexponentiell komplexitet: typ-inferens i vissa programmeringsspråk och vissa andra fenomen inom formella språk
    • $O(A(n, n))$ - Ackermann-funktionen: att beräkna hyperoperationer mha Ackermann-funktionen (se rekursion)
    • $O(BB(n))$ - Busy Beaver of n, förmodligen den värsta möjliga komplexiteten (men det är en förmodan, inte en sats)
  • Dvs. vi är egentligen bara intresserade av hur små problem som vi skulle kunna lösa eller för benchmarking av olika slag.
  • Se också: https://en.wikipedia.org/wiki/Time_complexity#Table_of_common_time_complexities

Se också https://www.bigocheatsheet.com/¶

Testning av kod¶


Testning och felsöking¶


  • Felsökning:
    • Jag vet att något är fel med den här koden. Jag måste nu ta reda på var och varför det blir fel.

  • Testning:
    • Jag vet inte om programmet fungerar eller inte. Jag behöver ta reda på om alla delar fungerar som de ska.

Felsökning¶

  • När vi vet att koden vi skrivit inte fungerar som den ska behöver vi t.ex. ta reda på:
    • Under vilka omständigheter fungerar inte koden?
    • Var i koden uppstår felet? / Vad orsakar felet?
  • Verktyg vi kan använda oss av vid felsökning
    • Felmeddelanden när programmet kraschar
    • Spårutskrifter
    • Olika debugverktyg

Testning¶

  • Syftet med testning är att ta reda på om det vi testar fungerar eller inte. T.ex. om programmet uppför sig som vi förväntar oss.
  • Stor bredd i det man kan referera till med "testning"
  • Testning hjälpa oss påvisa förekomsten av fel.
  • Rent praktiskt omöjligt att med hjälp av testning garantera frånvaron av fel, men vi kan använda testning för att minimera antalet fel!

Testning i denna kurs och utanför denna kurs¶

Testning i denna kurs¶

  • Testning är en del av lärandemålen för kursen
  • Ni har alla testat er kod på ett eller annat sätt

Testning inom mjukvaruutveckling¶

  • Test på olika abstraktionsnivåer:
    • enhetstestning - enskilda enheter, "units", av mjukvaran för sig själv, t.ex. enskilda funktioner, klasser
    • integrationstest - test av specifika enheters interaktion med varandra
    • systemtest - test av systemet som helhet
  • Test enligt olika kriterier:
    • funktionstest - test av specifik funktionalitet hos ett program, t.ex. "lägga till en uppgift"
    • prestandatest - test av prestanda hos programmet, t.ex. hur lång tid tar det att öppna en fil, hur mycket minne använder programmet, etc.
    • acceptanstest - test av alla krav ställda på programmet
    • användbarhetstestning - test av hur användbart programmet är för en viss användarkategori
  • Speciella ramverk och program finns för att underlätta testning, men användning av dessa ligger utanför kursen.

Automatiserad testning¶

  • Tester som kan skrivas i kod och köras automatiskt, t.ex. varje gång man lagt till ny funktionalitet eller refaktorerat (ändrat i existerande kod)
  • Ofta felaktigt använt synonymt med enhetstestning men även t.ex. integrationstest, och i viss utsträckning systemtest, brukar automatiseras.
    • Även prestandatest kan ofta automatiseras när programmen ställer höga krav på prestanda, t.ex. i realtidssystem, simuleringsmjukvaror eller spel.

  • Svårare att automattesta:
    • grafiska gränssnitt
    • funktionalitet som är beroende av komplex data som är resultatet av interaktion mellan många komponenter i ett system
    • funktionalitet som är beroende av yttre faktorer
  • De två sistnämnda angrips ofta mha sk "mocking" men det är överkurs.

Vilka typer av test ingår i kursen?¶

  • Test av funktioner, fungerar de som de ska?
    • funktion(indata) → returvärde
  • Enklare test av instanser av klasser, t.ex.
    • instans.metod() → returvärde
    • instans.metod(indata) → returvärde
    • värdet hos instansvariabler efter att olika metoder körts

Vad ska man tänka på vid testning?¶

  • Båda nedanstående funktioner ska addera talen v1, och v2 och sedan returnera resultatet.
  • I nedanstående testkörning verkar båda vara korrekt implementerade?
In [11]:
def add_values1(v1, v2):
    return v1 + v2

def add_values2(v1, v2):
    return v1 * v2

# båda anropen returnerar 4, är båda implementationerna korrekta?
print(add_values1(2, 3))
print(add_values2(2, 3))
5
6

Vad behöver man veta för att testa sin kod?¶

Vad måste vi veta för att testa en enhet?¶

  • Hur ser specifikationen ut? Dvs.

  • Vilka starttillstånd och testförhållanden är giltiga?
    • Indata?
    • Värden på instansvariabler innan en metod körs?
  • Vad ska enheten vi vill testa göra?
    • Hur ser utdata ut? Vilken utdata är giltig?
    • Vilken utdata ska vi förväntas oss givet en viss indata?

Testdata¶

  • I de flesta fall är det omöjligt att göra en uttömmande testning, dvs testa alla möjliga fall (t.ex. alla möjliga indata).
  • Vi behöver titta på giltiga indata och försöka hitta vilka kategorier av indata som finns.
  • Vilka indata ger är samma returvärde?
  • Vilka indata testar samma aspekt av implementationen?
  • Att hitta dessa kräver ofta att man förstår hur implementationen av en funktion eller klass kan göras.
  • Ett mål: med så få test som möjligt kunna täcka så många fall som möjligt.

Typfall och gränsfall¶

  • Typfall (eng. typical case) är de testvärden som motsvarar den vanligaste typen av input där enheten förväntas bete sig normalt.
  • Gränsfall (eng. edge case) är de testvärden som ligger nära, på eller utanför gränsen för normal användning.
    • Gränsfallen kan kräva fler test för olika typer av gränser och för att testa felhantering vid olika gränsöverträdelser.
    • Testning handlar till stor del om att hitta och definiera gränsfallen samt konstruera lämpliga tester för de fallen.
  • Vad är några typfall och gränsfall för funktionen nedan:
In [14]:
from math import floor

def split_winnings(cash_prize_amount, number_of_winners):
    return floor(cash_prize_amount / number_of_winners)
In [19]:
split_winnings(1000, '2')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[19], line 1
----> 1 split_winnings(1000, '2')

Cell In[14], line 4, in split_winnings(cash_prize_amount, number_of_winners)
      3 def split_winnings(cash_prize_amount, number_of_winners):
----> 4     return floor(cash_prize_amount / number_of_winners)

TypeError: unsupported operand type(s) for /: 'int' and 'str'
  • Typfall: cash_prize_amount = 10 000 number_of_winners = 2, 3, 10, etc.
  • Gränsfall: Negativa värden, 0 vinnare, värden som inte är tal över huvud taget, fel antal argument, etc

Vad behöver man göra för att testa sin kod?¶

Göra sitt program lättare att testa¶

  • Genom att dela upp ett program i fler funktioner/klasser/ metoder ökar man även i de flesta fall upplösningen på det som går att testa
  • Funktioner som returnerar värden är lättare att testa än funktioner som inte returnerar värden.
  • Genom att dokumentera sin kod finns det en specifikation som man kan använda som vägledning för att skapa rätt test.

Hur kan man testa?¶

  • Att testa sitt program interaktivt kan ta tid (för att det sker manuellt), men ger stora möjligheter till att utforska resultaten.
  • Att automatisera ett test, dvs skriva kod som testar funktionaliteten är snabbare och är upprepningsbart.
    • Viktigt att veta vad vi ska testa!

Exempel på automatisering av test¶

  • Automatisera genomförandet av testoperationen, t.ex.
    • anrop av funktioner + okulär besiktning av resultaten för att validera korrekthet
  • Exempel nedan där returvärden skrivs ut
In [20]:
def keep_even_values(values):
    output = []
    for value in values:
        if value % 2 == 0:
            output.append(value)
    return output

print(keep_even_values([]))
print(keep_even_values([1]))
print(keep_even_values([2]))
print(keep_even_values([1, 2, 3, 4]))
[]
[]
[2]
[2, 4]

Exempel på automatisering av test¶

  • Automatiserad validering
    • automatisk jämförelse av faktiskt resultat av testoperationen med förväntat resultat
  • Exempel med villkorssatser på nästa bild

Exempel på automatisering av test¶

In [21]:
def keep_even_values(values):
    output = []
    for value in values:
        if value % 2 == 0:
            output.append(value)
    return output


def test_keep_even_values():
    if keep_even_values([]) != []:
        return False
    if keep_even_values([1]) != []:
        return False
    if keep_even_values([2]) != [2]:
        return False
    if keep_even_values([1, 2, 3, 4]) != [2, 4]:
        return False
    return True


if test_keep_even_values():
    print("keep_even_values() fungerar.")
else:
    print("keep_even_values() fungerar INTE.")
keep_even_values() fungerar.

Nyckelordet assert¶

  • Nyckelordet assert används när man vill kontrollera om ett uttryck är sant i syfte att fånga upp fall när det inte är det.
  • Om uttrycket är sant går Python vidare i koden.
  • Om uttrycket är falskt, inträffar ett AssertionError som sedan kan fångas.
  • Man kan använda villkorssatser i samma syfte, men koden kan bli kortare och tydligare med assert
  • Syntax:
assert uttryck

Exempel på automatisering av test (med assert)¶

In [24]:
def keep_even_values(values):
    output = []
    for value in values:
        if value % 2 == 0:
            output.append(value)
    return output


def test_keep_even_values():
    try:
        assert keep_even_values([]) == []
        assert keep_even_values([1]) == []
        assert keep_even_values([2]) == [2]
        assert keep_even_values([1, 2, 3, 4]) == [2, 4]
        return True
    except AssertionError:
        return False


if test_keep_even_values():
    print("keep_even_values() fungerar.")
else:
    print("keep_even_values() fungerar INTE.")
keep_even_values() fungerar.

Skriva till fil¶

Öppna fil för att läsa/skriva¶

  • open(filepath, mode) returnerar den öppnade filen som ett file-objekt.
  • mode är en sträng vars defaultvärde är 'r' som i read
  • Andra giltiga värden på mode är 'w' som i write och 'a' som i append
    • mode='r' read: vi kan endast läsa från filen
    • mode='w' write: vi kan endast skriva till filen, existerande innehåll skrivs över
    • mode='a' append: vi kan endast skriva till filen, existerande innehåll bevaras, nytt innehåll läggs till slutet på filen
  • Metoden file.write(s) skriver strängen s till filen file.
    • Om inte filen redan finns så skapas den när vi skriver till den.

Öppna fil med explicit teckenkodning¶

  • open(filepath, mode, encoding='utf-8')
    • Vi kan tvinga python att öppna filen med en viss teckenkodning genom att använda nyckelordsargumentet encoding. Kan behövas t.ex. om man kör Windows och ska läsa/skriva textfiler kodade i utf-8.

Exempel, skriva till fil¶

In [9]:
# öppna fil för att lägga till text till
datafile = open("filnamn.txt", "a")
datafile.write("Glöm inte bort")
datafile.write("radbryt!\n")

# stäng filen som vi öppnat
datafile.close()

Repetition¶

Exempel på aggregering av data¶

  • City kan tillhandahålla befolkningssiffra genom att fråga alla hushåll efter hur många som ingår i hushållet.

No description has been provided for this image

Association och scope¶

  • Associationspil implementeras som en variabel i "från"-klassen.
  • Om fler än 1 referens ska finnas, använd t.ex. lista eller dictionary
  • Samma variabelnamn och metodnamn kan användas i flera klasser.

Vad händer när vi skapar en instans av en klass?¶

  • Skapa instans av klass: Klassnamn(arg1, arg2, ..., argn)
  • Python skapar ett objekt och kör klassens __init__()-metod
  • Objektet som skapats skickas som det första argumentet till __init__().
  • Argument som skickats med vid anrop av klassnamnet skickas vidare till __init__() efter self.

Hur kan nedanstående tolkas - syntax är viktigt¶

  1. a
  2. a()
  3. a(b)
  4. a[b]
  5. a.b
  6. a.b()
  7. a.b = c
  8. a = b.c
  9. a = b.c()

Beståndsdelar i ett program¶

  • värden (values): t.ex. siffror eller text (kallas för strängar)
  • operatorer (operators): token/symbol som står för en instruktion
  • variabler (variables): namngivna referenser till värden
  • uttryck (expressions): ett uttryck kan beräknas till ett värde (ibland säger man även utvärderas istället för beräknas)
  • nyckelord (keywords): speciella ord som refererar till instruktioner som ska utföras
  • skiljetecken (punctuators)
  • satser (statements): fullständig instruktion