Lab4

Forberedelser
Innlevering og automatisk retting

Oppgave 1

I filen uke_04_oppg_1.py skriv funksjonen insert_at(source_string, index, insertion_string) for å sette inn en insertion_string i en eksisterende source_string på en gitt posisjon index.

print("Tester insert_at... ", end="")
assert("XYabcd" == insert_at("abcd", 0, "XY"))
assert("abXYcd"== insert_at("abcd", 2, "XY"))
assert("abcdXY"== insert_at("abcd", 4, "XY"))
print("OK")

  • Les om beskjæring i kursnotater om strenger. Sørg for å forstå hvordan indekseringen virker.

  • Del opp source_string i to biter, første del og andre del, ved hjelp av beskjæring.

  • Konkatener første del, insertion_string og andre del.

Oppgave 2

I filen uke_04_oppg_2.py skriv funksjonen rotate_string(s, k) som tar en streng s og et positivt heltall k. Funksjonen returnerer strengen s rotert k plasser til venstre.

print("Tester rotate_string... ", end="")
assert(" World!Hello" == rotate_string("Hello World!", 5))
assert("BCdefghiA" == rotate_string("ABCdefghi", 1))
assert("defghiABC" == rotate_string("ABCdefghi", 3))
assert("bar" == rotate_string("bar", 0))
assert("arb" == rotate_string("bar", 1))
assert("rba" == rotate_string("bar", 2))
assert("bar" == rotate_string("bar", 3))
assert("arb" == rotate_string("bar", 4))
assert("arb" == rotate_string("bar", 304))
print("OK")

Alternativ A:

  • Les om beskjæring i kursnotater om strenger. Sørg for å forstå hvordan indekseringen virker.
  • Opprett en hjelpefunksjon som roterer strengen ett enkelt steg (flytter fremste bokstav bakerst) og returnerer resultatet.
  • Kall på hjelpefunksjonen k ganger.

Optimeringssteg (frivillig for alterantiv A):

  • Legg merke til at en rotasjon på len(s) bare fører strengen tilbake til utganspunktet. Hvis k er større enn len(s), vil en rotasjon på k gi samme resultat som en rotasjon på k - len(s). Vi kan derfor trekke fra det høyeste heltallet delbart med len(s) som er lavere enn k før vi begynner å rotere (minner dette deg om noe? Hvilken operasjon kan hjelpe oss med å finne det minste antallet rotasjoner som trengs?).

Alternativ B (mer effektiv løsning):

  • Les om beskjæring i kursnotater om strenger. Sørg for å forstå hvordan indekseringen virker.

  • Utfør optimeringssteget beskrevet over.

  • I stedet for å flytte ett og ett symbol, del opp strengen i to biter én gang basert på hvor stor rotasjonen skal være.

  • Konkatener de to bitene i motsatt rekkefølge av slik de stod opprinnelig.

Oppgave 3

I filen uke_04_oppg_3.py skriv funksjonen find_nth_occurence(search_string, character, n) som returerer posisjonen (indeks) til den n-te forekomsten av et symbolet character i strengen search_string. Funksjonen returnerer -1 hvis character-tegnet ikke forekommer n ganger.

print("Tester find_nth_occurence... ", end="")
assert(0 == find_nth_occurence("abcabc", "a", 1))
assert(2 == find_nth_occurence("abcabc", "c", 1))
assert(-1 == find_nth_occurence("abcabc", "x", 1))
assert(3 == find_nth_occurence("abcabc", "a", 2))
assert(5 == find_nth_occurence("abcabc", "c", 2))
assert(-1 == find_nth_occurence("abcab", "c", 2))
print("OK")

Det er mange måter å løse denne oppgaven på. Den enkleste utnytter at strengen vi søker etter alltid er kun ett symbol (engelsk: character).

  • Opprett en variabel som teller hvor mange tilfeller du har funnet så langt

  • Benytt en for-løkke med indeksering over strengen (se avsnitt om løkker i kursnotater om strenger).

  • Sammenlign symbolene fra strengen med symbolet vi leter etter: dersom de er like, øk telleren med én.

  • Dersom telleren er lik n, returner indeksen.

  • Dersom det ikke var funnet nok forekomster når løkker er ferdig, returnerers -1.

Oppgave 4a

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.

I filen uke_04_oppg_4.py 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.

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" * 81))
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. Det finnes eksempler på dette i kursnotater om strenger. Merk at metoden i kursnotatene for å iterere over linjene i en streng ikke inkluderer selve linjeskiftet i løkkevariabelen, så sammenligningen din må ta hensyn til at linjeskiftet kommer i tillegg.

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

Oppgave 4b

I filen uke_04_oppg_4.py 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.

For å teste koden kan du først opprette en fil test_file1.py i samme mappen som du kjører uke_04_oppg_4.py fra, med følgende innehold:

def distance(x0, y0, x1, y1):
    return ((x0 - x1)**2 + (y0 - y1)**2)**0.5

Opprett test_file2.py med følgende innehold:

def point_in_rectangle(x0, y0, x1, y1, x2, y2):
    return (min(x0, x1) <= x2 <= max(x0, x1)) and (min(y0, y1) <= y2 <= max(y0, y1))

Og filen test_file3.py med følgende innehold:

def point_in_rectangle(x0, y0, x1, y1, x2, y2):
    return (min(x0, x1) <= x2 <= max(x0, x1)) and (min(y0, y1) <= y2 <= max(y0, y1))
def distance(x0, y0, x1, y1):
    return ((x0 - x1)**2 + (y0 - y1)**2)**0.5

Til slutt, legg til disse linjene nederst i uke_04_oppg_4.py:

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"))
assert(good_style_from_file("uke_04_oppg_4.py"))
print("OK")

Merk: For at testene som leser filer skal fungere, er det viktig at du kjører programmet fra samme mappe hvor filene som skal leses ligger. I den siste testen over skal programmet analysere sin egen kildekode; for at denne testen skal fungere når du kjører fra VSCode, er det derfor viktig at du har åpnet VSCode i samme mappe som uke_04_oppg_4.py ligger.

For å åpne en mappe med VSCode, velg File -> Open folder i VSCode og naviger deretter til mappen.

  • Les avsnittet om lesing fra og skriving til fil i kursnotater om strenger. Du kan kopiere og kalle funksjonen som leser fra fil til din egen kode

  • Gjør et kall til funksjonen fra forrige deloppgave etter at du har lest innholdet i filen.

Oppgave 5

I filen uke_04_oppg_5.py skriv funksjonen get_common_symbols(s1, s2) som tar inn to strenger s1 og s2 og returnerer en streng som inneholder alle tegn som er felles for både s1 og s2, i den rekkefølgen de vises i s1. Tegn gjentas ikke i den resulterende strengen. Dersom s1 og s2 ikke har noen felles symboler vil funksjonen returnere en tom streng "".

print("Tester get_common_symbols... ", end="")
assert("" == get_common_symbols("foo", "bar"))
assert("fo" == get_common_symbols("foo", "foo"))
assert("Hvr e,a" == get_common_symbols("Hvor er du, Kari?", 
                                       "Her er jeg, Olav!"))
print("OK")

  • Les avsnittet om operasjoner og funksjoner i kursnotatene om strenger. Prøv å forutsi resultatet av å kjøre koden, og prøv deg frem med å gjøre små endringer i koden slik at du forstår hvordan den virker.

  • Opprett en variabel som representerer resultatet. La den være den tomme strengen i utgangspunktet.

  • Benytt en for-løkke over s1. Dersom symbolet vi ser på finnes i s2 og ikke er lagt til i resultatet fra før, legg symbolet til i resultatet.

Frivillige oppgaver

Oppgavene 6 og 7 er frivillige oppgaver. For studenter som har fått en ny sjanse i en tidligere lab, holder det å gjøre én oppgave (enten 6 eller 7a+b) for å få én “ny sjanse” -lab godkjent. Dersom du gjør begge de frivillige oppgavene, kan du få to “ny sjanse” -laber godkjent. For å få uttelling, er det viktig at du legger inn en kommentar øverst i filen du leverer inn om at du ønsker å bruke denne innleveringen for å få godkjent en tidligere lab. For eksempel:

# Jeg fikk en ny sjanse på lab X og jeg ønsker å benytte
# denne oppgaven for å omgjøre nevnte lab til godkjent.
Oppgave 6 (frivillig)

I filen uke_04_oppg_6.py skriv funksjonen mix(s1, s2) som kombiner to strenger s1 og s1 tegn for tegn. Annethvert tegn skal komme fra s1, og annethvert tegn skal komme fra s2 så lenge det er symboler igjen i begge strengene. Første tegn skal komme fra s1.

Lengen av den returnerte strengen skal være lik summen av lengdene av s1 og s2.


print("Tester mix... ", end="")
assert("a1b2c3" == mix("abc", "123"))
assert("1a2b3c"== mix("123", "abc"))
assert("abc"== mix("abc", ""))
assert("abc"== mix("", "abc"))
assert("aAbBcde" ==mix("abcde", "AB"))
assert("aAbBCDE"== mix("ab", "ABCDE"))
print("OK")

  • Begynn med å opprette en variabel som lagrer resultatet. La den være den tomme strengen "" i første omgang

  • La en for-løkke gå gjennom alle indekser i fra og med 0 opp til lengden av den lengste strengen

  • I hver itearsjon av løkken legges det først til neste symbol fra s1 (hvis det finnes), og så leggest det til neste symbol fra s2 (hvis det finnes)

Oppgave 7 (frivillig)

En CSV-fil er en ren tekst-fil hvor innholdet er organisert i rader og kolonner. Hver rad er skilt med linjeskift, mens hver kolonne er skilt med et spesielt skillesymbol, for eksempel semikolon. Et eksempel på en CSV-fil er:

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

Eksempelet over er en forkortet og forenklet oversikt over registrerte jordskjelv hentet fra CORGIS.

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) i filen uke_04_oppg_7.py som får én enkelt linje som input, og som returnerer impact-kolonnen i den linjen som et flyttall.

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"))
print("OK")

  • Bruk funksjonen vi skrev i oppgave 3 for å finne indeks til andre og tredje opptreden av semikolon i strengen (du kan legge til denne linjen øverst i filen: from uke_04_oppg_3 import find_nth_occurence og deretter bruke funksjonen fritt)

  • Beskjær strengen basert på indeksene du finner. Trenger du å justere litt på dem for å unngå å få med deg semikolon-symboler i den beskjærte strengen?

  • Benytt float -funksjonen for å konvertere til flyttall.

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 ikke er inkludert.

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

print("Tester filter_earthquakes... ", end="")
assert("""\
id;location;impact;time
nc72666881;California;1.43;2016-07-27 00:19:43
us20006i0y;Burma;4.9;2016-07-27 00:20:28
""" == filter_earthquakes("""\
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
""", 1.1))
assert("""\
id;location;impact;time
us20006i0y;Burma;4.9;2016-07-27 00:20:28
""" == filter_earthquakes("""\
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
""", 3.0))
assert("""\
id;location;impact;time
""" == filter_earthquakes("""\
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
""", 5.0))
print("OK")

  • Begynn med å dele strengen opp i to biter: første linje (overskriftene), og resten (selve dataen). Bruk .find -metoden og let etter det første linjeskiftet for å vite hvor du skal dele. (PS: når du deler, er det lurt å dele i to etter linjeskiftet. Det kan kreve litt justering av verdien du får fra .find når du beskjærer. Grunnen til å gjøre dette er primært fordi vi ikke ønsker at selve data’en begynner med et linjeskift).
  • Benytt en for-løkke og .splitlines -metoden for å gå igjennom alle linjene med data.
  • 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. Kopier read_file og write_file -funksjonene fra kursnotatene om strenger for å lese fra og skrive til fil.

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

print("Tester filter_earthquakes_file... ", end="")
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
"""
assert(expected_value == read_file("earthquakes_above_7.csv"))
print("OK")

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