Osa 5

Mer om listor

Listor med olika typer av data

I den förra modulen fokuserade vi främst på listor med element av heltalstyp, men listor kan innehålla vilka typer av data som helst. En lista bestående av strängar kunde exempelvis se ut så här:

namn = ["Maja", "Lotta", "Pontus"]
print(namn)
namn.append("Konrad")
print(namn)

print("Antal namn:", len(namn))
print("Namnen i alfabetisk ordning:")
namn.sort()
for person in namn:
  print(person)
Exempelutskrift

['Maja', 'Lotta', 'Pontus'] ['Maja', 'Lotta', 'Pontus', 'Konrad'] Antal namn: 4 Namnen i alfabetisk ordning: Konrad Lotta Maja Pontus

Flyttal kan också lagras i listor:

matningar = [-2.5, 1.1, 7.5, 14.6, 21.0, 19.2]

for matning in matningar:
    print(matning)

medeltal = sum(matningar) / len(matningar)

print("Medelvärde:", medeltal)
Exempelutskrift

-2.5 1.1 7.5 14.6 21.0 19.2 Medelvärde: 10.15

Varning: globala variabler inom funktioner

Vi har sett att det går att tilldela nya variabler inne i funktionsdefinitioner. Funktionen kan också komma åt variabler som finns utanför funktionen, i huvudfunktionen. Dessa variabler kallas globala variabler.

Att använda globala variabler inifrån funktioner är oftast en dålig idé. Det kan orsaka en hel del problem, till exempel buggar som är svåra att spåra.

Här är ett exempel på en funktion som använder en global variabel "av misstag":

def svangd_utskrift(namn: list):
    # använder av misstag den globala variabeln namnlista
    i = len(namnlista) - 1
    while i >= 0:
        print(namnlista[i])
        i -= 1

# global variabel
namnlista = ["Antti", "Emilia", "Erkki", "Margaret"]
svangd_utskrift(namnlista)
print()
svangd_utskrift(["Louise", "Ophelia", "Lotta"])
Exempelutskrift

Margaret Erkki Emilia Antti

Margaret Erkki Emilia Antti

Även om funktionen anropas korrekt skrivs alltid namnen i den globala variabeln namnlista ut.

All kod som testar funktioner ska skrivas inom ett separat block så att TMC-testen accepterar koden. Föregående exempel ska alltså skrivas så här:

def svangd_utskrift(namn: list):
    # använder av misstag den globala variabeln namnlista
    i = len(namnlista) - 1
    while i >= 0:
        print(namnlista[i])
        i -= 1

# kod som testar funktionen placeras här
if __name__ == "__main__":
    # global variabel
    namnlista = ["Antti", "Emilia", "Erkki", "Margaret"]
    svangd_utskrift(namnlista)
    print()
    svangd_utskrift(["Louise", "Ophelia", "Lotta"])

Nu definieras också den globala variabeln i if-blocket.

TMC-testerna körs alltid så att det som finns i detta if-block inte körs. Därför fungerar funktionen inte ens i teorin eftersom variabeln namnlista inte finns då testerna körs.

Varning: att skriva över en parameter och returnera för tidigt

Ju mer vi lär oss, desto fler potentiella orsaker till buggar får vi. Låt oss titta på en funktion som meddelar om ett heltal hittas i en lista. Både listan och talet är definierade som parametrar i funktionen:

def tal_i_listan(tal: list, t: int):
    for t in tal:
        if t == t:
            return True
        else:
            return False

Den här funktionen verkar alltid returnera värdet True. Orsaken till det är att for-loopen skriver över värdet som är lagrat i parametern t (det som gavs in som argument skrivs i for-loopen över av det första värdet i listan tal). Därför är villkoret i if-satsen alltid sant.

Att ändra på parameterns namn löser problemet:

def tal_i_listan(tal: list, tal_som_soks: int):
    for t in tal:
        if t == tal_som_soks:
            return True
        else:
            return False

Nu är villkoret i if-satsen i sin ordning. I stället har vi nu ett nytt problem, eftersom funktionen inte ännu heller fungerar som den ska. Om vi testar följande, märker vi en bugg:

resultat = tal_i_listan([1, 2, 3, 4], 3)
print(resultat) # False

Problemet här är att funktionen returnerar resultatet för tidigt, utan att gå igenom alla tal i listan. Funktionen kollar faktiskt endast det första värdet i listan och returnerar True eller False beroende på dess värde. Vi kan inte veta att ett tal inte finns i listan förrän vi har gått igenom hela listan. Instruktionen return False måste alltså placeras utanför for-loopen:

def tal_i_listan(tal: list, tal_som_soks: int):
    for t in tal:
        if t == tal_som_soks:
            return True

    return False

Låt oss ta en titt på en annan funktion som inte fungerar korrekt:

def tal_olika(tal: list):
    # hjälpvariabel där vi lagrar de värden som kollats
    tal = []
    for t in tal:
        # har talet redan förekommit?
        if t in tal:
            return False
        tal.append(t)

    return True

resultat = tal_olika([1, 2, 2])
print(resultat) # True

Funktionen borde kolla om alla tal i en lista är olika, men kommer alltid att returnera True. Varför det?

I det här fallet skriver funktionen igen över värdet som är lagrat i dess parameter. Funktionen försöker använda variabeln tal för att lagra de siffror som redan har kontrollerats, men det här skriver över det ursprungliga argumentet. Att namnge hjälpvariabeln på nytt löser vårt problem:

def tal_olika(tal: list):
    # hjälpvariabel där de tal som kollats lagras
    sedda_tal = []
    for t in tal:
        # har talet redan förekommit?
        if t in sedda_tal:
            return False
        sedda_tal.append(t)

    return True

resultat = tal_olika([1, 2, 2])
print(resultat) # False

Problem som dessa kan hittas och korrigeras med hjälp av debuggaren eller visualiseringsverktyget. Det lönar sig verkligen att lära sig använda dessa effektivt.

Loading

Listor inom listor

Elementen i en lista kan i sin tur vara listor:

lista = [[5, 2, 3], [4, 1], [2, 2, 5, 1]]
print(lista)
print(lista[1])
print(lista[1][0])
Exempelutskrift

[[5, 2, 3], [4, 1], [2, 2, 5, 1]] [4, 1] 4

När skulle det här kunna vara nyttigt?

Kom ihåg att listor kan innehålla element av olika typer. Du kan till exempel lagra information om en person i en lista. Det första elementet kan då exempelvis vara personens namn, det andra hens ålder och det tredje hens skostorlek:

["Nelly", 10, 32]

Vi kunde då skapa en databas med information om flera personer i form av en lista, vars element i sin tur också vore listor som innehåller information om en enskild person:

personer = [["Nelly", 10, 32], ["PO", 7, 26], ["Emilia", 32, 37], ["August", 39, 44]]

for person in personer:
  namn = person[0]
  alder = person[1]
  sko = person[2]
  print(f"{namn}: {alder} år, skostorlek {sko}")
Exempelutskrift

Nelly: 10 år, skostorlek 32 PO: 7 år, skostorlek 26 Emilia: 32 år, skostorlek 37 August: 39 år, skostorlek 44

For-loopen går igenom elementen i den yttre listan ett i taget. Variabeln person tildelas då ett element ur listan, som i sin tur utgör en lista med information om en person.

Listor är inte alltid det bästa sättet att presentera data som exempelvis information om en person. Vi kommer snart att se på ordlistor/lexikon (dictionary) i Python. Den är bättre anpassad för situationer som den ovannämnda.

Matriser

En tvådimensionell tabell – matris – är ett annat användningsområde för listor inom listor.

Till exempel kan följande matris…

5 1 0

… presenteras som en tvådimensionell lista i Python på följande sätt:

matris = [[1, 2, 3], [3, 2, 1], [4, 5, 6]]

Eftersom en matris är en lista som innehåller listor kan vi komma åt enskilda element inom matrisen med två index. Det första indexet hänvisar till raden, medan den andra hänvisar till kolumnen. Indexeringen startar från noll så matris[0][1] hänvisar till det andra elementet på den första raden.

matris = [[1, 2, 3], [3, 2, 1], [4, 5, 6]]

print(matris[0][1])
matris[1][0] = 10
print(matris)
Exempelutskrift

2 [[1, 2, 3], [10, 2, 1], [4, 5, 6]]

Som med vilken som helst lista, kan raderna i matrisen gås igenom med en for-loop. Den följande koden skriver ut varje rad i matrisen på en skild rad:

matris = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

for rad in matris:
    print(rad)
Exempelutskrift

[1, 2, 3] [4, 5, 6] [7, 8, 9]

På samma sätt kan vi använda nästlade loopar för att komma åt enskilda element inom matrisen. Följande kod skriver ut varje element i matrisen på en skild rad med hjälp av två for-loopar:

matris = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

for rad in matris:
    print("ny rad")
    for element in rad:
        print(element)
Exempelutskrift

ny rad 1 2 3 ny rad 4 5 6 ny rad 7 8 9

Visualisering av kod som innehåller listor inom listor

Program som består av listor inom listor kan till först vara svåra att greppa. Visualiseringsverktyget av Python Tutor är ett bra sätt att bekanta sig med hur de fungerar. Det följande är en visualisering av exemplet ovan:

5 1 0a

Bilden ovan avslöjar att en 3 x 3 -matris tekniskt sett består av fyra listor. Den första listan representerar hela matrisen. De tre resterande listorna är element i den första listan, och de representerar rader i matrisen.

Eftersom flerdimensionella listor kan gås igenom med nästlade loopar skulle man kunna tänka sig att listorna finns inuti varandra, men som bilden visar är detta inte fallet. Istället hänvisar den "stora listan" som representerar matrisen till skilda listor som alla representerar en rad i matrisen. Det här är en referens – något vi kommer att se mera på i nästa del.

I bilden ovan har programmet hunnit till den andra raden i matrisen och det är den listan som variabeln rad hänvisar till för tillfället. Variabeln element innehåller det element som programmet hanterar för tillfället, dvs. listans mellersta värde 5.

Komma åt element i en matris

Att komma åt en viss rad i en matris är enkelt. Det är bara att välja den raden. Följande funktion beräknar summan av elementen på en viss rad:

def summan_av_radens_element(matris, radnummer: int):
    # vi inspekterar bara en rad
    rad = matris[radnummer]
    summa = 0
    for element in rad:
        summa += element

    return summa

m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]

summa = summan_av_radens_element(m, 1)
print(summa) # 33 (dvs. 9 + 1 + 12 + 11)

Att arbeta med kolumner i en matris är aningen mer komplicerat eftersom matrisen är lagrad som rader:

def summan_av_kolumnens_element(matris, kolumnnummer: int):
    # till summan adderas för varje rad elementet på den önskade positionen
    summa = 0
    for rad in matris:
        summa += rad[kolumnnummer]

    return summa

m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]

summa = summan_av_kolumnens_element(m, 2)
print(summa) # 39 (dvs. 3 + 12 + 9 + 15)

Den kolumn som vi vill komma åt består av elementen på index 2 för varje rad.

Visualiseringsverktyget rekommenderas varmt för att bättre förstå hur det här fungerar.

Att ändra på ett värde hos ett specifikt element inom en matris är enkelt – du väljer helt enkelt den rad och kolumn där elementet finns lagrat.

def byt_varde(matris, radnummer: int, kolumnnummer: int, varde: int):
    # välj korrekt rad
    rad = matris[radnummer]
    # och korrekt ställe i raden
    rad[kolumnnummer] = varde

m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]

print(m)
byt_varde(m, 2, 3, 1000)
print(m)
Exempelutskrift

[[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]] [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 1000], [2, 9, 15, 1]]

Observera att vi ovan använde indexen för raden och kolumnen för att komma åt det element vi ville ändra på. Om vi vill ändra på innehållet i en matris måste vi komma åt elementen md hjälp av deras index. Vi kan inte bara använda oss av en for element in lista -loop för att gå igenom matrisen då vi vill ändra på innehållet.

Istället behöver vi hålla reda på indexen hos elementen: till exempel med en while-loop eller en for-loop och range-funktionen. Följande kod ökar värdet på varje element i en matris med ett:

m = [[1,2,3], [4,5,6], [7,8,9]]

for i in range(len(m)):
    for j in range(len(m[i])):
        m[i][j] += 1

print(m)
Exempelutskrift

[[2, 3, 4], [5, 6, 7], [8, 9, 10]]

Den yttre loopen går igenom indexen från noll till matrisens längd, alltså antalet rader i matrisen. Den inre loopen går igenom indexen från noll till radernas längd.

Loading

En tvådimensionell tabell som datastruktur i ett spel

En matris kan fungera som datastruktur i olika typer av spel. Till exempel kan rutorna i ett sudokuspel…

5 1 1

… representeras med en matris på följande sätt:

sudoku = [
  [9, 0, 0, 0, 8, 0, 3, 0, 0],
  [0, 0, 0, 2, 5, 0, 7, 0, 0],
  [0, 2, 0, 3, 0, 0, 0, 0, 4],
  [0, 9, 4, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 7, 3, 0, 5, 6, 0],
  [7, 0, 5, 0, 6, 0, 4, 0, 0],
  [0, 0, 7, 8, 0, 3, 9, 0, 0],
  [0, 0, 1, 0, 0, 0, 0, 0, 3],
  [3, 0, 0, 0, 0, 0, 0, 0, 2]
]

Nu representerar noll en tom ruta, eftersom noll inte är ett värde som kan användas i sudoku.

Här är en enkel funktion som skriver ut sudokurutor:

def skriv_ut(sudoku):
    for rad in sudoku:
        for ruta in rad:
            if ruta > 0:
                print(f" {ruta}", end="")
            else:
                print(" _", end="")
        print()

skriv_ut(sudoku)

Utskriften borde se ut så här:

 9 _ _ _ 8 _ 3 _ _
 _ _ _ 2 5 _ 7 _ _
 _ 2 _ 3 _ _ _ _ 4
 _ 9 4 _ _ _ _ _ _
 _ _ _ 7 3 _ 5 6 _
 7 _ 5 _ 6 _ 4 _ _
 _ _ 7 8 _ 3 9 _ _
 _ _ 1 _ _ _ _ _ 3
 3 _ _ _ _ _ _ _ 2

Flera andra spel kan också representeras på liknande sätt: till exempel schack, minröjning, sänka skepp och Mastermind. I sudoku fungerar tal väl för att representera spelets läge, medan för andra spel kan andra metoder vara bättre.

Loading
Loading
Loading
Loading
Loading
Loading...
:
Loading...

Log in to view the quiz

Du har nått slutet av den här delen! Fortsätt till nästa del:

Se dina poäng genom att klicka på cirkeln nere till höger av sidan.