Lister


Hva en liste er (egentlig)

Til vanlig tenker vi på en liste som en samling av verdier; men egentlig bør vi tenke på det som en samling av referanser til verdier. Under viser vi en illustrasjon av minnet til datamaskinen etter at vi har opprettet en liste med tre elementer.

# Oppretter en liste a med tre elementer
a = [42, 95, 'foo']
Illustrasjon av en liste inne i minne

Vi kan hente ut enkelt-verdier fra en liste ved å slå opp i listen med indeksering. Indeksering starter på 0, så første element i listen har indeks 0, andre element har indeks 1, osv.

Hvis vi henter ut en enkelt-verdi fra listen a ved å benytte indeksering (f. eks. a[2]) vil uttrykket evaluere til verdien som pekes på. Eksempel: uttrykket a[2] vil i eksempelet over evaluere til verdien 'foo'. Hvis vi angir at a[2] er verdien til en ny variabel x, vil minnet endres som vist under:

x = a[2]
Illustrasjon av minne etter x = a[2]

I motsetning til de verdi-typene vi har sett tidligere i emnet, er det mulig å endre en liste uten å opprette et helt nytt objekt i minnet. Dette kalles å mutere en liste. Eksempel: hvis vi angir at verdien til a[1] skal settes til verdien av x, vil minnet endres som vist under:

a[1] = x
Illustrasjon av minne etter a[1] = x

Til sammenligning: om vi endrer verdien av x (en streng) ved å konkatinere mer tekst, vil det opprettes en helt ny verdi i minnet – strengen 'foo' som x opprinnelig pekte på blir ikke endret:

x += 'bar'
Illustrasjon av minne etter x += 'bar'

Strenger kan nemlig ikke muteres.

a = [42, 95, 'foo']
x = a[2]
a[1] = x
x += 'bar'

print(a) # [42, 'foo', 'foo']
print(x) # foobar

Alias og mutasjon

I motsetning til datatyper vi har sett tidligere, er det (som vist i avsnittet over) mulig å endre på en liste uten å opprette en ny verdi i minnet. Dette kaller vi å mutere listen. Dette gjør at vi må være forsiktige dersom to variabler peker på samme liste. Dersom vi muterer listen via den ene variabelen, vil endringen også gjelde for den andre variabelen.

Hvis to variabler peker på samme liste, kaller vi dem aliaser for hverandre.

a = [42, 95, 'foo']
b = a                # b er nå et alias for a
Illustrasjon av to aliaser for samme liste

Om vi muterer listen gjennom a vil altså b påvirkes (og vice versa).

a[1] = 'foo'         # vi muterer a
Illustrasjon av minnet etter mutasjon av a

Komplett eksempel:

a = [42, 95, 'foo']
b = a                # b er nå et alias for a

print(a) # [42, 95, 'foo']
print(b) # [42, 95, 'foo']

a[1] = 'foo'         # vi muterer a
b[0] = 95            # vi muterer b

# Begge mutasjoner reflekteres i begge aliasene
print(a) # [95, 'foo', 'foo']
print(b) # [95, 'foo', 'foo']
Alias og funksjonsparametre (destruktive funksjoner)

En av de vanligste typen alias vi har, opplever vi dersom vi kaller en funksjon med et enkelt variabeluttrykk som argument. Da vil parameteren og det opprinnelige variabelen være aliaser.

def my_function(a_param):
    ... # a_var og a_param er aliaser når vi kommer hit

a_var = [42, 95, 'foo']
my_function(a_var)
Illustrasjon av aliaser for en liste, hvor det ene aliaset er en parameter.

Dersom funksjonen muterer parameteren, vil det også påvirke den opprinnelige variabelen (aliaset). Vi kaller slike funksjoner for destruktive funksjoner, siden de «ødelegger» (muterer) verdiene den blir gitt som argument. Dette noen ganger er ønskelig, og andre ganger ikke (avhengig av situasjonen). Det er viktig å være bevisst på om en funksjon er destruktiv eller ikke, ellers kan det oppstå feil i programmet som er krevende å debugge.

def my_function(a_param):
    a_param[1] = 'foo' # muterer a_param

a_var = [42, 95, 'foo']
my_function(a_var)
print(a_var) # [42, 'foo', 'foo']
Illustrasjon av aliaser for en liste, hvor det ene aliaset er en parameter etter mutasjon.
Opprette lister

Tomme lister

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

print(a) # []
print(b) # []
print(a == b) # True

# Lengden til en liste
print(len(a)) # 0

# Typen til en liste
print(type(a)) # <class 'list'>

Lister med ett element

a = ['foo']
b = [42]

print(a) # ['foo']
print(b) # [42]
print(len(a)) # 1
print(len(b)) # 1
print(a == b) # False

Lister med flere elementer

a = [2, 3, 5, 7, 11]
b = list(range(5))
c = ['foo', 42, True, None, '']

print(len(a), a) # 5 [2, 3, 5, 7, 11]
print(len(b), b) # 5 [0, 1, 2, 3, 4]
print(len(c), c) # 5 ['foo', 42, True, None, '']

Lister med et variabel antall elementer

n = 5
a = ['foo'] * n
b = [7, 99] * n
c = list(range(n))

print(len(a), a) # 5 ['foo', 'foo', 'foo', 'foo', 'foo']
print(len(b), b) # 10 [7, 99, 7, 99, 7, 99, 7, 99, 7, 99]
print(len(c), c) # 5 [0, 1, 2, 3, 4]
Funksjoner og operasjoner
a = [2, 3, 5, 3, 7]
print('a = ', a)
print('len =', len(a)) # 5
print('min =', min(a)) # 2
print('max =', max(a)) # 7
print('sum =', sum(a)) # 20

# Et par forskjellige lister
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)) # True
print('a == c', (a == c)) # False
print('a == d', (a == d)) # False

print('------------------')
print('a < c', (a < c)) # True (sammenligning skjer basert på første ulike element)
print('d < a', (d < a)) # True
print('d > a', (d > a)) # False
# Konkatenering
a = [3, 4]
b = [8, 9]
print('a     =', a)
print('b     =', b)
print('a + b =', a + b) # [3, 4, 8, 9]

# Repetisjon
print('a * 3 =', a * 3) # [3, 4, 3, 4, 3, 4]
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]) # 2
print('a[2]     =', a[2]) # 5

# Negative indekser
print('a[-1]    =', a[-1]) # 13
print('a[-3]    =', a[-3]) # 7

# Beskjæring a[start:slutt] eller a[start:slutt:steg]
print('a[0:2]   =', a[0:2]) # [2, 3]
print('a[1:4]   =', a[1:4]) # [3, 5, 7]
print('a[1:6:2] =', a[1:6:2]) # [3, 7, 13]
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 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) # [99, 42, 4]
print(b) # [99, 42, 4]

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(my_list_parameter):
    my_list_parameter[0] = 42

a = [2, 3, 5, 7]
print(a) # [2, 3, 5, 7]
f(a)
print(a) # [42, 3, 5, 7]
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) # [3, 2, 1]
foo(a)
print(a) # [99, 2, 1]

Vi kan benytte is og is not -operatorene for å sjekke om to variabler er aliaser for samme objekt.

# 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:", a)
print("       b:", b)
print("       c:", c)
print("== -operatoren forteller hvorvidt to verdier er LIKE")
print("  a == b:", a == b) # True
print("  a == c:", a == c) # True
print("is -operatoren forteller hvorvidt to verdier er DEN SAMME")
print("  a is b:", a is b) # True
print("  a is c:", a is c) # False
print("\n")

# 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) # [42, 3, 5, 7]
print("       b:", b) # [42, 3, 5, 7]
print("       c:", c) # [2, 3, 5, 7]
print("  a == b:", a==b) # True
print("  a == c:", a==c) # False
print("  a is b:", a is b) # True
print("  a is c:", a is c) # False
Kopiering av lister
# Vi må være forsiktig ved kopiering av lister, slik at vi ikke kommer i
# skade for å muterer en liste av vanvare gjenomm et alias.

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) # [2, 3]
print("   b =", b) # [2, 3]
print("   c =", c) # [2, 3]

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

Andre måter å kopiere

import copy

a = [2, 3]

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

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

print("Først...")
print(a, b, c, d, e, f, g, h, li)
a[0] = 42

print("Etter mutering a[0]") # Klarer du å gjette hvilke «kopier» som blir mutert?
print(a, b, c, d, e, f, g, h, li)
Destruktive funksjoner

En funksjon er destruktiv dersom den har sideeffekter som muterer en parameter (eller hvis den muterer en global variabel).

# 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) # [1, 2, 3, 4, 5]
fill(a, 42)
print("Etter fill(a, 42), a =", a) # [42, 42, 42, 42, 42]

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 så muterer vi den nye listen
    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) # [1, 2, 3, 4, 3, 2, 1]

destructive_remove_all(a, 2)
print("Etter destructive_remove_all(a, 2)")
print("   a =", a) # [1, 3, 4, 3, 1]

b = non_destructive_remove_all(a, 3)
print("Etter b = non_destructive_remove_all(a, 3)")
print("   a =", a) # [1, 3, 4, 3, 1]
print("   b =", b) # [1, 4, 1]

c = alternate_non_destructive_remove_all(a, 1)
print("Etter c = alternate_non_destructive_remove_all(a, 1)")
print("   a =", a) # [1, 3, 4, 3, 1]
print("   c =", c) # [3, 4, 3]
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)) # True
print("4 in a =", (4 in a)) # False

# eller ikke?
print("2 not in a =", (2 not in a)) # False
print("4 not in a =", (4 not in a)) # True
# 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)) # 0
print("a.count(2) =", a.count(2)) # 4
print("a.count(3) =", a.count(3)) # 1
# 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)) # 4
print("a.index(2)   =", a.index(2)) # 0
print("a.index(2,1) =", a.index(2,1)) # 3
print("a.index(2,4) =", a.index(2,4)) # 6
# 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, ingen krasj!")
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("Først, 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("Etter, a =", a) # [2, 3, 4]
# Bedre: mutering i en while-løkke.
# Her har vi full kontroll på hvordan indeks endrer seg.
a = [3, 3, 2, 3, 4]
print("Først, a =", a)

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

index = 0
while (index < len(a)):
    value = a[index]
    if (should_be_removed(value)):
        a.pop(index)
    else:
        index += 1

print("Huzza! a =", a) # [2, 4]
# Enda en annen variant som virker tilfeldigvis for akkurat å fjerne alle 3'ere
a = [3, 3, 2, 3, 4]
while 3 in a:
    a.remove(3)
print("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 ut» verdiene 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, det var visst ikke en tuple likevel

t = (42,) # bruk komma for å lage en tuple med ett element
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}")
Listeinklusjon / list comprehension (løkker inni lister)

I Python er det mulig å opprette lister med en løkke. Dette kalles for list comprehension på engelsk (listeinklusjon).

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

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

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

# Listeinklusjon 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)

# Listeinklusjon 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?