La lliçó anterior va acabar amb una pregunta: per què consultar cataleg["Faust"] és pràcticament instantani? La resposta viu en una estructura germana del diccionari: el conjunt (set). Un conjunt és una col·lecció sense ordre garantit, sense duplicats i sense valors associats — només elements, com les claus d'un diccionari sense els seus valors. A canvi de renunciar a l'ordre i a la repetició, ofereix dos superpoders: comprovar pertinença a tota velocitat i operar amb la lògica de la teoria de conjunts (unió, intersecció, diferència) que a Papyrus resoldrà preguntes com "quins socis van comprar aquest mes però no el passat?" en una sola línia.
Contingut
- Crear conjunts (i el parany del
{}buit) - Unicitat: els duplicats desapareixen
- Afegir i eliminar elements
- Pertinença ultraràpida: per què
invola - Operacions de conjunts: unió, intersecció, diferències
- Subconjunts i superconjunts
frozenset: el conjunt immutable- Comprensions de conjunts
Crear conjunts (i el parany del {} buit)
generes = {"narrativa", "teatre", "poesia"} # literal: claus amb elements solts
de_llista = set(["narrativa", "teatre", "narrativa"]) # set() converteix iterables i DEDUPLICA
print(de_llista) # {'narrativa', 'teatre'} → el duplicat ha marxat
lletres = set("papyrus") # {'p', 'a', 'y', 'r', 'u', 's'} → la 'p' repetida, fora
buit = set() # UNICA manera de crear un conjunt buit
parany = {} # aixo es un DICCIONARI buit, no un set!
print(type(buit), type(parany)) # <class 'set'> <class 'dict'>El parany del {} és històric: els diccionaris van arribar abans a Python i es van quedar les claus buides. Memoritza-ho: conjunt buit = set(), sempre.
Els elements han de ser immutables (hashables), com les claus d'un dict: cadenes, nombres i tuples sí; llistes i diccionaris no. I no hi ha indexació: generes[0] és un TypeError, perquè un conjunt no té posicions.
Unicitat: els duplicats desapareixen
La unicitat no és una restricció molesta: és la funcionalitat estrella. El cas d'ús més freqüent del món real — eliminar duplicats d'una llista — és una línia:
# Comandes del dia a Papyrus: en Lluis ha demanat El Quixot dues vegades per error
comandes = ["El Quixot", "Hamlet", "El Quixot", "Faust", "Hamlet"]
titols_unics = set(comandes)
print(titols_unics) # {'Faust', 'Hamlet', 'El Quixot'} (ordre no garantit)
print(len(titols_unics)) # 3 titols diferents, tot i que hi va haver 5 comandes
# Si necessites conservar una llista (p. ex., ordenada), converteix de tornada:
unics_ordenats = sorted(set(comandes))
print(unics_ordenats) # ['El Quixot', 'Faust', 'Hamlet']Compte: set() no conserva l'ordre original de la llista. Si l'ordre d'aparició importa, hi ha un idiom amb dict que veuràs als errors comuns.
Afegir i eliminar elements
Els conjunts són mutables (n'existeix la versió immutable: frozenset, més avall):
generes = {"narrativa", "teatre"}
generes.add("poesia") # afegeix un element (si ja hi es, no passa res)
generes.add("teatre") # sense efecte ni error: ja hi era
generes.update(["assaig", "narrativa"]) # n'afegeix diversos de cop (com extend a les llistes)
generes.remove("assaig") # elimina; KeyError si no existeix
generes.discard("ciencia") # elimina si hi es; SILENCI si no hi es
element = generes.pop() # treu un element ARBITRARI (no "l'ultim": no hi ha ordre)
generes.clear() # buida el conjunt| Mètode | Si l'element no hi és... |
|---|---|
remove(x) |
Llança KeyError |
discard(x) |
No fa res (l'opció tranquil·la) |
Pertinença ultraràpida: per què in vola
Amb una llista, x in llista obliga Python a comparar element a element, des del principi: si la llista té un milió d'entrades, en el pitjor cas fa un milió de comparacions. Amb un conjunt (o les claus d'un dict), Python calcula el hash de l'element — una empremta numèrica — i salta directament a la casella interna on seria si existís. Una comprovació, tingui el conjunt deu elements o deu milions.
socis = {"LLUIS-001", "MARTA-002", "PAU-003"} # codis de soci de Papyrus
def es_soci_valid(codi):
"""La funcio de papyrus_utils, ara amb un set al darrere: validacio immediata."""
return codi.strip().upper() in socis
print(es_soci_valid("lluis-001")) # True
print(es_soci_valid("ANNA-999")) # FalseAquesta és també la resposta a la pregunta de 04-03: cataleg["Faust"] és instantani perquè les claus d'un diccionari fan servir exactament el mateix mecanisme de hash. No necessites la teoria formal de complexitat (arribarà si algun dia la cerques com a "O(1) vs O(n)"); queda't amb la regla pràctica:
Moltes comprovacions de "hi és o no hi és?" sobre la mateixa col·lecció → converteix-la primer en
set.
Operacions de conjunts: unió, intersecció, diferències
Aquí els conjunts brillen amb llum pròpia. Cada operació existeix com a operador i com a mètode (el mètode accepta qualsevol iterable; l'operador exigeix dos sets):
| Operació | Operador | Mètode | Resultat |
|---|---|---|---|
| Unió | a | b |
a.union(b) |
Elements a a o a b (o tots dos) |
| Intersecció | a & b |
a.intersection(b) |
Elements a a i a b |
| Diferència | a - b |
a.difference(b) |
A a però no a b |
| Diferència simètrica | a ^ b |
a.symmetric_difference(b) |
A a o a b, però no a tots dos |
Amb dades de Papyrus: l'Anna vol analitzar la fidelitat dels seus socis comparant els compradors de juny i juliol:
compradors_juny = {"Lluis", "Marta", "Pau", "Julia"}
compradors_juliol = {"Lluis", "Julia", "Omar"}
print(compradors_juny | compradors_juliol) # {'Lluis','Marta','Pau','Julia','Omar'} → clientela activa
print(compradors_juny & compradors_juliol) # {'Lluis', 'Julia'} → els fidels: repeteixen mes a mes
print(compradors_juny - compradors_juliol) # {'Marta', 'Pau'} → van comprar al juny i al juliol NO: recuperar-los?
print(compradors_juliol - compradors_juny) # {'Omar'} → client nou al juliol
print(compradors_juny ^ compradors_juliol) # {'Marta','Pau','Omar'} → nomes un mes dels dosCinc preguntes de negoci, cinc línies, zero bucles. La diferència no és commutativa (a - b != b - a); la unió, la intersecció i la diferència simètrica sí que ho són.
Un altre ús natural: els gèneres de cada llibre com a conjunts, per respondre "quins llibres comparteixen gènere?":
generes_llibre = {
"L'Odissea": {"classic", "epica"},
"Hamlet": {"classic", "teatre", "tragedia"},
"Faust": {"classic", "teatre"},
}
comuns = generes_llibre["Hamlet"] & generes_llibre["Faust"]
print(comuns) # {'classic', 'teatre'}Subconjunts i superconjunts
tragedies = {"Hamlet", "Faust"}
classics = {"L'Odissea", "Hamlet", "El Quixot", "Faust"}
print(tragedies <= classics) # True: tragedies es SUBconjunt de classics
print(tragedies.issubset(classics)) # equivalent amb metode
print(classics >= tragedies) # True: classics es SUPERconjunt
print(tragedies.isdisjoint({"Iliada"})) # True: no comparteixen cap elementÚtil per a validacions: "tots els títols de la comanda són al catàleg?" és set(comanda) <= set(cataleg) — i sí, set(cataleg) sobre el dict de 04-03 n'extreu les claus.
frozenset: el conjunt immutable
La mateixa relació que tupla/llista: frozenset és un conjunt congelat. No admet add() ni remove(), i a canvi és hashable — pot ser clau de diccionari o element d'un altre conjunt:
PACK_CLASSICS = frozenset({"L'Odissea", "Hamlet"}) # oferta fixa de la botiga
ofertes = {
PACK_CLASSICS: 19.90, # un frozenset com a clau de dict: legal
}
print(ofertes[frozenset({"Hamlet", "L'Odissea"})]) # 19.9 → l'ordre no importaAmb un set normal com a clau obtindries TypeError: unhashable type: 'set'. Ús breu i ocasional, però quan el necessites, res més no el substitueix.
Comprensions de conjunts
Tercera comprensió de la família (llista → dict → set). Sintaxi: claus sense dos punts:
comandes = [" hamlet ", "FAUST", "Hamlet", "el quixot"]
# Titols normalitzats i unics, de cop (la normalitzacio a fons, a 04-05)
unics = {c.strip().lower() for c in comandes}
print(unics) # {'hamlet', 'faust', 'el quixot'} → 4 comandes, 3 titols reals
# Sobre el cataleg canonic de 04-03: titols amb estoc
cataleg = {
"L'Odissea": {"preu": 12.50, "estoc": 4},
"Hamlet": {"preu": 9.95, "estoc": 6},
"El Quixot": {"preu": 15.90, "estoc": 8},
"Faust": {"preu": 21.00, "estoc": 0},
}
disponibles = {t for t, fitxa in cataleg.items() if fitxa["estoc"] > 0}
print(disponibles) # {"L'Odissea", 'Hamlet', 'El Quixot'}| Comprensió | Sintaxi | Produeix |
|---|---|---|
| De llista | [x for x in it] |
Llista (ordenada, amb duplicats) |
| De diccionari | {k: v for ...} |
Dict (clau: valor) |
| De conjunt | {x for x in it} |
Set (únic, sense ordre) |
Errors Comuns i Consells
{}no és un conjunt buit: és un dict. L'error no dona missatge — simplementparany.add(x)fallarà ambAttributeErrormés tard. Conjunt buit =set().- Esperar ordre: els sets no garanteixen ordre d'iteració. Si imprimeixes un set i "surt desordenat", no és un bug. Per presentar,
sorted(el_meu_set). - Indexar un set:
s[0]llançaTypeError. Sense ordre no hi ha posicions; si necessites "el primer", et falta una llista. - Ficar-hi elements mutables:
{["a", "b"]}llançaTypeError: unhashable type: 'list'. Converteix a tupla si necessites agrupar. - Confondre
remove()ambdiscard(): si l'absència és normal,discard(); si és un bug,remove()(que falli aviat). El mateix criteri que claudàtors vsget()als dicts. - Deduplicar amb set quan l'ordre importa:
set(llista)perd l'ordre d'aparició. Idiom que el conserva:list(dict.fromkeys(llista))— els dicts dedupliquen claus i recorden l'ordre d'inserció. - Consell: si et sorprens escrivint bucles imbricats per comparar dues llistes ("els d'aquí que no són allà"), gairebé segur que era una operació de conjunts.
Exercicis
- Auditoria de comandes. De la llista
comandes = ["Hamlet", "Faust", "Hamlet", "Dracula", "El Quixot", "Faust"], obtén: (a) el conjunt de títols diferents demanats; (b) quantes comandes duplicades hi va haver (total de comandes menys títols diferents); (c) fent servir el catàleg canònic de 04-03 i una diferència de conjunts, quins títols demanats no són al catàleg. - Campanya de fidelització. Amb
juny = {"Lluis", "Marta", "Pau", "Julia"}ijuliol = {"Lluis", "Julia", "Omar"}, calcula: els socis que cal recuperar (van comprar al juny però no al juliol), els nous del juliol, i els fidels de tots dos mesos. Després comprova amb un operador de subconjunt si tots els fidels van comprar al juny. - Gèneres disponibles. Amb el dict
generes_llibrede la lliçó (L'Odissea, Hamlet, Faust) i el conjuntdisponiblescalculat sobre el catàleg canònic, construeix mitjançant operacions i/o una comprensió el conjunt de gèneres realment disponibles avui (els gèneres dels llibres amb estoc > 0). Pista: la unió de diversos conjunts es pot fer ambset().union(...).
Solucions
# Exercici 1
comandes = ["Hamlet", "Faust", "Hamlet", "Dracula", "El Quixot", "Faust"]
cataleg = {
"L'Odissea": {"preu": 12.50, "estoc": 4},
"Hamlet": {"preu": 9.95, "estoc": 6},
"El Quixot": {"preu": 15.90, "estoc": 8},
"Faust": {"preu": 21.00, "estoc": 0},
}
diferents = set(comandes) # (a) {'Hamlet','Faust','Dracula','El Quixot'}
duplicades = len(comandes) - len(diferents) # (b) 2
desconeguts = diferents - set(cataleg) # (c) {'Dracula'} → set(dict) pren les claus
print(diferents, duplicades, desconeguts)# Exercici 2
juny = {"Lluis", "Marta", "Pau", "Julia"}
juliol = {"Lluis", "Julia", "Omar"}
recuperar = juny - juliol # {'Marta', 'Pau'}
nous = juliol - juny # {'Omar'}
fidels = juny & juliol # {'Lluis', 'Julia'}
print(fidels <= juny) # True: tot fidel va comprar al juny (per definicio)Consell: anomena els resultats amb vocabulari de negoci (recuperar, fidels), no matemàtic (diferencia, interseccio): el codi s'explica sol.
# Exercici 3
generes_llibre = {
"L'Odissea": {"classic", "epica"},
"Hamlet": {"classic", "teatre", "tragedia"},
"Faust": {"classic", "teatre"},
}
disponibles = {t for t, fitxa in cataleg.items() if fitxa["estoc"] > 0}
generes_avui = set().union(*(generes_llibre[t] for t in disponibles if t in generes_llibre))
print(generes_avui) # {'classic', 'epica', 'teatre', 'tragedia'} → Faust (esgotat) no hi aporta resFixa't: el * desempaqueta els conjunts com a arguments d'union() (el mateix * de 03-02), i l'if t in generes_llibre protegeix contra llibres sense gèneres fitxats — "El Quixot" està disponible però no té entrada, i sense aquest filtre hi hauria un KeyError.
Conclusió
Els conjunts completen el trio de col·leccions "amb claus": saps crear-los (recordant que set() és l'única forma del buit), aprofitar la deduplicació automàtica, i expressar lògica de negoci amb unió, intersecció i diferències en una línia on abans hauries imbricat bucles. Entens a més per què in és ultraràpid en sets i dicts — la màgia del hash — i coneixes frozenset per a quan la immutabilitat importa. A Papyrus ja dedupliquen comandes, validen socis i comparen mesos de vendes. Però en gairebé tots els exemples ha tret el cap un actor que fem servir des del mòdul 1 sense estudiar-lo seriosament: la cadena de text, amb els seus strip(), lower() i companyia. La propera lliçó la tracta per fi com el que és — una seqüència amb superpoders propis — i sistematitza la normalització d'entrades d'usuari que cercar_llibre() i es_soci_valid() fan a mitges des de fa temps.
Curs de Programació en Python
Mòdul 1: Introducció a Python
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
