2.10. Halmazok

A halmaz (set) egyedi elemek rendezetlen gyűjteménye. Egy már jelen lévő érték hozzáadásának nincs hatása; az iteráció minden értéket pontosan egyszer ad vissza. A halmazok a megfelelő eszközök, amikor a tagság és a duplikációk kiszűrése számít, a sorrend viszont nem.

2.10.1. Halmaz létrehozása

Nem üres halmazhoz használj kapcsos zárójeleket, üreshez pedig a set() függvényt:

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

A kapcsos zárójelek úgy néznek ki, mint egy dict literál; a {} önmagában egy üres dict, nem üres halmaz – ez a Python egyik történelmi balesete. Az üres esetre használd a set() formát.

A set() bármilyen iterálható objektumból is halmazt épít, ami a szokásos módja egy sorozat duplikációinak eldobásához:

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

Kimenet:

{1, 2, 3, 4}

A nyomtatási sorrend változhat – a halmazok nem ígérnek semmilyen meghatározott iterálási sorrendet.

2.10.2. Halmaz kontra szótár

A halmazok és a szótárak egyaránt egyedi elemeket tárolnak egy hash-táblában. A különbség az, hogy az egyes elemek mit hordoznak magukkal:

  • A dict kulcs-érték párokat tárol. Egy kulcs kikeresése visszaadja az értékét.

  • A set csak az elemeket tárolja. Egy elem kikeresése megmondja, hogy ott van-e.

A kettő közötti választás arról szól, hogy az egyes elemek melletti érték jelent-e bármit:

  • Nyúlj a halmaz felé, amikor nincs érték, ami minden elem mellé tartozik – csak az érdekel, hogy az elem jelen van-e, vagy egyedi elemekből álló csoportokat egyesítesz unióval / metszettel.

  • Nyúlj a szótár felé, amikor minden elem párosítva van egy adattal, amelyet a kikeresés hivatott visszaadni – egy konfigurációs leképezés, egy gyorsítótár, egy név szerint kulcsolt számláló.

A két típus sok felszíni szintaxist oszt meg, és innen ered a legtöbb félreértés. A különbségek egy blokkban:

set

dict

tartalmaz

egyedi elemeket

egyedi kulcsokat, mindegyikhez egy értékkel

feltöltött literál

{1, 2, 3}

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

üres literál

set()

{}

tagság vizsgálata

x in s

k in d (csak kulcsok)

érték lekérése

n/a

d[k]

elem hozzáadása

s.add(x)

d[k] = v

iterálás

elemeket ad vissza

kulcsokat ad vissza (párokhoz használd a d.items() metódust)

A feltöltött és az üres literál közötti aszimmetria az a buktató, amelyet érdemes kiemelni:

  • A kapcsos zárójelek elemekkel{1, 2, 3} – halmazliterált alkotnak; a kapcsos zárójelek kulcs-érték párokkal{"a": 1} – szótárliterált. Az értelmező a benne lévők alapján különbözteti meg őket.

  • A kapcsos zárójelek semmivel a belsejükben{} – üres szótárt jelentenek, nem üres halmazt. A szótárak voltak előbb; az üres literál hozzájuk tartozik. Az üres halmaznak egyáltalán nincs zárójeles literálja, és set() formában kell leírni.

Gyakori minta, amikor egy szótárnak csak a kulcsait olvassuk valaha, hogy átváltunk halmazra – ez nyilvánvalóvá teszi a szándékot, és kivágja a felesleges értékeket a memóriából.

2.10.3. Hozzáadás és eltávolítás

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

Kimenet:

{1, 3, 4}

2.10.4. Tagság

Az in operátor tagságot vizsgál. Egy halmazon ez nagyjából konstans időt vesz igénybe a mérettől függetlenül – ez a fő ok, amiért egy halmazt választunk egy list helyett, ha csak azt kell megkérdezni, hogy „benne van-e ez az érték”:

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

Egy ugyanazt a tartalmat tartalmazó list minden alkalommal az elejéről kezdene végigpásztázni, ami tíz elemnél rendben van, de tízezernél lassú.

2.10.5. Halmazműveletek

Két halmaz a szokásos matematikai műveletekkel kombinálható. Mindegyiknek van operátoros és metódusos formája is:

  • a | b vagy a.union(b) – minden, ami bármelyik halmazban szerepel.

  • a & b vagy a.intersection(b) – csak az, ami mindkettőben megjelenik.

  • a - b vagy a.difference(b) – ami a-ban van, de b-ben nincs.

  • a ^ b vagy a.symmetric_difference(b) – ami az egyikben van, de mindkettőben nem.

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

Kimenet:

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

Az operátoros formák csak olvashatók; a metódusos formák bármilyen iterálható objektumot elfogadnak a jobb oldalon, nem csak egy másik halmazt (a.union([5, 6])). Válaszd azt, amelyik az adott kontextusban jobban olvasható.

2.10.6. Mi kerülhet egy halmazba

A halmaz elemeinek hashelhetőnek kell lenniük – ugyanaz a megkötés, mint a dict kulcsoknál. Az int, float, str, bool, bytes és tuple (ha a tartalma maga is hashelhető) mind működik. A list és a dict nem; egy ilyen hozzáadásának kísérlete TypeError kivételt vált ki.

2.10.7. frozenset

Egy szokásos set változtatható: az add / remove / discard minden hívása helyben módosítja az objektumot. Ez a változtathatóság kizárja, hogy hashelhető legyen, így egy halmaz nem használható dict kulcsként vagy egy másik halmaz elemeként.

A frozenset a megváltoztathatatlan megfelelője. Ugyanazokkal a kikeresésekkel és operátorokkal (in, |, &, -, ^) rendelkezik, mint a set, de nincs add / remove és nincsenek módosító metódusai. Mivel a tartalma soha nem változhat meg, egy frozenset hash-értéke jól meghatározott – így hashelhető:

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

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

print(palettes[primary])

Kimenet:

RGB

Egy frozenset bármilyen iterálható objektumból építhető – frozenset() az üres esetre, frozenset(some_set) egy létező halmaz megváltoztathatatlan pillanatképének elkészítéséhez:

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

Két gyakori ok, amiért érdemes hozzá nyúlni:

  • Használat szótárkulcsként vagy halmazelemként. Bárhol, ahol egyetlen érték nem képes megragadni, amire szükséged van, ott egy frozenset értékekből megteheti – „az ez az illesztőprogram által támogatott jellemzők halmaza”, „a profil által használt lábak halmaza”.

  • Egy konstans lezárása. Egy megengedett nevekből álló, modulszintű frozenset nem módosítható véletlenül egy hívó által; egy szokásos set viszont igen. Részesítsd előnyben a frozenset típust mindenhez, aminek a létrehozás után csak olvashatónak kell lennie.