Lab4

Oppgaver

Oppvarming

Obligatorisk

Valgfri

Hvordan fullføre laben

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


Oppvarming

Fjerne 3-ere
Oppvarming
Nivå E

I en fil threes_removed.py, Skriv en (ikke-destruktiv) funksjon threes_removed som tar inn en liste med tall a som parameter. Funksjonen skal returnere en ny liste med tall slik at alle forekomster av tallet 3 blir borte. Rekkefølgen skal være lik som i den originale listen a.

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

print('Tester threes_removed... ', end='')
# Test 1
a = [1, 2, 3, 4]
actual = threes_removed(a)
expected = [1, 2, 4]
assert expected == actual   # Sjekker at returverdien er som ønsket
assert [1, 2, 3, 4] == a    # Sjekker at input (a) ikke har blitt mutert

# Test 2
a = [1, 2, 3, 3]
actual = threes_removed(a)
expected = [1, 2]
assert expected == actual
assert [1, 2, 3, 3] == a

# Test 3
a = [3, 3, 1, 3, 2, 4, 3, 3, 3]
actual = threes_removed(a)
expected = [1, 2, 4]
assert expected == actual
assert [3, 3, 1, 3, 2, 4, 3, 3, 3] == a

# Test 4
a = [3, 3]
actual = threes_removed(a)
expected = []
assert expected == actual
assert [3, 3] == a
print('OK')

Du kan ta utgangspunkt i denne koden:

def threes_removed(a): 
    ... # Din kode her
    # Opprett en tom liste r.
    # For hvert element x i a:
    #   Hvis x ikke er lik 3, legg x til i listen r.
    # Returner listen r.

Trollmannens lærling
Oppvarming
Nivå E

Du er lærling av den mystiske trollmannen Gallius, som eier en rekke magiske gjenstander. Han har gitt deg som en øvelse å duplisere alle disse gjenstandene med magien din. Dessverre behøver du også å registrere denne endringen i listen som Gallius har over gjenstandene sine og hvor mange han har av hver.

I en fil duplicated.py, lag en ikke-destruktiv funksjon duplicated som tar inn en liste items og returnerer en ny liste hvor hvert element er dobbelt så stort som elementet i den originale listen.

For eksempel, hvis items er [1, 2, 3, 4, 5], skal funksjonen returnere [2, 4, 6, 8, 10].

Du kan kopiere følgende kode inn nederst i duplicated.py for å teste funksjonen din:

print("Testing duplicate... ", end="")
# Første test
a = [1, 2, 3, 4, 5]
actual = duplicated(a)
expected = [2, 4, 6, 8, 10]
assert expected == actual    # Sjekker at returverdien er som ønsket
assert [1, 2, 3, 4, 5] == a  # Sjekker at input (a) ikke har blitt mutert

# Flere tester
assert [4, 8, 12, 16, 20] == duplicated([2, 4, 6, 8, 10])
assert [0, 0, 0, 0, 0] == duplicated([0, 0, 0, 0, 0])
assert [-2, 4, 2, 10, 100] == duplicated([-1, 2, 1, 5, 50])
print("OK")

  • Opprett en ny liste (som du til slutt skal returnere, men som i utgangspunktet er tom).
  • Bruk en for-løkke for å gå gjennom verdiene i listen vi blir gitt som input.
  • Inne i for-løkken, bruk .append for å legge til det dobbelte av verdien vi ser på til den nye listen.
  • Returner svaret

Botanist
Oppvarming
Nivå E

Du er en botaniker som har vært på en ekspedisjon til en øy som er kjent for sine mange forskjellige planter. Du har samlet inn en rekke planter og har lagret dem i en liste plants. Du har også en liste dangerous_plants som inneholder navnene på alle de farlige plantene på øya. Du trenger å finne ut hvilke av plantene du har samlet inn som er farlige, slik at du kan fjærne dem fra samlingen din.

I en fil botanist.py, lag en funksjon filter_dangerous som tar inn en liste av planter plants og en liste av farlige planter dangerous_plants. Funksjonen skal returnere en liste med plantene i plants som ikke er farlige.

For eksempel, hvis plants er ["rose", "daisy", "lily", "tulip", "dandelion"] og dangerous_plants er ["dandelion", "poison ivy"], skal funksjonen returnere ["rose", "daisy", "lily", "tulip"].

Du kan starte fra denne koden:

def filter_dangerous(plants, dangerous_plants):
    # Din kode her

For å løse denne oppgaven, kan du bruke en for-løkke for å iterere over alle plantene i plants. Først lager du en tom liste, kall den gjerne safe_plants. For hver plante i plants, sjekk om den er i dangerous_plants. Hvis den ikke er det, legg den til i safe_plants. Til slutt, returner safe_plants.

Her er kode du kan lime inn på slutten av filen som tester funksjonen:

print("Testing filter_dangerous... ", end="")
assert ["rose", "daisy", "lily", "tulip"] == filter_dangerous(["rose", "daisy", "lily", "tulip", "dandelion"], ["dandelion", "poison ivy"])
assert ["minalia", "cherluvia", "kirina", "ajisai"] == filter_dangerous(["minalia", "cherluvia", "kirina", "ajisai", "evil_plant", "raa"], ["evil_plant", "raa"])
assert ["pythonium"] == filter_dangerous(["pythonium", "haskelia", "shakersperia"], ["haskelia", "jaskria", "shakersperia"])
print("OK")
Pythonesia
Oppvarming
Nivå D

Om du ikke allerede kan indeksering av 2d-lister veldig godt, anbefaler vi at du benytter fremgangsmåten og pseudokoden under for å se hvordan du kan iterere over en 2d-liste.

Du er med i en ekspedisjon til Pythonesia, et land som er kjent for sine mange rutenett av tall. Noe du har lest i reisehåndboken er at det er en gammel legende om at det finnes skatter gjemt under et av rutenettene i Pythonesia. Det er nemlig gjemt en skatt under alle ruter hvor radnummeret, kolonnenummeret og verdien i ruten (rad, kolonne) summerer til et spesielt tall. Du tenker du kan bruke dine programmeringsferdigheter til å finne posisjonene til disse skattene.

I en fil find_treasure.py, lag en funksjon find_treasure som tar inn en 2D-liste grid og et tall target_sum. Funksjonen skal returnere en liste av koordinater [row,col] i rutenettet hvor summen av rad, kolonne og verdi er lik target_sum. Hvis det ikke finnes noen slike skatter, skal funksjonen returnere en tom liste.

For eksempel, hvis du har følgende rutenett:

[ 
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

og target_sum er 5, så skal funksjonen returnere [[0, 2], [1, 0]] siden på rad 0, kolonne 2, så er summen 0 + 2 + 3 = 5, og på rad 1, kolonne 0, så er summen 1 + 0 + 4 = 5. Se bilder under for visualisering:

Eksempel 1
Eksempel 2

For å holde styr på indeksene til posisjonene mens vi itererer over de skal vi bruke en nøstet for ... in range(...) type løkke. Dette gjør at vi kan holde styr på indeksene mens vi itererer.

I funksjonen find_treasure gjør følgende:

  1. Opprett en tom liste treasures som skal inneholde koordinatene til skattene.
  2. Definer variabler rows og cols som er henholdsvis antall rader og kolonner i rutenettet. Dette kan du finne ved å bruke len funksjonen med grid og grid[0] som argument hver for seg. Mengden sublister i rutenettet (len(grid)) er antall rader, og lengden av en subliste (len(grid[0])) er antall kolonner. Vi velger subliste 0 fordi vi antar at alle sublister har samme lengde.
  3. Lag en ytre løkke for row in range(rows) og en indre løkke for col in range(cols). Dette vil la deg iterere over alle mulige kombinasjoner av rad og kolonne. Løkken itererer over hver rad-indeks, og for hver rad itererer den over hver kolonne-indeks.
  4. Inne i den indre løkken: hent ut verdien på posisjonen (row, col) value = grid[row][col], og sjekk om summen av rad, kolonne og verdi er lik target_sum. Hvis det er tilfellet, legg koordinatene [row, col] til treasures.
  5. Etter at du har gått gjennom alle elementene i rutenettet, returner treasures som nå inneholder koordinatene til skattene.


def find_treasure(grid, target_sum):
    ... # Din kode her

    # opprett en tom liste `treasures` som skal inneholde koordinatene til skattene.
    # definer en variabel `rows` som er antall rader i rutenettet
    # definer en variabel `cols` som er antall kolonner i rutenettet

    # lag en ytre løkke `for row in range(rows)`
        # lag en indre løkke `for col in range(cols)`
            # hent ut verdi til denne posisjonen
            # sjekk om summen av radnummer, kolonnenummer og verdi er lik `target_sum`
                # hvis det er tilfellet, legg koordinatene ``[row, col]`` til `treasures`
    
    # returner `treasures`

Her er kode du kan sette inn på slutten av find_treasure.py for å teste funksjonen din:

print("Testing find_treasure... ", end="")
# Test 1
grid = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]
target_value = 5
actual = find_treasure(grid, target_value)
expected = [[0, 2], [1, 0]]
assert expected == actual

# Test 2
actual = find_treasure([[1, 2], [3, 4]], 3)
expected = [[0, 1]]
assert expected == actual

# Test 3
actual = find_treasure([[1, 2], [3, 4]], 1)
expected = [[0, 0]]
assert expected == actual

# Test 4
actual = find_treasure([[1, 2], [3, 4]], 1000)
expected = []
assert expected == actual

print("OK")

Obligatorisk

Gressklipper
Obligatorisk
Nivå E

Du jobber som gressklipper og har fått i oppgave å klippe gresset i en hage. Eieren av hagen er veldig interessert i å vite den nøyaktige høyden på gresset etter at det er klippet. Du har fått en liste heights som eieren har laget over høyden på gresset på forskjellige steder i hagen. Du har også fått en høyde max_height som beskriver hvor høyt gresset maksimalt skal være. Det betyr at hvis gresset er høyere enn denne høyden, skal det klippes ned til denne høyden.

I en fil clip_grass.py, lag en funksjon clip_grass som tar inn en liste av høyder heights og en høyde max_height. Funksjonen skal mutere listen heights slik at alle høyder som er større enn max_height blir klippet ned til max_height. Fordi funksjonen er destruktiv, trenger den ikke returnere noe.

I denne oppgaven har vi laget en urealistisk historie for å skape konteksten til oppgaven; men denne oppgaven er egentlig en nokså vanlig databehandlingsoppgave. Å «klippe» et signal betyr at vi endrer verdiene i en liste slik at de aldri overstiger en gitt terskel.

Dette gjøres for eksempel for å beskytte elektronikk fra å bli skadet av for høye signaler (dersom verdiene skulle bli benyttet for å styre et elektrisk spenningsnivå), eller motsatt; for å fjerne støy fra et målt signal.

Som et eksempel, hvis du har følgende liste med høyder:

[1, 2, 3, 4, 5]

Og maksimal høyde er 3, så vil listen etter klipping være:

[1, 2, 3, 3, 3]

Her er kode du kan lime inn på slutten av filen for å teste funksjonen din:

print("Testing clip_grass... ", end="")
# Case 1
heights = [1, 2, 3, 4, 5]
clip_grass(heights, 3)
assert [1, 2, 3, 3, 3] == heights

# Case 2
heights = [1, 2, 3, 3, 3]
clip_grass(heights, 3)
assert [1, 2, 3, 3, 3] == heights

# Case 3
heights = [10, 20, 200, 20, 400]
clip_grass(heights, 25)
assert [10, 20, 25, 20, 25] == heights
print("OK")

  • For å mutere én enkelt posisjon i en liste heights, bruk indeksering. For eksempel,
    • for å angi 5 til indeks 0 kan du skrive heights[0] = 5,
    • for å angi x til indeks i kan du skrive heights[i] = x.
  • Benytt en løkke som itererer over indeksene til listen heights. Dette gjøres med en for-løkke som itererer over range(len(heights)). Iteranden vil da få angitt en ny indeks for hver iterasjon av løkken.

Trylledrikker
Obligatorisk
Nivå D

Trollmannen Gallius har en liste over trylledrikker som han har brygget. Gallius har en liste over nivåer som beskriver hvor kraftige disse trylledrikkene er. Men Gallius har brygget så mange trylledrikker at han har mistet helt oversikten over hvor kraftige de forskjellige trylledrikkene er. Han trenger din hjelp til å normalisere nivåene slik at de ligger mellom 0 og 1.

Normalisering er en prosess som brukes for å endre verdier i en liste slik at de ligger innenfor et bestemt intervall, men samtidig beholder forholdstallet for forskjellene mellom verdiene. Det er for eksempel nokså vanlig er å normalisere verdier slik at de faller mellom 0 og 1, eller mellom -1 og 1 avhengig av data’ens natur.

Dette brukes for eksempel for lydspor hvor mikrofonen kan ha hatt ulik avstand til personen som prater i ulike opptak. Normaliseringen endrer volumet på lyden slik at de forskjellige lydsporene får omtrent samme volum likevel.

Det finnes ulike måter å normalisere data på, og den metoden vi beskriver i denne oppgaven er bare én metode.

For å normalisere nivåene til Gallius, må du finne den minste og største verdien i listen over nivåer (fremover referert til som henholdsvis low og high). Deretter må du gå gjennom listen og regne ut en ny verdi for hvert nivå. Den nye verdien skal være lik:

$$\frac{{level - low}}{{high - low}} $$

Det er disse nye verdiene som skal være resultatet av normaliseringen.

Som et eksempel, hvis Gallius har følgende liste over nivåer:

[1, 2, 3, 4, 5]

Da er minste verdi 1 og største verdi 5. For eksempel vil nivået 3 normaliseres til:

$$ \frac{{3 - 1}}{{5 - 1}} = \frac{2}{4} = 0.5 $$

Dette gir mening siden 3 er halvveis mellom 1 og 5 (som er minste og største verdi).

Som oppsummering:

I en fil normalize_potions.py, lag en ikke-destruktiv funksjon normalize_potions som tar inn en liste av nivåer levels og returnerer en ny liste med normaliserte nivåer. Hvis alle nivåene er like, skal funksjonen returnere en liste med samme lengde som levels hvor alle verdiene er 1. Hvis du ikke håndterer dette tilfellet, vil du få en ZeroDivisionError når du prøver å dele med high - low i formelen for normaliseringen. Du kan anta at levels alltid vil inneholde minst ett element.

Du kan kopiere følgende kode inn i normalize_potions.py for å teste funksjonen din:

print("Tester normalize_potions... ", end="")
# Første test
levels = [4, 3, 2, 1, 0]
expected = [1.0, 0.75, 0.5, 0.25, 0.0]
actual = normalize_potions(levels)
assert [4, 3, 2, 1, 0] == levels, "Funksjonen skal ikke mutere listen. Hvis du har brukt .sort() er listen mutert. Du kan bruke min og max for å finne minste og største verdi i listen uten mutasjon."
assert expected == actual, f"Forventet {expected} men fikk {actual}"

# Flere tester
assert [0.0, 0.25, 0.5, 0.75, 1.0] == normalize_potions([0.01,0.02,0.03,0.04,0.05])
assert [0.0, 0.25, 0.5, 0.75, 1.0] == normalize_potions([2, 3, 4, 5, 6])
assert [1.0, 1.0, 1.0, 1.0, 1.0] == normalize_potions([3, 3, 3, 3, 3])
assert [0.0, 0.3, 0.2, 0.9, 1.0] == normalize_potions([-1, 2, 1, 8, 9])
assert [1.0, 0.75, 0.5, 0.0] == normalize_potions([5, 4, 3, 1])
print("OK")

  • Husk! min og max er innebygde funksjoner i Python som kan brukes til å finne henholdsvis minste og største verdi i en liste.

  • Opprett en liste normalized som skal inneholde de normaliserte verdiene. I begynnelsen kan den være en tom liste.

  • Benytt en løkke som itererer over levels og regner ut den normaliserte verdien for hvert element. Legg den normaliserte verdien til normalized-listen.

  • Husk å returnere!

Integraler
Obligatorisk
Nivå D

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="")
# Første test
x = 8.0
actual = g(x)
expected = 2.0
assert almost_equals(expected, actual)

# Flere tester
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="")
# Første test
x_lo = 4
x_hi = 5
actual = approx_area_under_g(x_lo, x_hi) 
expected = 4.0  # skal gi oss kun g(4), som altså er 4.0
assert almost_equals(expected, actual)

# Flere tester
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")

  • Opprett en variabel running_total som i utgangspunktet er 0. Bruk denne variabelen som en løpende totalsum, for å holde styr på det totale arealet under grafen «så langt».

  • Benytt en for-løkke som starter i x_lo og som går opp til x_hi. Iteranden i løkken blir da x_i. For hver iterasjon av løkken, regn ut \(g(x_i)\) og legg dette til i den løpende totalsummen.

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}\).

  • Tenk på hvert trappetrinn som om det har et nummer \(i\). Som gode programmere bruker vi 0-indeksering – det vil si at vi tenker på det første trappetrinnet som nummer «0», det andre trappetrinnet er nummer «1», og så videre.

  • 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.

  • Bruk en løkke der iteranden er \(i\), altså nummeret til hvert trappetrinn. Da må løkken gå over heltallsverdier fra og med 0 opp til \(n\). Om du vet \(i\) kan du regne ut \(x_i\) som vist over.

  • For å regne ut arealbidraget fra trappetrinn \(i\), multipliser bredden \(b\) med høyden til trappetrinnet (husk: høyden til trappetrinnet er verdien \(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. ↩︎

Flerfarget flagg
Obligatorisk
Nivå E

I filen multicolored_flag.py opprett en funksjon draw_multicolored_flagsom tar inn seks parametere: canvas, x1, y1, x2, y2, colors. Parameteren colors skal være en liste med strenger som representerer farger. Funksjonen skal tegne et flagg med vertikale striper 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)\). Flagget skal ha like mange striper som det er farger i listen colors og hver oppføring i liste skal reflektere fargen på hver tilsvarende stripe. Første stripe skal altså ha farge colors[0], andre stripe skal ha farge colors[1], osv. Funksjonen trenger ikke å ha noen returverdi.

Merk: I filen multicolored_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 multicolored_flag_test.py i samme mappe som multicolored_flag.py og kopier inn koden under. Når du kjører multicolored_flag_test.py, skal det tegnes tre flagg på skjermen som vist under:

from uib_inf100_graphics.simple import canvas, display
from multicolored_flag import draw_multicolored_flag

draw_multicolored_flag(canvas, 125, 135, 275, 265, ["red", "orange", "yellow", "green", "blue", "purple"])
draw_multicolored_flag(canvas, 10, 10, 40, 36, ["black", "yellow", "red"])
draw_multicolored_flag(canvas, 10, 340, 390, 360, ["black", "white", "black","black", "white", "black","black", "white", "black"])

display(canvas)
Eksempelkjøring 1

PS: Når du kaller på canvas.create_rectangle, legg til outline="" i argumentlisten. Da unngår du at det tegnes en svart ramme rundt rektangelet.

PPS: Om du syntes denne oppgaven var mistenkelig lik siste del av forrige ukes tog-oppgave, har du helt rett. Forskjellen er at den er obligatorisk denne uken. Du kan bruke din egen kode om igjen hvis du vil (yes! endelig får du sjansen til å sitere deg selv!).

Farget rutenett
Obligatorisk
Nivå E

I filen colored_grid.py opprett en funksjon draw_grid som tar in følgende parametre: canvas, x1, y1, x2, y2, color_grid. Parameteren color_grid er en 2D-liste med strenger som representerer farger. Funksjonen skal tegne et rutenett med farger med øvre venstre hjørne i punktet \((x_1, y_1)\) og nedre høyre hjørne i punktet \((x_2, y_2)\). Fargene i listen color_grid skal reflektere fargen på rutene i tilsvarende posisjon (se eksempelkjøringen). Funksjonen trenger ikke å ha noen returverdi.

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

from uib_inf100_graphics.simple import canvas, display
from colored_grid import draw_grid

# Et 4x2 rutenett med farger
draw_grid(canvas, 40, 100, 120, 180, [
        ["red", "darkred"],
        ["yellow", "orange"],
        ["white", ""],
        ["cyan", "blue"],
    ])

# Et sjakkbrett
draw_grid(canvas, 170, 30, 370, 250, [
        ["white", "black"] * 4,
        ["black", "white"] * 4,
    ] * 4)

# En 2D-liste med kun én rad
draw_grid(canvas, 50, 310, 350, 370, [
    ['#00c', '#01c', '#02c', '#03c', '#04c', '#05c', '#06c', '#07c',
     '#08c', '#09c', '#0ac', '#0bc', '#0cc', '#0dc', '#0ec', '#0fc']
])

display(canvas)
Eksempelkjøring 1

PS: I denne oppgaven skal outline være med når du tegner rektangler.

  • Begynn med å definere to variabler rows og cols som representere antall rader og antall kolonner i rutenettet. Antall rader er lengden på den ytre listen (len(color_grid)) mens antall kolonner er lengden på den første indre listen (len(color_grid[0])). Vi antar at det er like mange kolonner i hver rad, slik at vi ikke trenger å sjekke alle.

  • Definer to variabler cell_width og cell_height som representerer henholdsvis bredden og høyden på en celle i rutenettet. Bredden til en celle vil være differansen mellom x-koordinatene delt på antall kolonner, mens høyden til en celle vil være differansen mellom y-koordinatene delt på antall rader.

  • La oss gå gjennom hver eneste mulige kombinasjon av en rad og en kolonne. La row og col være iterandene i to nøstede for-løkker; la row gå fra 0 opp til (ikke inkludert) rows, og la col gå fra 0 og opp til (men ikke inkludert) cols.

  • Inne i den nøstede løkken skal vi tegne cellen i rad row og kolonne col. For å tegne cellen, må vi regne ut koordinatene (cell_x1, cell_y1) som er punktet til venstre øverst i cellen, og punktet (cell_x2, cell_y2) som er punktet til høyre nederst.

    • For å regne ut cell_x1, begynner vi på koordinatet x1 og flytter oss cell_width piksler til høyre col ganger.
    • For å regne ut cell_y1, begynner vi på koordinatet y1 og flytter oss cell_height piksler nedover row ganger.
    • For å regne ut cell_x2, begynn på cell_x1 og flytt deg cell_width piksler til høyre.
    • For å regne ut cell_y2, begynn på cell_y1 og flytt deg cell_height piksler nedover.
  • Etter at du har regnet ut koordinatene til cellen, kall create_rectanglecanvas. Etter de posisjonelle argumentene vi regnet ut over, bruk fill=color_grid[row][col] for å sette fargen.

Valgfri

Alternerende sum
Valgfri
Nivå D

I filen alternating_sum.py opprett funksjonen alternate_sign_sum som tar inn en liste med tall summands som parameter. Funksjonen skal regne ut den alternerende summen av tallenen i listen summands (summands[0] - summands[1] + summands[2] - summands[3]summands[n-1]).

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

print("Tester alternate_sign_sum... ", end="")
assert(3 == alternate_sign_sum([1, 2, 3, 4, 5]))
assert(30 == alternate_sign_sum([10, 20, 30, 40, 50]))

a = [-10, 20, -30, 40, -50]
assert(-150 == alternate_sign_sum([-10, 20, -30, 40, -50]))
assert([-10, 20, -30, 40, -50] == a) # Sjekker at funksjonen ikke er destruktiv
print("OK")

  • Vi trenger å bruke en løkke for å gå igjennom alle elementene i listen. Siden vi vet allerede før løkken begynner hvor mange iterarsjoner vi skal ha (nemlig len(summands)), bør vi bruke en for-løkke

  • Før løkken starter, opprett en variabel som holder den løpende totalsummen. For hver iterasjon av løkken legger vi til (eller trekker fra) et tall til denne variabelen.

  • Inne i løkken må vi avgjøre om vi skal legge til eller trekke fra i den iterasjonen som nå skal utføres. Hvis vi benytter en indeksert løkke, kan vi sjekke om indeks er et partall eller oddetall, og slik velge om vi legger til eller trekker fra.

  • Returner totalsummen når løkken er ferdig.

Minste absolutt-forskjell
Valgfri
Nivå C

I filen least_difference.py skriv funksjonen smallest_absolute_difference som har en liste med tall a som parameter. Funksjonen skal returnerer den minste absolutt-verdi som er forskjellen mellom to tall i listen a.

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

print("Tester smallest_absolute_difference... ", end="")
assert(1 == smallest_absolute_difference([1, 20, 4, 19, -5, 99])) # 20-19
assert(6 == smallest_absolute_difference([67, 19, 40, -5, 1])) # 1-(-5)
assert(0 == smallest_absolute_difference([2, 1, 4, 1, 5, 6])) #1-1
a = [50, 40, 70, 33]
assert(7 == smallest_absolute_difference(a))
assert([50, 40, 70, 33] == a) # Sjekker at funksjonen ikke er destruktiv
print("OK")

Alternativ A (kanskje enklest å komme på, men litt ineffektivt):

  • Før løkkene starter, opprett en variabel som har til hensikt å hold på den minste forskjellen mellom to elementer sammenlignet så langt. Initielt kan den for eksempel ha absolutt-verdien av avstanden mellom de to første elementene i listen
  • Benytt en indeksert for-løkke for å gå igjennom alle elementene i listen. I iterasjon nummer i av løkken er hensikten å finne den minste absolutt-verdi-forskjellen mellom elementet a[i] og et element som kommer senere i listen (vi trenger kun å sjekke det som kommer senere i listen – fordi hvis den minste forskjellen var mot et element tidligere i listen, vil denne forskjellen allerede være funnet da vi sjekket det elementet).
  • Benytt en nøstet for-løkke for å gå gjennom alle posisjoner j mellom i+1 og len(a); regn ut absolutt-verdien av forskjellen på a[i] og a[j], og dersom den er mindre enn noe vi har sett før, lagre denne avstanden i variabelen som har til hensikt å huske dette.
  • Når alle iterasjonene er ferdig, returner svaret

Alternativ B (mer effektivt):

  • Ikke-destruktivt sorter listen

  • Gå gjennom listen med en indeksert løkke, og regn ut forskjellen på alle etterfølgende elementer. Ta vare på den minste slike avstanden.

Ordspill
Valgfri
Nivå B

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

I denne oppgaven skal vi skrive et hjelpemiddel for deg som ønsker å bli bedre i ordspill som Scrabble og Wordfeud. Det er selvfølgelig ikke lov å bruke dette for å jukse når man spiller, men det er lov å bruke det for å evaluere spill i etterkant.

I spill som Scrabble og Wordfeud har hver spiller en bunke med brikker, som hver har én bokstav på seg. Poenget er å kombinere brikkene slik at de former et lovlig ord.

Del A

Opprett en funksjon can_be_made_of_letters(word, letters) som tar inn en streng word og en streng letters som parametre. Funksjonen skal returere True hvis strengen word kan lages med bokstavesamlingen letters, og False hvis ikke.

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

print("Tester can_be_made_of_letters... ", end="")
assert(can_be_made_of_letters("emoji", "abcdefghijklmno"))
assert(not can_be_made_of_letters("smilefjes", "abcdefghijklmnopqrs"))
assert(can_be_made_of_letters("smilefjes", "abcdeeefghijklmnopqrsss"))
assert(can_be_made_of_letters("lese", "esel"))

# Ekstra tester for mer avansert variant, med wildcard * i bokstavene
# assert(can_be_made_of_letters("lese", "ese*"))
# assert(not can_be_made_of_letters("lese", "esxz*"))
# assert(can_be_made_of_letters("smilefjes", "s*i*e*j*s"))
# assert(not can_be_made_of_letters("smilefjes", "s*i*e*j*z"))
print("OK")

Sjekk for hver bokstav i ordet som skal lages om antall forekomster av bokstaven i bokstavsamlingen er tilstrekkelig i forhold til antall forekomster av bokstaven i ordet. Strenger har en metode .count som kan brukes til dette.

Del B

Opprett en funksjon possible_words(wordlist, letters) som tar inn en liste med strenger wordlist og en streng letters som parametre. Funksjonen skal retunere en liste med alle ord fra wordlist som kan lages med bokstavene i letters.

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

print("Tester possible_words... ", end="")
hundsk =["tur", "mat", "kos", "hent", "sitt", "dekk", "voff"]
kattsk =["kos", "mat", "pus", "mus", "purr", "mjau", "hiss"]

assert(['kos', 'sitt'] == possible_words(hundsk, "fikmopsttuv"))
assert(['kos', 'pus', 'mus'] == possible_words(kattsk, "fikmopsttuv"))

# Ekstra tester for mer avansert variant, med wildcard * i bokstavene
# assert(['tur', 'mat', 'kos', 'sitt'] == possible_words(hundsk, "fikmopsttu*"))
# assert(['kos', 'mat', 'pus', 'mus'] == possible_words(kattsk, "fikmopsttu*"))
print("OK")
Komprimering
Valgfri
Nivå A

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

En fil består av en sekvens av 1’ere og 0’ere. Noen filformater har i praksis veldig lange sekvenser med kun 1’ere eller kun 0’ere. For eksempel: en sensor i et alarmsystem gir 1 som output når en dør er åpen, og 0 som output når en dør er lukket; sensoren blir avlest 1 gang i sekundet, og disse data’ene blir lagret som en sekvens av 0’ere og 1’ere i en fil. Et annet eksempel på slike filer er bilder med store hvite eller svarte felter.

For å komprimere slike filer, kan vi omgjøre måten vi skriver filen på til å bestå av en sekvens heltall som forteller vekselvis hvor mange 0’ere og 1’ere som kommer etter hverandre. Det første tallet i komprimeringen forteller hvor mange 0’er det er i begynnelsen. Dette tallet kan være 0 dersom binærtallet begynner med 1. Alle andre tall i komprimeringen vil være positive. Du kan lese mer om denne typen komprimering på Wikipedia.

For enkelhets skyld gjør vi denne oppgaven med strenger og lister og ikke direkte med 1’ere og 0’ere lagret i minnet.

Del A

Opprett en funksjon compress(raw_binary) som tar inn en streng raw_binary bestående av 0’ere og 1’ere. Funksjonen skal retunerer en liste som representerer sekvensen i komprimert form (tall skilles med mellomrom).

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

print("Tester compress... ", end="")
assert([2, 3, 4, 4] == compress("0011100001111"))
assert([0, 2, 1, 8, 1] == compress("110111111110"))
assert([4] == compress("0000"))
print("OK")

  • Det finnes mange måter å løse dette problemet på. Det kan være lurt å begynne med å sjekke om første tegn er “1”. Hvis det er tilfelle legg til 0. Fordi 0 indikerer at det første tegnet ikke er «0».

  • Lag en variabel som begynner på 0 (int) den kan brukes for å telle antall 0’ere og 1’ere, og en tom liste.

  • Lag en løkke som går fra 0 opp til lengden av stringen - 1. Dette lar oss sammenligne to tegn samtidig.

  • I løkken sjekk om tegnet er lik neste tegn, feks “if [i] == [i + 1]:” hvis dette er True øk telleren med 1. Hvis dette ikke stemmer legg verdien til telleren i listen, og tilbakestill teleren til 1.

  • Når løkken er feridg legg till telleren i listen igjen.

  • Bruk join funksjonen til å konvertere listen til en string, return den stringen.

  • Pass på å bruke str() for å konvertere int til str.

Del B

Opprett en funksjon decompress(compressed_binary) som tar inn en liste compressed_binary beståenden av heltall som representerer en komprimert sekvens av 0’ere og 1’ere. La funksjonen returnere den ukomprimerte sekvensen av 0’ere og 1’ere.

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

print("Tester decompress... ", end="")
assert("0011100001111" == decompress([2, 3, 4, 4]))
assert("110111111110" == decompress([0, 2, 1, 8, 1]))
assert("0000" == decompress([4]))
print("OK")