2.10. Skupovi

Skup je neuređena zbirka jedinstvenih stavki. Dodavanje vrijednosti koja je već prisutna nema učinka; iteracija daje svaku vrijednost točno jednom. Skupovi su pravi alat kada su pripadnost i uklanjanje duplikata bitni, a redoslijed nije.

2.10.1. Stvaranje skupa

Koristite vitičaste zagrade za neprazan skup ili set() za prazan:

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

Vitičaste zagrade izgledaju poput literala dict; {} samo za sebe prazan je rječnik, a ne prazan skup – jedna od povijesnih nesreća Pythona. Za prazan slučaj koristite set().

set() također gradi skup iz bilo kojeg iterabilnog objekta, što je standardni način za uklanjanje duplikata iz niza:

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

Izlaz:

{1, 2, 3, 4}

Redoslijed ispisa može varirati – skupovi ne jamče iteriranje u nekom određenom redoslijedu.

2.10.2. Skup nasuprot rječniku

Skupovi i rječnici oba pohranjuju jedinstvene stavke u raspršenoj tablici. Razlika je u tome što svaka stavka nosi sa sobom:

  • dict pohranjuje parove ključ-vrijednost. Pretraživanje ključa vraća njegovu vrijednost.

  • set pohranjuje samo stavke. Pretraživanje stavke govori vam je li ona prisutna.

Izbor između to dvoje ovisi o tome znači li vrijednost uz svaku stavku išta:

  • Posegnite za skupom kada uz svaku stavku ne pripada nikakva vrijednost – zanima vas samo je li stavka prisutna, ili kombinirate skupine jedinstvenih stavki unijom / presjekom.

  • Posegnite za rječnikom kada je svaka stavka uparena s podatkom koji pretraživanje treba dohvatiti – konfiguracijska mapa, predmemorija, brojač s ključem po imenu.

Ta dva tipa dijele dosta površinske sintakse, odakle dolazi najveća zbrka. Razlike u jednom bloku:

set

dict

sadrži

jedinstvene stavke

jedinstvene ključeve, svaki s vrijednošću

popunjen literal

{1, 2, 3}

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

prazan literal

set()

{}

provjera pripadnosti

x in s

k in d (samo ključevi)

dohvati vrijednost

nije primjenjivo

d[k]

dodaj stavku

s.add(x)

d[k] = v

iteriraj

daje stavke

daje ključeve (koristite d.items() za parove)

Asimetrija između popunjenog i praznog literala zamka je koju vrijedi istaknuti:

  • Vitičaste zagrade sa stavkama u sebi{1, 2, 3} – literal su skupa; vitičaste zagrade s parovima ključ-vrijednost{"a": 1} – literal su rječnika. Parser ih razlikuje prema onome što je unutra.

  • Vitičaste zagrade s ničim unutra{} – prazan su rječnik, a ne prazan skup. Rječnici su došli prvi; prazan literal pripada njima. Prazan skup uopće nema literal s vitičastim zagradama i mora se napisati kao set().

Uobičajen obrazac kada se uvijek čitaju samo ključevi rječnika jest prijelaz na skup – on čini namjeru očiglednom i uklanja neiskorištene vrijednosti iz memorije.

2.10.3. Dodavanje i uklanjanje

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

Izlaz:

{1, 3, 4}

2.10.4. Pripadnost

Operator in provjerava pripadnost. Na skupu je to otprilike konstantno vrijeme bez obzira na veličinu – što je glavni razlog da odaberete skup umjesto list kada samo trebate pitati „je li ova vrijednost unutra”:

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

list s istim sadržajem pretraživao bi se od početka svaki put, što je u redu za deset stavki, ali sporo za deset tisuća.

2.10.5. Operacije nad skupovima

Dva skupa mogu se kombinirati uobičajenim matematičkim operacijama. Svaka ima i oblik operatora i oblik metode:

  • a | b ili a.union(b) – sve što je u bilo kojem od skupova.

  • a & b ili a.intersection(b) – samo ono što se pojavljuje u oba.

  • a - b ili a.difference(b) – u a, ali ne u b.

  • a ^ b ili a.symmetric_difference(b) – u jednom, ali ne u oba.

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

Izlaz:

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

Oblici operatora samo su za čitanje; oblici metoda prihvaćaju bilo koji iterabilni objekt s desne strane, ne samo drugi skup (a.union([5, 6])). Odaberite onaj koji se bolje čita u kontekstu.

2.10.6. Što može ići u skup

Elementi skupa moraju biti raspršivi (hashable) – isto ograničenje kao kod ključeva dict. int, float, str, bool, bytes i tuple (kada je njezin sadržaj sam po sebi raspršiv) svi rade. list i dict ne rade; pokušaj dodavanja jednog od njih izaziva TypeError.

2.10.7. frozenset

Obični set je promjenjiv: svaki poziv add / remove / discard mijenja objekt na mjestu. Ta promjenjivost ga diskvalificira iz toga da bude raspršiv, pa se skup ne može koristiti kao ključ dict ni kao član drugog skupa.

frozenset nepromjenjiv je pandan. Ima iste pretrage i operatore (in, |, &, -, ^) kao set, ali nema add / remove ni metode koje ga mijenjaju. Budući da se njegov sadržaj nikad ne može promijeniti, raspršena vrijednost (hash) za frozenset dobro je definirana – pa jest raspršiv:

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

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

print(palettes[primary])

Izlaz:

RGB

Frozenset se gradi iz bilo kojeg iterabilnog objekta – frozenset() za prazan slučaj, frozenset(some_set) za nepromjenjivu snimku postojećeg skupa:

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

Dva česta razloga da posegnete za njim:

  • Uporaba kao ključa rječnika ili člana skupa. Svugdje gdje jedna vrijednost ne može obuhvatiti ono što vam treba, može frozenset vrijednosti – „skup značajki koje ovaj upravljač podržava”, „skup pinova koje ovaj profil koristi”.

  • Zaključavanje konstante. frozenset na razini modula s dopuštenim imenima ne može slučajno izmijeniti pozivatelj; obični set može. Dajte prednost frozenset za sve što treba biti samo za čitanje nakon stvaranja.