Diccionarios y conjuntos
Buscar por clave en tiempo constante y eliminar duplicados sin recorrer toda la colección
Los diccionarios asocian claves con valores y los conjuntos guardan elementos únicos. Aprenderás a consultar sin provocar KeyError, a recorrer pares clave-valor, a comprobar la hashabilidad de una clave y a usar operaciones de conjuntos para comparar datos.
- Crear diccionarios y acceder a valores de forma segura con get
- Recorrer claves, valores y pares con items
- Explicar qué es una clave hashable y por qué una lista no puede serlo
- Usar conjuntos para eliminar duplicados y comparar colecciones
- Elegir entre lista, tupla, diccionario y conjunto según el problema
Antes necesitas: Listas y tuplas · Strings, índices y slicing
En esta lección
- ¿Qué es un diccionario y qué es un conjunto?
- ¿Para qué sirve?
- ¿Cómo funciona por dentro?
- Sintaxis: crear, leer y actualizar
- Ejemplo básico: stock de una tienda
- Ejemplo intermedio: recorrer y clasificar notas
- Caso práctico: pedidos y clientes únicos
- Visualización: ventas por día
- Conjuntos: unicidad y operaciones
- Errores comunes
- Buenas prácticas
- Ejercicios
- Soluciones explicadas
- Resumen
- Evaluación
En una lista buscas por posición: notas[2] significa «el tercer elemento». Pero muchas veces no sabes la posición; sabes el nombre. Quieres el stock de "teclado", la nota de "Ana" o las ventas del "lunes". Para esas preguntas necesitas una estructura que relacione una clave con un valor.
El diccionario hace exactamente eso. El conjunto es su pariente cercano: guarda solo claves, sin valores, y garantiza que no haya repetidos. Juntos cubren dos necesidades constantes al procesar datos: buscar rápido por identificador y quedarse con los elementos únicos.
¿Qué es un diccionario y qué es un conjunto?
Un diccionario (dict) es una colección de pares clave-valor. Se escribe con llaves y dos puntos: edades = {"Ana": 21, "Luis": 19}. Cada clave es única; si repites una, el último valor sobrescribe al anterior. Desde Python 3.7 los diccionarios conservan el orden de inserción, aunque su acceso no dependa de la posición.
Un conjunto (set) es una colección de elementos únicos y sin orden garantizado. Se escribe con llaves sin dos puntos: colores = {"rojo", "verde"}. No admite duplicados y no se accede por índice, pero comprobar pertenencia es muy rápido. El conjunto vacío se crea con set(), porque {} es un diccionario vacío.
¿Para qué sirve?
- Contar frecuencias: cuántas veces aparece cada palabra en un texto o cada producto en una lista de pedidos.
- Buscar por identificador: obtener el precio de un código, la nota de un estudiante o el stock de un artículo.
- Agrupar datos: sumar ventas por día, por región o por categoría.
- Eliminar duplicados: convertir una lista de correos en un conjunto de correos únicos.
- Comparar colecciones: saber qué usuarios visitaron la web hoy pero no ayer.
- Preparar features: un diccionario de parámetros o un mapeo de categorías a números antes de entrenar un modelo.
¿Cómo funciona por dentro?
Ambas estructuras se apoyan en una tabla hash. Al insertar una clave, Python calcula su hash, un número derivado de su contenido, y lo usa para decidir en qué zona de la tabla guardarla. Al buscar, recalcula el hash y va casi directamente a la posición. Por eso d[clave] y x in conjunto son O(1) en promedio, frente al O(n) de una lista.
Ese mecanismo impone una condición: las claves deben ser hashables, es decir, tener un hash estable durante toda su vida. Los números, los strings y las tuplas de elementos hashables lo son; las listas, los diccionarios y los conjuntos no, porque son mutables y su contenido podría cambiar después de haber sido indexado. Si intentas usar una lista como clave, obtendrás TypeError: unhashable type: 'list'.
El coste medio O(1) no es una garantía absoluta: si muchas claves colisionan, las búsquedas se degradan. En la práctica, con strings y números bien distribuidos, el rendimiento es excelente y muy superior al de recorrer listas. La tabla siguiente resume cuándo cada estructura es la elección natural:
| Estructura | Orden | Duplicados | Acceso | Uso típico |
|---|---|---|---|---|
list | Sí | Permitidos | Por índice | Secuencia que crece o cambia |
tuple | Sí | Permitidos | Por índice | Registro fijo que no debe cambiar |
dict | Sí (inserción) | Claves no | Por clave | Mapear identificadores a valores |
set | No garantizado | No | Por pertenencia | Unicidad y comparación de grupos |
Sintaxis: crear, leer y actualizar
# Diccionario con dos pares
edades = {"Ana": 21, "Luis": 19}
# Leer, insertar y actualizar
print(edades["Ana"])
edades["Eva"] = 24 # clave nueva
edades["Luis"] = 20 # sobrescribe el valor
# get evita KeyError y permite un valor por defecto
print(edades.get("Mario"))
print(edades.get("Mario", 0))
# Recorrer pares clave-valor
for nombre, edad in edades.items():
print(nombre, edad)d[clave]lee un valor, pero lanzaKeyErrorsi la clave no existe.d[clave] = valorinserta o actualiza; no hay un método separado para cada caso.d.get(clave)devuelveNonesi falta;d.get(clave, defecto)devuelve el defecto.d.items()produce pares(clave, valor), ideales para desempaquetar en unfor.d.pop(clave)elimina y devuelve el valor;del d[clave]elimina sin devolverlo.s.add(x)añade al conjunto ys.discard(x)quita sin fallar si no existe.
Ejemplo básico: stock de una tienda
stock = {"teclado": 12, "mouse": 30}
stock["monitor"] = 5
stock["mouse"] = 25
print(stock["teclado"])
print(stock.get("webcam"))
print(stock.get("webcam", 0))
print(len(stock))La primera línea crea un diccionario con dos productos. La segunda añade "monitor" con valor 5: como la clave no existía, se inserta. La tercera actualiza "mouse" de 30 a 25, porque asignar a una clave existente reemplaza el valor. No hay que declarar el diccionario de antemano ni indicar cuántas claves tendrá.
stock["teclado"] devuelve 12. En cambio, stock["webcam"] lanzaría un KeyError, así que se usa get, que devuelve None cuando la clave falta. Con un segundo argumento, get("webcam", 0) devuelve 0, una opción muy útil para acumular. len(stock) cuenta pares, no valores sueltos: hay tres productos.
Ejemplo intermedio: recorrer y clasificar notas
items() es la puerta de entrada al diccionario cuando necesitas la clave y el valor a la vez. Si solo usas for nombre in notas, obtienes las claves; si usas notas.values(), obtienes los valores. El desempaquetado nombre, nota funciona porque cada elemento de items() es una tupla de dos posiciones.
notas = {"Ana": 15, "Luis": 9, "Eva": 18}
notas["Luis"] = 13
for nombre, nota in notas.items():
estado = "aprobado" if nota >= 13 else "desaprobado"
print(f"{nombre}: {nota} ({estado})")
print(f"Máxima: {max(notas.values())}")
print(f"Mínima: {min(notas.values())}")Actualizar una nota es una sola asignación: no hay que buscar la posición de Luis en ninguna lista. El bucle recorre los tres pares en orden de inserción y calcula el estado con una expresión condicional. max y min sobre values() devuelven 18 y 13, porque el 9 ya fue reemplazado. Fíjate en que las claves no se ordenan alfabéticamente: se respeta el orden en que se añadieron.
Caso práctico: pedidos y clientes únicos
Una lista de pedidos suele venir como pares (producto, cliente). Necesitas dos respuestas: cuántas unidades se vendieron de cada producto y cuántos clientes distintos compraron. El diccionario resuelve la primera; el conjunto, la segunda.
pedidos = [
("teclado", "Ana"),
("mouse", "Luis"),
("teclado", "Eva"),
("monitor", "Ana"),
]
unidades = {}
clientes = set()
for producto, cliente in pedidos:
unidades[producto] = unidades.get(producto, 0) + 1
clientes.add(cliente)
print(unidades)
print(f"Clientes únicos: {len(clientes)}")
print("¿Compró Ana?", "Ana" in clientes)El patrón unidades.get(producto, 0) + 1 es la forma canónica de contar: si la clave no existe, parte de cero; si existe, suma uno al valor actual. Sin get, la primera aparición de cada producto lanzaría KeyError. clientes.add(cliente) ignora los repetidos, así que al final el conjunto tiene tres nombres aunque haya cuatro pedidos. La pertenencia con in es inmediata gracias al hash.
Visualización: ventas por día
ventas = {"lunes": 120, "martes": 90}ventas["miércoles"] = 150total = sum(ventas.values())promedio = total / len(ventas)print(ventas.get("jueves", 0))print(f"{total} en {len(ventas)} días, media {promedio:.0f}")—
Avanza paso a paso: observa la línea resaltada, las variables y la salida.
El bloque anterior muestra las tres operaciones esenciales: insertar con corchetes, agregar con sum y consultar con get. Si hubieras escrito ventas["jueves"], el programa se habría detenido en la línea 5 con KeyError; el resto de la lección depende de elegir conscientemente entre acceso directo y acceso seguro.
Conjuntos: unicidad y operaciones
Los conjuntos admiten las operaciones matemáticas de unión, intersección, diferencia y diferencia simétrica. Puedes usar los operadores |, &, - y ^, o los métodos union, intersection, difference y symmetric_difference. Los métodos aceptan cualquier iterable; los operadores exigen conjuntos a ambos lados.
hoy = {"pan", "leche", "huevos", "pan"}
ayer = {"pan", "arroz"}
print(sorted(hoy))
print(sorted(hoy - ayer))
print(sorted(hoy & ayer))
print(sorted(hoy | ayer))
print(len(hoy))Aunque "pan" se escribe dos veces, el conjunto guarda una sola copia: len(hoy) devuelve 3. La diferencia hoy - ayer deja lo que está en hoy pero no en ayer (huevos, leche); la intersección & deja lo común (pan); la unión | reúne todo. Se usa sorted solo para imprimir en un orden estable, porque el orden interno de un conjunto no está garantizado.
Errores comunes
| Mensaje o síntoma | Por qué ocurre | Cómo se arregla |
|---|---|---|
KeyError: 'jueves' | Accediste con corchetes a una clave que no existe | Usa get o comprueba con in antes |
TypeError: unhashable type: 'list' | Usaste una lista como clave o elemento de un set | Convierte a tupla: tuple(lista) |
TypeError: 'set' object is not subscriptable | Intentaste indexar un conjunto | Convierte con list(s) o recorre con for |
RuntimeError: dictionary changed size during iteration | Insertaste o borraste claves mientras recorrías | Itera sobre list(d) o construye otro diccionario |
{} no se comporta como conjunto | {} crea un diccionario vacío | Usa set() para el conjunto vacío |
conteo = {}
for palabra in ["sol", "mar", "sol"]:
conteo[palabra] += 1conteo = {}
for palabra in ["sol", "mar", "sol"]:
conteo[palabra] = conteo.get(palabra, 0) + 1`conteo[palabra] += 1` intenta leer la clave antes de crearla y lanza `KeyError`. `get(palabra, 0)` devuelve cero cuando no existe y permite crear el par con la suma. Desde Python 3.8 también existe `collections.Counter`, que hace este trabajo directamente.
Buenas prácticas
- Usa
getcon valor por defecto cuando la ausencia de una clave sea normal, no un error. - Reserva el acceso con corchetes para claves que deben existir; así un fallo se detecta pronto.
- Recorre con
items()cuando necesites clave y valor; evita buscar el valor dentro del bucle. - Elige claves inmutables y estables: strings, números o tuplas de ambos.
- Usa conjuntos para pertenencia y unicidad, no para guardar datos que necesitas ordenados.
- No dependas del orden de un conjunto al imprimir; si importa, ordena antes.
- Si cuentas frecuencias, valora
collections.Counteren lugar de reinventar el patrón.
precios = {"pan": 2, "leche": 3}
precios["cafe"] = 5
print(precios.get("te", 0))
print(len(precios))#include <iostream>
#include <string>
#include <unordered_map>
int main() {
std::unordered_map<std::string, int> precios{{"pan", 2}, {"leche", 3}};
precios["cafe"] = 5;
auto it = precios.find("te");
std::cout << (it != precios.end() ? it->second : 0) << "\n";
std::cout << precios.size() << std::endl;
return 0;
}Python resuelve la ausencia con `get`; C++ usa `find` y compara con `end()`. En C++ hay que declarar los tipos de clave y valor, y elegir entre `map` (ordenado) y `unordered_map` (hash). Python conserva el orden de inserción sin coste adicional.
Ejercicios
Frecuencia de palabras
"sol mar sol viento mar sol", construye un diccionario que asocie cada palabra con su número de apariciones. Recorre las palabras con split() y muestra el resultado y la palabra más repetida.Pista
Usa conteo.get(palabra, 0) + 1. Para la palabra más repetida, recorre conteo.items() y guarda el par con el valor mayor.
edades = {"Ana": 21, "Luis": 19}
print(edades.("Eva", 0))
for nombre, edad in edades.():
print(nombre, edad) El primero devuelve un valor por defecto; el segundo entrega pares clave-valor.
Visitantes de dos días
{"ana", "luis", "eva"} y el martes {"luis", "eva", "mario"}. Calcula quiénes repitieron, quiénes son nuevos el martes y cuántos usuarios distintos hubo en total. Muestra cada grupo ordenado.Pista
La intersección es &, la diferencia es - y la unión es |. Envuelve el resultado en sorted para imprimirlo.
Soluciones explicadas
En el conteo, el diccionario empieza vacío y crece a medida que aparecen palabras nuevas. La clave es la palabra y el valor es el contador. get cumple dos funciones: comprobar si existe y aportar un punto de partida. Si usaras defaultdict(int) de collections, el mismo bucle se escribiría con conteo[palabra] += 1; es una alternativa válida que verás en proyectos reales.
En el ejercicio de visitantes, los conjuntos expresan la pregunta casi en lenguaje natural: intersección para los que repiten, diferencia para los nuevos, unión para el total. La diferencia A - B no es simétrica: martes - lunes da los nuevos del martes, mientras que lunes - martes daría quienes no volvieron. Leer el operador en voz alta ayuda a no invertirlo.
Resumen
- Un diccionario asocia claves únicas con valores y conserva el orden de inserción.
d[clave]falla conKeyErrorsi falta;d.get(clave, defecto)nunca falla por ausencia.items()devuelve pares y permitefor clave, valor in d.items().- Las claves deben ser hashables: strings, números y tuplas de elementos hashables.
- Un conjunto guarda elementos únicos y ofrece
|,&,-y^. - La pertenencia en diccionarios y conjuntos es O(1) en promedio; en listas es O(n).
- Usa
set()para el conjunto vacío:{}crea un diccionario.
Evaluación
¿Qué devuelve {"a": 1}.get("b")?
¿Por qué {[1, 2]: "valor"} falla?
Si a = {1, 2, 3} y b = {2, 3, 4}, ¿qué contiene a & b?
¿Ya lo entendiste?
Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.