Uke 6 - Lister

Les https://automatetheboringstuff.com/2e/chapter4/ frem til «EXCEPTIONS TO INDENTATION RULES IN PYTHON»-boksen.

Eksempler

Denne uken skal vi se på noen eksempler på lister og hva man kan gjøre med dem.

Eksempel: Basics

Last ned filen her: eksempel_basic_lists.py og kjør koden. Prøv å gjøre endringer i listene og se hva som skjer.

a = [5, 7, 2, 9, 10]

# You can use 'len()' with lists
print(a, len(a))

# You can use 'if _ in _:' with lists
if 9 in a:
    print(f"9 is in the list a = {a}!")

b = [5, "str", 9.3]
print("Lists can have mixed types inside:", b)

c = a + b
print(f"We can concatenate lists: {c = }")

# You can extract values from a list
x, y, z = [1, 5, 7]
print(x)
print(y)
print(z)

Eksempel: Indeks

Hvert element i en liste har et tilsvarende indeks, dette tilsvarer elementets posisjon i listen. Det første elementet har indeks 0, neste har indeks 1, etc. Vi kan bruke indeks for å få ut/endre verdien på en viss posisjon i listen. Vi kan bruke slice-notasjon for å få ut en delliste.

Last ned filen her: eksempel_index.py og kjør koden. Skjønner du hvordan indeks fungerer?

en_liste = ["a", "b", "c", "d", "e"]
print(f"{en_liste = }")

# Vi kommer åt verdien på indeks i ved å skrive en_liste[i].
print(f"{en_liste[0] = }")
print(f"{en_liste[1] = }")

# Siden indeks begynner på 0 så kommer det siste elementet ha indeks
# et mindre enn lengden av listen.
# print(en_liste[len(en_liste)])  # Dette gir en feilmelding.
print(f"{en_liste[4] = }")

# Indeks på siste elementet er alltid lengden av listen minus én.
print(f"{en_liste[len(en_liste) - 1] = }")

# Men vi kan også få det siste elementet ved å skrive kun [-1].
# Python subtraherer negative indeksverdier fra len() automatisk
print(f"{en_liste[-1] = }")

# Vi kan også endre verdiene i listen ved å bruke indeks.
print("Vi endrer listen ved å skrive: en_liste[2] = 'å'")
en_liste[2] = "å"
print(f"{en_liste = }")

# Vi kan få ut dellisten fra index i til og med j-1 ved å skrive [i:j].
print(f"{en_liste[1:4] = }")

# Vi kan dessuten hoppe over element.
print(f"{en_liste[0:5:2] = }")  # en_liste[start:stop:step]

# Vi gå andre veien i listen.
print(f"{en_liste[3:1:-1] = }")

# Vi må ikke angi start og/eller stop om vi vil gå helt fra start og/eller helt til slutten.
print(f"{en_liste[:3] = }")
print(f"{en_liste[3::-1] = }")
print(f"{en_liste[:] = }")

# Vi kan også fjerne verdiene i listen ved å bruke del.
print("Vi endrer listen ved å skrive: del en_liste[3]")
del en_liste[3]  # Tar bort elementet på indeks 3.
print(f"{en_liste = }")  # en_liste er endret.

Obs

I Python kan vi bruke asterisk (*) for å pakke og utpakke flere verdier i lister. Dette er en litt mer avansert måte å bruke Python og du må ikke bruke det selv, men noen ganger kan det være veldig praktisk.

Last ned filen her: eksempel_asterisk.py og prøv å kjøre koden.

# Du kan bruke * for å ekstrahere en delliste.
col_name, first, *rest = ["temp", 20.3, 21.0, 18.6, 18.2, 15.0, 12.5]
print(col_name)
print(first)
print(rest)  # 'rest' er en liste

# Du kan bruke * i funksjonskall.
# Gitt en liste bruker vi * for å utpakke den til en sekvens av argumenter.

def add(a, b):
    return a + b

xs = [4, 7]

print(add(*xs)) # det samme som print(add(4, 7))
# print(add(xs)) -> feil antall argumenter

print(*rest)  # Ekvivalent med print(r_0,...,r_n) for alle elementer i 'rest'.

Eksempel: Lister av lister

Lister kan også inneholde andre lister. Da må vi bruke to tall for å indeksere dem. Last ned eksempelet her eksempel_2D_lists.py. Hva tror du den vil skrive ut? Var det riktig?

date_temp = [["2003-03-13", 9.8], ["2004-03-13", 10.2]]

print(f"The list date_temp: {date_temp}")
print(f"{date_temp[0] = }")
print(f"{date_temp[0][1] = }")
# Hva er date_temp[0][0]?
# Hva er date_temp[1][0]?

print()

numbers = [[1], [1, 2], [1, 2, 3]]
print(f"The list numbers: {numbers}")
print(f"{numbers[2] = }")
print(f"{numbers[2][1] = }")
# Hva skjer om du skriver numbers[1][2]?

print()

# Ulike muligheter til å printe en liste av lister
for row in numbers:
    for n in row:
        # Argumentet 'end' sier hva som skal printes aller sist. Default er "\n".
        print(n, end=";")
    print()

for row in numbers:
    # Argumentet 'sep' sier hva som skal printes mellom argumentene. Default er " ".
    print(*row, sep=" <> ")

Lister av lister kalles ofte 2-dimensjonale (2D) lister fordi du kan tenke på dem som et 2D koordinatsystem

grid = [["00", "01", "02"],
        ["10", "11", "12"],
        ["20", "21", "22"]]

print(grid[2][1]) # printer "21"

Eksempel: Listefunksjoner

Her er noen ulike metoder man kan bruke på lister. Du kan finne ut mer på https://docs.python.org/3/tutorial/datastructures.html#more-on-lists

Last ned filen her: eksempel_list_functions.py og kjør koden. Skjønner du hva som skjer? Prøv de ulike metodene selv.

# https://docs.python.org/3/tutorial/datastructures.html

a = [5, 7, 2, 9, 10]
print(f"a = {a}")


# .append()

a.append(7)
print(f"We append 7: {a}")


# .insert()

a.insert(2, 6)
print(f"We insert 6 at index 2: {a}")


# .reverse()

a.reverse()
print(f"We reverse a: {a}")


# .remove()

a.remove(7)
print(f"We remove the first occurence of 7: {a}")


# .sort()

a.sort()
print(f"We sort a: {a}")

Obs

Ingen av disse funksjonene har en returverdi. Alle endrer på listen direkte, slik at det er forskjell på listen før og etter at du brukt funksjonen.

Eksempel: Input i lister

I dette eksemplet spør vi brukeren om positive tall, og siden beregnes maksimum av tallene på to måter.

Last ned filen her: eksempel_input.py og kjør koden. Skjønner du hvordan while-løkken fungerer? Hvordan blir den avsluttet? Hva gjør den siste raden i den løkken? Om du er usikker kan du gå igjenom noen iterasjoner av løkken med papir og penn og se hva som skjer.

Skjønner du hvordan for-løkken fungerer?

# ask for several numbers
# store in list
# find maximum

def numbers_from_input():
    """Ask user for input to build a list of positive integers."""
    nums = []
    while True:
        n = input("A positive number (q to stop): ")
        if n == "q":
            break
        n = int(n)
        if n < 0:
            print("That was negative. Try again.")
            continue
        nums.append(n)
    return nums


def my_max(nums):
    """Find the maximum of a list of numbers."""
    maxnum = nums[0]
    for num in nums[1:]:
        if num > maxnum:
            maxnum = num
    return maxnum


nums = numbers_from_input()
print(f"You gave me {len(nums)} numbers.")
print("Your numbers are:", nums)

print("The maximum number is:", max(nums))

print("Doing max by hand...")
maxnum = my_max(nums)
print("My own max is:", maxnum)

I dette eksemplet lagrer vi tallene fra brukeren i en liste. Det er fordi vi ikke vet hvor mange tall vi kommer til å få fra brukeren. En liste i Python kan vi gjøre så lang som vi trenger. Vi må ikke vite på forhånd hvor lang den skal være.

Eksempel: Vanlige løkkestrukturer

Følgende er noen eksempler på en vanlig løkkestruktur når vi jobber med lister.

Last ned filen her: eksempel_common_loops.py og kjør koden. Kommer du på flere problemer man kan løse med denne strukturen?

def my_sum(numbers):
    # Instansiere en variabel å bygge på.
    summe = 0

    # Kjør en for-løkke som bygger opp verdien i variabelen.
    for number in numbers:
        summe += number

    return summe


# Beregner minimum av en liste med negative tall.
def my_min(numbers):
    # Instansiere en variabel å bygge på.
    minimum = numbers[0]

    # Kjør en for-løkke som bygger opp verdien i variabelen.
    for number in numbers:
        if number < minimum:
            minimum = number

    return minimum


# Returnerer alle verdiene i 'xs' som er større enn 'lower_bound'.
def filter_greater_than(xs, lower_bound):
    # Instansiere en variabel å bygge på.
    filtered_list = []

    # Kjør en for-løkke som bygger opp verdien i variabelen.
    for number in xs:
        if number > lower_bound:
            filtered_list.append(number)

    return filtered_list


# Konstruerer et histogram fra verdiene i 'values'.
def render_histogram(values):
    # Instansiere en variabel å bygge på.
    rows = []

    # Kjør en for-løkke som bygger opp verdien i variabelen.
    for value in values:
        rows.append("*" * value)

    return "\n".join(rows)  # det neste eksempelet viser mer om .join()


print(my_sum([1, 2, 3, 4, 5]))
print(my_min([-1, -2, -8, -4, -5]))
print(filter_greater_than([1, 2, 3, 4, 5], 3))
print(render_histogram([1, 2, 3, 4, 5, 3, 8, 1]))

Eksempel: Strings og lister

Her er noen eksempler på metodene split() og join() som vi bruker for å konvertere en streng i en liste av strenger og motsatt.

Last ned filen her: eksempel_strings.py og kjør koden. Hvordan fungerer split()? Hvordan fungerer join()? Hva gjør for-løkken?

text = "Alice was beginning to get very tired"
print(text)

print("Default: splitting on whitespace")
words = text.split()
print(words)
print(len(words), "words")

print("Splitting on 't' instead:")
weird = text.split("t")
print(weird)

joined = "===".join(words)
print(joined)

nospaces = "".join(words)
print(nospaces)
print()

print("Lines of 3 words each:")
# Se til at du skjønner hva som skjer her!
line = []
for w in words:
    if len(line) < 3:
        line.append(w)
    else:
        print(" ".join(line))
        line = [w]  # Starte neste linje.
print(" ".join(line))

Eksempel: List Comprehension

Her er et eksempel på list comprehension (listeinklusjon). Det er en litt mer avansert syntaks og du må ikke bruke dette selv, men det kan være veldig praktisk ibland.

Listeinklusjon er en rask måte å lage nye lister fra verdiene til en eksisterende liste. Det er egentlig bare en annen måte å skrive en spesiell type for-løkke. Det går alltid å skrive om en listeinklusjon til en for-løkke. Sammenlign eksemplene nedenfor og forklar forskjellene til en venn.

animals = ["dog", "elephant", "bird"]

# Create new list with a for-loop.
lengths = []
for a in animals:
    lengths.append(len(a))
print(lengths)


# Do the same thing using list comprehension.
lengths = [len(a) for a in animals]
print(lengths)


# Transforming a list into a new one in a for-loop.
xs = [-4, 1, 3, 7, 10, 12]
ys = []
for x in xs:
    ys.append(x ** 2 - 2 * x + 1)
print(ys)

# Do the same thing using list comprehension.
ys = [x ** 2 - 2 * x + 1 for x in xs]
print(ys)

# We need to specify which is the variable we are looping over.
a = 5
multiples_of_a = [a * n for n in range(5)]  # n is the variable we want to loop over.
print(multiples_of_a)


# We can also have an if-statement in a list comprehension.
squares_of_odds = [x ** 2 for x in range(20) if x % 2 == 1]
print(squares_of_odds)

Leseforståelse: Polynomer

I dette eksempelet lager vi en funksjon poly_string() som gjør den samme ting som funksjonen med samme navn i Eksempel 4, uke 5 men med forskjellen at funksjonen tar en liste med koeffisienter som argument, sånn at polynomet kan være av hvilken grad som helst.

Vi lager også en funksjon poly_val() som beregner verdien av et polynom i en punkt. Argumenten til funksjonen er en liste med koeffisienter og et tall.

Kan du forstå hva programmet gjør uten å kjøre det?

Last ned filen her: eksempel_poly.py.

# Example of enumerate:
# printing polynomials,
# calculating values of polynomials


def poly_val(coeffs, x):
    """Calculate value of polynomial at a point

    coeffs: a list of non-negative integer coefficients of a polynomial
            sorted from lowest degree to highest degree
    x: a float, the point at which to evaluate the polynomial
    """

    sum = 0
    for i, coe in enumerate(coeffs):
        sum += coe * x ** i
    return sum


def poly_string(coeffs):
    """Give a string representation of a polynomial

    coeffs: a list of non-negative integer coefficients of a polynomial
            sorted from lowest degree to highest degree
    """
    terms = []

    if coeffs:  # Adding the constant term
        coe = coeffs[0]
        if coe != 0:
            terms.append(str(coe))
        coeffs = coeffs[1:]

    if coeffs:  # Adding the linear term
        coe = coeffs[0]
        if coe == 1:
            terms.append("x")
        elif coe > 1:
            terms.append(f"{coe}x")
        coeffs = coeffs[1:]

    for i, coe in enumerate(coeffs): # Adding all other terms
        if coe == 1:
            terms.append(f"x^{i+2}")
        elif coe > 1:
            terms.append(f"{coe}x^{i+2}")

    if not terms:  # If we were given an empty list or only zeros
        return "0"
    else:
        terms.reverse()
        return " + ".join(terms)


# Give a list of coefficients sorted from highest to lowest degree
coeffs = [6, 12, 0, 1, 5]  # 6x^4 + 12x^3 + x + 5
coeffs.reverse()
text = poly_string(coeffs)
x = 2
value = poly_val(coeffs, x)
print(f"The value of {text} at {x = } is {value}.")

Eksempel: in

Her er et eksempel på bruk av in med lister.

Last ned filen her: eksempel_in.py. Før du kjører koden, hva tror du output blir? Kjør koden. Var det riktig?

# This code should tell a visitor entering a museum what
# they must pay, depending on whether the visitor is a
# senior, a student or none of the above. Try some different values.


def ticket_price(visitor_type):
    """Calculate the ticket price based on visitor category.

    visitor_type: a list of categories
    """
    if "senior" in visitor_type:
        return 80
    elif "student" in visitor_type:
        return 90
    else:
        return 100


alice = ["Alice", "student", "senior"]
bob = ["Bob"]
carol = ["Carol", "senior"]
dan = ["Dan", "student"]

visitors = [alice, bob, carol, dan]

for v in visitors:
    name = v[0]
    price = ticket_price(v)
    print(f"{name} has to pay kr.{price}.")

Eksempel: turtle

Her er et eksempel til bruk av lister med turtle. Vi tegner en spiral ut fra to lister, en med vinkler og en med lengder.

Vi bruker python-funksjonen exit() et sted for å avslutte programmet om listene ikke har den samme lengden.

Last ned filen her: turtle_ex.py.

import turtle as t

# list of angles, list of lengths
angles = [90.0, 90.0, 90.0, 90.0, 90.0]
lengths = [100, 120, 150, 180, 300]

# loop over each item in the two lists
if len(angles) != len(lengths):  # why have this here?
    print("lists not same length")
    exit() # stops the program

# we use this combined list to give instructions to the turtle to draw a square:
for i in range(len(angles)):
    t.left(angles[i])
    t.forward(lengths[i])

t.done()

Eksempel: Feil

Her er et eksempel på ting som kan gå galt med lister.

Last ned filen her: eks/errors_1.py. Før du kjør koden, se om du finner alle feil. Prøv å kjøre koden. Endre sånn at koden går å kjøre.

 1a = ["xyz", "abc", 5.4, "z", True, 7, "Hei there"]
 2length = len(a)
 3print(a[length])
 4
 5
 6b = [12, 34, 1, 107, "14", 16, 19]
 7b.sort()
 8print(b)
 9
10#####
11
12print("Returning coins:")
13
14coins = [1, 5, 10, 20]
15
16price = 24
17payment = 50
18
19# work out return coins
20diff = payment - price
21for c in coins:
22    while diff > c:
23        print(f"Returning kr.{c}")
24        diff -= c

Koden i slutten skal beregne vekslepenger som skal returneres ved en betaling. Det finnes 20-kr, 10-kr, 5-kr og 1-kr. Den skal returnere slik at man får så få mynter som mulig. For eksempel, om prisen er 24 kr og betalingen er 50 kr skal den returnere én 20-kr, én 5-kr og én 1-kr. Men noe er feil med koden sånn at den ikke gjør helt som den skal. Kan du endre sånn at den fungerer som den skal?

Ekstra øving:

For å sjekke hva du har lært kan du gjøre ’practice questions’ 1-10 i kapittel 4 i Automate the Boring Stuff.

Oppgaver

Obs

Alle oppgavene er obligatoriske denne uken!

Oppgave 1

I filen uke_o6_oppg_1.py skal du definere følgende funksjoner:

  • remove_fives(): tar en liste og returnerer samme liste men med alle forekomster av tallet \(5\) fjernet.

    Eksempelkjøring:

    >>> remove_fives([0, 5, 95, 25, 50, 7, 20, 11])
    [0, 95, 25, 50, 7, 20, 11]
    
  • every_third(): tar en liste og returnerer en liste med kun hvert tredje element.

    Eksempelkjøring:

    >>> every_third(['a', 1, 'b', 'c', 2, 6, 'g', 4, 'p', 'q'])
    ['a', 'c', 'g', 'q']
    

    Obs

    TIPPS: bruk slice-notasjon

  • reverse(): tar en liste og snur rekkefølgen på den.

    Eksempelkjøring:

    >>> reverse([5, 6, 7, 8, 9, 10])
    [10, 9, 8, 7, 6, 5]
    
  • halve_values(): tar en liste med tall og returnerer listen hvor alle tallene i listen halveres.

    Eksempelkjøring:

    >>> halve_values([1, 2, 3, 4])
    [0.5, 1.0, 1.5, 2.0]
    

    Obs

    TIPPS: bruk append() eller list comprehension.

  • unique_values(): tar en liste og returnerer en liste hvor alle multiple forekomster av verdiene er fjernet (i den rekkefølge verdiene først dukker opp).

    Eksempelkjøring:

    >>> unique_values([3, 1, 6, 10, 30, 30, 30, 3, 0, 6])
    [3, 1, 6, 10, 30, 0]
    

    Obs

    TIPPS: Du kan enten lage en ny liste med append() eller så kan du bruke remove() med den opprinnelige listen.

  • Skriv din egen versjon av den innbygde funksjonen len, med navnet my_len(). Den skal returnere lengden av en liste eller en streng, slik som len. Du kan ikke kalle på andre funksjoner inne i my_len().

    Eksempelkjøring:

    >>> my_len([])
    0
    >>> my_len("123")
    3
    

Oppgave 2

Gitt en liste med koordinater i formen (x,y), i filen uke_06_oppg_2.py skriv funksjonen find_highest() som ta som input 2D-listen og går gjennom listen for å returnere indeksen som inneholder den høyeste verdien for y.

Eksempelkjøring:

>>> find_highest([[96, 33], [20, 96], [30, 69], [59, 9], [22, 93]])
1

Oppgave 3

En DNA-sekvens (f.eks ATAGCAGT) sammensettes av 4 ulike «baser», som beskrives med A, T, G og C.

Under replikasjon av sekvensen kan hver base settes sammen med eksakt én av de andre. A med T, og C med G

original ------->
         ATAGCAGT
         ||||||||
         TATCGTCA
         <------- complement

Slik får man en «komplementstreng» av den opprinnelige DNA-sekvensen.

Det vanlig å skrive ned komplementstrenger baklengs, slik at i vårt eksempel sier vi at komplementstrengen til ATAGCAGT er ACTGCTAT (ikke TATCGTCA).

I filen uke_06_oppg_3.py lag en funksion som heter complement() og som returnerer komplementstrengen av en DNA-sekvens. Funksjonen skal ta inn én streng som argument, og skal returnere én streng. Du kan anta at alle input-strenger er gyldige DNA-sekvenser.

Oppgave 4

I filen uke_06_oppg_4.py skal du lage en funksjon som heter render_histogram() som tar en liste med positive heltall og returnerer et histogram som bruker stapler av "*" for verdiene i listen. For eksempel, print(render_histogram([5, 4, 2, 7, 0, 3, 10])) skal gi følgende output:

      *
      *
      *
   *  *
   *  *
*  *  *
** *  *
** * **
**** **
**** **

Obs

TIPPS: Du kan starte med versjonen av render_histogram som finnes i eksemplene ovenfor.

Pytest-filer

Pytest-filene vi bruker på Codegrade: uke_06_tests.zip