Les og utforsk

Denne uken avslutter vi med lister og tupler. Les https://automatetheboringstuff.com/2e/chapter4/ frem til «A Short Program: Conway’s Game of Life.»

Tupler

En tuple er en slags liste som ikke kan 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.

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])
# parentesene trengs ikke, men brukes til bedre lesbarhet 
x = (3, 2, 1)
y = 3, 2, 1
print(x == y)
# 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)

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}")

Tupler er litt som en liste, men bruker () isteden for []. Vi bruker vanligvis tupler for objekter hvor lengden ikke vil endres, dvs. antall objekter/elementer forventes å være statiske. Det er noen viktige forskjeller på tupler og lister som vi skal se på seinere. I dette eksempelet lager vi noen tupler og ser hvordan de kan brukes.

# a tuple with three elements:
bergen = ("rain", 60.39, 5.32)  # bergen weather, latitude, longitude

# a tuple with one element (notice the comma - what happens if you remove the comma?)
bergen_weather = ("rain",)

print("Two tuples:")
print(f"bergen = {bergen}\nbergen_weather = {bergen_weather}\n")

print("Tuple unpacking:")
weather, latitude, longitude = bergen  # we can assign multiple variables to values in a tuple
print(f"weather = {weather}")
print(f"latitude = {latitude}")
print(f"longitude = {longitude}")

print("Lists of tuples:")
# here is a list of tuples
bergen_temp_forecast = [(16.0, "onsdag"), (14.0, "torsdag"), (13.0, "fredag")]

# we can unpack them automatically while going through the list
for temp, day in bergen_temp_forecast:
    print(f"På {day} blir det {temp} grader.")
Sekvenser

Her er noen eksempler på lister, tupler og strenger. Alle tre kan indekseres på den samme måten, og alle tre kan brukes i for-løkker. Vi kan også konvertere mellom disse tre sekvensene.

# sequence types: list, tuple, str

# list
x = [2, 3, 5, 6, 7]  # list type
print(x)
print(x[1])  # index 1st position of list x
print(x[1:3])  # slice list x 1st to 3rd position
x = x + [3, 4]  # add values to list x
x[1] = 999  # only for list: change element
print(x)


# tuple
y = (2, 3, 5, 6, 7)  # tuple type
print(y[1])  # index 1st position of tuple y
print(y[1:3])  # slice tuple y 1st to 3rd position
y = y + (3, 4)  # add values to tuple y
# y[1] = 999 # not possible to change elements
print(y)

# string
z = "23567"
print(z)
print(z[1])  # index 1st position of string z
print(z[1:3])  # slice string z 1st to 3rd position
z = z + "9876"  # add to string z
# z[1] = "A" # not possible to change elements
print(z)

print("\n" * 3)
x = [2, 3, 5, 6, 7]
y = (2, 3, 5, 6, 7)
z = "23567"
# Sequences can be used interchangeably in many places:
# Can use all sequences in for loops

for e in x:
    print("...", e, "...")

for e in y:
    print("===", e, "===")

for e in z:
    print("***", e, "***")

print("\n" * 3)
#######

# conversions between list - tuple - string

some_list = [
    4,
    5,
    6,
    7,
    8,
]
some_tuple = (6, 7, 8, 9, 10, 11, 12, 13)
some_str = "Hello there"

print("Convert to list")
print(list(some_list))
print(list(some_tuple))
print(list(some_str))

print("Convert to tuple")
print(tuple(some_list))
print(tuple(some_tuple))
print(tuple(some_str))

print("Convert to str")
print(str(some_list))
print(str(some_tuple))
print(str(some_str))
Ikke-destruktive operasjoner for å fjerne elementer

Forrige uke så vi destruktive metoder(.remove(), .pop()) for å fjerne elementer fra em liste. Her er 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) 
Mutasjon og alias av funksjonsparametre
# 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("---")
Foranderlig / ikke foranderlig

Lister, tupler og strenger er like på mange måter, hovedforskjellen er at listene kan endres, vi kaller det for «mutable». Her er noen eksempler på forskjellen mellom lister (som kan forandres), og tupler, som er ’immutable’ (som ikke kan forandres).

# Lists are mutable, assignment works by reference
a = [2, 4, 8, 16]
b = a
c = a
d = a[:]

print(f"{a = }\n{b = }\n{c = }\n{d = }")

print("Change a:")
a += [999, 777]
# not the same as a = a + [999, 777] (why?)
# a = a + [999, 777]
print(f"{a = }\n{b = }\n{c = }\n{d = }")
print("Change b:")
b += [111, 222]
print(f"{a = }\n{b = }\n{c = }\n{d = }")


print("\n\n\n")


# Tuples (and str) are immutable, assignment works by copy
a = (2, 4, 8, 16)
b = a
c = a
d = a[:]

print(f"{a = }\n{b = }\n{c = }\n{d = }")

print("Change a:")
a += (999, 777)
print(f"{a = }\n{b = }\n{c = }\n{d = }")
print("Change b:")
b += (111, 222)
print(f"{a = }\n{b = }\n{c = }\n{d = }")

# try with str...
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). List comprehensions 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 list comprehension 
a = [i + 1 for i in range(10)]
print(a)

List comprehensions kan også inkludere if-setninger:


a = [i + 1 for i in range(20) if i % 2 == 0]
print(a)

# er akkurat det samme som
a = []
for i in range(20):
    if i % 2 == 0:
        a.append(i + 1)
print(a)

Ved bruk av en list-comprehension uttrykker man at det skal lages en ny liste basert på de elementene som finnes i en kildeliste.

# List comprehension for å filtrere en liste med en egendefinert funksjon
def divisible_by_3(x):
    return x % 3 == 0

b = [x for x in a if divisible_by_3(x)]
print(b)

# List comprehension for å anvende en funksjon på
# hvert element i en liste
def square(x):
    return x * x

c = [square(x) for x in b]
print(c)

# kombinere begge:
d = [square(x) for x in b if divisible_by_3(x)]
print(d)
Opprette 2D-lister og indeksering
# opprett en 2D-liste med gitte verdier (statisk opprettelse)
a = [[2, 3, 4], [5, 6, 7]]
print(a)
print(a[0])     # Første elment i a er en vanlig 1D-liste
print(a[0][1])  # Andre element (index 1) i første element i a
# MISLYKKET forsøkt på å opprette 2D-liste med dynamisk størrelse
rows = 3
cols = 2

a = [[0] * cols] * rows # Oppretter en "grunn" kopi
                        # Oppretter én unik rad, resten er aliaser

print("Dette VIRKER SOM det er ok.  I begynnelsen:")
print("   a =", a)

a[0][0] = 42
print("MEN, hva skjer etter at vi muterer a[0][0]=42?")
print("   a =", a)
# RIKTIG måte å opprette en 2D-liste med dynamisk størrelse
rows = 3
cols = 2

a = []
for _ in range(rows):
    a.append([0] * cols)

print("Dette ER ok.  I begynnelsen:")
print("   a =", a)

a[0][0] = 42
print("Etter at vi muterer a[0][0]=42")
print("   a =", a)
# Alternativ RIKTIG måte å opprette en 2D-liste med dynamisk størrelse
# Her benytter vi list comprehension
rows = 3
cols = 2

a = [[0] * cols for _ in range(rows)]

print("Dette ER ok.  I begynnelsen:")
print("   a =", a)

a[0][0] = 42
print("Etter at vi muterer a[0][0]=42")
print("   a =", a)
Dimensjonene til en 2D-liste

En 2D-liste er en liste av lister. Selv om det ikke er påkrevd fra Python sin side, er det vanlig at alle de “innerste” listene er like lange. På den måten representerer 2D-listen et rutenett eller et regneark. Vi tenker oss at hver av de innerste listene representerer én rad, mens den ytterste listen inneholder alle radene.

# Anta at a er en hvilken som helst 2D-liste som representerer et rutenett
a = [[2, 3, 5], [1, 4, 7]]
print("a = ", a)

# La oss finne dimensjonene til rutenettet
rows = len(a)
cols = len(a[0])
print("rows =", rows)
print("cols =", cols)
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)
Løkker over 2D-lister
# Anta at a er en hvilken som helst 2D-liste som representerer et rutenett
a = [[2, 3, 5], [1, 4, 7]]
print("Først: a =", a)

# Vi finner dimensjonene til rutenettet
rows = len(a)
cols = len(a[0])

# En løkke over hvert element i listen
# I eksempelet under øker vi verdien til hver celle i rutenettet med 1
for row in range(rows):
    for col in range(cols):
        # Koden her inne kjøres rows*cols ganger, én gang for hver
        # kombinasjon av verdier for row og col.
        a[row][col] += 1

# Til slutt, utskrift av resultatet
print("Etter:  a =", a)
3D-lister

En 2D-liste er bare en liste av lister. Det er selvsagt mulig å ha en liste av 2D-lister. Dette blir da en 3D-liste. En liste med 3D-lister blir en 4D-liste, og så videre.

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

for i in range(len(a)):
    for j in range(len(a[i])):
        for k in range(len(a[i][j])):
            print(f'a[{i}][{j}][{k}] = {a[i][j][k]}')
zip()

zip() er en innebygd funksjon som «zipper» sammen sekvensene som blir gitt inn og returnerer en liste av tupler. En sekvens kan f.eks. være en liste, streng eller en tuple. zip tar det første elementet fra hver sekvens og legger de sammen i en tuple, og så det andre elementet i hver sekvens og legger de sammen osv. Om sekvensene har ulik lengde, er det den korteste sekvensen som bestemmer lengden til zip() -outputet.

# zip() tuples of same size
tup = ("cat", "dog", "cow")
tup2 = ("meow", "woof", "moo")

zip_tup = zip(tup, tup2)

# A zip object must be converted to list or tuple to be printed as a list or tuple.
print(zip_tup)
print(list(zip_tup))

# ...but it can be used directly in a loop:
for animal, sound in zip(tup, tup2):
    print(f"<<{sound}>> says the {animal}")


print("\n------------------------\n")

# zip() list of different size
list1 = ["a", "b", "c", "d"]
list2 = [1, 2, 3]

zip_list = list(zip(list1, list2))  # convert to list for multiple uses

for t in zip_list:
    print(t)

# to revert a zip, you can use * (see the explanation in week 06)
reverted = list(zip(*zip_list))
print(reverted)
Turtle

Vi kan demonstrere bruken av zip() med turtle eksemplet fra uke_06 med de to listene angles og lengths hvor zip() er lett å bruke for å kombinere disse to listene til én 2D list i format [(angle1, length1),(angle2, length2), …].

import turtle as t

# list of angles, list of lengths (same number of items!)
angles = [90.0, 90.0, 90.0, 90.0]
lengths = [100, 100, 100, 100]

print("Approach with zip")
print(list(zip(angles, lengths)))
# we use either of these combined lists to give instructions to the turtle to draw a square:
for angle, length in zip(angles, lengths):
    t.left(angle)
    t.forward(length)
t.done()
enumerate()

Den innebygde funksjonen enumerate() er et alternativ til å bruke range(len(myList)) med en for-løkke for å få indeksen til elementene i listen.

På hver iterasjon av løkken vil enumerate() returnere to verdier: (1) indeksen til elementet i listen og (2) selve elementet i listen.

daily_temp_values = [16.0, 13.0, 14.0, 13.0, 15.0, 13.0]

# One clumsy way to get indices/values
for i in range(len(daily_temp_values)):
    print(f"Index {i} in daily_temp_values is {daily_temp_values[i]}")

print("\n" * 2)

# Using enumerate is always preferred!
for index, temp in enumerate(daily_temp_values):
    print(f"Index {index} in daily_temp_values is {temp}")
Finne feilene

Her er noen eksempler på ting som kan gå feil.

Før du kjør koden, se om du finner alle feil som gjør at den ikke går å kjøre. Kjør siden koden. Fant du alle feil? Endre koden så at den går å kjøre. Koderaden greeting_str = str(full_greeting) er ikke feil slik at man får en error fra Python, men den gjør ikke akkurat hva vi vil at den skal gjøre. Endre så at det blir riktig output.

a = [2, 4, 8, 9]

b = a
c = a
d = a[:]
a = a + [999, 777]
b += [111, 333]
c = c.append(55)
d.insert(len(d),77)

print(f'{a = }\n{b = }\n{c = }\n{d = }')


#############
some_list = [
4, 
   5
       6,

   7,
8,

]


# write a greeting string from a collection of letters
greeting_1 = ('H','e','l','l','o'
greeting_2 = ['t','h','e','r','e','!']
full_greeting = greeting_1 + ' ' + greeting_2
greeting_str = str(full_greeting)
print('Our result:', greeting_str)
print('Expected result:', 'Hello there!')