Lab4

Nivå E:

Nivå A-D:

For å bestå laben, må alle oppgavene på nivå E være bestått. Laben leveres i CodeGrade; du finner som vanlig knappen dit på mitt.uib.


Nivå E
Diverse

Denne oppgaven består av flere deloppgaver. Skriv alle funksjoner i én felles fil, miscellaneous.py.

Del A

Skriv en funksjon multiply_5_minus_pi(my_number) som:

  • Tar et argument my_number av typen int.
  • Multipliserer dette med 5 og trekker fra akkurat 3.14.
  • Returnerer resultatet (du skal ikke skrive ut resultatet til terminalen).

Test koden din ved å legge til disse linjene nederst i filen:

from math import isclose
print("Tester multiply_5_minus__pi... ", end="")
assert isclose(1.86, multiply_5_minus_pi(1))
assert isclose(56.86, multiply_5_minus_pi(12))
assert isclose(611.86, multiply_5_minus_pi(123))
print("OK")

Del B

Nå skal vi prøve et likt problem som Del A, men denne gangen med typen str. Skriv en funksjon shout(text) som:

  • Tar et argument text.
  • Legger til et utropstegn («!») til slutten av ordet eller frasen.
  • Returnerer resultatet. (Ikke print noe!).

Test koden din ved å legge til disse linjene nederst i filen:

print("Tester shout... ", end="")
assert "I love programming!" == shout("I love programming") 
assert "Adventure awaits!" == shout("Adventure awaits") 
assert "Ikke print noe!" == shout("Ikke print noe") 
print("OK")

Del C

Skriv en funksjon som heter name_age(name, gender, age). Funksjonen skal ta tre argumenter: Navnet til en person som streng, personens kjønn som streng, og personens alder som int. Funksjonen skal returnere en streng med setningen «name er gender og er age år gammel.» For eksempel skal name_age("Alex", "kvinne", 7) returnere strengen «Alex er kvinne og er 7 år gammel.»

Test koden din ved å legge til disse linjene nederst i filen:

print("Tester name_age... ", end="")
assert "Alex er kvinne og er 7 år gammel." == name_age("Alex", "kvinne", 7) 
assert "Sam er mann og er 20 år gammel." == name_age("Sam", "mann", 20) 
print("OK")

Del D

Skriv en funksjon som heter kinetic_energy(m, v). Funksjonen skal ta to argumenter \(m\) og \(v\) begge av typen int, og skal returnere \(\frac{1}{2}mv^2\).

Test koden din ved å legge til disse linjene nederst i filen:

from math import isclose
print("Tester kinetic_energy... ", end="")
assert isclose(4.0, kinetic_energy(2,2))
assert isclose(128.0, kinetic_energy(4,8))
assert isclose(2.5, kinetic_energy(5,1))
print("OK")

Del E

En funksjon count_letters(s, t) som tar en en streng s og en streng t som argumenter, og returnerer hvor mange av symbolene i s som også er tilstede i t.

Test koden din ved å legge til disse linjene nederst i filen:

print("Tester count_letters... ", end="")
assert  8 == count_letters("Ouagadougou", "aeiouAEIOU")
assert  0 == count_letters("hei", "x")
assert  1 == count_letters("hei", "h")
assert  2 == count_letters("heihei", "h")
assert  4 == count_letters("heihei", "ei")
assert  1 == count_letters("hei", "hhh")
print("OK")

Del F

Skriv en funksjon distance(x1, y1, x2, y2) som regner ut og returnerer avstanden mellom to punkter \((x_1, y_1)\) og \((x_2, y_2)\).

Illustrasjon av avstandsformelen d=sqrt((x1-x2)^2 + (y1-y2)^2)

For å teste koden, kan du legge til denne linjen nederst i filen:

from math import isclose
print("Tester distance... ", end="")
assert isclose(1.414213562373, distance(0, 0, 1, 1))
print("OK")

  • For å finne absoluttverdien av et tall kan du benytte abs() -funksjonen som er innebygget i Python.
  • Operatoren for eksponentiering er **, f. eks. vil 3**2 evaluere til 9.
  • Å ta kvadratroten av et tall er det samme som å opphøye tallet i 0.5. For eksempel, 9**0.5 vil evaluere til 3.0.

Del G

Et hyperrektangel er et rektangel hvor sidene er vannrette og loddrette (ikke rotert). Vi kan representere et hyperrektangel med to koordinater \((x_1, y_1)\) og \((x_2, y_2)\) som representerer to diagonalt motsatte hjørner (du kan ikke anta noe om hvilken rekkefølge disse punktene kommer i, eller hvilke to motsatte hjørner i rektangelet de beskriver). I denne oppgaven skal du avgjøre hvorvidt et tredje punkt \((x_p, y_p)\) befinner seg innenfor et slikt rektangel eller ikke.

Skriv en funksjon point_in_rectangle som har seks parametre x1, y1, x2, y2, xp, yp, hvor de fire første parametrene representerer et hyperrektangel, og de to siste representerer et vilkårlig punkt. La metoden returnere True dersom punktet er innenfor rektangelet, og False hvis ikke. Dersom punktet befinner seg akkurat på linjen, regner vi det som at den er innenfor.

Illustrasjon av punkt i rektangel

Test koden din:

print("Tester point_in_rectangle... ", end="")
assert point_in_rectangle(0, 0, 5, 5, 3, 3)  # Midt i
assert point_in_rectangle(0, 5, 5, 0, 5, 3) # På kanten
assert not point_in_rectangle(0, 0, 5, 5, 6, 3)  # Utenfor
print("OK")

  • Omregn rektangelet slik at du vet hva som er høyre og venstre, top og bunn. For eksempel, opprett variabler x_left = min(x1, x2) og x_right = max(x1, x2). Tilsvarende for topp og bunn med y-aksen.
  • Sjekk at punktet \((x_p, y_p)\) både befinner seg mellom venstre- og høyresiden, og også mellom topp og bunn.
  • For eksempel: punktet \((x_p, y_p)\) er mellom høyre- og venstre siden dersom både x_left er mindre eller lik xp og xp er mindre eller lik x_right.

Nivå E
Belgisk flagg

I denne oppgaven skal vi gjøre koden fra lab 2 (Belgisk flagg oppgaven) om til en funksjon.

Opprett filen belgian_flag.py der du oppretter en funksjon draw_belgian_flag som tar inn fem parametere: canvas, x1, y1, x2, y2. Funksjonen skal tegne et belgisk flagg på canvas med øvre venstre hjørne i punktet \((x_1, y_1)\) og nedre høyre hjørne i punktet \((x_2, y_2)\). Funksjonen trenger ikke å ha noen returverdi.

Merk: I filen belgian_flag.py skal du ikke importere noe, og du skal heller ikke kalle på display-funksjonen. For å teste koden, opprett i stedet en separat fil belgian_flag_test.py i samme mappe som belgian_flag.py og kopier inn koden under. Når du kjører belgian_flag_test.py, skal det tegnes tre belgiske flagg på skjermen som vist under:

from uib_inf100_graphics.simple import canvas, display
from belgian_flag import draw_belgian_flag

draw_belgian_flag(canvas, 125, 135, 275, 265)
draw_belgian_flag(canvas, 10, 10, 40, 36)
draw_belgian_flag(canvas, 10, 340, 390, 360)

display(canvas)
Eksempelkjøring 1
Nivå D
Høyblokk

I denne oppgaven skal vi gjøre koden fra lab 3 (skyskraper) om til en funksjon.

Opprett filen highrise.py der du oppretter en funksjon draw_highrise som tar inn syv parametere: canvas, x1, y1, x2, y2, floors og windows_per_floor. Funksjonen skal tegne en høyblokk på canvas med øvre venstre hjørne i punktet \((x_1, y_1)\) og nedre høyre hjørne i punktet \((x_2, y_2)\). Den skal ha floors etasjer, og hver etasje skal ha windows_per_floor vinduer. Avstanden skal være like stor mellom alle vinduene (f. eks. 4 piksler). Funksjonen trenger ikke å ha noen returverdi.

Merk: I filen highrise.py skal du ikke importere noe, og du skal heller ikke kalle på display-funksjonen. For å teste koden, opprett i stedet en separat fil highrise_test.py i samme mappe som highrise.py og kopier inn koden under. Når du kjører highrise_test.py, skal det tegnes tre høyblokker på skjermen som vist under:

from uib_inf100_graphics.simple import canvas, display
from highrise import draw_highrise

draw_highrise(canvas, 10, 10, 100, 390, 5, 3)
draw_highrise(canvas, 110, 200, 200, 350, 10, 2)
draw_highrise(canvas, 210, 90, 350, 390, 15, 1)

display(canvas)
En kjøring av høyblokk-oppgaven
Nivå C
Kollisjonsdeteksjon

Denne oppgaven består av to deler. Skriv funksjoner til begge deloppgaver (A-B) i én felles fil, collision_detection.py.

Del A

I filen collision_detection.py, skriv en funksjon rectangles_overlap som har åtte parametre x1, y1, x2, y2, x3, y3, x4, y4, hvor de fire første parametrene representerer ett hyperrektangel, og de fire siste representerer et annet. La metoden returnere True dersom rektanglene overlapper hverandre, og False hvis ikke. Vi sier at rektanglene overlapper selv om de kun deler ett enkelt punkt.

Illustrasjon av overlappende rektangler

Test koden din:

print("Tester rectangles_overlap... ", end="")
assert(rectangles_overlap(0, 0, 5, 5, 2, 2, 6, 6)) # Delvis overlapp
assert(rectangles_overlap(0, 5, 5, 0, 1, 1, 4, 4)) # Fullstendig overlapp
assert(rectangles_overlap(0, 1, 7, 2, 1, 0, 2, 7)) # Kryssende rektangler
assert(rectangles_overlap(0, 5, 5, 0, 5, 5, 7, 7)) # Deler et hjørne
assert(not rectangles_overlap(0, 0, 5, 5, 3, 6, 5, 8)) # Utenfor
print("OK")

  • Omregn begge rektangler slik at du vet hva som er høyre og venstre, top og bunn (se hint for oppgaven Hyperrektangel).
  • Dersom høyresiden til ett rektangel er til venstre for venstresiden av det andre, blir svaret false (tilsvarende logikk med topp og bunn). Husk å sjekke begge retninger.

Illustrasjon av testcasene i assert’ene over:

Illustrasjon av testcasene over

Del B

I filen collision_detection.py, skriv en funksjon circle_overlaps_rectangle som har syv parametre x1, y1, x2, y2, xc, yc, rc, hvor de fire første parametrene representerer to motstående hjørner i et hyperrektangel, og de tre siste representerer en sirkel sentrert i \((x_c, y_c)\) med radius \(r_c\). La metoden returnere True dersom sirkelen overlapper rektangelet, og False hvis ikke. Dersom sirkelen og rektangelet deler kun ett enkelt punkt regnes det fremdeles som at de er overlappende.

Illustrasjon av sirkel og rektangel som ikke overlapper

Test koden din:

print("Tester circle_overlaps_rectangle... ", end="")
assert(circle_overlaps_rectangle(0, 0, 5, 5, 2.5, 2.5, 2)) # sirkel i midten
assert(not circle_overlaps_rectangle(0, 5, 5, 0, 8, 3, 2)) # langt utenfor
assert(circle_overlaps_rectangle(0, 0, 5, 5, 2.5, 7, 2.01)) # langs kanten
assert(circle_overlaps_rectangle(0, 5, 5, 0, 5.1, 5.1, 1)) # på hjørnet
assert(circle_overlaps_rectangle(0, 0, 5, 5, 8, 8.99, 5)) # på hjørnet
assert(not circle_overlaps_rectangle(0, 0, 5, 5, 8, 9.01, 5)) # akkurat ikke
print("OK")

  • Dersom sirkelens sentrum er inne i rektangelet, er svaret True. Bruk funksjonen du skrev i oppgaven over om hyperrektangel for å sjekke dette.
    • Du kan importere funksjonen ved å legge til from miscellaneous import point_in_rectangle øverst i collision_detection.py.
  • La det minste x-koordinatet av x1 og x2 kalles x_left, og la det største kalles x_right. På samme måte, slutt å tenke på punktene y1 og y2, og regn i stedet ut punktene y_top og y_bottom.
  • «Utvid» rektangelet med sirkelens radius i alle retninger. Hvis sirkelens sentrum er utenfor dette utvidede rektangelet (bruk funksjonen point_in_rectangle igjen), er det garantert ikke noe overlapp.
  • I de gjenstående tilfellene befinner sirkelen sitt sentrum seg i rammen rundt rektangelet som er tegnet med stiplet linje i illustrasjonen over.
    • Dersom sirkelens x-koordinat befinner seg mellom x-koordinatene til det opprinnelige rektangelet, er det overlapp.
    • Tilsvarende for y-aksen.
    • Hvis sirkelens sentrum ikke tilfredsstiller noen av de to punktene over, befinner det seg i et av hjørnene. Dersom sirkelens sentrum har større avstand enn \(r_c\) til samtlige hjørner i det opprinnelige rektangelet, er det ingen overlapp (f. eks. slik som på figuren over). For å finne avstanden, bruk funksjonen distance som du kan importere fra en tidligere oppgave (from miscellaneous import distance).

Illustrasjon av testcasene oppgitt over (en sirkel per testcase):

Illustrasjon av testcasene

Flytdiagram for circle_overlaps_rectangle

if point_in_rectangle(...):  # sirkelens sentrum inne i rektangel?
    return True
elif not point_in_rectange(...): # sentrum utenfor utvidet rektangel?
    return False
elif ... # punkt er mellom venstre og høyresiden til rektangel (x-aksen)
    return True
elif ... # punkt er mellom topp og bunn til rektangel (y-aksen)
    return True
elif distance(...) # sirkelen overlapper hjørnet oppe til venstre
    return True
elif ... # sirkelen overlapper hjørnet oppe til høyre
    return True
...

Nivå B
Integraler

Denne oppgaven består av fire deler. Skriv alle funksjoner til de fire delene (A-D) i én felles fil, integrals.py.

Del A

I matematikken kan en funksjon for eksempel være definert som \(g(x) = \frac{1}{8}x^2 - 2x + 10\). La oss modellere denne matematiske funksjonen som en programmeringsfunksjon:

  • I filen integrals.py, opprett en funksjon g som har en parameter x og som regner ut verdien av \(\frac{1}{8}x^2 - 2x + 10\) for den gitte verdien av x.

For eksempel skal et kall til g(8.0) returnere verdien 2.0, og et kall til g(4.0) skal returnere verdien 4.0.

Test koden din:

def almost_equals(a, b):
    return abs(a - b) < 0.0000001

print("Tester g... ", end="")
assert almost_equals(2.0, g(8.0))
assert almost_equals(4.0, g(4.0))
assert almost_equals(10.0, g(0.0))
print("OK")

Del B

I denne oppgaven skal vi regne ut en tilnærming for arealet under funksjonen \(g\) (fra del A) mellom to gitte punkter \(x_\text{lo}\) og \(x_\text{hi}\) på x-aksen. Vi antar at \(x_\text{lo} \leq x_\text{hi}\), og i denne omgang antar vi også at \(x_\text{lo}\) og \(x_\text{hi}\) er heltall.

For å gjøre vår tilnærming, skal vi enkelt nok regne ut summen av g(x_i) for alle heltalls-verdier \(x_i\) fra og med \(x_\text{lo}\) opp til (og ikke inkludert) \(x_\text{hi}\). For eksempel, dersom \(x_\text{lo} = 3\) og \(x_\text{hi} = 7\), er vi interessert i å vite hva summen \(g(3) + g(4) + g(5) + g(6)\) blir.

  • I filen integrals.py, opprett en funksjon approx_area_under_g med parametre x_lo og x_hi som returnerer summen av g(x_i) for alle heltall x_i fra og med x_lo opp til x_hi.

Arealet under grafen (til venstre) versus en tilnærmet utregning (til høyre)

Test kode din:

print("Tester approx_area_under_g... ", end="")
assert almost_equals(4.0, approx_area_under_g(4, 5))   # g(4)
assert almost_equals(3.125, approx_area_under_g(5, 6)) # g(5)
assert almost_equals(7.125, approx_area_under_g(4, 6)) # g(4)+g(5)
assert almost_equals(23.75, approx_area_under_g(1, 5)) # g(1)+g(2)+g(3)+g(4)
print("OK")

  • Benytt en variabel som oppdaterer en løpende totalareal.
  • Benytt en for-løkke som starter i x_lo og som går opp til x_hi

Del C

Estimatet for arealet vi regnet ut i forrige deloppgave er bare et estimat, siden vi later som funksjonen går i «trapper» i stedet for å være kontinuerlig, slik den egentlig er. For å gjøre estimatet vårt bedre, kan vi redusere bredden på hvert trappetrinn. Jo smalere trappetrinn vi velger, jo bedre blir estimatet vårt for arealet.

På figuren under vises det ulike bredder på trappetrinnene. Jo flere trappetrinn, jo mer nøyaktig vil arealet av det grønne området (som vi regner ut) stemme med det faktiske arealet under grafen (den røde streken).

Illustrasjon av hvordan bredden på trinnene påvirker arealberegningen

Illustrasjon av 09glasgow09, CC BY-SA 3.0.

I denne oppgaven skal vi forbedre estimatet for arealet under grafen ved å la dem som kaller funksjonen vår bestemme hvor mange trappetrinn de ønsker å benytte. Dette kalles en (venstresidet) Riemann sum.1

  • I filen integrals.py, opprett en funksjon riemann_sum_g med parametre x_lo, x_hi og n. Funksjonen skal returnere en tilnærming av arealet under grafen g fra oppgave A basert på n antall trappetrinn.

  • På samme måte som i del B skal vi oppdatere en løpende total i en løkke.

  • På samme måte som i oppgaven «Oppdelt linjestykke» fra lab 3, skal vi dele opp linjestykket \([x_\text{lo}, x_\text{hi}]\) i \(n\) like store biter.

Merk: i denne oppgaven vet du allerede før løkken starter hvor mange iterasjoner løkken skal ha (nemlig n). Derfor bør du velge en for-løkke, og ikke en while-løkke. I denne oppgaven kan faktisk det å velge en while-løkke, i kombinasjon med avrundingsfeil som alltid vil skje når vi jobber med flyttall, gjøre at du får feil svar!

  • Legg merke til at hvert trappetrinn har bredde \(b = ({x_\text{hi} - x_\text{lo}})/{n}\).
  • Observer at trappetrinn nummer \(i\) begynner på x-verdien \(x_i = x_\text{lo} + b \cdot i\). Det første trappetrinnet har nummer 0. Ergo begynner det første trappetrinnet ved \(x_\text{lo}\), det andre trappetrinnet begynner ved \(x_\text{lo} + b\), det tredje deretter begynner ved \(x_\text{lo} + 2b\) og så videre.
  • For å regne ut arealbidraget fra trappetrinn \(i\), multipliser bredden \(b\) med verdien du får fra g(x_i).

Test koden din:

print("Tester riemann_sum_g... ", end="")
assert almost_equals(7.125, riemann_sum_g(4, 6, 2))
assert almost_equals(6.71875, riemann_sum_g(4, 6, 4))
assert almost_equals(6.3348335, riemann_sum_g(4, 6, 1000))

assert almost_equals(23.75, riemann_sum_g(1, 5, 4))
assert almost_equals(22.4375, riemann_sum_g(1, 5, 8))
assert almost_equals(21.166676666, riemann_sum_g(1, 5, 1_000_000))
print("OK")

Del D

Vi ønsker nå å regne ut arealet under flere grafer. Når alt kommer til alt er funksjonen \(g(x) = \frac{1}{8}x^2 - 2x + 10\) relativt obskur.

Opprett en funksjon riemann_sum med parametre f, x_lo, x_hi og n. Funksjonen skal returnere en tilnærming av arealet under grafen f mellom x_lo og x_hi basert på n antall trappetrinn. Koden vil være helt identisk med forrige deloppgave, bortsett fra at du kaller f(x_i) istedet for g(x_i) inne i løkken.

For å teste funksjonen, legg denne koden nederst i filen:

# Vi sjekker først at riemann_sum med funksjonen g som argument gir
# samme svar som riemann_sum_g -metoden fra forrige deloppgave.
print("Tester riemann_sum med funksjonen g... ", end="")
assert almost_equals(7.125, riemann_sum(g, 4, 6, 2))
assert almost_equals(6.71875, riemann_sum(g, 4, 6, 4))
assert almost_equals(6.3348335, riemann_sum(g, 4, 6, 1000))

assert almost_equals(23.75, riemann_sum(g, 1, 5, 4))
assert almost_equals(22.4375, riemann_sum(g, 1, 5, 8))
assert almost_equals(21.166676666, riemann_sum(g, 1, 5, 1_000_000))
print("OK")

# Så tester vi med et par andre funksjoner
# Funksjonen som kvadrerer, square(x) = x**2
def square(x):
    return x**2

## Arealet under grafen square(x) = x**2 mellom 1 og 3
## Eksakt svar  er 8 + 2/3, altså 8.66666666....
## Merk at vi kommer gradvis nærmere eksakt svar ved å øke n
print("Tester riemann_sum med funksjonen square... ", end="")
assert almost_equals(5.0, riemann_sum(square, 1, 3, 2))
assert almost_equals(7.88, riemann_sum(square, 1, 3, 10))
assert almost_equals(8.5868, riemann_sum(square, 1, 3, 100))
print("OK")

# Funksjonen som er en jevnt stigende, linear(x) = x
def linear(x):
    return x

## Arealet under grafen for funksjonen f(x) = x mellom 2 og 4
## Eksakt svar er 6.
## Merk at vi kommer gradvis nærmere riktig svar ved å øke n
print("Tester riemann_sum med funksjonen linear... ", end="")
assert almost_equals(5.0, riemann_sum(linear, 2, 4, 2))
assert almost_equals(5.5, riemann_sum(linear, 2, 4, 4))
assert almost_equals(5.998046875, riemann_sum(linear, 2, 4, 1024))
print("OK")

  1. Spesielt interesserte kan lese mer om ulike varianter av Riemann sum på Wikipedia. ↩︎