Uke 9 - Dict / Files

Denne uken skal vi se på en ny datastruktur, dict, som er brukt ofte for å lagre strukturert data.

Først, les gjennom https://automatetheboringstuff.com/2e/chapter5/ frem til «Pretty Printing», og se på «Practice Questions 1-7»

Vi skal også lese data fra en fil for første gang her. Se på Section 7.2 og 7.2.1 i Python Tutorial: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

Eksempler

Eksempel 1

Her er et eksempel på en dictionary i Python. I dette eksemplet sier den hvilket husdyr ulike personer har.

Last ned filen her: eksempel_1.py, og kjør koden. Er navnet ’keys’ eller ’values’ i pet? Er dyret ’keys’ eller ’values’ i pet?

Hva gjør koden pet['Alice']? Hva gjør koden pet['Xavier'] = 'Alien'? Hvorfor blir det feil i den siste linjen?

pet = {
    'Alice' : 'Dog',
    'Bob' : 'Cat',
    'Claire' : 'Stick insect',
    'Dan' : 'Crocodile',
    'Eve' : 'Elephant',
    'Fred' : 'Dolphin'
}

print('All pets',pet)

print(pet['Alice'])
print(pet['Eve'])

pet['Xavier'] = 'Alien'

print(pet)

print(pet['Karl']) # KeyError

Eksempel 2

Vi kan bruke .keys(), .values() og .items() sammen med en for-løkke.

Last ned filen her: eksempel_2.py, og kjør koden.

Hva looper vi over når vi bruker en for-løkke med .keys()? Hva om vi bruker .values()? Hva om vi bruker .items()?

Hva skjer i den siste løkken?

pet = {
    "Bob": "Cat",
    "Fred": "Dolphin",
    "Dan": "Crocodile",
    "Claire": "Stick insect",
    "Alice": "Dog",
    "Eve": "Elephant",
}

print("All owners")
for name in pet.keys():
    print(name)

print("\n\n\n")

print("All pets")
for animal in pet.values():
    print(animal)

print("\n\n\n")

print("All pairs")
for p in pet.items():
    print(p)

print("\n\n\n")

print("Unpack the pair tuple")
for name, animal in pet.items():
    print(f"{name} has a pet, and it is a {animal}")

Eksempel 3

Vi kan bruke alle ’immutable data types’ som keys i en dictionary. For eksempel kan vi bruke heltall.

Last ned filen her: eksempel_3.py. Hva gjør koden?

Kjør koden og se om det var riktig.

Hvordan ser number_name ut på slutten av kjøringen?

number_name = {
    0 : 'zero',
    1 : 'one',
    2 : 'two',
    3 : 'three',
}



def print_name(n):
    if n in number_name:
        print(f'{n} is called {number_name[n]}')
    else:
        print(f"I don't know what {n} is called.")
        name = input("Please tell me: ")
        number_name[n] = name

print_name(3)
print_name(56)

print_name(2)
print_name(56)

Eksempel 4

En dictionary kan f.eks brukes for å holde rede på hvor mange vi har av forskjellige ting. Her bruker vi dictionaries til å telle hvor mange ganger vi bruker ulike bokstaver og ord i begynnelsen av ’Alice in wonderland’.

Du finner informasjon om modulen string i Pythons dokumentasjon.

Last ned filen her: eksempel_4.py, og kjør koden. Skjønner du hva som skjer? Hva gjør .setdefault()? (Du kan lese om den i Pythons dokumentasjon.)

I slutten av koden finner vi de 5 vanligste ordene. Prøv å bruk ’option 2’ i stedet. Som vanlig kan du finne informasjon om hvordan sorted() fungerer i Pythons dokumentasjon.

from string import ascii_lowercase

text = """Alice was beginning to get very tired of sitting by her sister
            on the bank, and of having nothing to do: once or twice she had peeped
            into the book her sister was reading, but it had no pictures
            or conversations in it, 'and what is the use
            of a book,' thought Alice 'without pictures or conversation?'"""


            
letter_count = {}

for l in ascii_lowercase: # abcd...z
    letter_count[l] = 0

for let in text:
    let = let.lower()
    if let in letter_count:
        letter_count[let] += 1
    
for let, count in letter_count.items():
    print(f'{let} is used {count:3d} times')

    
print('\n\n\n')


word_count = {}

for word in text.split():
    word = word.lower()
    word_count.setdefault(word, 0)
    word_count[word] += 1

for w, count in word_count.items():
    print(f'{w:14} is used {count:3d} times')


print('\n\n\n')

# The 5 most used words
# need to sort by the value in the (key,value) tuple

# option 1
def get_second(tpl):
    """Return the second element"""
    return tpl[1]

sorted_result = sorted(word_count.items(), key=get_second, reverse=True)


# option 2, python has "get_second" built-in as "itemgetter"
#from operator import itemgetter
#sorted_result = sorted(word_count.items(), key=itemgetter(1), reverse=True)

print('The 5 most common words')
for w, c in sorted_result[:5]:
    print(f'{w:14} is used {c:3d} times')

Eksempel 5

I dette eksemplet skal vi prøve å lese og skrive til en fil. Du må laste ned denne tekstfilen: timemachine.txt

Last ned filen her: eksempel_5.py, og kjør koden. Hva er forskjellen mellom for line in f, f.readlines() og f.read() for å lese fra filen?

Hva skjer når du åpner filen ’numbers.txt’ med mode ’w’ i slutten?

# 3 ways of getting information from a file

with open('timemachine.txt', encoding="utf8") as f:
    for line in f:
        l = len(line)
        print('The line is',l,'long, and starts with')
        print('>>>',line[0:20],'<<<')
        
# ========

with open('timemachine.txt', encoding="utf8") as f:
    all_lines = f.readlines()
    
print(len(all_lines),'lines in the text')

# ==========

with open('timemachine.txt', encoding="utf8") as f:
    all_lines = f.read()
    
print("vvvvvvv")
print(all_lines)
print("^^^^^^^")
    
# ========

with open('numbers.txt', 'w', encoding="utf8") as f:
    for i in range(100):
        f.write(f'{i} is a nice number\n')
        
# now open the file numbers.txt in your editor and see what's inside

# write other files!

Eksempel 6

I dette eksempelet teller vi hvor mange ganger ulike bokstaver og ord blir brukt i hele boken ’Alice in wonderland’. Derfor vil vi lese in teksten fra en fil, istedet for å lime hele teksten inn i filen med python-kode.

Til dette eksemplet må du laste ned denne tekstfilen: alice.txt.

Koden er i denne filen: eksempel_6.py. Kjør koden. Skjønner du hva som skjer?

from string import ascii_lowercase


with open('alice.txt', encoding="utf8") as f:
    text = f.read()


            
letter_count = {}

for l in ascii_lowercase:
    letter_count[l] = 0

for let in text:
    let = let.lower()
    if let in letter_count:
        letter_count[let] += 1
    
for let, count in letter_count.items():
    print(f'{let} is used {count:5d} times')

    
print('\n\n\n')


word_count = {}

for word in text.split():
    word = word.lower()
    word_count.setdefault(word, 0)
    word_count[word] += 1

def get_second(tpl):
    return tpl[1]

sorted_result = sorted(word_count.items(), key=get_second, reverse=True)

N = 15
print(f'The {N} most common words')
for w, c in sorted_result[:N]:
    print(f'{w:14} is used {c:5d} times')

Oppgaver

..note:

Gå gjennom denne ukens innhold før du begynner!

Du får din kopi av oppgavene på git.app.uib.no. Oppgavene skal leveres ved push til det repoet innenfor 2020-10-23 23:59. Du kan pushe så mange ganger du vil før fristen og du får automatisk tilbakemelding hver gang.

Del 1 - Euler 17

https://projecteuler.net/problem=17

If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.

If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used?

NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The use of «and» when writing out numbers is in compliance with British usage.

Skriv en funksjon number_name(N) som tar inn et heltall (opp til 1000) som argument N og returnerer det engelske navnet til tallet som en streng. (Tips: begynn med dict’et som finnes i eksempel 3)

Skriv en funksjon all_numbernames(N) som tar inn et heltall (opp til 1000) som argument N og returnerer den summerte lengden av alle navnene fra 1 til N (sjekk eksemplene fra Euler-teksten)

Skriv en funksjon solve_euler_17() som returnerer den summerte lengden av alle navnene fra 1 til 1000

Del 2 - Filnavn (basert på eksamen 20V)

I løpet av forskningsprosjektet ditt har du samlet inn mange datafiler. Alle filer har .txt format, og de første linjene i hver fil inneholder sted og tidspunkt hvor og når data ble samlet inn. Akkurat nå har filene tilfeldige navn (qwghlm, qwerty, …), som gjør det vanskelig å finne et datasett uten å åpne alle filene. Du vil gjerne gi bedre navn til filene, basert på informasjonen som finnes i hver fil.

For eksempel, en fil som heter «qwghlm.txt» starter med:

Tromso
2020-05-07
3.141 2.7172 4.567 1.234
2.7172 4.567 1.234 9.8165
[... mange flere data-linjer ...]

og filen «qwerty.txt» starter med:

Oslo
2019-06-01
-5.141 8.7272 -4.567 1.2364
12.7172 44.5367 1.234 9.81372
[...]

Skriv en funksjon rename_from_data(filename) som

  • tar inn et filnavn som argument,

  • leser inn filen som er nevnt,

  • tar sted og dato fra de første to linjene

  • lager et nytt filnavn fra dato og sted, med .txt til slutt. Formatet skal være YYYY-MM-DD_PLACENAME.txt

  • lagrer all data fra den opprinnelige filen (uten sted og tid) inn i en ny fil med det nye filnavnet

Skriv en funksjon rename_all(namelist) som tar inn en liste med filnavn og bruker rename_from_data() på hver fil i listen.

I eksempelet vårt inneholder namelist listen [«qwghlm.txt»,»qwerty.txt»]. Funksjonen din skal gå gjennom listen og lagre en ny fil for hver gammel fil. Innholdet av «qwghlm.txt» skal lagres som «2020-05-07_Tromso.txt» og innholdet av «qwerty.txt» skal lagres som «2019-06-01_Oslo.txt».

Det finnes ingen testfiler i mappen, men du kan lage dem selv om du trenger det.

Del 3 - Havnivå 1

Du skal ikke bruke ``import`` her, men du kan legge til flere hjelpefunksjoner

I filen VIK_sealevel_2000.txt finnes målinger av havnivået på et sted, hvor en linje i filen representerer én måling, repetert for hver time i året 2000. Det finnes 8784 linjene i alt, og formatet er

year month day hour height[cm]

Skriv en funksjon read_file(filename) som tar et filnavn av en slik datafil som argument og returnerer en liste av tuples med 5 heltall. I vårt eksempelfil begynner og slutter listen slik:

[
  (2000, 1, 1, 1, 335),
  (2000, 1, 1, 2, 336),
  (2000, 1, 1, 3, 338),
  # ...
  (2000, 12, 31, 22, 337),
  (2000, 12, 31, 23, 338),
  (2000, 12, 31, 24, 339)
]

Alle følgende funksjonene tar inn denne listen som argument.

Skriv en funksjon average(data, month=None) som tar inn datalisten og et argument month med standardverdi None. Hvis month er None, skal funksjonen returnere gjennomsnittet av alle målingene. Hvis month er gitt som et tall 1-12, skal funksjonen returnere gjennomsnittet av alle målingene i den tilsvarende måneden.

Skriv en funksjon add_weekday(data) som tar inn datalisten og returnerer an ny dataliste med 6 kolonner. Den 6. kolonnen skal inneholde ukedag som en streng (Sat Sun Mon Tue Wed Thu Fri). 2000-01-01 var en lørdag (Sat), 2000-12-31 var en søndag (Sun). For eksempeldatasettet ser returnverdien slik ut:

[
  (2000, 1, 1, 1, 335, 'Sat'),
  (2000, 1, 1, 2, 336, 'Sat'),
  (2000, 1, 1, 3, 338, 'Sat'),
  # ...
  (2000, 12, 31, 22, 337, 'Sun'),
  (2000, 12, 31, 23, 338, 'Sun'),
  (2000, 12, 31, 24, 339, 'Sun')
]

Skriv en funksjon average_weekday(data, weekday) som tar inn den utvidete datalisten og et argument weekday som er en av strengene Sat Sun Mon Tue Wed Thu Fri. Funksjonen skal returnere gjennomsnittet av alle målingene for tilsvarende ukedagen.

Del 4 - Havnivå 2

Du skal ikke bruke ``import`` her, men du kan legge til flere hjelpefunksjoner*

Skriv en funksjon max_indexes(xs) som tar inn en liste xs med tall, og returnerer en liste av indeksposisjoner hvor listen har lokale maksimalverdier. Et lokalt maksimum er et tall i listen som har mindre tall før og etter. F.eks har denne listen et lokalt maksimum på posisjon 2 og 7:

[3, 4, 5, 2, 1, 0, 4, 6, 4, 2, 1]
.     ***            ***

Funksjonen skal returnere [2, 7] her. Om flere verdier er like, returnerer vi den siste indexposisjonen: Input [2,3,3,1] har output [2].

Bruk denne funksjonen sammen med read_file(filename) funksjonen og datasett fra del 3 for å finne ut gjennomsnittsavstand mellom to høyvann-episoder. Du kan strukturere koden fritt, slik som du trenger.