Lab6
Kursnotater for tema som er nye i denne laben:
- En lab er en samling med én eller flere oppgaver.
- Hver lab kan gi opp til 25 poeng.
- Dersom du består alle oppgavene i hovedfeltet, får du 20 poeng. Alt eller ingenting.
- De resterende 5 poengene kan du få ved å gjøre oppgaver fra ekstrafeltet.
- Alle oppgaver rettes automatisk. Hvis du ikke består de automatiske testene, får du ikke poeng.
- Vi vil trekke poeng manuelt dersom vi oppdager at en oppgave åpenbart ikke er løst, eller man forsøker å trikse seg gjennom de automatiske testene.
- Hver lab kan leveres så mange ganger du vil; det er siste innlevering som teller. Du kan umiddelbart se resultatene på de automatiske testene og hvor mange poeng du har fått etter hver innlevering.
Hver lab utgjør 2.5% av den endelige karakteren din i emnet. Du må få til sammen 100 poeng eller mer på labene for å kunne ta eksamen.
Det er veldig viktig at du siterer alle kilder og eventuelle samarbeid. Se mitt.uib for mer informasjon om vår policy på plagiat og samarbeid.
Det er lurt å levere inn mange ganger underveis. Du får umiddelbart en automatisk tilbakemelding på oppgavene, og du kan prøve igjen hvis noe ikke virker.
Innlevering av lab’er foregår via verktøyet Codegrade som du kommer til via mitt.uib.
- Gå til «Oppgåver» i menyen til venstre
- Klikk deg videre til CodeGrade ved å klikke på «Last lab i eit nytt vindauge»
- Last opp filene du har laget for lab’en og trykk på «Submit.»
Fremdeles problemer? Se (merk at videoen er litt gammel, så det kan være noen små forskjeller i hvordan det ser ut nå.)
Ser du ikke knappen du skal trykke på i mitt.uib?
- Dobbeltsjekk at du er innlogget på mitt.uib.
- Prøv å laste siden i nettleseren på nytt.
- Prøv en «force refresh» (F5 eller Ctrl+F5 eller Ctrl+Shift+F5 eller Ctrl+Shift+R eller Command+R eller Command+Shift+R avhengig av nettleser og operativsystem).
- Prøv å tøm cache i nettleseren din (se refreshyourcache.com for hvordan du gjør dette) og prøv på nytt.
- Prøv en annen nettleser.
Filtrer ordliste
I en fil wordlist.py, lag en funksjon filter_wordlist
som har parametre
path
, en streng som angir et filnavn, ogsearch_string
, en streng vi søker etter.
Funksjonen skal returnere en liste med alle ord fra filen som inneholder søkestrengen. Anta at filen inneholder ett ord på hver linje, og at den er lagret i UTF-8.
For eksempel, hvis filen inneholder
data
datasett
database
baser
syrer
bås
og søkestrengen er 'base'
, da skal funksjonene returnere en liste ['database', 'baser']
fordi dette er ordene i filen som inneholder «base» som en del av ordet.
Last først ned sample.txt og ordlisten fra Norsk Scrabbleforbund nsf2022.txt og legg filene i arbeidsmappen din (altså samme mappe du har åpnet VSCode i). Test funksjonen din:
def test_filter_wordlist():
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 inneholdesearch_string
i denne listen etter hvert. - Les innholdet i filen (se notater om filer). 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 medif search_string in line
. - Hvis den er det, legg til linjen i
filtered_words
med.append
-metoden.
- For hver linje, sjekk om
-
Når du er ferdig med løkken, returner
filtered_words
Bibliotek
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).
def test_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 stk '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 stk '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).
def test_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 stk '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)
.
- Fjern boken fra biblioteket. Dette kan gjøres med
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).
def test_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
.
- For hver bok, legg til antallet av boken til
-
Returner
running_total
Personlig postkort
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:
def test_count_letters():
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, 'æ mangler, har du husket utf-8 encoding?'
assert expected == actual
print('OK')
I funksjonen:
- Les inn filen med
open
ogread
. - Fjern all whitespace fra teksten.
- Lag et tomt oppslagsverk.
- 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.
- Returner oppslagsverket.
Gallius' varer
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.
def test_total_income():
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
.
- I utgangspunktet vil hver linje være en streng. Vi kan konverter linjen til en liste av verdier med
-
Når du er ferdig med å behandle alle linjene, returner
income
Handleliste
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:
def test_shopping_list_to_dict():
print('Tester shopping_list_to_dict... ', end='')
arg = '2 brød\n3 pizza\n10 poteter\n1 kaffe\n1 ost\n14 epler\n'
expected = {
'brød': 2,
'pizza': 3,
'poteter': 10,
'kaffe': 1,
'ost': 1,
'epler': 14,
}
actual = shopping_list_to_dict(arg)
assert expected == actual
print('OK')
if __name__ == '__main__':
test_shopping_list_to_dict()
-
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:
def test_shopping_list_file_to_dict():
print('Tester shopping_list_file_to_dict... ', end='')
expected = {
'brød': 2,
'pizza': 3,
'poteter': 10,
'kaffe': 1,
'ost': 1,
'epler': 13,
}
actual = shopping_list_file_to_dict('handleliste.txt')
assert expected == actual
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.
Strengsum
Denne oppgaven består av tre deler. Skriv alle deloppgaver i én felles fil, string_sum.py. Før du gjør oppgaven, les gjerne igjennom notater om å håndtere krasj.
Del A
Skriv en funksjon kalt get_stringsum med en parameter s. Anta at parameteren s er en streng hvor noen av «ordene» representerer tall (ulike ord i strengen er skilt av mellomrom). Funksjonen skal returnere summen av de ordene i strengen som er heltall. Hvis ingen av strengene inneholder heltall, skal funksjonen returnere 0.
Test koden din:
def test_get_stringsum():
print('Testing get_stringsum... ', end='')
assert 6 == get_stringsum('4 2')
assert 9 == get_stringsum('5 -1 3 +2')
assert 11 == get_stringsum('5 - 1 3 + 2')
assert 42 == get_stringsum('42')
assert 42 == get_stringsum('forty-one 42 førtitre')
assert 42 == get_stringsum('foo2 42 2qux 3x1')
assert 0 == get_stringsum('')
assert 0 == get_stringsum('foo bar qux')
assert 0 == get_stringsum('-9- 3+2')
print('OK')
Split-metoden klipper opp en streng ved et gitt symbol, og returnerer en liste av bitene som er igjen.
# Eksempler på bruk av .split()
s = 'foo bar qux'
parts = s.split(' ') # parts er nå en liste ['foo', 'bar', 'qux']
print(parts[1]) # bar
s = '3;9;42'
parts = s.split(';') # parts er nå en liste ['3', '9', '42']
print(parts[0] + parts[2]) # 342
-
Begynn med å benytte
.split()
-metoden for å omgjøre strengen til en liste av ord. -
Bruk en løkke for å gå igjennom alle elementene i listen.
-
Før løkken starter, opprett en variabel som holder den løpende totalsummen initiert med verdien
0
. For hver iterasjon av løkken kan det være vi legger til et nytt tall til denne variabelen. -
Benytt try-except når du forsøker å konvertere strengen til heltall. Dersom omgjøringen feiler, hopp over dette elementet og fortsett videre med neste element i listen.
-
Returner totalsummen når løkken er ferdig.
Del B
I samme fil, skriv en funksjon get_line_with_highest_stringsum med en parameter s som representerer en streng. Funksjonen skal returnere en tuple (i, t, r)
hvor i er linjenummeret i strengen s med den høyeste strengsummen, t er selve strengsummen, og r er linjen som har denne strengsummen. Dersom det er flere linjer med samme høyeste strengsum, skal den tidligste av dem returneres. I denne oppgaven teller vi slik at første linje har linjenummer 1. Se også tester for detaljer.
def test_get_line_with_highest_stringsum():
print('Testing get_line_with_highest_stringsum... ', end='')
arg = '4 2\n3 3\n6 6 6 6 12 6\n'
assert (3, 42, '6 6 6 6 12 6') == get_line_with_highest_stringsum(arg)
arg = '4 99 -98\nfoo 42 qux\nfoo bar quz\n'
assert (2, 42, 'foo 42 qux') == get_line_with_highest_stringsum(arg)
arg = '4 2\n3 3\n'
assert (1, 6, '4 2') == get_line_with_highest_stringsum(arg)
print('OK')
Del C
I samme fil, skriv en funksjon main uten parametre som leser inn et filnavn fra brukeren. Deretter skal funksjonen åpne filen og rapportere hvilken linje som har den høyeste strengsummen. Kall main-funksjonen i if __name__ == '__main__'
-blokken i programmet ditt, slik at når du kjører programmet får brukeren anledning til å bruke det. Eksempelkjøring (med filen stringsums_file.txt):
Filnavn: stringsums_file.txt
Høyeste strengsum er 42, funnet først på linje 2: «foo 42 qux»
DNA-fragmenter
Som du kanskje har hørt, består DNA av en veldig lang tråd av nukleobaser. Hver nukleobase kan representeres som en av bokstavene A, C, G eller T (henholdsvis adenine, cytosine, guanine, og thymine). Det er derfor mulig å representere et individ sitt DNA som en veldig lang streng med bokstavene ACGT. Det er akkurat dette man har gjort i Human Genome Project, hvor man beskriver det fullstendige DNA’et til et «gjennomsnittsmenneske» som en enkelt streng med omtrent 3.1 milliarder symboler. Prosjektet med å lage denne strengen startet i 1990, og ble helt ferdigstilt først i 2022. Om vi skulle lagret hele strengen i en vanlig tekstfil ville den vært 3.1GB (siden ett symbol i en streng normalt krever én byte å lagre).
Et «genom» er en komplett DNA-streng. Dette til forskjell fra en «DNA-sekvens» som ikke nødvendigvis er komplett.
Ulike mennesker har selvfølgelig forskjellige genomer, men forskjellene er egentlig ganske små: de aller fleste posisjoner i genomet er like for alle mennesker. Selv om det er langt mellom dem, vil noen forskjeller likevel alltid forekomme; til og med én-eggede tvillinger er bittelitt forskjellige fra hverandre grunnet naturlige mutasjoner som oppstår som tidlig foster.
Når man tar en DNA-test av for eksempel en spyttprøve, klarer ikke måleinstrumentet som brukes å lese hele genomet på én gang. I stedet klarer den å lese av en liten bit av det, en såkalt «DNA-sekvens». Instrumentet vet dessverre ikke hvilken bit den har lest. Ved hjelp av et dataprogram (som vi nå skal lage) kan vi derimot sammenligne avlesningen fra instrumentet med genomet for gjennonsnittsmennesket, og på den måten kan vi finne i hvilken posisjon den avleste DNA-sekvensen «hører hjemme».
For eksempel, la oss si at gjennomsnittsmennesket sitt genom er strengen
AAACACCCCCGGGGGTGTTTTTTTTTTTTTTTTTTTTTTTTTTTT
og måleinstrumentet vårt brukt på en bestemt spyttprøve har lest av sekvensen
ACACCCCCGGGGATGT
Vi ønsker da å bestemme en plassering av sekvensen vår slik at den passer best mulig inn med gjennomsnittsmennesket sitt genom. I vårt eksempel er best mulige match å plassere sekvensen slik at den starter på index 2 i genomet; da blir det det kun ett avvik fra gjennomsnittsmennesket. Alle andre plasseringer ville gitt flere avvik.
AAACACCCCCGGGGGTGTTTTTTTTTTTTTTTTTTTTTTTTTTTT
|||||||||||| |||
ACACCCCCGGGGATGT
Del A
I filen dna_fragment_match.py skriv en funksjon best_alignment
med to parametre:
genome
en streng som beskriver genomet til et gjennomsnittsmenneske, ogsequence
en (kortere) streng som beskriver en måling gjort av måleinstrumentet.
Funksjonen skal returnere et tall i som beskriver hvilken posisjon i genomet sekvensen mest sannsynlig stammer fra. Dette tallet i skal være indeksen slik at målingen passer best mulig med begynnelsen av genome[i:]
. I eksempelet over, er altså svaret 2.
def test_best_alignment():
print('Testing best_alignment...', end='')
genome = 'AAACACCCCCGGGGGTGTTTTTTTTTTTTTTTTTTTTTTTTTTTT'
sequence = 'ACACCCCCGGGGATGT'
assert 2 == best_alignment(genome, sequence)
genome = 'AAAAAAAAAAAAAAAAACACCCCCGGGGGTGTTTTTTTTTTTTTT'
sequence = 'CCGGGGATGT'
assert 22 == best_alignment(genome, sequence)
genome = 'TTTAAG'
sequence = 'AAGT'
assert 2 == best_alignment(genome, sequence)
print(' OK')
Når du sammenligner to plasseringer, er én plassering bedre enn den andre dersom færre posisjoner må endres i mønsteret for å gjøre det helt likt som den gitte delen av genomet.
PS: du skal aldri returnere så stor i at sekvensen plasseres «utenfor» genomet.
Opprett en hjelpe-funksjon alignment_difference
med parametre genome, sequence, i
som returnerer hvor mange posisjoner som er forskjellig dersom man begynner sammenligningen på posisjon i
i genomet.
def test_aligment_difference():
print('Testing alignment_difference...', end='')
genome = 'AAACCC'
sequence = 'ACC'
assert 2 == alignment_difference(genome, sequence, 0)
assert 1 == alignment_difference(genome, sequence, 1)
assert 0 == alignment_difference(genome, sequence, 2)
assert 1 == alignment_difference(genome, sequence, 3)
print(' OK')
Prøv så alle mulige valg av i i en løkke og ta vare på den som gir lavest aligment_difference.
Del B
I samme fil, skriv en funksjon best_alignment_to_file
med parametere path
og sequence
. Denne funksjonen skal lese inn genomet fra filen gitt ved path
, men ellers fungere som funksjonen i del A.
Siden det fører til unødvendig mye datatrafikk om alle skal laste ned en 3.1GB tekstfil bare for å gjøre lab-oppgaven sin i INF100, har vi i stedet laget et bittelite eksempel human_genome_excerpt.txt på bare 50KB i stedet. Den inneholder selvfølgelig ikke hele det menneskelige DNA’et, bare et lite utvalg: de 49941 første nukleobasene fra det menneskelige genomet (versjon GRCh38.p14).
def test_best_alignment_to_file():
print('Testing best_alignment_to_file...', end='')
path = 'human_genome_excerpt.txt'
assert 30864 == best_alignment_to_file(path, 'AAACAAAGAA')
assert 2097 == best_alignment_to_file(path, 'GAGTGGGATGAGCCATTGTTCATCT')
assert 0 == best_alignment_to_file(path, 'TAACCC' * 18)
assert 49913 == best_alignment_to_file(path, 'CATTTCAGTAGTAATAGGAATCTCCAC')
print(' OK')
Kraftige jordskjelv
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:
def test_get_impact():
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_num;2016-07-27 00:20:28')
print('OK')
if __name__ == '__main__':
test_get_impact()
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:
def test_filter_earthquakes():
print('Tester filter_earthquakes... ', end='')
input_arg = '''\
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_arg, 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_arg, 3.0)
assert expected_value.strip() == actual_value.strip()
# Test 3
expected_value = 'id;location;impact;time\n'
actual_value = filter_earthquakes(input_arg, 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. Så kan du bruke denne funksjonen for å teste:
def test_filter_earthquakes_file():
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\n'
'us100068jg;Northern Mariana Islands;7.7;2016-07-29 17:18:26\n'
'us10006d5h;New Caledonia;7.2;2016-08-11 21:26:35\n'
'us10006exl;South Georgia Island region;7.4;2016-08-19 03:32:22\n'
)
actual_value = read_file('earthquakes_above_7.csv')
assert expected_value.strip() == actual_value.strip()
print('OK')
# Manuell test: Åpne earthquakes_above_7.csv og se at innholdet stemmer
Kvidoria
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).
def test_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 iword_count.keys()
-
For hvert ord i oppslagsverket:
- Hvis ordet forekommer flere ganger enn
max_word
, settmax_word
til å være det ordet. - Hvis ordet forekommer like mange ganger som
max_word
, settmax_word
til å være det ordet bare hvis det kommer førmax_word
i alfabetet.
- Hvis ordet forekommer flere ganger enn
-
Fjern
max_word
fra oppslagsverket -
Returner
max_word
Test funksjonen din:
def test_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'returnerte {actual}, forventet {expected}'
assert 'pusur' not in my_word_count, "'pusur' fortsatt her etter test 1"
# Test 2
expected = 'var'
actual = pop_most_common_word(my_word_count)
assert actual == expected, f'returnerte {actual}, forventet {expected}'
assert 'var' not in my_word_count, "'var' fortsatt her etter test 2"
# Test 3
expected = 'en'
actual = pop_most_common_word(my_word_count)
assert actual == expected, f'returnerte {actual}, forventet {expected}'
assert 'en' not in my_word_count, "'en' fortsatt her 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 icommon_words
.
Test funksjonen din ved å kopiere denne testen inn i kvidoria.py (legg testen inn etter funksjonen n_common_words).
def test_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 bruker en kopi (lages med dict()) for å unngå mutasjon
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).
def test_common_words():
print('Tester common_words... ', end='')
# Test 1
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')
Mulige ord
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
med scrabble-brikker. La funksjonen returnere en liste med alle ord man kan lage av de gitte bokstavbrikkene. Det er ikke lov å bruke samme bokstav i ordene du returnerer flere ganger enn den opptrer i letters.
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:
def test_possible_words_from_file():
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')
if __name__ == '__main__':
test_possible_words_from_file()
God stil
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:
def test_good_style():
print('Tester good_style... ', end='')
assert good_style('''\
def distance(x0, y0, x1, y1):
return ((x0 - x1)**2 + (y0 - y1)**2)**0.5
''') is True
assert good_style((('x' * 79) + '\n') * 20) is True
assert good_style('x' * 80) is False
assert good_style(
(('x' * 79) + '\n') * 5
+ (('x' * 80) + '\n')
+ (('x' * 79) + '\n') * 5
) is False
print('OK')
if __name__ == '__main__':
test_good_style()
Benytt en løkke som itererer over alle linjene i en streng. string.splitlines()
kan hjelpe her. Pass på å ta hensyn til linjeskiftene (se også testene over).
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 med denne funksjonen (gjør et kall til den fra if __name__ == '__main__'
-blokken). Det kan være du må justere filstien litt slik at «nice_style.py» viser til riktig fil, altså viser til samme fil du kjører koden fra:
def test_good_style_from_file():
print('Tester good_style_from_file... ', end='')
assert good_style_from_file('test_file1.py') is True
assert good_style_from_file('test_file2.py') is False
assert good_style_from_file('test_file3.py') is False
assert good_style_from_file('nice_style.py') is True
print('OK')
Fantasy Game Inventory
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
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
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 gruppe som er interessert i å utnytte marsboer-DNA til forskning på kunstig intelligens 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")