Tilbake
4.2

4.2 Lister vs. NumPy — «med løkke / uten løkke»

Den faste sjangeren der samme tabell skal fylles først med løkke og `.append`, deretter vektorisert med `linspace` — sjanger M.

45 min
9 oppgaver
Lister vs. NumPymed løkke / uten løkke
Din fremgang i kapitlet
0 / 9 oppgaver
Kapitlets plass i kurset

Forkunnskaper

- NumPy-tabeller, særlig np.linspace og at N intervaller gir N+1 punkter: kap. 4.1.
- Lister og .append fra kap. 1.2.
- Løkker med range fra kap. 1.3.
- Formatert utskrift med f-strenger fra kap. 1.4, til den siste løkka der vi skriver ut tabellen.

Kapitlet er delt i tre løkker med tidsanslag.

Løkke 1 — de to malene (~15 min)

Tenk på en enkel oppgave fra havforskning: du skal lage en tabell over lydhastighetens avhengighet av dybden, eller — enklere — en tabell over y=xy = \sqrt{x} for 51 punkter mellom 0 og 4. Tabellen betyr her to kolonner: en kolonne med xx-verdier og en kolonne med de tilhørende yy-verdiene.

Det finnes to helt likeverdige måter å lage den. Den første går gjennom punktene ett for ett med en løkke og bygger to lister. Den andre lager hele xx-kolonnen på én linje og hele yy-kolonnen på én linje til.

Sensor er ikke ute etter at den ene er «bedre». Sensor er ute etter at du kan begge, og at du vet hva som er forskjellen.

📜Kodemønsteret «med løkke / uten løkke»

De to malene, side om side. Lær dem utenat — de er nesten uendret fra sett til sett.

Med løkke og lister (del a): du regner ut steglengden selv, og fyller to tomme lister.

x = []
y = []
dx = (stop - start)/N
for i in range(N+1):
    xi = start + i*dx
    x.append(xi)
    y.append(f(xi))

Uten løkke, vektorisert (del b): np.linspace lager hele xx-kolonnen, og funksjonen brukes på hele tabellen på én gang.

x = np.linspace(start, stop, N+1)
y = f(x)

Tre presisjoner som avgjør poengene:

1. Nevneren i dx er N, ikke N+1. Steglengden er avstanden mellom to nabopunkter, og med N+1 punkter er det N avstander.
2. Løkken går over range(N+1). Den skal produsere N+1 punkter, ett per delepunkt, med både første og siste med.
3. Tredje argument i np.linspace er N+1. Begge versjonene har da nøyaktig like mange punkter — det er dette sensor sjekker først.

Steglengden `dx = (stop - start)/N`

Avstanden mellom to nabopunkter når intervallet fra start til stop deles i N like biter. Nevneren er antall intervaller, altså N, aldri N+1 og aldri antall punkter. Punkt nummer i er da start + i*dx, og for i = N gir det nøyaktig stop.

start, stop, N = 0.0, 4.0, 50
dx = (stop - start)/N
print(dx, start + N*dx)

Utskrift:

0.08 4.0

Å bygge en tabell med `[]` og `.append`

Løkkeversjonen starter med to tomme lister, x = [] og y = [], og legger til én verdi i hver per runde med .append(verdi). Etter løkka har begge listene N+1 elementer, og element nummer i i den ene hører sammen med element nummer i i den andre. Lengden sjekkes med len(x), ikke med .size — det siste er bare for arrays.

y = []
for i in range(3):
    y.append(i*i)
print(y, len(y))

Utskrift:

[0, 1, 4] 3

✏️Eksempel 1: samme tabell to ganger, og en kontroll på at de er like

Lag en tabell over y=xy=\sqrt{x} med 51 punkter i intervallet [0,4][0, 4] — først med løkke og lister, så vektorisert. Kontroller at de to versjonene gir samme antall punkter og samme verdier.


51 punkter betyr N=50N = 50 intervaller. Legg merke til at løkkeversjonen bruker sqrt fra math (ett tall om gangen), mens den vektoriserte bruker np.sqrt (hele tabellen).

from math import sqrt
import numpy as np

start = 0.0
stop = 4.0
N = 50

# a) med løkke og lister
x1 = []
y1 = []
dx = (stop - start)/N
for i in range(N+1):
    xi = start + i*dx
    x1.append(xi)
    y1.append(sqrt(xi))

# b) uten løkke
x2 = np.linspace(start, stop, N+1)
y2 = np.sqrt(x2)

print('Antall punkter:', len(x1), x2.size)
print('Endepunkter:', x1[0], x1[-1], x2[0], x2[-1])
print('Midt i tabellen:', y1[25], y2[25])
print('Største avvik:', max(abs(np.array(y1) - y2)))

Utskrift:

Antall punkter: 51 51
Endepunkter: 0.0 4.0 0.0 4.0
Midt i tabellen: 1.4142135623730951 1.4142135623730951
Største avvik: 0.0

Tabellene er identiske — største avvik er null. Det er ikke garantert for alle intervaller (flyttall regnes ut i litt ulik rekkefølge i de to versjonene), men avviket vil aldri være større enn de siste sifrene.

Sensormerknad: det som gir uttelling i del a) er de fire tingene dx med nevner N, range(N+1), to .append-kall, og at f kalles på xi og ikke på hele lista. I del b) er det np.linspace(start, stop, N+1) og at funksjonen brukes direkte på arrayet. Bommer du på ett tall i dx, men har resten riktig, får du fortsatt mesteparten av poengene.

📝Oppgave 1

(Innstegsoppgave.) Et intervall fra 2 til 8 skal deles i 12 like intervaller.

a) Hvor mange punkter blir det?

b) Hva blir steglengden dx?

c) Skriv linjen som lager punktene vektorisert.

Løkke 2 — der poengene forsvinner (~15 min)

Nå kommer den delen som avgjør karakteren på denne sjangeren. De tre feilene under står for nesten alle trekk, og de er alle av samme slag: en av-én.

Oppgaveteksten oppgir ofte antall punkter direkte («lag en tabell med 101 punkter i intervallet [1,10][1, 10]»), ikke antall intervaller. Da er N+1=101N+1 = 101, altså N=100N = 100, og steglengden er 9/100=0.099/100 = 0.09. Den regnestykket er verdt å gjøre eksplisitt på kladdepapiret før du skriver kode.

✏️Eksempel 2: eksamensnivå — 101 punkter og en logaritme

En oppgave ber om en tabell over y=lnxy = \ln x med 101 punkter i intervallet [1,10][1, 10], først med løkke, deretter uten. Skriv begge, og skriv ut de tre første og de tre siste radene i tabellen med to desimaler på xx og fire på yy.


101 punkter betyr N=100N = 100 og dx=9/100=0.09dx = 9/100 = 0.09.

from math import log
import numpy as np

start = 1.0
stop = 10.0
N = 100

# a) med løkke
x1 = []
y1 = []
dx = (stop - start)/N
for i in range(N+1):
    xi = start + i*dx
    x1.append(xi)
    y1.append(log(xi))

# b) uten løkke
x2 = np.linspace(start, stop, N+1)
y2 = np.log(x2)

print('Punkter:', len(x1), x2.size, 'dx =', dx)
for i in [0, 1, 2, 98, 99, 100]:
    print(f'{x1[i]:6.2f} {y1[i]:8.4f} {x2[i]:6.2f} {y2[i]:8.4f}')

Utskrift:

Punkter: 101 101 dx = 0.09
  1.00   0.0000   1.00   0.0000
  1.09   0.0862   1.09   0.0862
  1.18   0.1655   1.18   0.1655
  9.82   2.2844   9.82   2.2844
  9.91   2.2935   9.91   2.2935
 10.00   2.3026  10.00   2.3026

De to første kolonnene kommer fra løkkeversjonen, de to siste fra den vektoriserte. De er like rad for rad — det er nettopp kontrollen sensor gjør.

Legg merke til at log fra math og np.log er samme funksjon matematisk sett (naturlig logaritme), men den første tar ett tall og den andre en hel tabell. Bytter du dem om, får du en feilmelding i løkkeversjonen og en tabell full av feil i den vektoriserte.

— naturlig pausepunkt —

📝Oppgave 2
Eksamensnivå, sjanger M

En bølgeprofil er gitt ved h(x)=2sin(x)h(x) = 2\sin(x), der xx måles i radianer. Lag en tabell over xx og h(x)h(x) med 21 punkter i intervallet [0,6][0, 6].

a) Skriv koden med en løkke og to lister.

b) Skriv koden uten løkke.

c) Hvor mange intervaller er det mellom punktene, og hva er steglengden?

📝Oppgave 3

(Midtveisnivå, sjanger A — kodesporing.) De to programdelene skal lage samme tabell, men gjør det ikke. Hva skriver programmet ut?

import numpy as np

N = 4
start, stop = 0.0, 2.0

x1 = []
dx = (stop - start)/(N+1)
for i in range(N+1):
    x1.append(start + i*dx)

x2 = np.linspace(start, stop, N+1)
print(len(x1), x2.size)
print(x1[-1])
print(x2[-1])

Løkke 3 — når løkka er det riktige valget (~15 min)

Det er lett å tro at vektorisering alltid er «riktigere». Det er ikke slik i dette faget. Vektorisering kreves bare når oppgaven eksplisitt ber om det — står det «uten bruk av løkke» eller «vektorisert», skal du vektorisere. Ellers er en løkke helt greit og gir full uttelling.

Det finnes dessuten tabeller du bygge med løkke:

- når verdien i punkt i avhenger av verdien i punkt i-1 (differenslikninger, kap. 6.2)
- når regelen inneholder en if-test som skiller mellom punkter
- når du underveis skal stoppe, telle eller samle opp noe

En NumPy-tabell kan du gjerne lage plass til på forhånd med np.zeros(N+1) og fylle i en løkke. Det er ikke vektorisering, men det er den mest brukte formen i resten av boka.

Når må du vektorisere?

Bare når oppgaveteksten sier det: «uten bruk av løkke», «vektorisert», «bruk NumPy». Da skal du hverken bruke for, while eller .append — hele tabellen skal lages med np.linspace eller np.zeros, og funksjonen brukes på hele arrayet.

Sier oppgaven ingenting, velger du fritt, og en løkke er like mye verdt. Krever regelen at forrige verdi er kjent, eller inneholder den en if-test, du bruke løkke.

Løkke som fyller et ferdig array

Mellomformen mellom de to malene, og den vanligste i resten av boka: lag plassen med y = np.zeros(N+1), og fyll den i en løkke med y[i] = .... Du får et array (og kan derfor regne elementvis med det etterpå), men du har full kontroll punkt for punkt. Merk at du her ikke bruker .append — plassen finnes allerede, du skriver bare inn i den.

import numpy as np
y = np.zeros(4)
for i in range(4):
    y[i] = 2*i
print(y)

Utskrift:

[0. 2. 4. 6.]

✏️Eksempel 3: en tabell som må bygges med løkke

Et strømmålepunkt registrerer én verdi per time. Under 10 grader regnes forbruket som 3,0+0,5(10T)3{,}0 + 0{,}5(10-T) kilowattimer, ellers som en fast verdi på 3,03{,}0. Lag tabellen for fem målte temperaturer, og forklar hvorfor den vektoriserte malen ikke kan brukes direkte.


Regelen inneholder en if-test, og en if-test kan ikke stå rett på et array: if T < 10 med et array inn spør om «hele tabellen», og Python nekter å svare. Da bygger vi tabellen med løkke, i et ferdig np.zeros-array.

import numpy as np

T = np.array([12.0, 8.0, 10.0, 3.0, 15.0])
forbruk = np.zeros(T.size)

for i in range(T.size):
    if T[i] < 10.0:
        forbruk[i] = 3.0 + 0.5*(10.0 - T[i])
    else:
        forbruk[i] = 3.0

print(forbruk)
print('Sum:', sum(forbruk))

Utskrift:

[3.  4.  3.  6.5 3. ]
Sum: 19.5

Legg merke til np.zeros(T.size): plassen lages ut fra hvor mange målinger som finnes, så lengdene kan ikke sprike. Løkka går over range(T.size) og fyller ett element per runde.

Forsøker du if T < 10.0: med hele arrayet, stopper programmet med ValueError: The truth value of an array with more than one element is ambiguous. Det er en av de mest lærerike feilmeldingene i NumPy: den sier at «er tabellen mindre enn 10» ikke er ett spørsmål, men fem.

📝Oppgave 4
Eksamensnivå, sjanger M

En biolog skal lage en tabell over veksthastigheten v(t)=0,8e0,2tv(t) = 0{,}8\,e^{-0{,}2t} for 26 punkter i tidsrommet [0,5][0, 5].

a) Skriv koden vektorisert, uten løkke.

b) Skriv den samme tabellen med løkke og lister.

c) Hvilken av de to versjonene ville du valgt hvis oppgaven i tillegg ba deg stoppe så snart vv ble mindre enn 0,40{,}4? Begrunn kort.

📝Oppgave 5
Eksamensnivå, sjanger M…

En oppgave ber om en tabell over f(x)=x23xf(x) = x^2 - 3x for 11 punkter i [0,3][0, 3], skrevet ut med to desimaler i to kolonner.

a) Skriv en vektorisert løsning som lager tabellen og skriver den ut linje for linje.

b) Hvor mange linjer skriver programmet ut, og hva står på den første og den siste?

c) En medstudents kode bruker np.linspace(0, 3, 10). Hvilke to ting blir da galt?

📝Oppgave 6
Eksamensnivå, sjanger O

Programmet skal lage en tabell over y=xy=\sqrt{x} med 21 punkter i [0,5][0, 5], vektorisert. Finn og rett feilene, og forklar hva hver av dem gjør.

from math import sqrt
import numpy as np

x = np.linspace(0, 5, 20)
y = sqrt(x)
print(len(y))

API- og konstruksjonsliste

Begrepsbank

Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.

Sjanger M

Eksamensoppgaven «lag denne tabellen først med løkke og lister, så uten løkke med NumPy». Den er i 6 av de 8 nyere settene (75 %) og er verdt rundt 5 poeng. Del a) er løkkemalen, del b) er den vektoriserte malen, og sensor sjekker først at de to gir like mange punkter.

Løkkemalen for en tabell
x = [], y = [], dx = (stop - start)/N, deretter for i in range(N+1) med xi = start + i*dx, x.append(xi) og y.append(f(xi)). Funksjonen kalles på ett punkt om gangen, så den kan komme fra math.
Den vektoriserte malen
x = np.linspace(start, stop, N+1) og y = f(x), der f bare bruker aritmetikk og NumPy-funksjoner. To linjer erstatter hele løkka, og resultatet er to arrays i stedet for to lister.
Antall punkter oppgitt mot antall intervaller oppgitt

Sier oppgaven «101 punkter», er N+1=101N+1 = 101 og N=100N = 100. Sier den «100 intervaller» eller «100 steg», er N=100N = 100 og du får 101 punkter. Steglengden er i begge tilfeller (ba)/100(b-a)/100. Å avklare dette før du skriver kode fjerner den vanligste feilen i sjangeren.

`.append` mot `y[i] = ...`
.append legger til på slutten av en liste som vokser underveis. y[i] = ... skriver inn i et array der plassen alt er laget med np.zeros. Et array har ingen .append, og en liste kan ikke tilordnes en indeks som ikke finnes ennå.
`if`-test på et array
if x < 3: med et array som x gir ValueError: The truth value of an array with more than one element is ambiguous, fordi spørsmålet har ett svar per element og ikke ett svar i alt. Løsningen i dette faget er å løkke over elementene og teste x[i] < 3.
Kontroll av en punkttabell

To utskrifter avslører nesten alle feil: len(x) (eller x.size) skal være antallet oppgaven ba om, og x[-1] skal være stop. Er begge riktige, er tabellen riktig. På hjelpemiddelfri eksamen gjør du samme kontroll i hodet med N=2N = 2.

Når en løkke er nødvendig

Tre tilfeller: når verdien i punkt i avhenger av forrige verdi (differenslikninger), når regelen har en if-test, og når du skal avbryte eller telle underveis. I alle tre kan ingen vektorisert linje erstatte løkka.

`len(x)` mot `.size`
len(x) gir antall elementer og virker på både liste og array. .size finnes bare på arrays. Begge er trygge kontroller på at en tabell har det antallet punkter oppgaven ba om, og i sjanger M skal de to versjonene gi samme tall.
`np.log` på en liste

NumPy-funksjonene godtar faktisk en vanlig liste som argument: np.log([1.0, 2.0]) gjør lista om til et array først og returnerer et array. Det er math-funksjonene som stopper med TypeError på en liste, og aritmetikk som liste ** 2 som ikke finnes for lister. Presisjonen er verdt å ha med seg, slik at du ikke leter etter en feil som ikke er der.

Utskrift av en tabell

Å skrive tabellen ut linje for linje krever alltid en løkke, også når tabellen selv er laget vektorisert — print skriver én linje per kall. Mønsteret er for i in range(x.size): print(f'{x[i]:6.2f} {y[i]:8.4f}'), med feltbredder slik at kolonnene står rett.

Repetisjonsoppgaver
Din fremgang
0 / 3 oppgaver

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.

Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.