Lister


Opprette lister
# To standard måter å opprette lister på
a = []
b = list()

print(type(a), len(a), a)
print(type(b), len(b), b)
print(a == b)
# Liste med ett element
a = ["foo"]
b = [42]

print(type(a), len(a), a)
print(type(b), len(b), b)
print(a == b)
# Lister med flere elementer
a = [2, 3, 5, 7, 11]
b = list(range(5))
c = ["foo", 42, True, None, ""]

print(type(a), len(a), a)
print(type(b), len(b), b)
print(type(c), len(c), c)
# Lister med variabelt antall elementer
a = ["foo"] * 5
b = list(range(5))

print(type(a), len(a), a)
print(type(b), len(b), b)
Funksjoner og operasjoner
a = [2, 3, 5, 2]
print("a = ", a)
print("len =", len(a))
print("min =", min(a))
print("max =", max(a))
print("sum =", sum(a))

# Et par forskjellige lister
a = [2, 3, 5, 3, 7]
b = [2, 3, 5, 3, 7]   # lik til a
c = [2, 3, 5, 3, 8]   # forskjellig fra a
d = [2, 3, 5]         # prefix for a

print("a =", a)
print("b =", b)
print("c =", c)
print("d =", d)

print("------------------")
print("a == b", (a == b))
print("a == c", (a == c))
print("a != b", (a != b))
print("a != c", (a != c))

print("------------------")
print("a < c", (a < c)) # Sammenligning basert på første ulike element
print("d < a", (d < a)) # Prefiksen er "mindre enn"
# Konkatenering
a = [3, 4]
b = [8, 9]
print("a     =", a)
print("b     =", b)
print("a + b =", a + b)

# Repetisjon
print("a * 3 = ", a * 3)
Indeksering og beskjæring
# Indeksering og beskjæring fungerer på samme måte som for strenger
a = [2, 3, 5, 7, 11, 13]
print("a        =", a)

# Indeksering. Første indeks er 0.
print("a[0]     =", a[0])
print("a[2]     =", a[2])

# Negative indekser
print("a[-1]    =", a[-1])
print("a[-3]    =", a[-3])

# Beskjæring a[start:slutt] eller a[start:slutt:steg]
print("a[0:2]   =", a[0:2])
print("a[1:4]   =", a[1:4])
print("a[1:6:2] =", a[1:6:2])
Mutasjon og alias

I motsetning til datatyper vi har sett hittil, kan vi endre på en liste uten å opprette en ny verdi i minnet. Dette kaller vi å mutere listen.

# Opprett en liste
a = [2, 3, 4]

# La b være en variabel som referer til samme liste som a
# Siden a og b er to variabler som refererer til det samme muterbare
# objekt, kaller vi a og b for aliaser.
b = a 

# Mutasjon (endring) av listen
a[0] = 99
b[1] = 42
print(a)
print(b)

Dersom to variabler refererer til samme muterbare objekt, kalles de for aliaser. Funksjonsparametre er eksempler på aliaser.

# Når en funksjon muterer en liste via et alias har funksjonen en
# sideeffekt
def f(a):
    a[0] = 42

a = [2, 3, 5, 7]
print(a)
f(a)
print(a)
print("---")

# Alias kan bli brutt ved å endre variabelen
def foo(a):
     a[0] = 99
     a = [5, 2, 0] # aliaset blir brutt her
     a[0] = 42

a = [3, 2, 1]
print(a)
foo(a)
print(a)
# Opprett en liste
a = [2, 3, 5, 7]

# Opprett et alias for listen
b = a

# Opprett en ny liste med de samme elementene
c = [2, 3, 5, 7]

# a og b er referanser til (/aliaser for) DEN SAMME listen
# c er en referanse til en annen, men LIK liste

print("først:")
print("  a == b:", a == b)
print("  a == c:", a == c) # == -operatoren sier om to verdier er LIKE
print("  a is b:", a is b) # is -operatoren sier om to verdier er DEN SAMME
print("  a is c:", a is c)

# Mutasjon av a endrer også b (DEN SAMME listen) men ikke c (en annen liste)
a[0] = 42
print("etter mutasjonen a[0] = 42")
print("  a =", a)
print("  b =", b)
print("  c =", c)
print("  a == b:", a==b)
print("  a == c:", a==c)
print("  a is b:", a is b)
print("  a is c:", a is c)
Kopiering av lister
# Vi må være forsiktig ved kopiering av lister, slik at vi ikke muterer en
# liste gjenomm et alias av vanvare.

import copy

a = [2, 3]

# To kopier
b = a               # Ikke en kopi, bare et alias
c = copy.copy(a)    # Ekte kopi

# I begynnelsen ser kopiene tilforlatelig like ut
print("Først...")
print("   a =", a)
print("   b =", b)
print("   c =", c)

# Så muterer vi a[0]
a[0] = 42
print("Etter mutasjonen a[0] = 42")
print("   a =", a)
print("   b =", b)
print("   c =", c)

Andre måter å kopiere

import copy

a = [2, 3]

b = copy.copy(a)
c = a[:]
d = a + []
e = list(a)
f = a.copy()
*g, = a

h = []
for element in a:
    h.append(element)

a[0] = 42
print(a, b, c, d, e, f, g, h)
Destruktive funksjoner

En funksjon er destruktiv dersom den har sideeffekter som muterer en liste.

# En destruktiv funksjon er skrevet for å mutere en liste. Den trenger ikke
# returnere noe, siden den som kaller også har et alias til listen.
def fill(a, value):
    for i in range(len(a)):
        a[i] = value

a = [1, 2, 3, 4, 5]
print("Først, a =", a)
fill(a, 42)
print("Etter fill(a, 42), a =", a)

En ikke-destruktiv funksjon vil ikke ha sideeffekter, og vi benytter oss av returverdien i stedet.

import copy 

def destructive_remove_all(a, value):
    while value in a:
        a.remove(value)

def non_destructive_remove_all(a, value):
    # Vanligvis skriver vi ikke-destruktive funksjoner ved å opprette
    # en ny liste fra scratch, og jobber på den
    result = []
    for element in a:
        if element != value:
            result.append(element)
    return result # ikke-destruktive funksjoner MÅ returnere svaret!

def alternate_non_destructive_remove_all(a, value):
    # Vi kan også skrive en ikke-destruktiv funksjon ved å først bryte
    # aliaset, og deretter benytte en destruktiv tilnærming
    a = copy.copy(a)
    destructive_remove_all(a, value)
    return a # ikke-destruktive funksjoner må uansett returnere!

a = [1, 2, 3, 4, 3, 2, 1]
print("Først")
print("   a =", a)

destructive_remove_all(a, 2)
print("Etter destructive_remove_all(a, 2)")
print("   a =", a)

b = non_destructive_remove_all(a, 3)
print("Etter b = non_destructive_remove_all(a, 3)")
print("   a =", a)
print("   b =", b)

c = alternate_non_destructive_remove_all(a, 1)
print("Etter c = alternate_non_destructive_remove_all(a, 1)")
print("   a =", a)
print("   c =", c)
Leting etter elementer
# Inneholder listen min verdi?
a = [2, 3, 5, 2, 6, 2, 2, 7]
print("a      =", a)
print("2 in a =", (2 in a))
print("4 in a =", (4 in a))

# eller ikke?
print("2 not in a =", (2 not in a))
print("4 not in a =", (4 not in a))
# Hvor mange ganger opptrer min verdi?
a = [2, 3, 5, 2, 6, 2, 2, 7]
print("a          =", a)
print("a.count(1) =", a.count(1))
print("a.count(2) =", a.count(2))
print("a.count(3) =", a.count(3))
# Hvor i listen befinner verdien seg, da?
# a.index(element) eller a.index(element, start)
a = [2, 3, 5, 2, 6, 2, 2, 7]
print("a            =", a)
print("a.index(6)   =", a.index(6))
print("a.index(2)   =", a.index(2))
print("a.index(2,1) =", a.index(2,1))
print("a.index(2,4) =", a.index(2,4))
# Oj! Krasjer dersom elmentet ikke er der
a = [2, 3, 5, 2]
print("a          =", a)
print("a.index(9) =", a.index(9)) # krasjer!
print("Vi kom visst ikke så langt...")
# Løsning: benytt (element in liste) først.
a = [2, 3, 5, 2]
print("a =", a)
if (9 in a):
    print("a.index(9) =", a.index(9))
else:
    print("9 er ikke der", a)
print("Hurra!")
Legge til elementer

Destruktive metoder for å legge til elementer:

# Vi oppretter en liste og gir den et alias. Alle endringer vi gjør her
# reflekteres i aliaset, hvilket betyr at endringene er destruktive
a = [2, 3]
alias = a

# Legg til på slutten med .append
a.append(7)
print(a) # [2, 3, 7]

# Legg til på en bestemt posisjon med .insert
a.insert(2, 42)
print(a) # [2, 3, 42, 7]

# Utvid listen med flere elementer på en gang med .extend eller '+='
b = [100, 200]
a.extend(b)
print(a) # [2, 3, 42, 7, 100, 200]
a += b
print(a) # [2, 3, 42, 7, 100, 200, 100, 200]
print() 

print(alias) # [2, 3, 42, 7, 100, 200, 100, 200]

Ikke-destruktive operasjoner for å legge til elementer:

a = [2, 3]

# Legg til på slutten med +
b = a + [13, 17]
print(a)
print(b)

# Legg til midt inne i listen med beskjæring
c = a[:1] + [42] + a[1:]
print(a)
print(c)

Destruktiv vs. ikke-destruktiv utvidelse

print("Destruktiv:")
a = [2, 3]
b = a           # lager alias
a += [4]
print(a)
print(b)

print("Ikke-destruktiv:")
a = [2, 3]
b = a           # lager alias
a = a + [4]   # bryter aliaset med b, a er nå referanse til ny liste
print(a)
print(b)
Fjerne elementer

Destruktive metoder for å fjerne elementer

a = [2, 3, 5, 3, 7, 6, 5, 11, 13]
print("a =", a)

# Fjerne første opptreden av et bestemt element
a.remove(5)
print("Etter a.remove(5), a=", a)
a.remove(5)
print("Etter enda en a.remove(5), a=", a)

# Fjerne det siste elementet i listen
item = a.pop()
print("Etter item = a.pop()")
print("   item =", item)
print("   a =", a)

# Fjerne et element på en bestemt indeks
item = a.pop(3)
print("Etter item = a.pop(3)")
print("   item =", item)
print("   a =", a)

Ikke-destruktive operasjoner for å fjerne elementer

a = [2, 3, 5, 3, 7, 5, 11, 13]
print("a =", a)

# Ikke-destruktiv fjerning av elementene mellom indeks 2 og 3
b = a[:2] + a[3:]
print("Etter b = a[:2] + a[3:]")
print("   a =", a)
print("   b =", b)
Løkker over lister
# Iterasjon med indeks
a = [2, 3, 5, 7]
for index in range(len(a)):
    print(f"a[{index}] =", a[index])

print("---")

for index, item in enumerate(a):
    print(f"a[{index}] =", item)
# Iterasjon uten indeks, såkalt for-hver -løkke (engelsk: foreach)
# Lister og strenger er begge samlinger, såkalte "itererbare" typer.
# Det betyr at vi kan benytte en for-løkke på dem direkte
a = [2, 3, 5, 7]
for item in a:
    print(item)
# IKKE FJERN ELLER LEGG TIL ELEMENTER TIL SAMME LISTE DU GÅR GJENNOM
# MED EN FOR-LØKKE! INDEKSER KRØLLER SEG TIL!(dette er ikke et problem
# for strenger, siden de ikke kan muteres)
a = [2, 3, 5, 3, 7]
print("a =", a)

# Mislykket forsøk på å fjerne alle 3'erne
for index in range(len(a)):
    if (a[index] == 3):  # vi krasjer her etter en stund
        a.pop(index)

print("Hit kommer vi ikke")
# IKKE MUTER EN LISTE INNI EN FOR-HVER -LØKKE!
# Vil ikke krasje, men gjør heller ikke som vi forventer
a = [3, 3, 2, 3, 4]
print("a =", a)

# Mislykket forsøk på å fjerne alle 3'erne
def should_be_removed(x):
    return x == 3

for item in a:
    if should_be_removed(item):
        a.remove(item)

print(a)
# Bedre: mutering i en while-løkke.
# Her har vi full kontroll på hvordan indeks endrer seg.
a = [2, 3, 5, 3, 7]
print("a =", a)

# Vellykket forsøk på å fjerne alle 3'erne
index = 0
while (index < len(a)):
    if (a[index] == 3):
        a.pop(index)
    else:
        index += 1

print("Huzza! a =", a)
Sortering og reversering

Destruktiv sortering og reversering

# Sortering
a = [7, 2, 5, 3, 5, 11, 7]
print("Først, a =", a)
a.sort()
print("Etter a.sort(), a =",a)
print("---")

# Reversering
a = [2, 3, 5, 7]
print("Først, a =", a)
a.reverse()
print("Etter a.reverse(), a =", a)

Ikke-destruktiv sortering og reversering

# Sortering
a = [7, 2, 5, 3, 5, 11, 7]
print("Først, a =", a)
b = sorted(a)
print("Etter b = sorted(a)")
print("    a =", a)
print("    b =", b)
print("---")

# Reversering
a = [2, 3, 5, 7]
print("Først, a =", a)
b = reversed(a)
c = list(reversed(a))
print("Etter b = reversed(a)  og  c = list(reversed(a))")
print("    a =", a)
print("    b =", b)
print("    c =", c)
print("Her er elementene i b:")
for x in b:
    print(x, end=" ")
print()

print("Her er elementene i b en gang til (men hæ???):")
for x in b:
    print(x, end=" ")
print()
print("---")
Pakke ut en liste i variabler

Gitt en liste kan du “pakke den ut” i variabler.

a = ["Florida", 15.4, "2022-09-16"]

place, temp, date = a
print(f"{place = }", f" {temp = }", f" {date = }", sep="\n")

Hvis listen er lang, kan du pakke opp kun de par første verdiene og la resten bli en ny liste. Operasjonen er ikke-destruktiv.

a = ["Florida", 15.2, 13.5, 17.2, 13.6, 14.2]

place, *temps = a
print(f"{a=}")
print(f"{place=}")
print(f"{temps=}")

eller de par første og de par siste. Variabelen med * foran plukker opp resten i en ny liste.

a = ["Florida", "not interested", 15.2, 13.5, 17.2, 13.6, 14.2, "OK"]

place, _, *temps, last_temp, status = a

print(f"{a         = }")
print(f"{place     = }")
print(f"{_         = }")
print(f"{temps     = }")
print(f"{last_temp = }")
print(f"{status    = }")

Ved å sette * foran en liste, kan vi pakke opp elementene i listen og bruke dem som om vi bare skilte verdiene med komma uten at de var i en liste. For eksempel kan vi bruke elementene som argumenter til et funksjonskall.

def add(x, y):
    return x + y

a = [2, 2]
result = add(*a)
print(result)

Eller vi kan kombinere to lister ikke-destruktivt:

a = [1, 2]
b = [3, 4]
c1 = [a, b] # En liste av lister -- en 2-dimensjonell liste
c2 = [*a, *b] # En liste med verdiene fra to lister -- en "flat" liste
print(a, b, c1, c2, sep="\n")

En funksjon kan akseptere et ukjent antall argumenter ved å pakke dem inn i en liste

def multiply(*nums):
    # nums er en liste som inneholder alle argumentene
    result = 1
    for num in nums:
        result *= num
    return result

print(multiply(2, 2))
print(multiply(2, 2, 3))

Eller kreve et minimums antall argumenter ved å bare pakke inn bare de siste argumentene i en liste.

def multiply(first_num, second_num, *rest):
    result = first_num * second_num
    for num in rest:
        result *= num
    return result

print(multiply(2, 2, 3))
print(multiply(2, 2))
print(multiply(2)) # krasjer, ikke nok argumenter
Tupler

En tuple er en slags liste som ikke kan muteres.

t = (1, 2, 3)
print(type(t), len(t), t)

a = [1, 2, 3]
t = tuple(a)
print(type(t), len(t), t)
# Tupler kan ikke muteres
t = (1, 2, 3)
print(t[0])

t[0] = 42    # Krasj!
print(t[0])
# Parallell tilording av verdier
(x, y) = (1, 2)
print(x)
print(y)

# Paralell tilordning er nyttig for bytting av verdier
(x, y) = (y, x)
print(x)
print(y)
# Tuple med kun ett element
t = (42)
print(type(t), t*5) # oj

t = (42,) # bruk komma for å lage en tuple
print(type(t), t*5)

Tupler fungerer på samme måte som lister, men de kan ikke muteres. For å endre på tupler må man bruke ikke-destruktive funksjoner. Ikke-destruktive funksjoner for lister og for tupler er de samme og pleier å ha identisk syntaks.

En vanlig bruk av tupler er å returnere flere verdier fra samme funksjon:

# Bruk en tuple til å returnere flere verdier
def positive_and_negative_of(x):
    return (x, -x)

# Pakk ut resultatet til flere variabler (variablene skilles med ,)
hi, lo = positive_and_negative_of(5)
print(f"{hi} {lo}")

# Behold resultatet som en tuple (én variabel på venstresiden av =)
hilo = positive_and_negative_of(7)
print(f"{hilo}")
Listeforståelse (løkker inni lister)

I Python er det mulig å opprette lister med en løkke. Dette kalles for list comprehension på engelsk (listeforståelse) og det kalles dette fordi det er en kompakt og relativt forståelig måte å beskrive innholdet som skal være i listen man oppretter.

# Den lange måten
a = []
for i in range(10):
    a.append(i + 1)
print(a)

# Med listeforståelse
a = [i + 1 for i in range(10)]
print(a)

# For de ambisiøse: listeforståelse med betingelser
a = [i + 1 for i in range(20) if i % 2 == 0]
print(a)

# Listeforståelse for å ikke-destruktivt filtrere en liste
def divisible_by_3(x):
    return x % 3 == 0
b = [x for x in a if divisible_by_3(x)]
print(b)

# Listeforståelse for å ikke-destruktivt anvende en funksjon på
# hvert element i en liste
def square(x):
    return x * x
c = [square(x) for x in b]
print(c)
Konvertering mellom lister og strenger (split/join)
# bruk list(s) for å konvertere en streng til liste med tegn
a = list("hurra!")
print(a)  # ['h', 'u', 'r', 'r', 'a', '!']

# bruk s1.split(s2) for å konvertere en streng s1 til en liste
# med strenger, klippet opp langs s2'er inne i s1
a = "Hva holder du på med?".split(" ")
print(a) # ['Hva', 'holder', 'du', 'på', 'med?']

# bruk "".join(a) for å lime sammen/konkatenere en liste med strenger
print("".join(a))  #  Hvaholderdupåmed?

# s.join(a) for å lime sammen med s som lime-streng
print(" ".join(a)) # Hva holder du på med?
print("--".join(a)) # Hva--holder--du--på--med?