2.10. Joukot

Joukko on järjestämätön kokoelma yksilöllisiä alkioita. Jo olemassa olevan arvon lisäämisellä ei ole vaikutusta; iterointi tuottaa kunkin arvon täsmälleen kerran. Joukot ovat oikea työkalu, kun kuuluvuus ja kaksoiskappaleiden poisto ovat tärkeitä eikä järjestyksellä ole väliä.

2.10.1. Joukon luominen

Käytä aaltosulkeita ei-tyhjälle joukolle tai set() tyhjälle:

colours = {"red", "green", "blue"}
empty = set()

Aaltosulkeet näyttävät dict -literaalilta; pelkkä {} on tyhjä sanakirja, ei tyhjä joukko – yksi Pythonin historiallisista vahingoista. Käytä set() tyhjässä tapauksessa.

set() rakentaa myös joukon mistä tahansa iteroitavasta, mikä on tavallinen tapa pudottaa kaksoiskappaleet jonosta:

nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)

Tuloste:

{1, 2, 3, 4}

Tulostusjärjestys voi vaihdella – joukot eivät lupaa iteroida missään tietyssä järjestyksessä.

2.10.2. Joukko vs. sanakirja

Sekä joukot että sanakirjat tallentavat yksilöllisiä alkioita hajautustauluun. Ero on siinä, mitä kukin alkio kantaa mukanaan:

  • dict tallentaa avain-arvo-pareja. Avaimen haku palauttaa sen arvon.

  • set tallentaa pelkät alkiot. Alkion haku kertoo, onko se siellä.

Valinta näiden kahden välillä koskee sitä, tarkoittaako kunkin alkion vieressä oleva arvo mitään:

  • Valitse joukko, kun mikään arvo ei kuulu kunkin alkion viereen – välität vain siitä, onko alkio mukana, tai yhdistät ryhmiä yksilöllisiä alkioita unionilla / leikkauksella.

  • Valitse sanakirja, kun kukin alkio on pariutettu datan kanssa, jonka haku on tarkoitettu noutamaan – konfiguraatiokuvaus, välimuisti tai nimellä avaintettu laskuri.

Nämä kaksi tyyppiä jakavat paljon pintasyntaksia, mistä suurin osa sekaannuksesta johtuu. Erot yhdessä lohkossa:

joukko

sanakirja

sisältää

yksilöllisiä alkioita

yksilöllisiä avaimia, kullakin arvo

täytetty literaali

{1, 2, 3}

{"a": 1, "b": 2}

tyhjä literaali

set()

{}

kuuluvuustesti

x in s

k in d (vain avaimet)

arvon haku

ei käytössä

d[k]

alkion lisäys

s.add(x)

d[k] = v

iterointi

tuottaa alkiot

tuottaa avaimet (käytä d.items() parien saamiseksi)

Epäsymmetria täytetyn ja tyhjän literaalin välillä on sudenkuoppa, joka kannattaa nostaa esiin:

  • Aaltosulkeet, joissa on alkioita sisällä{1, 2, 3} – ovat joukkoliteraali; aaltosulkeet, joissa on avain-arvo-pareja{"a": 1} – ovat sanakirjaliteraali. Jäsennin erottaa ne sen perusteella, mitä sisällä on.

  • Aaltosulkeet, joissa ei ole mitään sisällä{} – ovat tyhjä sanakirja, ei tyhjä joukko. Sanakirjat tulivat ensin; tyhjä literaali kuuluu niille. Tyhjällä joukolla ei ole lainkaan aaltosulkeliteraalia, ja se on kirjoitettava muodossa set().

Yleinen kuvio silloin, kun sanakirjasta luetaan koskaan vain avaimet, on vaihtaa joukkoon – se tekee tarkoituksen ilmeiseksi ja karsii käyttämättömät arvot pois muistista.

2.10.3. Lisääminen ja poistaminen

s = {1, 2, 3}
s.add(4)
s.discard(99)            # silent: 99 not in s
s.remove(2)
print(s)

Tuloste:

{1, 3, 4}

2.10.4. Kuuluvuus

Operaattori in testaa kuuluvuutta. Joukolla se on suunnilleen vakioaikainen koosta riippumatta – mikä on pääasiallinen syy valita joukko list -tyypin sijaan, kun haluat vain kysyä ”onko tämä arvo siellä”:

if "red" in colours:
    print("colour is allowed")

list, jolla on sama sisältö, kävisi läpi alusta joka kerta, mikä on hyvä kymmenelle alkiolle mutta hidas kymmenelletuhannelle.

2.10.5. Joukko-operaatiot

Kaksi joukkoa voidaan yhdistää tavanomaisilla matemaattisilla operaatioilla. Jokaisella on sekä operaattorimuoto että metodimuoto:

  • a | b tai a.union(b) – kaikki kummassa tahansa joukossa.

  • a & b tai a.intersection(b) – vain se, mikä esiintyy molemmissa.

  • a - b tai a.difference(b)a:ssa mutta ei b:ssä.

  • a ^ b tai a.symmetric_difference(b) – yhdessä mutta ei molemmissa.

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)

Tuloste:

{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}

Operaattorimuodot ovat vain luettavissa; metodimuodot hyväksyvät oikealla puolella minkä tahansa iteroitavan, ei vain toista joukkoa (a.union([5, 6])). Valitse se, joka lukeutuu paremmin asiayhteydessä.

2.10.6. Mikä voi mennä joukkoon

Joukon alkioiden on oltava hajautettavia – sama rajoitus kuin dict -avaimilla. int, float, str, bool, bytes ja tuple (kun sen sisältö on itse hajautettavaa) toimivat kaikki. list ja dict eivät; sellaisen lisäämisen yrittäminen nostaa TypeError -poikkeuksen.

2.10.7. frozenset

Tavallinen set on muuttuva: jokainen kutsu add / remove / discard muuttaa oliota paikan päällä. Tuo muuttuvuus estää sitä olemasta hajautettava, joten joukkoa ei voi käyttää dict -avaimena eikä toisen joukon jäsenenä.

frozenset on muuttumaton vastine. Sillä on samat haut ja operaattorit (in, |, &, -, ^) kuin set -tyypillä, mutta ei add / remove -metodeja eikä mitään muuttavia metodeja. Koska mikään ei voi koskaan muuttaa sen sisältöä, frozenset -olion hajautusarvo on hyvin määritelty – joten se on hajautettava:

primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})

palettes = {
    primary: "RGB",
    secondary: "mixed",
}

print(palettes[primary])

Tuloste:

RGB

Rakenna frozenset mistä tahansa iteroitavasta – frozenset() tyhjälle tapaukselle, frozenset(some_set) muuttumattoman tilannekuvan ottamiseksi olemassa olevasta joukosta:

snapshot = frozenset(s)         # immutable copy of s
s.add("new")                    # snapshot does not change

Kaksi yleistä syytä turvautua siihen:

  • Käyttö sanakirjan avaimena tai joukon jäsenenä. Aina kun yksittäinen arvo ei voi tallentaa tarvitsemaasi, frozenset arvoja voi – ”tämän ajurin tukemien piirteiden joukko”, ”tämän profiilin käyttämien nastojen joukko”.

  • Lukitse vakio. Moduulitason frozenset sallituista nimistä ei voi vahingossa muuttua kutsujan toimesta; tavallinen set voi. Suosi frozenset -tyyppiä kaikelle, jonka on tarkoitus olla vain luettavissa luonnin jälkeen.