Uke 7 — list / tuple / string

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

Eksempler

Eksempel 1: Tupler

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.

Last ned filen her: eksempel_1_tuple.py, og kjør koden.

# 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.")

Oppgave 1

I uke_07_oppg_1.py skal du skrive en funksjon som bytter ut favorittleken til en hund. Lag en funksjon kalt swap_tuple() som tar som argument en tuple med tre strenger. Den første strengen er hundens navn. Den andre strengen er hundens personlighet. Den tredje strengen er favoritleken til hunden. Du skal bytte ut favoritleken til "ball".

Eksempelkjøring:

>>> dog = ("Ollie", "joy", "squeaky toy")
>>> print(swap_tuple(dog))
("Ollie", "joy", "ball")

Eksempel 2: 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.

Last ned filen her: eksempel_2_sequence_types.py, og kjør koden. Skjønner du hva som skjer?

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

#######

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

Oppgave 2

I denne oppgaven får vi data om ukens maks temperaturer som én str. For eksempel kan dataen se slik ut:

"6.0 9.0 10.0 10.0 12.0 10.0 11.0" # Mandag - Søndag

I uke_07_oppg_2.py, skriv en funksjon som heter update_weather() og som tar en slik streng med én ukes temperaturer og lager en tuple med temperaturene konvertert til float (i samme orden).

Eksempelkjøring:

>>> update_weather("6.0 9.0 10.0 10.0 12.0 10.0 11.0")
(6.0, 9.0, 10.0, 10.0, 12.0, 10.0, 11.0)

I uke_07_oppg_2.py skriv også en funksjon som heter wednesday_weather() og som tar en tuple med en ukes temperaturer som float og returnerer temperaturen på onsdag (også som en float).

Eksempelkjøring:

>>> wednesday_weather(6.0, 9.0, 10.0, 10.0, 12.0, 10.0, 11.0)
 10.0

Eksempel 3a: 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).

Last ned filen her: eksempel_3a_mutable.py, og kjør koden. Skjønner du hva som skjer? Hvorfor blir b endret etter a += [999, 777]? Hvorfor blir b ikke endret etter a += (999, 777)?

# 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...

Eksempel 3b: Referanser

Nå skal vi bruke visualiseringsverktøyet på pythontutor.com for å se hvordan referanser fungerer i python med et konkret ekesempel.

Åpne lenken ovenfor og klikk «Next» for å gå trinnvis gjennom koden. Følg nøye med på hva som skjer på hvert trinn. Hvordan er m relatert til a? Hvordan forholder b seg til m? Hvordan forholder c seg til m? Hvilke listene er det som får appended den ekstra listen i linje 9? (Du kan også laste ned filen her: eksempel_3b_ref.py, og kjøre koden på datamaskinen din uten visualiseringen.)

# Understanding copies and references

from copy import copy, deepcopy

m = [[17.5, 19.1, 18.6], [20.0, 19.0, 19.5]]  # measurements from 2 days
a = m
b = copy(m)  # same as m[:]
c = deepcopy(m)

# we can see the differences especially when we try to append additional data
# to our original dataset (m) -- to which list of a, b, and/or c is this new
# data also appended?
m.append([16.2, 19.1, 18.6])  # add a third day's measurements to original list

# if we find an error in our measurement and want to change our original
# dataset m, to which copies are these changes made to as well (a, b, and/or c)?
m[1][1] = 15.2

I dette eksempelet kan vi se at m er en liste av lister og at a er den samme listen av lister. Variabelen b er en kopi av listen m, som har referanser til de andre listene i listen m. Derimot så er c helt annerledes fra de andre: det er en ny liste, med en ny versjon av listene i listen med de samme verdiene. Listene i c referer ikke til de samme listene som i m.

Oppgave 3

La oss nå prøve en oppgave som viser mutabilitet og referanser, som ligner på dette eksemplet. Som før, om du blir sittende fast, kan du lese gjennom dette eksemplet ovenfor igjen for å få litt hjelp.

Du har et dataset som inneholder de forutsagte makstemperaturene fra mandag til lørdag denne uken (se nedenfor). Men du fant ut at den forutsagte temperaturen på torsdagen ikke var riktig. Makstemperaturen skal bli 12.0. Du vil gjerne korrigere den, men vil heller gjøre det med en kopi av originalen som lar originalen være uendret. I oppgaven uke_07_oppg_3.py skriv en funksjon som heter adjust_daily_temps() og som endrer fredagens temperatur til 10.0, og returnerer det oppdaterte resultatet. Resultatet må være en tuple som originallisten.

Eksempelkjøring:

>>> daily_temps = (('mandag', 6.0), ('tirsdag', 9.0), ('onsdag', 10.0), ('torsdag', 10.0), ('fredag', 12.0), ('lørdag', 10.0), ('søndag', 11.0))
>>> adjust_daily_temps(daily_temps)
(('mandag', 6.0), ('tirsdag', 9.0), ('onsdag', 10.0), ('torsdag', 10.0), ('fredag', 10.0), ('lørdag', 10.0), ('søndag', 11.0))

Eksempel 4a: 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.

Last ned filen her: eksempel_4_zip.py, og kjør koden.

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

Eksempel 4b: 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 å combinere disse to listene til én 2D list i format [(angle1, length1),(angle2, length2), ...]. Vi kontrast denne metoden med en standard for loop-metode i koden nedenfor.

Last ned filen her: eksempel_4_turtle.py, og kjør koden.

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]

# combine angles and lengths into 2D list according to index, f.eks. [[90.0,100], ... ]

angles_lengths = []  # create empty list for combined angles and lengths

# for-loop approach: loop over each item in the two lists and combine this way
for i in range(len(angles)):
    angles_lengths.append([angles[i], lengths[i]])

print("Approach with for-loop")
print(angles_lengths)  # now we have a 2D list of paired angles and lengths


# zip() approach: Much shorter/cleaner!
angles_lengths_zipped = list(
    zip(angles, lengths)
)  # alternatively, [*zip(angles, lengths)]

print("Approach with zip")
print(angles_lengths_zipped)


# we use either of these combined lists to give instructions to the turtle to draw a square:
for i in range(len(angles_lengths_zipped)):
    t.left(angles_lengths_zipped[i][0])
    t.forward(angles_lengths_zipped[i][1])
t.done()

Oppgave 4a

I denne oppgaven får vi gitt to tupler med navn, personlighet og favorittlek til to hunder. I filen uke_07_oppg_4a.py, skriv en funksjon som heter tuple_repack() og tar to tupler med hundinformasjon og returnerer en liste med hudenes personligheter (i samme orden).

Eksempelkjøring:

>>> tuple_repack(("Ollie","joy","ball"), ("Iggy","nervous","squeaky toy"))
["joy", "nervous"]

Obs

TIPPS: Du kan bruke zip() og få ut resultatet derfra.

Oppgave 4b

I denne oppgaven blir vi igjen gitt en tuple med forutsagte temperaturer i en uke. I filen uke_07_oppg_4b.py, skriv en funksjon som heter data_reorganize() og som zipper tuplen dataen til en ny tuple som inneholder dagene som en tuple og temperaturene some en annen tuple. Funksjonen skal den resulterende omorganiserte tuplen.

Eksempelkjøring:

>>> days_paired_temperatures = (("mandag", 10.0),("tirsdag", 9.0),("onsdag", 10.0),("torsdag", 9.5),("fredag", 11.5),("lørdag", 8.5),("søndag", 11.0))
>>> data_reorganize(days_paired_temperatures)
(('mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag', 'søndag'),
  (10.0, 9.0, 10.0, 9.5, 11.5, 8.5, 11.0))

Eksempel 5: 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.

Last ned filen her: eksempel_5_enum.py, og kjør koden. Hva er forskjellen mellom disse to? Hvilken er lettere for deg å forstå?

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

# One 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:
for index, temp in enumerate(daily_temp_values):
    print(f"Index {index} in daily_temp_values is {temp}")

Oppgave 5

Du har en database med målinger hentet fra en sonde som måler vindhastighet. Sonden registrerer vindhastighet hver time. Hver 12. time kalibreres sonden. I uke_07_oppg_5.py skriv en funksjon som heter calibration_readout() og som tar en liste med float som argument (vindhastighetene). Anta at den første verdien i listen er målet i løpet av en kalibreringstime. Funksjonen skal bruke enumerate() for å returnere listen med vindhastigheter men hvor alle verdiene som er fra kalibreringstimer er byttet ut til en tuple med første verdien "Calibration hour" og andre verdien vindhastigheten. For eksempel, om calibration_readout() brukes på følgende liste:

>>> wind_speed = [3.0, 4.0, 3.5, 5.5, 1.0, 0.3, 3.0, 4.5, 6.0, 11.0, 12.0, 1.0, 2.0, 2.0]
[('Calibration hour', 3.0), 4.0, 3.5, 5.5, 1.0, 0.3, 3.0, 4.5, 6.0, 11.0, 12.0, 1.0, ('Calibration hour', 2.0), 2.0]

Eksempel 6: Finne feilene

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

Last ned filen her: errors_1.py. 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.

 1a = [2, 4, 8, 9]
 2b = a
 3c = a
 4d = a[:]
 5
 6a = a + [999, 777]
 7
 8b += [111, 333]
 9
10c = c.append(55)
11
12d.insert(len(d),77)
13
14print(f'{a = }\n{b = }\n{c = }\n{d = }')
15
16#############
17
18some_list = [    
194,
20    5
21                6,
22    7,
238,
24]
25
26# write a greeting string from a collection of letters
27
28greeting_1 = ('H','e','l','l','o')
29greeting_2 = ['t','h','e','r','e','!']
30
31full_greeting = greeting_1 + ' ' + greeting_2
32
33greeting_str = str(full_greeting)
34
35print('Our result:', greeting_str)
36print('Expected result:', 'Hello there!')

Oppgaver

Oppgave 1

Se Oppgave 1 rett nedenfor ekesempelen tupler.

Oppgave 2

Se Oppgave 2 rett nedenfor ekesempelen sekvenser.

Oppgave 3

Se Oppgave 3 rett nedenfor ekesempelen forandres vs ikke forandres.

Oppgave 4a

Se Oppgave 4a rett nedenfor ekesempelen zip().

Oppgave 4b

Se Oppgave 4b rett nedenfor ekesempelen zip().

Oppgave 5

Se Oppgave 5 rett nedenfor ekesempelen enumerate()

Oppgave 6

I filen uke_07_oppg_6.py, skriv en funksjon som heter pigify() og som oversetter et ord fra engelsk til pig latin. Oversettelsen gjøres på følgende måte:

  1. Hvis et ord begynner med en vokal, skal funksjonen legge til -way på slutten av ordet (e.g., apple ↦ appleway). (I denne oppgaven regner vi ikke y som en vokal.)

  2. Hvis ordet begynner med en konsonant, skal funksjonen flytte alle bokstavene i begynnelsen av ordet frem til den første vokalen, til slutten av ordet, og legge til -ay etter det (e.g., banana ↦ ananabay, string ↦ ingstray). (Hvis ordet bare inneholder konsonanter så skal kun -ay legges til på slutten av ordet.)

Eksemplkjøring:

>>> pigify("door")
"oorday"

Skriv også en funksjon som heter pigify_sentence() som tar en setning som argument (som en string) og som returnerer tilsvarende setning men ver hvert ord er oversatt til pig latin. Du kan anta at alle setinger funksjonen blir kalt med ikke inneholder spesialtegn eller store bokstaver.

Eksempelkjøring:

>>> pigify_sentence("time will not slow down when something unpleasant lies ahead"
"imetay illway otnay owslay ownday enwhay omethingsay unpleasantway ieslay aheadway"

Oppgave 7

Tilpasset fra The Python Workbook, Ex. 117:

En linje med best tilpasning er en rett linje som best tilnærmer en samling av \({n}\) datapunkter. Variablene \(\bar{x}\) og \(\bar{y}\) brukes å representere gjennomsnittsverdiene i samlingen. Linjen med best tilpasning er representert ved ligningen \(y = mx + b\) hvor \({m}\) og \({b}\) beregnes ved hjelp av følgende formler:

\[m = \frac{ \sum{xy} - \frac{( \sum{x})( \sum{y})}{n}}{ \sum{x^2} - \frac{( \sum{x})^2}{n}}\]
\[b = \bar{y} - m \bar{x}\]

I filen uke_07_oppg_7.py skal du lage en funksjon med navnet line_best_fit() som tar som argument en tuple av koordinater i tuple form ((x1, y1), (x2, y2), ...). Funksjonen skal returnere formelen for linjen med best tilpasning i formen \(y = mx + b\), hvor variablene \({m}\) og \({b}\) erstattes med verdiene du beregnet for formlene ovenfor for \({m}\) og \({b}\). Om verdien til \({b}\) er 0, må formlene ikke inkludere \({b}\).

Eksempelkjøring:

>>> coord_1 = ((1, 1), (2, 2.1), (3, 2.9))
>>> line_best_fit(coord_1)
y = 0.95x + 0.1

>>> coord_2 = ((0, 0), (1, 1), (2, 2), (3, 3))
>>> line_best_fit(coord_2)
y = 1.0x

Obs

TIPPS: Du kan bruke zip() for noen av beregningene for x og y for å løse for m og b, eller du kan bruke for-løkker. Før du begynner å prøve å kode, sørg for at du vet hva du trenger å beregne, og del problemet i mindre biter basert på hver av disse mindre verdiberegningene du må gjøre.