Funksjoner

from pylab import *

I programmering betyr funksjon mye mer enn det gjør i matematikken. I hovedsak så er en funksjon en samling instruksjoner som hører sammen, og som til sammen utfører en oppgave som vi kan sette et navn på. Gjerne noe vi ønsker å kunne utføre flere ganger uten å måtte skrive opp de samme instruksjonene flere ganger.

En funksjon i sin enkleste for kan være for eksempel slik:

def f(a):
    return a**2

Her har vi valgt en funksjon som likner på dem vi kjenner fra matematikken.

Vi kan kalle på denne funksjonen for å få verdier fra den

verdi = f(2)
print(verdi)
4

Og vi kan plotte verdiene, slik som vi nettopp lærte.

x = linspace(0, 2, 10)
y = f(x)

plot(x, y)
xlabel("x")
ylabel("y")
show()

png

De grunnleggende elementene i en funksjon i Python er

def minFunksjon(argument):
    <gjør noe>
    return <returvariable>

Vi skriver ordet def for å si til python at det kommer en funksjon. Så skriver vi opp det vi vil at funksjonen skal hete, og i parentes bak navnet skriver vi hvilke argumenter funksjonen skal kunne ta imot. Så kommer :, som forteller at vi nå skal gå over til innholdet i funksjonen. Innholdet, altså det som skal utføres i funksjonen, ligger på de neste linjene med innrykk. Innholdet i funksjonen fortsetter helt til vi kommet til en linje som ikke er rykket inn i forhold til def-ordet. Funksjonen sier seg ferdig enten når den møter return, altså der funksjonen returnerer, eller hvis den når bunnen av det innrykkede området.

Merk at vi gjør en distinksjon mellom å definere funksjonen og det å kalle på funksjonen. Det er når vi kaller på funksjonen at koden i funksjonen faktisk kjører.

Vi kaller på funksjonen med funksjonsnavn etterfulgt av parenteser der vi putter inn argumentet.

minFunksjon(arg)

eller

x = minFunksjon(arg)

Vi skal først passe på at vi kan syntaks for å definere og å kalle på funksjoner. Derfor går vi rett på første underveisoppgave.

Underveisoppgave

a) Programmer en funksjon som konverterer Celsiusgrader til Fahrenheit. Du har sikkert lyst på å vite at $T_F = \frac{9}{5} T_C + 32$
b) Kall på funksjonen for å regne ut funksjonsverdier som du kan plotte.

Husk at

def minFunksjon(argument):
    <gjør noe>
    return <returvariable>
x = minFunksjon(arg)

f. eks.

def f(a): 
    return a**2

y = f(4)

Oppgaven er ment å trene

  • Syntaks
  • Kalle på funksjoner

I programmering er det vanlig at vi lager oss funksjoner for å sette et navn på noe slik at vi kan huske det. For et menneske kan det være fint å ha en funksjon som heter noe som sier hva funksjonen gjør eller kan brukes til. For eksempel kan vi ta for oss vinkelen mellom to vektorer. Den kan vi godt kalle for vektorVinkel, slik at et menneske kan forstå hva den gjør. Her skal vi også se at funksjoner godt kan ta mer enn ett argument.

Formelen for vinkelen mellom to vektorer
$\cos \angle(a, b) = \frac{\overrightarrow a\cdot \overrightarrow b}{ |\overrightarrow a| |\overrightarrow b]}$

def vektorVinkel(a, b): 
    # Regner ut vinkelen mellom to vektorer. 
    len_a = sqrt(dot(a,a))
    len_b = sqrt(dot(b,b))
    cosvinkel = dot(a, b)/(len_a*len_b)
    vinkel = arccos(cosvinkel) # Det vi er vant til at heter cos^-1 heter arccos i python
    return vinkel
    
vektor_a = array([1,2]) # konvertering direkte til array
vektor_b = array([2,3])

vinkel_ab = vektorVinkel(vektor_a, vektor_b)
# i python sånn som vi har satt opp med pylab import *, så regner vi alltid i radianer. 
print("Vinkelen mellom ", vektor_a, "og", vektor_b, "er ", round(vinkel_ab,3), "rad\n", "altså", round(degrees(vinkel_ab), 2), "grader")
Vinkelen mellom  [1 2] og [2 3] er  0.124 rad
 altså 7.13 grader

Underveisoppgave

a) Skriv inn funksjonen vektorVinkel på egen maskin. Kall på den med to vektorer.
b) Gjør om a til en 3D-vektor og prøv igjen. Gjør så også b til en 3D-vektor og prøv igjen.
c) Hvilken funksjon er det som avgjør om det går bra eller ikke å sette inn 3D-vektorer her?

def vektorVinkel(a, b): 
    len_a = sqrt(dot(a,a))
    len_b = sqrt(dot(b,b))
    cosvinkel = dot(a, b)/(len_a*len_b)
    vinkel = arccos(cosvinkel)
    return vinkel

Oppgaven er ment å trene

  • Funksjoner kan ha flere linjer kode før return
  • Skrive inn flere linjer kode uten feil, eller feilsøke. Her er det en del parenteser, så feilsannsynligheten er stor, men feilene skal ikke være så vanskelige å løse
  • Skrive og kalle på funksjoner med flere argumenter
  • Bruk av funksjon inni funksjon. Vi gjør det eksplisitt gjennom poenget med dot-funksjonen

Forklaringer

  • Noen funksjoner tåler å ta imot arrays. Det gjelder de fleste matematiske funksjonene. Men dot-produktet krever at arraysene har samme lengde, ellers gir ikke skalarproduktet mening, og vi får en feilmelding.

Funksjoner som ikke er matematiske (i skolematematisk forstand)

Så funksjoner i programmering er beslektet med funksjoner i matematikken. Eller kanskje mer presist: Vi kan få funksjoner i programmering til å gjøre de tingene som funksjoner i matematikken gjør. Men vi kan også få dem til å gjøre så mye mer!

Vi kan for eksempel jobbe litt med en streng

def fulltNavn(fornavn, etternavn):
    return fornavn + " " + etternavn

fulltNavn("Ola", "Normann")
'Ola Normann'

Python tolker ikke det å legge sammen to strenger som noe matematisk. Den velger rett og slett bare å sette sammen strengene, som om $2 + 3 = 23$.

Eller vi kan se på noe som ble annerledes enn vi kanskje hadde tenkt oss:

def g(a, b, c, x):
    y = a*x**2 + b*x + c
    
print(g(2, 4, 1, 7))
None

Her ser jo alt veldig matematisk ut, og vi regner ut et annengradsuttrykk. Men når det kommer til stykket så returnerer vi ingenting, og da er det jo ikke en matematisk funksjon.

Vi kan definere g på nytt med en print inni

def g(a, b, c, x):
    y = a*x**2 + b*x + c
    print("g(", a, ",", b, ",", c, ",",x,") = ", y)
    
g(2, 4, 1, 7)
g( 2 , 4 , 1 , 7 ) =  127

Vi returnerer fremdeles ingenting, men nå får vi i det minste se hva som har blitt regnet ut inni funksjonen.

Underveisoppgave

c) Modifiser funksjonen med konvertering fra Celsius til Fahrenheit slik at i stedet for å returnere et tall, så returnerer den en streng som sier noe slikt som “XX grader Celsius tilsvarer XX grader Fahrenheit”

Oppgaven er ment å trene

  • Programmeringsfunksjoner trenger ikke være matematiske
  • Input og output trenger ikke å ha samme type

Vi kan returnere alt fra ingen til mange variable. Der det er flere enn én returvariabel skiller vi dem med komma. Vi kan også lage funksjoner som ikke returnerer, men som bare gjør noe.

def figurMinimum(x, y, xaksetekst, yaksetekst, tittel):
    plot(x, y) 
    xlabel(xaksetekst)
    ylabel(yaksetekst)
    title(tittel)
    
x = linspace(0, 2*pi)
y = sin(x)

figurMinimum(x, y, "Tid", "Posisjon", "Svingebevegelse")

png

Om vi skal være pirkeformelle, så kan vi legge til at funksjoner som ikke returnerer noe rent faktisk returnerer None, som er et objekt av typen NoneType.

def f(x): 
    y = x**2
    
print(f(3))
print(type(f(3)))
None
<class 'NoneType'>

Funksjon kaller på funksjon

Det er ikke noe i veien for å kalle på funksjoner inni funksjoner. Pass da bare på at funksjonene er definert i riktig rekkefølge, slik at funksjonen som kaller på en annen funksjon er definert etter funksjonen som den kaller på:

def kontrollerNavn(navn):
    # Følger navnet reglene vi har satt for navn?
    if len(navn) < 2:
        return False
    elif navn[0].islower():
        return False
    else:
        return True

def fulltNavn(fornavn, etternavn):
    if not kontrollerNavn(fornavn):
        print("Fornavnet", fornavn, "er ikke gyldig")
        return None
    if not kontrollerNavn(etternavn):
        print("Etternavnet", etternavn, "er ikke gyldig")
        return None
    else:
        return fornavn + " " + etternavn
        

print(fulltNavn("Eirik", "Jensen"))

    
Eirik Jensen

Når sant skal sies, så har vi faktisk drevet med å kalle på funksjoner inni funksjoner gjennom hele økta. For både dot, arccosog sqrter funksjoner. Disse er riktignok innebygde funksjoner, men det betyr bare at det er noen andre som har laget dem, med def, returnog hele pakka. De oppfører seg akkurat som dem vi lager selv.

Underveisoppgave

Lag en funksjon som beregner arealet som utspennes mellom to vektorer.
Det kan hende du har lyst på formelen $\frac{1}{2} ab \sin{(\angle \overrightarrow a, \overrightarrow b)}$. Glem heller ikke at vi beregnet vinkelen mellom vektorene tidligere i økta med funksjonen vektorVinkel.

Oppgaven er ment å trene

  • Funksjon i funksjon
  • Repetisjon av syntaks

Oppgave – Rydd opp i koden

I koden under skal vi behandle et par datasett. Vi har gjort det på en kronglete måte. Rydd opp i koden slik at den blir mer oversiktlig.

tid1 = np.asarray([1, 2, 3, 4, 5, 6, 7, 8])
posisjon1 = array([0.4, 0.5, 1.6, 2.6, 2.8, 4.4, 5.7, 7.0])

tid2 = np.asarray([1, 2, 3, 4, 5, 6, 7, 8])
posisjon2 = array([0.9, 0.6, 1.2, 2.4, 3.5, 4.7, 6.6, 8.6])

tid3 = np.asarray([1, 2, 3, 4, 5, 6, 7, 8])
posisjon3 = array([0.5, 0.6, 1.3, 2.2, 3.8, 5.1, 6.9, 8.4])

plot(log(tid1), log(posisjon1))
xlabel("log(Tid)")
ylabel("log(Posisjon)")
title("Forsøk 1")

figure()
plot(log(tid2), log(posisjon2))
xlabel("Tid")
ylabel("Posisjon")
title("Forsøk 2")

figure()
plot(log(tid3), log(posisjon3))
xlabel("Tid")
ylabel("Posisjon")
title("Forsøk 3")
Text(0.5,1,'Forsøk 3')

png

png

png

tid1 = np.asarray([1, 2, 3, 4, 5, 6, 7, 8])
posisjon1 = array([0.4, 0.5, 1.6, 2.6, 2.8, 4.4, 5.7, 7.0])

tid2 = np.asarray([1, 2, 3, 4, 5, 6, 7, 8])
posisjon2 = array([0.9, 0.6, 1.2, 2.4, 3.5, 4.7, 6.6, 8.6])

tid3 = np.asarray([1, 2, 3, 4, 5, 6, 7, 8])
posisjon3 = array([0.5, 0.6, 1.3, 2.2, 3.8, 5.1, 6.9, 8.4])

def analyse(x, y, datasett):
    figure()
    plot(log(x), log(y))
    xlabel("log(tid)")
    ylabel("log(posisjon)")
    title("Datasett "+datasett)
    
analyse(tid1, posisjon1, "1")
analyse(tid2, posisjon2, "2")
analyse(tid3, posisjon3, "3")

png

png

png

Funksjoner med løkker i

def isPalindromtall(heltall):
    tallListe = list(str(heltall))
    isPalindromtall = True
    for i in range(len(tallListe)):
        if not tallListe[i] == tallListe[-i-1]:
            return False
    return True

isPalindromtall(1881)
True

Her har vi laget en funksjon som sjekker om et tall er et palindromtall. Noen vil kanskje si at dette er fortsatt bare en matematisk funksjon. Så la oss prøve å putte inn et vanlig palindrom til funksjonen, for å se hva som skjer.

isPalindromtall("rekker")
True

Det viser seg faktisk at denne funksjonen fungerer på tekst også. Da er det kanskje på sin plass å heller kalle funksjonen for isPalindrom. Om noen fortsatt vil hevde at det bare er en matematisk funksjon, så greit nok. I bunn og grunn vil alt vi gjør på datamaskinen kode til noen matematiske operasjoner i 2-tallssystemet. Men i vanlig forstand, så har vi nå laget en funksjon som ikke er en matematisk funksjon.

Vi kan også bruke funksjoner til andre ting. For eksempel om vi skal lage flere plott som inneholder samme fiksfakseri, men der man kun skal bytte ut dataene som inngår.

def plottXKCDPolynomer(eksponentliste):
    with xkcd():
        x = linspace(0, 1, 100)
        for i in eksponentliste:
            plot(x, x**i, label="$x^%d$" % i)
        legend()
        
plottXKCDPolynomer([2, 3, 5, 7, 9, 10, 12])

png