Lab6

I denne laben skal vi lese og skrive filer. En vanlig kilde til forvirring er at filen du ønsker å lese fra eller skrive til ligge i en annen mappe enn den du forventer. Den absolutt letteste måten å unngå denne forvirringen på i begynnelsen, er å åpne VSCode i samme mappe som filene du skriver. For eksempel, dersom du planlegger å jobbe i en mappe som heter lab6, så bør du

  • opprette mappen lab6 der du ønsker, og deretter
  • åpne VSCode i denne mappen. Dette gjør du ved å velge «open folder» fra fil-menyen i VSCode, og deretter velge mappen lab6 du nettopp opprettet.

Les mer om dette problemet i avsnittet «hjelp, filen blir ikke funnet» i kursnotater om filer og csv.

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

Filtrer ordliste
Oppvarming
Nivå E

I en fil wordlist.py, lag en funksjon filter_wordlist som har parametre

  • path, en streng som angir et filnavn, og
  • search_string, en streng vi søker etter.

Funksjonen skal returnere en liste med alle ord fra filen som inneholder search_string. Anta at filen inneholder ett ord på hver linje, og at den er lagret i UTF-8.

For eksempel, hvis filen angitt av path inneholder teksten

data
datasett
database
baser
syrer
bås

og search_string har verdien 'base': da skal funksjonene returnere en liste ['database', 'baser'] fordi dette er ordene i filen som inneholder «base».

Last først ned sample.txt og ordlisten fra Norsk Scrabbleforbund og legg filene i arbeidsmappen din (altså samme mappe du har åpnet VSCode i). Test funksjonen din ved å kopiere denne testen inn i wordlist.py.

print('Tester filter_wordlist... ', end='')

# Test 1
expected = ['database', 'baser']
actual = filter_wordlist('sample.txt', 'base')
assert expected == actual

# Test 2
expected = [
  'småstad', 'småstaden', 'småstas', 'småstasen', 'småstat', 'småstaten',
  'småstatene', 'småstater',
]
actual = filter_wordlist('nsf2022.txt', 'småsta')
assert expected == actual

# Test 3
expected = [
  'stjerneskudd', 'stjerneskudda', 'stjerneskuddene', 'stjerneskuddet', 
]
actual = filter_wordlist('nsf2022.txt', 'stjerneskudd')
assert expected == actual

print('OK')

  • Opprett en variabel filtered_words som initielt peker på en tom liste. Hensikten er at vi skal legge til de ordene som inneholde search_string i denne listen etter hvert.
  • Åpne filen for lesing med with open. Husk å spesifisere encoding utf-8, siden norske bokstaver kan gi problemer.
  • Les innholdet i filen med path.read(). Dette gir en streng med hele filen.
  • Del opp strengen til en liste med linjer med splitlines().
  • Gå gjennom linjene med en for-løkke:
    • For hver linje, sjekk om search_string er en del av linjen med if search_string in line.
    • Hvis den er det, legg til linjen i filtered_words.
  • Når du er ferdig med løkken, returner filtered_words

Rettskriving
Oppvarming
Nivå E
Intro for mengder og hastighet

Les gjerne om mengder i kursnotatene før du begynner. Forklaringen under forklarer det viktigste for akkurat denne oppgaven.

Før vi begynner på oppgaven skal vi forklare litt mer om mengder enn det vi har sett på i forelesning. En mengde er en samling av elementer der alle elementene er unike. Dette betyr at det ikke kan være to like elementer i en mengde. I motsetning til en liste har ikke en mengde egentlig noen orden, så det er ikke mulig å hente ut elementer fra en mengde ved å bruke indeksering. Dette er en konsekvens av måten mengder lagres i minne, som vi ikke skal gå inn på her.

Likevel, grunnet denne måten å lagre elementer på, er det mye raskere å sjekke om et element finnes i en mengde enn i en liste (for store datasett). Se for deg at du har en liste med alle studentene på UiB, og vil sjekke om en person er student. Bruker du en liste og sjekker if maybe_student in students, vil programmet gå gjennom og utføre en sammenligning for hvert element i listen students. Se koden under for hva dette betyr:

students = ["Bob", "Charlie", "David", "Eve", "Alice"]
maybe_student = "Alice"

# Dette:
if maybe_student in students:
    print(f"{maybe_student} is a student")

# Er omtrent ekvivalent med dette (som er treigt):
for student in students:
    if student == maybe_student:
        print(f"{maybe_student} is a student")

Hvis listen er lang (la oss si over 1000 elementer), vil det ta lang tid å finne ut om en person er student. Dersom vi gjør listen om til en mengde i stedet, vil det gå mye raskere:

students = ["Bob", "Charlie", "David", "Eve", "Alice"]
students = set(students)
maybe_student = "Alice"
if maybe_student in students:
    print(f"{maybe_student} is a student")

Forskjellen merker vi ikke ved små datamengder som dette på grunn av tiden det tar å gjøre listen om til en mengde, men med flere tusen verdier i datasettet vi ønsker å søke i, vil vi merke en stor forskjell.

Oppgave
Del A - Rettskriving

I filen spellcheck.py skal du først lage to funksjoner spelling_errors_slow og spelling_errors_fast. Disse funksjonene vil gjøre akkurat det samme, men den ene tar i bruk bare lister og den andre bruker også mengder. Du kan gjerne skrive den treige versjonen først også kopiere og lime inn koden og gjøre den om til den raske versjonen.

Funksjonene tar inn to filstier, en for en tekst og en for en ordbok. Teksten vil kun ha bokstaver, mellomrom og enkle linjeskifte, og ingen komma, punktum osv. Ordboken vil være en tekstfil med ett ord per linje.

Funksjonene skal lese inn filene og returnere hvor mange ord i teksten som ikke finnes i ordboken, altså hvor mange ord som sannsynligvis er skrevet feil. I den treige versjonen skal du lagre både ordene fra teksten og ordboken i 2 forskjellige lister, mens i den raske versjonen skal du lagre ordene fra teksten som en liste og ordboken som en mengde. I begge tilfeller itererer du over ordene i teksten og teller opp hvor mange av ordene som ikke finnes i ordboken.

Her er framgangsmåte for begge funksjonene siden de er så like:

  • Les inn teksten fra filen og gjør det om til en liste med ord.

  • Les inn ordboken fra filen. (Hvis det er den raske versjonen, gjør ordboken om til en mengde.)

  • Gå gjennom listen med ord fra teksten og tell opp hvor mange ord som ikke finnes i ordboken.

For å teste funksjonene dine kan du laste ned tekst1.txt og nsf2022.txt og legge dem i arbeidsmappen din, og så lime inn koden under på slutten av spellcheck.py:

print('Tester spelling_errors_*... ', end='')
assert 5 == spelling_errors_slow('tekst1.txt', 'nsf2022.txt')
assert 5 == spelling_errors_fast('tekst1.txt', 'nsf2022.txt')
print("OK")

Del B - Sjekk hastigheten

Skriv en funksjon speed_test som tar inn de samme filstiene som de forrige funksjonene. Funksjonen skal returnere forholdet av hvor mye treigere den treige versjonen er enn den raske. Dette er altså slow_time / fast_time. Dette kan du finne ut ved å bruke biblioteket time og funksjonen time.time() til å måle hvor lang tid hver av funksjonene tar å kjøre, og så sammenligne disse tidene. Her er starten på funksjonen:

import time

def speed_test(text_path, dict_file):
    start = time.time()
    spelling_errors_slow(text_path, dict_file)
    end = time.time()
    slow_time = end - start
    ... # Din kode her:
    # Gjør det samme for den raske versjonen og returner forholdet

For å teste funksjonen din kan du laste ned tekst2.txt og nsf2022.txt (hvis du ikke allerede har den) og legge dem i arbeidsmappen din, og så lime inn koden under på slutten av spellcheck.py:

print('Tester speed_test... ', end='')
assert speed_test('tekst2.txt', 'nsf2022.txt') > 3
print("OK")

Bibliotek
Oppvarming
Nivå E

Les om oppslagsverk i kursnotatene før du begynner. Denne oppgaven gir deg anledning til å bli kjent med dem i praksis.

Vi skal lage noen små funksjoner for å håndtere et bibliotek med bøker. Vi skal lage en funksjon som legger til bøker i biblioteket, en funksjon som fjerner bøker fra biblioteket, og en funksjon som sjekker hvor mange bøker som totalt finnes i biblioteket. Vi skal gjøre alt dette i en fil library.py.

Del A: Legg til bøker

I library.py, skriv en destruktiv funksjon add_book som tar inn et oppslagsverk library og en streng book. Hvis boken allerde finnes i biblioteket, skal funksjonen øke antallet av boken med 1. Hvis boken ikke finnes i biblioteket, skal funksjonen legge til boken i biblioteket med antallet 1.

Test funksjonen din ved å kopiere denne testen inn i library.py (legg testen inn etter funksjonen add_book).

print('Tester add_book... ', end='')
my_library = {
    'The Little Prince': 4,
    'The Hobbit': 2,
    'Norwegian Wood': 1
}
add_book(my_library, 'The Hobbit')
add_book(my_library, 'Peter Pan')

assert 3 == my_library['The Hobbit'], (
    f"Forventet 3 kopier av 'The Hobbit' men fant {my_library['The Hobbit']}"
)
assert 'Peter Pan' in my_library, "'Peter Pan' mangler i biblioteket"
assert 1 == my_library['Peter Pan'], (
    f"Forventet 1 kopi av 'Peter Pan' men fant {my_library['Peter Pan']}"
)
print('OK')

I funksjonen add_book kan du gjøre følgende:

  • Sjekk om boken finnes i biblioteket med if book in library:

  • Hvis boken finnes, øk antallet av boken med 1. Dette kan gjøres med library[book] += 1

  • Hvis boken ikke finnes, legg til boken i biblioteket med antallet 1. Dette kan gjøres med library[book] = 1

Del B: Fjern bøker

I library.py, skriv en destruktiv funksjon remove_book som tar inn et oppslagsverk library og en streng book. Hvis boken ikke finnes i oppslagsverket skal funksjonen returnere og gjøre ingenting. Ellers (hvis boken finnes i biblioteket) skal funksjonen redusere antallet av boken med 1. Hvis antallet av boken blir 0, skal funksjonen fjerne boken fra biblioteket.

Test funksjonen din ved å kopiere denne testen inn i library.py (legg testen inn etter funksjonen remove_book).

print('Tester remove_book... ', end='')
my_library = {
	'Black Beauty': 1,
	'The Train': 5,
	'Fireworks in Winter': 3
}
remove_book(my_library, 'The Train')
remove_book(my_library, 'The Train')
remove_book(my_library, 'Black Beauty')

assert 'The Train' in my_library, (
    "Forventet ikke at 'The Train' skulle forsvinne fra biblioteket"
)
assert 3 == my_library["The Train"], (
    f"Forventet 3 kopier av 'The Train' men fant {my_library['The Train']}"
)
assert 'Black Beauty' not in my_library, (
    "Forventet ikke at 'Black Beauty' fortsatt er i biblioteket"
)
# Sjekker at programmet ikke krasjer hvis vi prøver å fjerne en bok 
# som ikke finnes i biblioteket
remove_book(my_library, 'Black Beauty')
print('OK')

I funksjonen remove_book kan du gjøre følgende:

  • Sjekk om boken finnes i biblioteket med if book in library:

  • Hvis boken ikke finnes, returner med en gang.

  • Hvis boken finnes, reduser antallet av boken med 1. Dette kan gjøres med library[book] -= 1

  • Hvis antallet av boken nå er 0:

    • Fjern boken fra biblioteket. Dette kan gjøres med library.pop(book).

Del C: Totalt antall bøker

I library.py, skriv en funksjon total_books som tar inn et oppslagsverk library og returnerer den totale mengden av bøker i biblioteket.

Test funksjonen din ved å kopiere denne testen inn i library.py (legg testen inn etter funksjonen total_books).

print("Tester total_books... ", end="")
my_library = {
    "Apothecary Diaries Volume 1": 3,
    "Apothecary Diaries Volume 2": 200,
    "Apothecary Diaries Volume 3": 1,
    "Apothecary Diaries Volume 4": 0,
}

expected = 204
actual = total_books(my_library)
assert expected == actual, f"{expected=},  {actual=}"
my_library["Apothecary Diaries Volume 4"] += 10

expected = 214
actual = total_books(my_library)
assert expected == actual, f"{expected=},  {actual=}"

print("OK")

I funksjonen total_books kan du gjøre følgende:

  • Opprett en variabel running_total og sett den til 0.
  • Benytt en for-løkke for å gå gjennom alle bøkene i biblioteket (se avsnitt om løkker i kursnotater om oppslagsverk).
    • For hver bok, legg til antallet av boken til running_total.
  • Returner running_total

Personlig postkort
Oppvarming
Nivå E

Du er en kreativ sjel som elsker å lage personlige og unike postkort til venner og familie ved å bruke bokstaver og bilder klippet ut fra aviser og ukeblader. For å lage et spesielt postkort til en venn, har du bestemt deg for å skrive en hilsen som bruker bokstaver og tegn fra favorittmagasinet deres. For å sikre at du har nok og at du ikke kaster bort tid på å klippe for mye, trenger du å lage en oversikt over nøyaktig hvor mange av hver bokstav og tegn du trenger.

I en fil postcard.py, skriv en funksjon symbol_count som tar inn et filnavn path og returnerer et oppslagsverk som teller hvor mange ganger hvert tegn forekommer i filen. Mellomrom og linjeskift (såkalt whitespace) skal ikke telles. Du kan anta at filen er lagret i UTF-8.

Her er en nyttig funksjon for å fjerne all whitespace fra en tekststreng:

def remove_whitespace(s):
    return ''.join(s.split())

Funksjonen split deler en tekststreng ved mellomrom og linjeskift (og annen whitespace), og returnerer en liste av ordene mellom. Deretter brukes join for å sette sammen ordene igjen til en enkelt tekststreng.

Test funksjonen din ved å laste ned postcard.txt i arbeidsmappen din og lim inn den følgende koden på slutten av postcard.py:

print('Tester count_letters... ', end='')
expected = {
    'K': 2, 'j': 1, 'æ': 1, 'r': 10, 'e': 15, 'v': 3, 'n': 10, ',': 2,
    'S': 2, 'a': 3, 't': 2, 'y': 3, '.': 2, '"': 2, 'F': 2, 'i': 3, ':': 1,
    'B': 1, 'o': 2, 'd': 2, 'J': 1, 'u': 1, "'": 1, 's': 4, 'E': 1, 'p': 1,
    '!': 1, 'l': 2, 'm': 3,
}
actual = symbol_count('postcard.txt')
assert 'æ' in actual, 'æ er ikke i resultatet, har du husket utf-8 encoding?'
assert expected == actual
print('OK')

I funksjonen:

  1. Les inn filen med open og read.
  2. Fjern all whitespace fra teksten.
  3. Lag et tomt oppslagsverk.
  4. Gå gjennom hvert tegn i teksten og gjør følgende:
    • Hvis tegnet allerede finnes, øk verdien med 1.
    • Hvis tegnet ikke finnes, legg det til med verdien 1.
  5. Returner oppslagsverket.

Gallius' varer
Oppvarming
Nivå E

Trollmannen Gallius har bestemt seg for å åpne en butikk i landsbyen sin. Han har samlet sammen en mengde varer, og har så laget en csv-fil med varene, prisen av å lage varen, antall solgte varer og hva han selger dem for. Siden du enda ikke har sluttet som hans lærling, har han bestemt seg for å gi deg i oppgave å finne ut hvor mye han har tjent på varene sine.

Når skal han lære deg magi, egentlig?

I en fil sales.py, skriv en funksjon total_income som tar inn en filsti path og returnerer total inntekt fra varene opplistet i filen. Anta at filen er kodet i UTF-8 og har følgende format:

name,production_cost,price,sold
Potion of Healing,10,50,100
Potion of Strength,20,100,50
Crystal Ball,50,500,10
Cloak,100,1000,5
Rose of Versailles,500,10000,2

Last først ned sales.csv og legg den i arbeidsmappen din (samme mappe du har åpnet VSCode i). Test funksjonen din ved å kopiere denne testen inn nederst i sales.py.

print('Tester total_income... ', end='')
expected = (
    (50 - 10) * 100
    + (100 - 20) * 50
    + (500 - 50) * 10
    + (1000 - 100) * 5
    + (10000 - 500) * 2
)
actual = total_income('sales.csv')
assert expected == actual, f'{expected=}, {actual=}'
print('OK')

Relevante kursnotater: lister (særlig avsnittet om konvertering mellom lister og strenger, samt avsnitt om beskjæring) og naturligvis filer og csv.

  • Opprett en variabel income som starter på 0. Denne skal holde styr på total inntekt.
  • Åpne filen og les innholdet som en streng.
  • Konverter strengen til en liste av linjer med .splitlines().
  • Den første linjen i filen er kun overskrifter, så vi kan fjerne den ved å beskjære listen med [1:].
  • Gå gjennom hver av de resterende linjene i filen med en for-løkke.
    • I utgangspunktet vil hver linje være en streng. Vi kan konverter linjen til en liste av verdier med .split(','). Dette vil gi oss en liste med strenger, klippet opp med komma som skilletegn.
    • Hent verdiene for produktkostnad, pris og antall solgte varer fra listen vi nettopp laget; de befinner seg henholdsvis på indeks 1, 2 og 3. Husk at disse er strenger, så vi må konvertere dem til tall før vi kan bruke dem i regneoperasjoner (bruk int() -funksjonen).
    • Regn ut inntekten for hver vare og legg den til income.
  • Når du er ferdig med å behandle alle linjene, returner income

Obligatorisk

Genetisk overlapp
Obligatorisk
Nivå E
En organisme

Du jobber som forsker ved et biologisk institutt som studerer genetisk mangfold blant ulike arter. Instituttet har samlet DNA-prøver fra et bredt spekter av organismer, fra mikrober til planter, insekter og større dyr, inkludert mennesker. Prøvene har blitt analysert og lagret i filer som inneholder DNA-sekvenser, som er korte biter av DNA som kan brukes til å identifisere en spesifikk egenskap eller markør i organismen.

Din oppgave er å utvikle en funksjon som kan sammenligne to slike filer for å finne ut hvor mye genetikk de har til felles. Dette vil gi innsikt i det genetiske slektskapet mellom de to organismene, og kan hjelpe forskere med å forstå evolusjonære forhold og artenes opprinnelse.

I en fil genetics.py, skriv en funksjon count_overlap som tar inn to filnavn path1 og path2 som begge viser til filer med genetisk data for hver sin organisme. Hver linje i filene inneholder en DNA-sekvens, og ingen sekvens forekommer mer enn én gang i samme fil. Funksjonen count_overlap skal returnere antall sekvenser som er inkludert i begge datasettene. Husk at filene kan ha newline på slutten, og denne telles ikke som en DNA-sekvens.

Et utdrag av filene path1 eller path2 kan se for eksempel slik ut:

CGAAGAGTGTATGACTAGGC
TATTAGGTGCATGTCTCCTA
CTTTCGCCATTTATTTAAAA
GAGGGGTTAGAGTGCCTAGT

For å teste koden din kan du laste ned genetics.zip og legge alle filene inn i arbeidsmappen din, og så lime inn koden under på slutten av genetics.py:

print('Tester count_overlap... ', end='')
# Tester funksjonalitet:
# Se gjerne på sample filene direkte, det er enkelt å se hvor mange sekvenser som er i begge filene
expected = 2
actual = count_overlap('sample1.txt', 'sample2.txt')
assert expected == actual, 'sample\'ene har ' + ('mer' if actual > expected else 'mindre') + ' felles enn forventet'

# Tester hastighet (testen tar veldig lang tid ved feil løsning):
expected = 100001 # Det er antatt at du fjerner newline fra slutten av filen, ellers er svaret 100002
actual = count_overlap('id1.txt', 'id2.txt')
assert expected == actual, 'id\'ene har ' + ('mer' if actual > expected else 'mindre') + ' felles enn forventet'
print("OK")

Merk at selv den riktige løsningen kjører ofte på mer enn 1 sekund avhengig av maskinen den kjøres på, men om løsningen din tar mer enn 10 sekunder er det sannsynligvis for treigt.

  • Les inn filene til lister med dna-sekvenser.

  • Gjør den første listen om til en mengde.

  • Gå gjennom den andre listen og tell opp hvor mange av sekvensene som også er i den første.

  • Returner antall sekvenser som er i begge listene.

Handleliste
Obligatorisk
Nivå E

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

Del A

Skriv funksjonen shopping_list_to_dict med en parameter shopping_list som er en streng som innholder en handleliste. Funksjonen skal returnere et oppslagsverk med varer som nøkler og antall som verdier. Du kan anta at strengen består av flere linjer, hvor hver linje (som ikke er tom) består av først et heltall, deretter et mellomrom, og deretter navnet på varen (uten mellomrom).

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

print("Tester shopping_list_to_dict... ", end="")
shopping_list = """\
2 brød
3 pizza
10 poteter
1 kaffe
1 ost
14 epler
"""
shopping_list_as_dict = shopping_list_to_dict(shopping_list)
assert({
    "brød": 2,
    "pizza": 3,
    "poteter": 10,
    "kaffe": 1,
    "ost": 1,
    "epler": 14,
} == shopping_list_as_dict)
print("OK")

  • Begynn med å opprette et tomt oppslagsverk (som skal returneres på slutten av funksjonen).

  • Bruk en løkke over hver av linjene i strengen

  • Inne i løkken, sjekk at linjen ikke er den tomme strengen (hvis den er det, bruk f. eks. continue for å hoppe over denne linjen og fortsette med neste).

  • Inne i løkken, bruk linjen num, food_name = line.split(" ") for å dele opp strengen i to biter; da blir num en variabel som holder en streng med antallet (f. eks. "2"), og food_name blir en streng som inneholder navnet på maten (f. eks. "brød").

  • I oppslagsverket, legg til food_name som en nøkkel med verdien int(num).

Del B

Skriv funksjonen shopping_list_file_to_dict med en parameter path som er en streng som representerer en filsti til en fil som inneholder en handleliste.

For å teste funksjonen, last ned filen handleliste.txt og legg den i samme mappe programmet kjøres fra (husk at dette ikke nødvendigvis er samme mappe hvor shopping_list.py ligger med mindre du har åpnet VSCode i den samme mappen).

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

print("Tester shopping_list_file_to_dict... ", end="")
shopping_list_as_dict = shopping_list_file_to_dict("handleliste.txt")
assert({
    "brød": 2,
    "pizza": 3,
    "poteter": 10,
    "kaffe": 1,
    "ost": 1,
    "epler": 13,
} == shopping_list_as_dict)
print("OK")

Denne funksjonen består av 1-3 linjer med kode avhengig av hvor kompakt du skriver.

  • Begynn med å lese inn hele filen som en enkelt streng.

  • Bruk denne strengen og kall på funksjonen du skrev i forrige deloppgave.

  • Returner resultatet.

Kraftige jordskjelv
Obligatorisk
Nivå E

Denne oppgaven består av tre deler. Skriv funksjoner til alle deloppgaver i én felles fil, high_impact.py.

Eksempelet under er en forkortet og forenklet oversikt over registrerte jordskjelv i CSV format hentet fra CORGIS.

id;location;impact;time
nc72666881;California;1.43;2016-07-27 00:19:43
us20006i0y;Burma;4.9;2016-07-27 00:20:28
nc72666891;California;0.06;2016-07-27 00:31:37

I dennne oppgaven skal vi skrive et program som leser inn en CSV-fil med formatet over, og produserer en ny fil som inneholder alle de rekkene i den første filen hvor impact er større enn en gitt verdi.

Det finnes biblioteker som gjør håndtering av CSV-filer enklere, og det skal vi se mer på senere i kurset. I denne oppgaven skal vi derimot ikke importere biblioteker for CSV-håndtering, men kun bruke standard streng-metoder og kode vi har skrevet selv.

Del A

Skriv en funksjon get_impact(line) som får én enkelt linje (en streng) som input, og som returnerer impact-kolonnen i den linjen som et flyttall. Dersom det er noe feil med input som gjør at det ikke er mulig å se hvilken styrke jordskjelvet har, skal funksjonen returnere None, men ikke krasje.

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

print("Tester get_impact... ", end="")
assert 1.43 == get_impact("nc72666881;California;1.43;2016-07-27 00:19:43")
assert 4.9 == get_impact("us20006i0y;Burma;4.9;2016-07-27 00:20:28")
assert None is get_impact("us20006i0y;Burma;not_a_number;2016-07-27 00:20:28")
print("OK")

Del B

Skriv en funksjon filter_earthquakes(earthquake_csv_string, threshold) som tar inn en streng earthquake_csv_string med CSV-data på formatet vist over, samt et flyttall threshold. Funksjonen skal returnere en streng på samme format, men hvor linjer med impact strengt lavere enn threshold -verdien og linjer hvor det ikke er gyldig impact-verdi ikke er inkludert.

Test koden din ved å legge til følgende linjer nederst i filen:

print("Tester filter_earthquakes... ", end="")
input_string = """\
id;location;impact;time
nc72666881;California;1.43;2016-07-27 00:19:43
us20006i0y;Burma;4.9;2016-07-27 00:20:28
nc72666891;California;0.06;2016-07-27 00:31:37
nc72666892;California;not_a_number;2016-08-23 03:21:18
"""
# Test 1
expected_value = """\
id;location;impact;time
nc72666881;California;1.43;2016-07-27 00:19:43
us20006i0y;Burma;4.9;2016-07-27 00:20:28
"""
actual_value = filter_earthquakes(input_string, 1.1)
assert expected_value.strip() == actual_value.strip()

# Test 2
expected_value = """\
id;location;impact;time
us20006i0y;Burma;4.9;2016-07-27 00:20:28
""" 
actual_value = filter_earthquakes(input_string, 3.0)
assert expected_value.strip() == actual_value.strip()

# Test 3
expected_value = """\
id;location;impact;time
"""
actual_value = filter_earthquakes(input_string, 5.0)
assert expected_value.strip() == actual_value.strip()
print("OK")

  • Benytt en for-løkke og .splitlines -metoden for å gå igjennom alle linjene med data. Husk at den første linjen må behandles annerledes

  • Bruk funksjonene fra forrige deloppgave for å finne ut om en gitt linje skal legges til i resultatet eller ikke.

  • Husk å legge til linjeskift.

Del C

Skriv en funksjon filter_earthquakes_file(source_filename, target_filename, threshold) som tar inn navnet på to filer, samt en grenseverdi. Funksjonen skal gjøre det samme som i forrige deloppgave, men leser inn data fra source_filename, og skriver ut data til target_filename.

For å teste funksjonen, last ned earthquakes_simple.csv og legg den i mappen hvor du kjører programmet fra. Legg deretter til koden under nederst i filen:

print('Tester filter_earthquakes_file... ', end='')

def read_file(path):
    with open(path, 'rt', encoding='utf-8') as f:
        return f.read()

filter_earthquakes_file('earthquakes_simple.csv',
                        'earthquakes_above_7.csv', 7.0)
expected_value = '''\
id;location;impact;time
us100068jg;Northern Mariana Islands;7.7;2016-07-29 17:18:26
us10006d5h;New Caledonia;7.2;2016-08-11 21:26:35
us10006exl;South Georgia Island region;7.4;2016-08-19 03:32:22
'''
actual_value = read_file('earthquakes_above_7.csv')
assert expected_value.strip() == actual_value.strip()
print('OK')

# Manuell test: Finn earthquakes_above_7.csv, åpne og se at innholdet stemmer
Kvidoria
Obligatorisk
Nivå D

Den store Kvidoria har skrevet en ny episk roman. Du er ansatt hos forlaget som skal gi ut boken, og de er opptatt av et levende og variert språk. Redaktøren ber deg derfor om å lage en funksjon som finner de n mest vanlige ordene i romanen, slik at de kan fortelle Kvidoria at romanen er for ensformig og at disse ordene bør variere mer. (Kanskje Kvidoria ikke er den språkmesteren vi trodde.)

For eksempel, hvis filen pusur.txt inneholder teksten

Det var en katt som het Pusur. Pusur var snill. Pusur var en katt.

skal funksjonen ved n = 2 returnere ['pusur', 'var'] fordi ordene pusur og var forekommer tre ganger hver, og pusur kommer før var i alfabetet.

Strategien vår vil være å skrive flere hjelpefunksjoner som vi kan sette sammen for å løse oppgaven. Det blir kjørt automatisk tester på funksjonene dine, så det er viktig at du følger denne strategien.


Del A: Les filen og tell hvor mange ganger hvert ord forekommer

I kvidoria.py, skriv en funksjon get_word_count som tar inn et filnavn path, og returnerer et oppslagsverk som teller hvor mange ganger hvert ord forekommer i filen.

Først lager vi en hjelpemetode cleaned_text som tar inn en tekststreng text og returnerer en streng hvor du har fjernet alle tegn som ikke er bokstaver, mellomrom eller linjeskift. Hint:

  • I funksjonen kan du opprette en legal_chars variabel som inneholder alle bokstaver, mellomrom og linjeskift. Denne kan du bruke til å filtre ut uønskede tegn.

I funksjonen get_word_count kan du gjøre følgende:

  • Les filen og lagre alt innholdet i filen i en streng

  • Gjør hele teksten om til lowercase

  • Bruk cleaned_text for å fjerne alle tegn som ikke er bokstaver, mellomrom eller linjeskift

  • Del opp teksten i en liste av ord

  • Opprett et oppslagsverk words og bruk en løkke for å telle hvor mange ganger hvert ord forekommer i listen

Test funksjonen din ved å legge til pusur.txt i samme mappe som kvidoria.py og kopiere denne testen inn i kvidoria.py (legg testen inn etter funksjonen get_word_count).

print("Tester get_word_count... ", end="")
expected = {
    'det': 1,
    'var': 3,
    'en': 2,
    'katt': 2,
    'som': 1,
    'het': 1,
    'pusur': 3,
    'snill': 1
}
actual = get_word_count("pusur.txt")
assert(actual == expected)
print("OK")


Del B: Skriv en funksjon som finner og fjerner det mest vanlige ordet i oppslagsverket

I kvidoria.py, skriv en destruktiv funksjon pop_most_common_word som tar inn et oppslagsverk word_count og fjerner og returnerer det ordet som forekommer mest. Dersom to ord forekommer like mange ganger, skal det ordet som kommer først i alfabetet returneres.

For eksempel, hvis du gir dette oppslagsverket som argument til funksjonen:

my_word_count = {
    'som': 1,
    'pusur': 3,
    'var': 3,
    'katt': 2,
    'het': 1,
    'snill': 1,
    'en': 2,
    'det': 1
}

skal kallet most_common_word(my_word_count) returnere 'pusur' fordi 'pusur' og 'var' forekommer tre ganger hver, og 'pusur' kommer før 'var' i alfabetet. Oppslagsverket vil etter funksjonskallet se slik ut fordi 'pusur' er fjernet:

{
    'som': 1,
    'var': 3,
    'katt': 2,
    'het': 1,
    'snill': 1,
    'en': 2,
    'det': 1
}

I funksjonen most_common_word kan du gjøre følgende:

  • Lag en variabel max_word som er første ordet i word_count.keys()

  • For hvert ord i oppslagsverket:

    • Hvis ordet forekommer flere ganger enn max_word, sett max_word til å være det ordet.
    • Hvis ordet forekommer like mange ganger som max_word, sett max_word til å være det ordet bare hvis det kommer før max_word i alfabetet.
  • Fjern max_word fra oppslagsverket

  • Returner max_word

Test funksjonen din ved å kopiere denne testen inn i kvidoria.py (legg testen inn etter funksjonen pop_most_common_word).

print("Tester pop_most_common_word... ", end="")
my_word_count = {
    'som': 1,
    'pusur': 3,
    'var': 3,
    'katt': 2,
    'het': 1,
    'snill': 1,
    'en': 2,
    'det': 1
}

# Test 1
expected = 'pusur'
actual = pop_most_common_word(my_word_count)
assert actual == expected, f"pop_most_common_word returnerte {actual}, forventet {expected}"
assert 'pusur' not in my_word_count, "'pusur' er fortsatt i oppslagsverket etter test 1"

# Test 2
expected = 'var'
actual = pop_most_common_word(my_word_count)
assert actual == expected, f"pop_most_common_word returnerte {actual}, forventet {expected}"
assert 'var' not in my_word_count, "'var' er fortsatt i oppslagsverket etter test 2"

# Test 3
expected = 'en'
actual = pop_most_common_word(my_word_count)
assert actual == expected, f"pop_most_common_word returnerte {actual}, forventet {expected}"
assert 'en' not in my_word_count, "'en' er fortsatt i oppslagsverket etter test 3"

print("OK")


Del C: Skriv en funksjon som finner de n mest vanlige ordene

I kvidoria.py, skriv en funksjon n_common_words som tar inn et oppslagsverk word_count og et heltall n, og returnerer en liste med de n mest vanlige ordene i oppslagsverket.

For eksempel, hvis oppslagsverket my_word_count er som i forrige deloppgave, skal kallet common_words(my_word_count, 2) returnere ['pusur', 'var'].

I funksjonen common_words kan du gjøre følgende:

  • Lag en tom liste common_words

  • Lag en løkke som kjører n ganger, hvor hver iterasjon fjerner det mest vanlige ordet fra oppslagsverket og legger det til i common_words.

Test funksjonen din ved å kopiere denne testen inn i kvidoria.py (legg testen inn etter funksjonen n_common_words).

print("Tester n_common_words... ", end="")
my_word_count = {
    'som': 1,
    'pusur': 3,
    'var': 3,
    'katt': 2,
    'het': 1,
    'snill': 1,
    'en': 2,
    'det': 1
}

# Test 1
expected = ['pusur', 'var']
# Vi lager en kopi av oppslagsverket med dict() slik at neste test også funker
actual = n_common_words(dict(my_word_count), 2) 
assert actual == expected

# Test 2
expected = ['pusur', 'var', 'en', 'katt', 'det', 'het', 'snill', 'som']
actual = n_common_words(dict(my_word_count), 8)
assert actual == expected

print("OK")


Del D: Sett sammen funksjonene

I kvidoria.py, skriv en funksjon common_words som tar inn et filnavn path og et heltall n, og returnerer en liste med de n mest vanlige ordene i filen i lowercase.

Du har alle hjelpefunksjonene du trenger for å løse denne oppgaven. Bruk get_word_count for å lage et oppslagsverk over ordene i filen, og bruk n_common_words for å finne de n mest vanlige ordene.

Test funksjonen din ved å kopiere denne testen inn i kvidoria.py (legg testen inn etter funksjonen common_words).


# Test 1
print("Tester common_words... ", end="")
expected = ['pusur', 'var']
actual = common_words("pusur.txt", 2)
assert actual == expected

# Test 2
expected = ['pusur', 'var', 'en', 'katt', 'det', 'het', 'snill', 'som']
actual = common_words("pusur.txt", 8)
assert actual == expected

print("OK")

Valgfri

Mulige ord
Valgfri
Nivå C

Denne oppgaven baserer seg på ordspill fra lab4. Vi anbefaler at du har gjort den oppgaven før du begynner på denne.

I filen possible_words.py skriv en funksjon possible_words_from_file(path, letters) som tar inn en filsti path til en fil som inneholder en ordliste med ett lovlig ord på hver linje, samt en bokstavsamling letters. La funksjonen returnere en liste med alle ord man kan lage av de gitte bokstavene.

For å teste funksjonen kan du laste ned den offisielle ordlisten fra Norsk Scrabbleforbund (nsf2022.txt) og legge den i samme mappe possible_words.py kjøres fra.

Husk at hvilken mappe du kjører fra ikke alltid er den samme mappen hvor programmet ligger; men dersom du åpner VSCode i samme mappe som skriptet ditt, vil dette også være den mappen du kjører programmet fra.

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

print("Tester possible_words_from_file... ", end="")
assert(['du', 'dun', 'hu', 'hud', 'hun', 'hund', 'nu', 'uh']
        == possible_words_from_file("nsf2022.txt", "hund"))

# Ekstra test for varianten hvor det er wildcard i bokstavene
# assert(['a', 'cd', 'cv', 'e', 'i', 'pc', 'wc', 'æ', 'å']
#         == possible_words_from_file("nsf2022.txt", "c*"))
print("OK")
God stil
Valgfri
Nivå C

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

Del A

Tradisjonelt regnes 80 tegn for å være den maksimale lengden på en linje for å ha en god kodestil. Selv om moderne skjermer er i stand til å vise flere tegn på en linje, regnes grensen på 80 fremdeles for å være en god tommelfingerregel, og er for eksempel en del av Python sin offisielle stil-guide PEP 8.

Skriv funksjonen good_style(source_code) som returnerer True hvis alle linjene i strengen source_code er mindre enn eller lik 80 tegn, False ellers.

Merk at grensen på 80 tegn inkluderer selve linjeskift-symbolet på slutten av linjen, slik at det i praksis blir maksimalt 79 tegn på hver linje.

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

print("Tester good_style... ", end="")
assert(good_style("""\
def distance(x0, y0, x1, y1):
    return ((x0 - x1)**2 + (y0 - y1)**2)**0.5
"""))
assert(good_style((("x" * 79) + "\n") * 20))
assert(not good_style("x" * 80))
assert(not good_style((("x" * 79) + "\n") * 5 +
                      (("x" * 80) + "\n")     +
                      (("x" * 79) + "\n") * 5))
print("OK")

Benytt en løkke som itererer over alle linjene i en streng. string.splitlines() kan hjelpe her. Pass på å telle med linjeskiftene.

Dersom vi blir ferdige med løkken uten å finne en eneste linje som er for lang, er svaret True.

Del B

Skriv funksjonen good_style_from_file(filename) som leser inneholdet i filen filename og returerer True hvis inneholdet har god kodestil (alle linjene har mindre enn eller lik 80 tegn), False hvis ikke. Kjør koden din på filene test_file1.py, test_file2.py og test_file3.py.

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

print("Tester good_style_from_file... ", end="")
assert(good_style_from_file("test_file1.py"))
assert(not good_style_from_file("test_file2.py"))
assert(not good_style_from_file("test_file3.py"))
print("OK")
Fantasy Game Inventory
Valgfri
Nivå B

Fantasy Game Inventory er tatt fra boken https://automatetheboringstuff.com/2e/chapter5/.

Du lager et fantasy-videospill. Datastrukturen for å modellere spillerens beholdning skal være en dictionary hvor nøklene er strings som beskriver tingene i beholdningen, og verdiene er integers som forteller hvor mange av hver ting spilleren har. For eksempel, verdiene i dictionary {'rope': 1, 'torch': 6, 'gold coin': 42, 'dagger': 1, 'arrow': 12} betyr at spilleren har 1 rope, 6 torches, 42 gold coins, osv.

I filen fantasy_game_inventory.py skriv funksjonen display_inventory(d) som tar inn en dictionary av beholdningen d. Funksjonen skal returnere en streng som beskriver hele innholdet i beholdningen samt den totale antall gjenstander i beholdningen i formatet vist i eksempelkjøringen nedenfor.

stuff = {"rope": 1, "torch": 6, "gold coin": 42, "dagger": 1, "arrow": 12}
print(display_inventory(stuff))
Inventory:
1 rope
6 torch
42 gold coin
1 dagger
12 arrow

Total number of items: 62
Dragon Hoard
Valgfri
Nivå B

Dragon Hoard er tatt fra boken https://automatetheboringstuff.com/2e/chapter5/.

Skatten til en beseiret drage er representert som en liste med strenger som dette: dragon_loot = ['gold coin', 'dagger', 'gold coin', 'gold coin', 'ruby']

I filen dragon_hoard.py skriv en (ikke-destruktiv) funksjon som heter add_to_inventory(inventory, added_items), hvor inventory parameteren er en dictionary av spillerens beholdning og added_items parameteren er en liste som f.eks dragon_loot.

Funksjonen add_to_inventory() skal returnere en dictionary som representerer den oppdaterte beholdningen. Merk at listen over tilleggsartikler kan inneholde flere av samme element.

Om du kaller display_inventory() etter å ha oppdatert beholdingen med add_to_inventory() funksjonen skal du få ut likt som nedenfor:

from fantasy_game_inventory import display_inventory 

inv = {"gold coin": 42, "rope": 1}
dragon_loot = ["gold coin", "dagger", "gold coin", "gold coin", "ruby"]
updated_inv = add_to_inventory(inv, dragon_loot)
print(display_inventory(updated_inv))
Inventory:
45 gold coin
1 rope
1 dagger
1 ruby

Total number of items: 48
Marsboer-DNA
Valgfri
Nivå A

Denne oppgaven er tatt fra https://open.kattis.com/problems/martiandna.

Menneskelig DNA kan representeres som en lang streng over et alfabet av størrelse fire (A, C, G, T), der hvert symbol representerer en distinkt nukleobase (henholdsvis; adenin, cytosin, guanin og tymin).

For marsboere er ting imidlertid litt annerledes; forskning utført på den siste marsboeren fanget av NASA har vist at mars-DNA består av \(K\) forskjellige nukleobaser! Mars-DNA kan dermed representeres som en streng over et alfabet av størrelse \(K\).

En viss forskningsgruppe som er interessert i å utnytte marsboer-DNA (til videre forskning) har bedt om å få en enkelt kontinuerlig del av en mars-DNA-streng. For \(R\) \((R \leq K)\) av nukleobasene har de spesifisert en minimumsmengde av hvor mange de trenger av den aktuelle nukleobasen å være tilstede i prøven deres.

Vi er interessert i å finne den korteste delstrengen av DNA som tilfredsstiller deres krav.

I filen martian_dna.py skriv en funksjon martian_dna_subsequence(dna) som tar in en filsti dna som input parameter. Den første linjen i filen dna inneholder tre heltall \(N\), \(K\) og \(R\) \((1 \leq R \leq K \leq N)\), som angir henholdsvis den totale lengden på mars-DNA, alfabetets størrelse og antall nukleobaser som forskerne har et minimumskrav på. Den andre linjen inneholder \(N\) mellomromseparerte heltall: den komplette DNA-strengen. Den \(i\)-te av disse heltallene, indikerer hvilken nukleobase som er i den \(i\)-te posisjonen av DNA-strengen. Hver nukleobase vil forekomme minst én gang i DNA-strengen. Hver av de følgende \(R\) linjene inneholder to heltall \(B\) og \(Q\) \((0 \leq B < K, 1\leq Q \leq N)\) som representerer henholdsvis en nukleobase og minimum ønsket antall forekomster av den neukleobasen. Ingen nukleobase vil bli oppført mer enn én gang i disse \(R\) linjene.

Funksjonen skal returnere et enkelt heltall, lengden på den korteste kontinuerlige delstrengen av DNA som tilfredsstiller forskernes krav. Hvis ingen slik delstreng eksisterer, skal programmet returnere “impossible”.

Kjør koden din på filene martian1.txt, martian2.txt og martian3.txt.

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

print("Tester martian_dna_subsequence... ", end="")
assert(martian_dna_subsequence("martian1.txt") == 2)
assert(martian_dna_subsequence("martian2.txt") == 7)
assert(martian_dna_subsequence("martian3.txt") == "impossible")
print("OK")