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.
Sjanger M er oppgaven «lag denne tabellen to ganger: først med en løkke og vanlige lister, så uten løkke ved hjelp av NumPy». Den er i 6 av de 8 nyere settene (75 %) og er verdt rundt 5 poeng. Oppgaveteksten er nesten alltid delt i to: del a) ber om løkkeversjonen, del b) ber om den samme tabellen «uten bruk av løkke» eller «vektorisert».
Prioritet: kunne (nivå 2 av tre: må sitte, kunne, kjenne til). Poengene er lette å hente, fordi du kan øve inn begge malene på under en halvtime — men de er også lette å miste, av én grunn: de to versjonene må gi like mange punkter. Det er feil #1 i feilkatalogen, av-én-feilen, og den er hovedgrunnen til trekk på denne sjangeren.
Kapitlet er kort med vilje. Målet er at du kan skrive begge malene fra hukommelsen, i sensors korte stil, uten å tenke.
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 for 51 punkter mellom 0 og 4. Tabellen betyr her to kolonner: en kolonne med -verdier og en kolonne med de tilhørende -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 -kolonnen på én linje og hele -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.
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 -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.
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.0Lø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] 3Lag en tabell over med 51 punkter i intervallet — først med løkke og lister, så vektorisert. Kontroller at de to versjonene gir samme antall punkter og samme verdier.
51 punkter betyr 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.0Tabellene 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.
(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 »), ikke antall intervaller. Da er , altså , og steglengden er . Den regnestykket er verdt å gjøre eksplisitt på kladdepapiret før du skriver kode.
Alle tre er varianter av feil #1 i feilkatalogen (av-én i range og i antall punkter):
1. Ulikt antall punkter i a) og b). Løkka går over range(N+1) mens linspace fikk N som tredje argument, eller omvendt. Sensor sammenligner de to delene direkte, så dette ses umiddelbart.
2. Feil nevner i dx. dx = (stop - start)/(N+1) gir en for kort steglengde, og siste punkt havner innenfor stop. Riktig nevner er N.
3. range(N) i løkka. Da mangler siste punkt: du får N punkter der du skulle hatt N+1, og tabellen slutter ett steg for tidlig.
Kontrollen som fjerner alle tre på fem sekunder: skriv ut len(x) og x[-1]. Er antallet det oppgaven ba om, og er siste verdi lik stop, er tabellen riktig. På en hjelpemiddelfri eksamen der du ikke kan kjøre koden, gjør du samme kontroll i hodet med : punktene skal bli start, midtpunktet og stop.
En oppgave ber om en tabell over med 101 punkter i intervallet , 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å og fire på .
101 punkter betyr og .
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.3026De 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 —
En bølgeprofil er gitt ved , der måles i radianer. Lag en tabell over og med 21 punkter i intervallet .
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?
(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 må 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.
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, må du bruke løkke.
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.]Et strømmålepunkt registrerer én verdi per time. Under 10 grader regnes forbruket som kilowattimer, ellers som en fast verdi på . 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.5Legg 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.
En biolog skal lage en tabell over veksthastigheten for 26 punkter i tidsrommet .
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 ble mindre enn ? Begrunn kort.
En oppgave ber om en tabell over for 11 punkter i , 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?
Programmet skal lage en tabell over med 21 punkter i , 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))1. Ulikt antall punkter i de to versjonene (#1). Løkka skal ha range(N+1), og linspace skal ha N+1.
2. Feil nevner i steglengden (#1). dx = (stop - start)/N. Kontroll: start + N*dx skal bli stop.
3. Løkke der oppgaven ba om vektorisering. Står det «uten bruk av løkke», gir en løkke ikke poeng for den deloppgaven — les oppgaveteksten to ganger.
4. math-funksjon på et array. sqrt, exp og log fra math tar ett tall. Vektorisert kode skal bruke np.sqrt, np.exp, np.log.
5. if-test på et helt array. Gir ValueError fordi spørsmålet ikke har ett svar. Trenger regelen en if, må du bruke løkke.
Merk en presisering som ofte misforstås: np.log([1.0, 2.0]) med en liste som argument virker faktisk fint — NumPy gjør lista om til et array først, og du får et array tilbake. Det er math-funksjonene, og aritmetikk som liste ** 2, som ikke godtar en liste.
Begrepsbank
Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
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.
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.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.Sier oppgaven «101 punkter», er og . Sier den «100 intervaller» eller «100 steg», er og du får 101 punkter. Steglengden er i begge tilfeller . Å avklare dette før du skriver kode fjerner den vanligste feilen i sjangeren.
.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 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.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 .
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) 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.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.
Å 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.
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.