Mengder
Se også offisiell dokumentasjon for set
.
Enkelt eksempel
En mengde (engelsk: set) er en datastruktur som kan holde på mange verdier (i likhet med lister, tupler og oppslagsverk). Det er noen forskjeller:
- verdiene i en mengde har ikke en posisjon, og er derfor ikke tilknyttet en indeks eller nøkkel, og
- det er ikke mulig å ha flere like verdier i en mengde.
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 (muterer mengden)
s.add(6)
# Antall elementer i mengden
print(len(s)) # 4
# Spør om en verdi er i mengden
print(3 in s) # True
print(4 in s) # False
# Se gjennom verdiene i mengden
for x in s:
print(x, end=' ') # 2 3 5 6
print()
# Fjern en verdi fra mengden (muterer mengden)
s.discard(3)
print(s) # {2, 5, 6}
Opprette mengder
# Opprett en tom mengde
s = set()
print(s)
# PS: MISLYKKET forsøk på å opprette en tom mengde:
s = {} # dette oppretter et oppslagsverk, ikke en mengde!
print(type(s))
# Opprett en mengde statisk (med verdier angitt direkte i kildekoden)
s = {2, 3, 5}
print(s)
# Opprett en mengde fra en liste (eller annen samling med elementer)
# Legg merke til at mengden kun inneholder hvert element én gang
a = [2, 3, 3, 5]
s = set(a)
print(s) # {2, 3, 5}
greeting = 'hello'
required_letters = set(greeting)
print(required_letters) # {'h', 'e', 'l', 'o'}
# Mengdeinklusjon.
# Vi tar utgangspunkt i en annen samling (her a), og bruker deretter
# benytte en inklusjons-for-løkke mellom krølleparentesene
a = ['foo', 'bar', 'baz', 'qatchita']
myset = {s[0] for s in a}
print(myset) # {'f', 'b', 'q'}
# Vi kan også legge til en betingelse for inklusjon etter 'if'
myset = {s[0] for s in a if len(s) <= 3}
print(myset) # {'f', 'b'}
Egenskaper ved mengder
Elementene i en mengde har ingen rekkefølge. Når du går igjennom elementene i en mengde med en for-løkke eller du printer ut mengden vil det selvfølgelig være en eller annen rekkefølge, men: du kan ikke gjøre noen antakelser om hvilken rekkefølge dette er. Den kan variere fra maskin til maskin og Python-versjon til Python-versjon og fra kjøring til kjøring.
To mengder er ansett for å være like dersom de inneholder de samme elementene, uavhengig av hvilken rekkefølge elementene ble lagt til i mengdene.
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ølgen kan være ulik fra maskin til maskin
print()
print({2, 3, 5} == {5, 3, 2}) # True
Elementer er unike (én 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}
alias = s
s.add(9)
print(s) # {2, 3, 5, 9}
print(alias) # {2, 3, 5, 9}
Elementene i en mengde må ikke være mulig å mutere.1
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))
import time
time_before = time.time()
for _ in range(trails):
does_contain_minus_one = -1 in a
time_after = time.time()
elapsed_a = (time_after - time_before) * 1000
print(f'Det tok {elapsed_a:.0f}ms å sjekke listen {trails} ganger')
time_before = time.time()
for _ in range(trails):
does_contain_minus_one = -1 in s
time_after = time.time()
elapsed_s = (time_after - time_before) * 1000
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')
Operasjoner på mengder
Det finnes flere måter å manipulere mengder på. For hver av operasjonene her finnes det destruktive metoder som muterer mengden vår, i tillegg finnes også ikke-destruktive alternativer som oppretter en helt nytt objekt i minnet. Det destruktive alternativet vil ofte være mer effektivt med tanke på minnebruk og kjøretid – samtidig er det av og til nødvendig for korrektheten i programmet ditt for øvrig at du benytter en ikke-destruktiv variant.
Se mer detaljer om operasjoner på mengder i den offisielle dokumentasjonen.
Legg til elementer (add/update/union/|
)
# Legg til elementer
# destruktivt (ved mutasjon)
myset = {1, 2}
alias = myset
myset.add(6) # ett
myset.update([2, 8]) # flere
myset |= {1, 2, 9} # flere
print(myset) # {1, 2, 6, 8, 9}
print(alias) # {1, 2, 6, 8, 9}
# Legg til elementer
# ikke-destruktivt (nytt objekt)
myset = {1, 2}
alias = myset
myset = myset.union([2, 8])
myset = myset | {1, 2, 9}
print(myset) # {1, 2, 8, 9}
print(alias) # {1, 2}
Fjerne elementer (remove/discard/difference/-
/mengdeinklusjon)
# Fjern elementer
# destruktivt (ved mutasjon)
myset = {1, 2, 3, 4, 5}
alias = myset
# 'remove' fjerner ett element
# (ikke funnet -> krasjer)
myset.remove(1)
# 'discard' fjerner ett element
# (ikke funnet -> ignorer)
myset.discard(2)
myset.discard(42)
# difference_update/-= fjerner
# flere elementer hvis de finnes
myset.difference_update([4, 6])
myset -= {5, 7, 9}
print(myset) # {3}
print(alias) # {3}
# Fjern elementer
# ikke-destruktivt (nytt objekt)
myset = {1, 2, 3, 4, 5}
alias = myset
# Mengdeinklusjon med betingelse
myset = {x for x in myset if x != 2}
myset = {x for x in myset if x != 42}
# difference/- fjerner elementer
# hvis de finnes
myset = myset.difference([4, 6])
myset = myset - {5, 7, 9}
print(myset) # {1, 3}
print(alias) # {1, 2, 3, 4, 5}
Beholde elementer (intersection/&
)
# Behold kun elementer som er
# i begge mengdene.
# destruktivt (ved mutasjon)
myset = {1, 2, 3, 4, 5}
alias = myset
a = [3, 4, 5, 6, 7]
myset.intersection_update(a)
# myset er nå {3, 4, 5}
myset &= {1, 3, 5, 7, 9}
print(myset) # {3, 5}
print(alias) # {3, 5}
# Behold kun elementer som er
# i begge mengdene.
# ikke-destruktivt (nytt objekt)
myset = {1, 2, 3, 4, 5}
alias = myset
a = [3, 4, 5, 6, 7]
myset = myset.intersection(a)
# myset er nå {3, 4, 5}
myset = myset & {1, 3, 5, 7, 9}
print(myset) # {3, 5}
print(alias) # {1, 2, 3, 4, 5}
Symetrisk forskjell (symmetric_difference/^
)
# Behold kun elementer som er
# i akkurat én av mengdene
# destruktivt (ved mutasjon)
myset = {1, 2, 3, 4, 5}
alias = myset
a = [3, 4, 5, 6, 7]
myset.symmetric_difference_update(a)
# myset er nå {1, 2, 6, 7}
myset ^= {1, 3, 5, 7, 9}
print(myset) # {2, 3, 5, 6, 9}
print(alias) # {2, 3, 5, 6, 9}
# Behold kun elementer som er
# i begge mengdene.
# ikke-destruktivt (nytt objekt)
myset = {1, 2, 3, 4, 5}
alias = myset
a = [3, 4, 5, 6, 7]
myset = myset.symmetric_difference(a)
# myset er nå {1, 2, 6, 7}
myset = myset ^ {1, 3, 5, 7, 9}
print(myset) # {2, 3, 5, 6, 9}
print(alias) # {1, 2, 3, 4, 5}
Frozenset
Det finnes en type mengder som ikke kan muteres, kalt frozenset
. De fungerer nøyaktig som set
, men operasjonene som ville mutert set vil nå enten opprette et nytt objekt eller det vil krasje.
Fordelen med frozenset er at de kan brukes som nøkler i oppslagsverk, eller de kan legges som elementer i andre mengder (siden de ikke kan muteres, tilfredstiller de kravet).
# Opprett et frozenset
myset = frozenset({2, 3, 5})
alias = myset
myset |= {42, 43} # muterer ikke, men oppretter nytt objekt
print(myset) # {2, 3, 5, 42, 43}
print(alias) # {2, 3, 5}
myset.discard(2) # Krasjer
Det er ikke heelt sant at elementene i en mengde ikke kan muteres, men det er en hvit løgn vi lever godt med i INF100. ↩︎