Les og utforsk
- Basics
- Enkle eksempler
- Opprette oppslagsverk
- Egenskaper ved oppslagsverk
- Operatorer, funksjoner og metoder
- Løkker over oppslagsverk
Denne uken skal vi se på en ny datastruktur, dict, som er ofte brukt for å lagre strukturert data.
Først, les gjennom https://automatetheboringstuff.com/2e/chapter5/ frem til «Pretty Printing», og se på «Practice Questions 1-7»
Basics
Et oppslagsverk (engelsk: dictionary) er en datastruktur hvor man kan slå opp på nøkkelverdier (“keys”) og hente ut en verdi som tidligere har blitt knyttet til denne nøkkelverdien. Tenk på nøkkelverdi som et slags «variabelnavn» og på et oppslagsverk som en samling med variabler.
# Opprett et tomt oppslagsverk
d = dict() # kan også skrives som: d = {}
# Legg til nøkler og verdier
d["my key"] = "my value"
d["name"] = "Arnoldus"
d["age"] = 42
# Hent ut verdiene
print(d["my key"]) # my value
print(d["name"]) # Arnoldus
print(d["age"]) # 42
print(d["age"] + 53) # 95
# Hente ut verdiene, med default-verdi dersom nøkkel ikke eksisterer
print(d.get("age", 9)) # 42
print(d.get("foo", 9)) # 9 ("foo" er ikke en nøkkel i d)
# Endre på en verdi
d["age"] += 1 # kan også skrives som: d["age"] = d["age"] + 1
# Verdien er endret
print(d["age"]) # 43
# Statisk opprettelse av oppslagsverk
d = { "key": "value", "foo": 42, 95: "McQueen", 99: 200 }
print(d["key"]) # value
print(d["foo"]) # 42
print(d[95]) # McQueen
print(d[99]) # 200
print(d["key_does_not_exist"]) # Krasjer
Enkle eksempler
# Oppretter oppslagsverket
country_map = {
'Oslo': 'Østlandet',
'Bergen': 'Vestlandet',
'Drammen': 'Østlandet',
'Stavanger': 'Vestlandet',
'Kristiansand': 'Sørlandet',
}
# Ber bruker om navnet på en by og skriver ut hvor byen ligger
city = input("Skriv inn navnet på en by: ").title() # .title() gjør om til 'Title Case'
print()
if city in country_map:
print(f"{city} er på {country_map[city]}")
else:
print(f"Unnskyld, jeg aner ikke hvor {city} er")
# Oppretter et oppslagsverk
counts = dict()
# Ber brukeren om å skrive tall, og forteller så brukeren hvor mange ganger
# tallet er sett før.
while True:
n = input("Skriv inn et tall (eller ingenting for å avslutte): ")
if (n == ""):
break
n = int(n)
if n in counts:
counts[n] += 1
else:
counts[n] = 1
print("Jeg har nå sett tallet", n, "totalt", counts[n], "ganger.")
print("Ferdig, counts:", counts)
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
Opprette oppslagsverk
# Opprett et tomt oppslagsverk
d1 = dict()
print(d1)
d2 = {}
print(d2)
# Opprett oppslagsverk statisk
d3 = {"foo":"bar", 42:99}
print(d3)
d4 = dict(foo="bar", baz=[1, 2, 3])
print(d4)
# Opprett oppslagsverk fra en liste med tupler på størrelse 2
a = [("ku", 5), ("hund", 98), ("katt", 1), ("kanin", 999), ("mus", 2)]
d5 = dict(a)
print(d5)
Egenskaper ved oppslagsverk
Oppslagsverk knytter nøkler til verdier.
ages = dict()
key = "fred"
value = 38
ages[key] = value # "fred" er nøkkelen, 38 er verdien
print(ages[key])
En nøkkel er unik, og er tilknyttet kun én verdi.
d = dict()
d[2] = 100
d[2] = 200
d[2] = 400
print(d) # { 2:400 }
Rekkefølge betyr egentlig ingenting; men ved iterasjon er det den rekkefølgen nøklene ble først opprettet som teller.
d1 = dict()
d1["a"] = "foo" # Oppretter nøkkelen 'a' først i d1
d1["b"] = "B"
d1["a"] = "A" # Selv om vi endrer på 'a' igjen, er den fremdeles først
print(d1) # {'a':'A', 'b':'B'}
d2 = {"b":"B", "a":"A"} # Nøkkelen 'b' kommer først i d2
print(d2) # {'b':'B', 'a':'A'}
print(d1 == d2) # True, rekkefølge betyr ingenting for likhet
Et oppslagsverk er muterbart.
d1 = { "foo": 42 }
d2 = d1
d2["foo"] = 95
print(d1["foo"]) # 95
Nøklene i et oppslagsverk kan være mange forskjellige slags typer; men de kan ikke være av en muterbar type. Verdiene i et oppslagsverk kan være hva som helst, inkludert muterbare verdier.
d = dict()
d["this key is a string"] = 42
d[95] = "this value has an int as key"
d[("this key", "is", "a tuple")] = ["this", "value", "is", "a", "list"]
d[False] = "booleans are also fine as keys"
d[None] = "even None is OK"
# Hent ut noen verdier
print(d["this key is a string"]) # 42
print(d[False]) # booleans are also fine as keys
# Prøver å bruke en liste som nøkkel
d[["trying", "to", "use", "list", "as", "key"]] = "foo" # Krasjer
Oppslagsverk er svært effektive. Her har vi et eksempel til det som kalles for “profiling”, dvs. måling av kjøretiden. Vi bruker time-biblioteken her for å sammenligne to funksjoner, en som jobber med lister, og en som tar et dict.
# Vi kan bruke en liste av tupler som om det var et oppslagsverk
# Prøv å endre på n (hvor mye data vi har) og se effekten på kjøretiden
n = 200
trials = 100
a = [(i, i) for i in range(n)] + [("foo", 42), ("bar", 95)]
# La oss sammenligne hvor effektivt det er i forhold til et oppslagsverk
d = dict(a)
# Operasjonen vi skal sammenligne:
# Sjekk om vi et gitt et (key, value) -par eksisterer i samlingen
def contains_key_value_pair_list(a, key, value):
return (key, value) in a
def contains_key_value_pair_dict(d, key, value):
return key in d and value == d[key]
# func her er navnet til funksjonen som skal kjøres
def measure_time(obj, func):
import time
start_time = time.time()
for _ in range(trials):
val = func(obj, "foo", 42)
end_time = time.time()
time_taken = (start_time - end_time) * 1000 / trials
return time_taken
time_taken_list = measure_time(a, contains_key_value_pair_list)
print(f"Tid for oppslag på ('foo', 42) med liste: {time_taken_list:.0f}ms")
time_taken_dict = measure_time(d, contains_key_value_pair_dict)
print(f"Tid for oppslag på ('foo', 42) med dict: {time_taken_dict:.0f}ms")
ratio = time_taken_list / time_taken_dict
print(f"For {n=} er oppslagsverk {ratio:.1f} ganger raskere enn lister")
print(f"Prøv med høyere verdi av n for å se større forskjeller")
Operatorer, funksjoner og metoder
Funksjoner og operatorer
d = { "a" : 1, "b" : 2, "c" : 3 }
print(len(d)) # Antall nøkler i oppslagsverket
print("a" in d) # True
print(2 in d) # False (in -operatoren sjekker kun nøkler)
print(2 not in d) # True
print("a" not in d) # False
Metoder for å mutere oppslagsverk
d = { "a" : 1, "b" : 2, "c" : 3, "d" : 4}
# Fjern en nøkkel (og tilhørende verdi)
d.pop("d") # Fjerner nøkkelen uten å bry seg om verdien
value = d.pop("b") # Fjerner nøkkelen og tar vare på verdien
print(value) # 2
print(d) # {'a': 1, 'c': 3}
# Fjern en nøkkel og returner default-verdi dersom nøkkelen ikke finnes
print(d.pop("x", 42)) # 42
print(d.pop("c", 42)) # 3
print(d) # {'a': 1}
# Legg til en nøkkel eller endre dens verdi
d["e"] = 5
d.update({"f": 6})
print(d)
# Slå sammen to oppslagsverk
d2 = {"f": 106, "g": 107}
d.update(d2)
print(f"{d=}") # d bli mutert. Verdier fra d2 overskriver verdier fra d.
print(f"{d2=}") # d2 er urørt
Løkker over oppslagsverk
d = {"foo": 42, "bar": 25, "baz": 95}
# Løkke over nøklene
for key in d:
print(key, end=" ")
print()
# Alternativ løkker over nøklene
for key in d.keys():
print(key, end=" ")
print()
# Løkke over kun verdiene
for value in d.values():
print(value, end=" ")
print()
# Løkke over både nøkler og verdier
for key in d:
value = d[key]
print(f"{key}:{value}", end=" ")
print()
# Alternativ løkke over både nøkler og verdier:
for key, value in d.items():
print(f"{key}:{value}", end=" ")
print()
Mengde
En relatert datatype er mengde (engelsk: set) so, er en datastruktur som kan holde mange verdier, men uten at det finnes noen rekkefølge på verdiene. Verdiene har ingen indeks/posisjon, slik de har i en liste. Med en mengde kan man i hovedsak gjøre fire ting:
- legge en verdi inn i mengden
- fjerne en verdi fra mengden
- spørre om en verdi er i mengden (veldig effektivt!), og
- se gjennom verdiene i mengden.
# Opprett en mengde
s = {2, 3, 5}
# Legg til en verdi i mengden
s.add(6)
# Spør om en verdi er i mengden
print(3 in s) # True
print(4 in s) # False
for x in range(7):
if (x not in s):
print(x, end=" ") # 0 1 4
print()
# Se gjennom verdiene i mengden
for x in s:
print(x, end=" ") # 2 3 5 6
print()
# Fjern en verdi fra mengden
s.remove(3)
print(s) # {2, 5, 6}
Opprette mengder
# Opprett en tom mengde
s = set()
print(s)
# Opprett en mengde fra en liste
a = [2, 3, 5]
s = set(a)
print(s)
# Opprett en mengde statisk
s = {2, 3, 5}
print(s)
# PS: MISLYKKET forsøk på å opprette en tom mengde:
s = {} # dette oppretter et oppslagsverk, ikke en mengde!
print(type(s))
Egenskaper ved mengder
Elementene i en mengde har ingen (forutsigbar) rekkefølge.
s = set()
s.add(2)
s.add(44)
s.add(11)
s.add(5)
s.add(33)
for e in s:
print(e, end=" ") # Rekkefølge er forskjellig fra maskin til maskin
print()
Elementer er unike (en verdi finnes bare én gang i mengden).
# Duplikater blir borte
s = set([2, 2, 2])
print(s) # {2}
print(len(s)) # 1
Mengder kan muteres.
s = {2, 3, 5}
r = s
r.add(9)
print(s) # {2, 3, 5, 9}
Elementene i en mengde må være immutable.
s = set()
s.add(42) # int OK
s.add("foo") # str er OK
s.add(False) # bool er OK
s.add(1.4) # float er OK
s.add((2, 3)) # tupler er OK
print(s)
s.add([2, 3]) # Krasj! lister er IKKE OK (lister kan muteres)
s.add({2, 3}) # Ville også krasjet! (mengder kan også muteres)
Mengder er svært effektive.
# En liste kan brukes for samme formål som en mengde. La oss sammenligne
# hvor effektive de er til oppgaven "spør om en verdi er tilstede"
n = 2000
trails = 1000 # Flere forsøk utjevner forskjeller som skyldes forstyrrelser
a = list(range(n))
s = set(range(n))
# func her er navnet til funksjonen som skal kjøres
def measure_time(obj):
import time
start_time = time.time()
for _ in range(trials):
# try to find -1
val = -1 in obj
end_time = time.time()
time_taken = (start_time - end_time) * 1000 / trials
return time_taken
elapsed_a = measure_time(a)
print(f"Det tok {elapsed_a:.0f}ms å sjekke listen {trails} ganger")
elapsed_s = measure_time(s)
print(f"Det tok {elapsed_s:.0f}ms å sjekke mengden {trails} ganger")
ratio = elapsed_a/elapsed_s
print(f"Mengder var {ratio:.1f} ganger raskere enn lister for {n=}")
print("Prøv større verdi for `n` for å se større forskjeller")