Lab8

Endelig er den her: semesterets siste lab! Denne lab’en er en god blanding av alt vi har lært i løpet av semesteret. Ulikt tidligere lab’er, er denne lab’en basert på poeng, og du må få minst 12 poeng for å bestå. Du kan velge selv hvilke oppgaver du ønsker å løse. Hvor mange poeng hver oppgave gir, står oppgitt her:

Oppgave Poeng
Tutorial 5. april: labyrint 3
Tutorial 12. april: q-learning 4
Lister vs oppslagsverk 1
Collatz-sekvensen 1
Filter for høye temperaturer 1
Kolonnesum 2
Lønnsberegning 1
Raskeste løper 1
Prikkprodukt 1
Akvakulturregisteret 3
Endre farge på prikk med piltaster 2
Lengste varmeperiode (dummydata) 2
Lengste varmeperiode (ekte data) 3

Enkelte valgfrie oppgaver fra tidligere lab’er gir også poeng:

Oppgave Poeng
Haiku fra lab 1 1
Kollisjonsdeteksjon fra lab 2 3
Tverrsum fra lab 3 2
Minste absolutt-forskjell fra lab 4 1
Komprimering fra lab 4 3
Mulige ord fra lab 6 2
God stil fra lab 6 1
Kombinere CSV-filer fra lab 7 2

Vi minner dessuten om at åpen lab kan gjøres som et alternativ til denne lab’en.


Nivå C
Tutorial 5. april: labyrint

Gjennomfør labyrint-tutorial fra forelesningen 5. april. Denne oppgaven gir 3 poeng, og rettes manuelt. Du skal levere filene laby_main.py, laby_maze.py, og laby_view.py samt eventuelle andre filer som er nødvendige for at programmet skal kjøre (laby_ai.py, level1.lev etc. hvis de brukes).

Startkode finner du her, og slides med guide finner du her. For å få godkjent, må du ha fullført til og med steg 5 i tutorialen (det vises en vinnerskjerm når spilleren har nådd målet).

Nivå A
Tutorial 12. april: q-learning

Gjennomfør q-learning-tutorial fra forelesningen 12. april. Denne oppgaven gir 4 poeng. Du må ha fullført labyrint-tutorial fra forelesningen 5. april før du kan begynne på denne oppgaven.

Nivå E
Lister vs oppslagsverk

Denne oppgaven består av to deler. Skriv funksjoner til begge deloppgaver i én felles fil, list_vs_dictionary.py.

I denne oppgaven skal vi undersøke slektskapet mellom oppslagsverk og lister. En nøkkel spiller på mange måter samme rolle for et oppslagsverk som en indeks gjør for en liste.

Del A

Skriv funksjonen key_value_getter(d) som tar inn en dictionary d, og skriver ut til skjermen nøklene, verdiene og nøkkel/verdi-par. Eksempelkjøring:

key_value_getter({
  "monday": 0,
  "tuesday": 0.7,
  "wednesday": 0,
  "thursday": 4.7,
  "friday": 10
})

skal gi utskriften:

Dictionary keys:
monday
tuesday
wednesday
thursday
friday

Dictionary values:
0
0.7
0
4.7
10

Dictionary keys/value:
monday 0
tuesday 0.7
wednesday 0
thursday 4.7
friday 10

Del B

Skriv funksjonen index_value_getter(a) som tar inn en liste a og skriver ut til skjermen indeksene, verdiene og indeks/verdi -parene. Eksempelkjøring:

index_value_getter([7.0, 8.0, 10.0, 9.0, 10.0])

skal gi utskriften:

List indices:
0
1
2
3
4

List values:
7.0
8.0
10.0
9.0
10.0

List indices/value:
0 7.0
1 8.0
2 10.0
3 9.0
4 10.0

PS: Det skal ikke komme noen utskrift i terminalen dersom noen importerer filen din som en modul. Hvis du vil teste funksjonene dine lokalt på egen maskin kan du legge til dine egne kall til funksjonene nederst i filen din under if __name__ == "__main__":.

Nivå E
Collatz-sekvensen

Collatz-sekvensen er definert som følger:

  1. Start med et tall \(n\)
  2. Hvis \(n\) er jevnt så er neste tall \(\frac{n}{2}\), ellers så er neste tall \(3n +1\)
  3. Repeter steg \(2\) med det nye tallet helt til du får \(1\). Da er du ferdig.

Her er en funksjon som beregner Collatz-sekvensen gitt en en startverdi \(n\):

def collatz_sequence(n):
    sequence = [n]
    while n > 1:
        if n % 2 == 0:
            n = n // 2
        else:
            n = 3 * n + 1
        sequence.append(n)
    return sequence

I filen collatz.py, skriv en funksjon som heter collect_collatz(a, b) som bruker collatz_sequence -funksjonen gitt over til å beregne Collatz-sekvensen for alle tall fra og med \(a\) og opp til (men ikke inkludert) \(b\). Sekvensene skal returneres i form av et oppslagsverk hvor startverdiene er nøkler.

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

def test_collect_collatz():
    print('Tester collect_collatz... ', end='')

    # Test 1
    expected = {
        1: [1],
        2: [2, 1], 
        3: [3, 10, 5, 16, 8, 4, 2, 1],
    }
    actual = collect_collatz(1, 4)
    assert expected == actual

    # Test 2
    expected = {
        3: [3, 10, 5, 16, 8, 4, 2, 1],
        4: [4, 2, 1],
        5: [5, 16, 8, 4, 2, 1],
    }
    actual = collect_collatz(3, 6)
    assert expected == actual
    print('OK')

if __name__ == '__main__':
    test_collect_collatz()

  • Begynn med å opprette et tomt oppslagsverk (som du skal returnere på slutten av funksjonen, når det er ferdig fylt opp med nøkler og verdier).
  • Bruk en løkke for å gå gjennom alle tall fra og med \(a\) opp til men ikke inkludert \(b\) (se kursnotater om løkker).
  • Inne i løkken: gjør et kall til collatz_sequence -metoden med iteranden som argument. Oppdater oppslagsverket slik at iteranden blir en ny nøkkel med returverdien fra kallet til collatz_sequence som verdi.

Nivå D
Filter for høye temperaturer

I filen filter_high_temperatures.py, skriv en funksjon som heter filter_high_temperatures med parametre:

  • path_input, en filsti til en eksisterende fil hvor hver linje inneholder først en dag, deretter et mellomrom, og så en temperatur. For eksempel, temperatures.txt.
  • path_output, en filsti til en fil som skal opprettes, og
  • threshold_temp, et flyttall som representerer en temperatur.

La funksjonen åpne filen path_input, gå gjennom linjene i filen og lager en ny fil path_output hvor kun de linjene der temperaturen er minst threshold_temp er inkludert. Om ingen dager har en temperatur som er minst threshold_temp så skal path_output være en tom fil.

For å teste programmet ditt, legg til nederst i filen:

def test_filter_high_temperatures():
    print('Tester filter_high_temperatures... ', end='')
    filter_high_temperatures('temperatures.txt', 'high_temps.txt', 23.5)
    expected = (
        'Monday 23.5\n'
        'Wednesday 24.0\n'
        'Thursday 23.9\n'
        'Sunday 23.9\n'
    )
    with open('high_temps.txt', 'rt', encoding='utf-8') as file:
        actual = file.read()
    assert expected.strip() == actual.strip()
    print('OK')

if __name__ == '__main__':
    test_filter_high_temperatures()
Nivå E
Kolonnesum

I filen sum_of_column.py, skriv en funsjon sum_of_column(path, col) som returnerer summen av verdiene fra den oppgitte kolonnen i csv-filen med filsti path. Ikke inkluderer verdier i summen som ikke er flyttall; hvis det ikke finnes noen tallverdier i oppgitt kolonne, skal funksjonen returnere 0. Kolonne 0 er den første kolonnen fra venstre i csv-filen. Du kan anta at csv-filen benytter komma (,) som skilletegn og dobbel rett apostrof (") som anførselstegn/grupperingssymbol («quotechar»).

Eksempelkjøringer med foo.csv, Statistikk_Tilsyn_ar.csv og airport-codes.csv

def test_sum_of_column():
    print('Tester sum_of_column... ', end='')
    assert(42.0 == sum_of_column('foo.csv', 0))
    assert(95.0 == sum_of_column('foo.csv', 1))
    assert(0.0 == sum_of_column('foo.csv', 2))
    assert(76363.0 == sum_of_column('Statistikk_Tilsyn_ar.csv', 1))
    assert(46007.0 == sum_of_column('Statistikk_Tilsyn_ar.csv', 2))
    assert(5024518.0 == sum_of_column('airport-codes.csv', 3))
    print('OK')

if __name__ == '__main__':
    test_sum_of_column()
Nivå E
Lønnsberegning

RandomFirma AS trenger et program for å beregne hvor mye de skal betale sine timeansatte. Arbeidsmiljøloven krever at ansatte får lønn for 1,5 time for alle timer over 40 som de jobber i løpet av en enkelt uke. For eksempel, hvis en ansatt jobber 45 timer, får de 5 timer overtid, til 1,5 ganger grunnlønnen. Regjeringen har innført minstelønn i bransjen dette firmaet operer i, og minstlønnen er 200 kr per time. RandomFirma AS krever også at en ansatt ikke jobber mer enn 60 timer i en uke.

Her er regler oppsummert:

  • En ansatt får betalt (arbeidstimer) × (grunnlønn), for hver time inntil 40 timer.
  • For hver time over 40 får de overtid = (grunnlønn) × 1,5.
  • Grunnlønnen må ikke være lavere enn minstelønnen (200 i timen).
  • Antall timer kan ikke være større enn 60.

I filen salary.py skriv en funksjon weekly_pay(hourly_rate, hours) som tar grunnlønn og antall timer en anstatt har jobbet som parametere, og returnerer enten totallønnen som en tallverdi, eller streng med en feilmelding. Feilmeldingene er 'Minstelønnskravet er ikke oppfylt' eller 'En ansatt jobber mer enn 60 timer'. Dersom begge reglene brytes, skal det returneres 'Minstelønnskravet er ikke oppfylt'.

def test_weekly_pay():
    print('Tester weekly_pay... ', end='')
    assert 2_000 == weekly_pay(200, 10)
    assert 40_000 == weekly_pay(1000, 40)
    assert 20_000 == weekly_pay(500, 40)
    assert 41_500 == weekly_pay(1000, 41)
    assert 70_000 == weekly_pay(1000, 60)
    assert 'En ansatt jobber mer enn 60 timer' == weekly_pay(1000, 61)
    assert 'Minstelønnskravet er ikke oppfylt' == weekly_pay(199, 40)
    assert 'Minstelønnskravet er ikke oppfylt' == weekly_pay(100, 100)
    print('OK')

if __name__ == '__main__':
    test_weekly_pay()
Nivå D
Raskeste løper

En gruppe venner bestemmer seg for å løpe Bergen City Marathon. Eksempel på fil med navn og tider (i minutter) kan lastes ned her: marathon.txt. I filen fastest_runner.py skriv en funksjon fastest_runner(path) som returnerer navnet på den raskeste løperen og antall minutter vedkommende har brukt som en streng. Parameteren path er en filsti til en fil som inneholder navn og tider for alle løperne.

Du kan anta at hver linje i filen begynner med et fullt navn, etterfulgt av et mellomrom og deretter et heltall som representerer tiden i minutter. Du kan ikke gjøre noen antagelser om hvor mange løpere som er nedtegnet i filen, eller hvor mange etternavn og mellomnavn de har.

def test_fastest_runner():
    print('Tester fastest_runner... ', end='')
    assert 'Tien Pengyen Fei 199' == fastest_runner('marathon.txt')
    print('OK')

if __name__ == '__main__':
    test_fastest_runner()
Nivå D
Prikkprodukt

I filen dot_product.py skriv funksjonen dot_product(a, b) som regner ut prikkprodukuktet for to like lange lister med tall a og b. Prikkproduktet er definert som summen av produktene av verdiene som har lik posisjon i de to listene; altså (a[0] * b[0] + a[1] * b[1] + …)

def test_dot_product():
    print('Tester dot_product... ', end='')
    assert 36 == dot_product([1, 2, 3, 4], [4, 5, 6, 1])
    assert 12 == dot_product([0, 6, 1], [400, 1, 6])
    assert 651 == dot_product([43, 6], [15, 1])
    print('OK')

if __name__ == '__main__':
    test_dot_product()
Nivå C
Endre farge på prikk med piltaster

I denne oppgaven skal vi bruke uib_inf100_graphics.event_app for å lage et program hvor brukeren kan forandre fargen på en prikk ved å trykke på piltastene. Denne oppgaven rettes manuelt (det er ingen automatiske tester på CodeGrade).

Les deg gjerne opp på farger i kursnotatene om grafikk før du setter i gang.

En farge er i RGB-systemet representert av tre tall som beskriver lysintensiteten til rødt, grønt og blått lys. Hvert tall er mellom 0 og 255. I programmet vi skal lage i denne oppgaven, skal du tegne en prikk midt på skjermen. Brukeren skal kunne trykke på tastaturet for å endre fargen på prikken

  • trykker brukeren på pil opp, økes mengden rød,
  • trykker brukeren på pil ned reduseres mengden rød,
  • trykker brukeren på pil høyre økes mengden grønn,
  • trykker brukeren på pil venstre reduserers mengden grønn,
  • trykker brukeren på a økes mengden blå,
  • trykker brukeren på z reduseres mengden blå.

Ingen farge-verdier skal kunne være mindre enn 0 eller høyere enn 255.

Det ferdige programmet skal skrives i filen colorful_dot.py og skal se omtrent slik ut:

def rgb_to_hex(r, g, b):
    return f'#{r:02x}{g:02x}{b:02x}'
# Example usage
hex_string = rgb_to_hex(255, 0, 128)
print(hex_string)  # Output: #ff0080

  • La modellen (app) består av fire variabler: r, g, b (tallverdier) og message (en streng). Initialiser dem i app_started.
  • La redraw all tegne teksten og en runding midt på skjermen. La fargen være bestemt av r, g og b fra app.
  • La key_pressed håndtere tastetrykk som beskrevet i oppgaveteksten. I videoen over endres verdien med 10 for hvert tastetrykk.

Bonus:

  • Ha ulike modus som bestemmer hvor store steg du tar hver gang. Endre modus ved å trykke på space.
  • Ha ulike prikker, der du kan klikke på hvilken prikk du nå skal endre fargen til. Vis hvilken prikk som er valgt f. eks. ved å la den ha outline.
Nivå B
Akvakulturregisteret

I denne oppgaven skal vi bruke datafilen Akvakulturregisteret.csv.

PS: akvakulturregisteret er dessverre ikke lagret i utf-8. Prøv deg frem til du finner riktig encoding (se notater om tekstkoding).

Del A (1 poeng)

I filen aquaculture_a.py skriv en funksjon count_facilities_by_species(path) som tar som input en filsti til akvakulturregisteret, og som så skriver ut til terminalen en unicode-alfabetisk liste over antall oppdrettsanlegg for hver art. Med «unicode-alfabetisk» menes den rekkefølgen man får dersom man sorterer en liste med artsnavn med sorted -funksjonen innebygget i Python. Du kan ta utgangspunkt i følgende kode:

def count_facilities_by_species(path):
    ... # din kode her
    
if __name__ == '__main__':
    count_facilities_by_species('Akvakulturregisteret.csv')

Eksempel på utskrift når programmet kjøres:

Abbor: 8
Acartia tonsa **(oppdrett): 4
Akkar: 1
Amerikansk hummer: 2
Arctic sea ice amphipod *: 1
Arktisk knurrulke: 1
Berggylt: 58
...

  • Les innholdet i filen til en egnet datastruktur ved å benytte csv -biblioteket.
  • Bruk et oppslagsverk (dict) for å telle hver art; bruk en løkke over hver rad i filen.
  • Første gangen du ser en art, opprett en ny nøkkel med artsnavnet i oppslagsverket og gi den verdien 1
  • Dersom nøkkelen derimot var i oppslagsverket fra før, øk verdien dens med 1
  • For å finne artene i ‘alfabetisk’ rekkefølge, bruk sorted -funksjonen og gi listen med nøklene fra oppslagsverket som argument.

Del B (1 poeng, rettes manuelt)

I filen aquaculture_bc.py, skriv et program som plotter alle oppdrettsanleggene i akvakulturregisteret på et kart. Bruk et scatterplot fra matplotlib for å plotte punktene. Posisjonene til oppdrettsanleggene er gitt i kolonnene som kalles Ø_GEOWGS84 og N_GEOWGS84 i csv-filen. Det er fint om punktene tegnes med en viss gjennomsiktighet.

Når du kjører programmet skal omtrent følgende vises:

Eksempel på program som besvarer del B

Del C (1 poeng, rettes manuelt)

I filen aquaculture_bc.py, endre programmet slik at punktene får ulik farge avhengig av om det er et oppdrettsanlegg i sjø eller på land.

Når du kjører programmet skal omtrent følgende vises:

Eksempel på program som besvarer del C
Nivå B
Lengste varmeperiode (dummydata)

I denne oppgaven skal du skrive en funksjon som finner den lengste sammenhengende perioden med temperaturer over ett gitt antall varmegrader.

I filen longest_hot_period_dummy.py, skriv en funksjon longest_hot_period(temperatures, threshold) som tar inn en liste temperatures med temperaturer og et flyttall threshold som representerer hvor mange grader som definerer en varm dag. Funksjonen skal returnere indeksene som definerer den lengste sammenhengende perioden med temperaturer med threshold eller flere varmegrader. Hvis det er flere perioder som er like lange, skal den returnere den tidligste perioden.

Hvis det ikke er noen perioder med temperaturer over threshold, skal funksjonen returnere (-1, -1). Se også eksemplene i testene du kan lime inn nederst i filen for å se hvordan funksjonen skal fungere.

def test_longest_hot_period():
    print('Testing longest_hot_period... ', end='')
    temps = [25, 23, 19, 22, 24, 25, 21, 25, 26]

    threshold = 22
    expected_start, expected_end = 3, 6
    actual_start, actual_end = longest_hot_period(temps, threshold)
    assert (expected_start, expected_end) == (actual_start, actual_end)

    threshold = 23
    expected_start, expected_end = 0, 2
    actual_start, actual_end = longest_hot_period(temps, threshold)
    assert (expected_start, expected_end) == (actual_start, actual_end)

    threshold = 15
    expected_start, expected_end = 0, 9
    actual_start, actual_end = longest_hot_period(temps, threshold)
    assert (expected_start, expected_end) == (actual_start, actual_end)

    threshold = 21
    expected_start, expected_end = 3, 9
    actual_start, actual_end = longest_hot_period(temps, threshold)
    assert (expected_start, expected_end) == (actual_start, actual_end)

    threshold = 25
    expected_start, expected_end = 7, 9
    actual_start, actual_end = longest_hot_period(temps, threshold)
    assert (expected_start, expected_end) == (actual_start, actual_end)

    threshold = 30
    expected_start, expected_end = -1, -1
    actual_start, actual_end = longest_hot_period(temps, threshold)
    assert (expected_start, expected_end) == (actual_start, actual_end)

    print('OK')

if __name__ == '__main__':
    test_longest_hot_period()

Nivå A
Lengste varmeperiode (ekte data)

I denne oppgaven skal du skrive en funksjon som finner den lengste sammenhengende perioden der hver dag har temperaturer over ett gitt antall varmegrader. Denne gangen skal vi bruke ekte data hentet fra frost.met.no.

  • Å hente data fra frost.met.no er gratis, men for å hindre misbruk må du registrere deg. Registrering er en uvanlig kortfattet prosess som du kan gjøre her: https://frost.met.no/auth/requestCredentials.html. For å gjennomføre denne oppgaven må du ta vare på din client_id; for enkelhets skyld kan du kopiere denne inn i en fil som heter client_id.txt. I kodeeksemplene under antar vi at din client id er det eneste som ligger lagret i denne filen.

Full dokumentasjon for hva slags data du kan hente fra frost.met.no kan være litt overveldende; vi presenterer derfor noen relevante eksempler vi kan begynne å utforske her.

import requests
import json
from pathlib import Path

client_id = Path('client_id.txt').read_text(encoding='utf-8').strip()
api_endpoint = 'https://frost.met.no/observations/v0.jsonld'
source_id = 'SN50540' # SN50540 er Florida målestasjon i Bergen

response = requests.get(
    api_endpoint,
    params={
        'sources': source_id,
        'referencetime': '2024-04-01/2024-04-07',
        'elements': 'mean(air_temperature P1D)',
        'levels': 'default',
        'timeoffsets': 'default',
    },
    headers={'User-Agent': 'inf100.ii.uib.no student'},
    auth=(client_id, '')
)

json_data = response.json()
print(json.dumps(json_data, indent=2))

Tips for å utforske dataene du mottar: sett et breakpoint like etter at json_data har blitt opprettet, og bruk debuggeren for å utforske hva som ligger i denne variabelen.

  • client_id er en variabel som peker på en streng, nemlig din client id.
  • api_endpoint er en variabel som peker på en streng, nemlig adressen til frost.met.no hvor vi kan hente data.
  • source_id er en variabel som peker på en streng, i dette tilfellet representerer strengen id-en til Florida målestasjon i Bergen.
  • response er en variabel som peker på responsen vi får fra frost.met.no når vi spør om data.

Argumentene vi gir ved kallet til requests.get er

  • api_endpoint er stammen for nettadressen hvor vi henter data. Dette utgjør begynnelsen av en URL/nettadresse.
  • til params gir vi et oppslagsverk med nøkkel-verdi-par som vil utgjøre slutten av url’en. Den komplette URL’en vil til slutt bli https://frost.met.no/observations/v0.jsonld?sources=SN50540&referencetime=2024-04-01/2024-04-07&elements=mean(air_temperature%20P1D)&levels=default&timeoffsets=default. Vi kunne laget denne strengen på andre måter og ikke benyttet params-parameteren i det hele tatt; men det ser mer oversiktelig og fint ut å bruke params og et oppslagsverk enn å bedrive omfattende streng-manipulasjon på egen hånd.
    • sources er en komma-separert liste over målestasjoner vi ønsker data fra. I vårt tilfelle er det kun én målestasjon.
    • referencetime tidsrommet vi ønsker måledata fra. <fra dato>/<til dato>. Merk at fra-dato er inklusiv mens til-dato er ekslusiv (i tråd med god standard praksis i programmering for øvrig).
    • elements angir hva slags måledata vi ønsker. Strengen mean(air_temperature P1D) innebærer at vi ønsker oss gjennomsnittstemperaturen for tidsperioder på én dag.
    • levels angir hvilket «nivå» målingen er gjort på. For temperaturdata er standardnivået 2 meter over bakken, mens noen målestasjoner har temperaturmålinger også på andre høyder. Ved å angi default her eksluderer vi data fra andre målehøyder enn standardnivået.
    • timeoffsets angir hvilket tidspunkt på døgnet gjennomsnittsmålingen begynner. Default som verdi her betyr at gjennomsnittsmålingen begynner ved midnatt.
  • til headers gir vi et oppslagsverk hvor vi angir en verdi for «User-Agent». Dette feltet benyttes for å beskrive hva slags software som brukes.
  • til auth gir vi en tuple med (brukernavn, passord). I vårt tilfelle er ikke passord nødvendig, siden frost.met.no ikke krever dette når vi bare ber om å lese informasjon. Derfor oppgir vi en tom streng som passord.

requests har innebygget en metode for å konvertere en respons som kommer i json-format til et oppslagsverk/json-objekt. Dette gjøres ved å kalle response.json(). Vi bruker json.dumps for å skrive ut dette oppslagsverket på en lesbar måte.

Et lite program for å finne id’ene til værstasjoner i Bergen. Vi laster først ned en oversikt over alle værstasjoner i Norge, og søker etter dem som har Bergen i navnet.

import requests
from pathlib import Path

client_id = Path('client_id.txt').read_text(encoding='utf-8').strip()
api_endpoint = 'https://frost.met.no/sources/v0.jsonld'

response = requests.get(
    api_endpoint,
    headers={'User-Agent': 'inf100.ii.uib.no student'},
    auth=(client_id, '')
)
json_data = response.json()

for item in json_data['data']:
    if 'name' not in item:
        continue
    if 'bergen' in item['name'].lower():
        print(item['id'], item['name'])

I filen longest_hot_period_realdata.py skriv en funksjon longest_hot_period(client_id, source_id, start_date, end_date, threshold) som tar inn en client id, en stasjonsid, en start-dato, en slutt-dato og en temperaturgrense, og som så returnerer den lengste sammenhengende perioden i den angitte perioden med temperaturer over threshold. Hvis det ikke er noen perioder med temperaturer over threshold, skal funksjonen returnere (None, None). Dersom det er flere periode med samme varighet, skal den siste av dem returneres.

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

from pathlib import Path
from datetime import datetime

def longest_hot_period(client_id, source_id, start_date, end_date, threshold):
    ... # din kode her

def test_longest_hot_period():
    client_id = Path('client_id.txt').read_text(encoding='utf-8').strip()
    start_date = datetime(year=2000, month=1, day=1)
    end_date = datetime(year=2024, month=1, day=1)
    threshold = 20
    source_id = 'SN50540' # Florida målestasjon i Bergen

    actual_start, actual_end = longest_hot_period(
        client_id, source_id, start_date, end_date, threshold
    )
    actual_start = actual_start.replace(tzinfo=None) # Fjerner tidssone
    actual_end = actual_end.replace(tzinfo=None) # Fjerner tidssone
    expected_start = datetime(year=2009, month=6, day=25)
    expected_end = datetime(year=2009, month=7, day=5)

    assert actual_start == expected_start
    assert actual_end == expected_end

if __name__ == '__main__':
    test_longest_hot_period()