AulaML Python · C++
Python principiante · 40 min · Estructuras de datos

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.

Lección 3 de 4 del módulo
Al terminar esta lección podrás
  • 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
  1. ¿Qué es un diccionario y qué es un conjunto?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro?
  4. Sintaxis: crear, leer y actualizar
  5. Ejemplo básico: stock de una tienda
  6. Ejemplo intermedio: recorrer y clasificar notas
  7. Caso práctico: pedidos y clientes únicos
  8. Visualización: ventas por día
  9. Conjuntos: unicidad y operaciones
  10. Errores comunes
  11. Buenas prácticas
  12. Ejercicios
  13. Soluciones explicadas
  14. Resumen
  15. 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:

EstructuraOrdenDuplicadosAccesoUso típico
listSíPermitidosPor índiceSecuencia que crece o cambia
tupleSíPermitidosPor índiceRegistro fijo que no debe cambiar
dictSí (inserción)Claves noPor claveMapear identificadores a valores
setNo garantizadoNoPor pertenenciaUnicidad y comparación de grupos

Sintaxis: crear, leer y actualizar

PYTHON diccionarios.py
# 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 lanza KeyError si la clave no existe.
  • d[clave] = valor inserta o actualiza; no hay un método separado para cada caso.
  • d.get(clave) devuelve None si falta; d.get(clave, defecto) devuelve el defecto.
  • d.items() produce pares (clave, valor), ideales para desempaquetar en un for.
  • d.pop(clave) elimina y devuelve el valor; del d[clave] elimina sin devolverlo.
  • s.add(x) añade al conjunto y s.discard(x) quita sin fallar si no existe.

Ejemplo básico: stock de una tienda

PYTHON stock.py
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.

PYTHON notas.py
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.

PYTHON pedidos.py
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

Ejecución visual ·Insertar, sumar y consultar con get
1 / 6
1 ventas = {"lunes": 120, "martes": 90}
2 ventas["miércoles"] = 150
3 total = sum(ventas.values())
4 promedio = total / len(ventas)
5 print(ventas.get("jueves", 0))
6 print(f"{total} en {len(ventas)} días, media {promedio:.0f}")
Se crea el diccionario con dos pares. Las claves son strings y los valores enteros.
Variables
ventas = {'lunes': 120, 'martes': 90}
Salida
—

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.

PYTHON conjuntos.py
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íntomaPor qué ocurreCómo se arregla
KeyError: 'jueves'Accediste con corchetes a una clave que no existeUsa get o comprueba con in antes
TypeError: unhashable type: 'list'Usaste una lista como clave o elemento de un setConvierte a tupla: tuple(lista)
TypeError: 'set' object is not subscriptableIntentaste indexar un conjuntoConvierte con list(s) o recorre con for
RuntimeError: dictionary changed size during iterationInsertaste o borraste claves mientras recorríasItera sobre list(d) o construye otro diccionario
{} no se comporta como conjunto{} crea un diccionario vacíoUsa set() para el conjunto vacío
Contar sin provocar KeyError
Incorrecto
PYTHON Falla en la primera aparición
conteo = {}
for palabra in ["sol", "mar", "sol"]:
    conteo[palabra] += 1
Correcto
PYTHON Correcto
conteo = {}
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 get con 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.Counter en lugar de reinventar el patrón.
Diccionario frente a std::unordered_map
Python
PYTHON dict.py
precios = {"pan": 2, "leche": 3}
precios["cafe"] = 5
print(precios.get("te", 0))
print(len(precios))
C++
CPP map.cpp
#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

Ejercicio 1

Frecuencia de palabras

Dado el texto "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.

Completar código Consulta segura y recorrido
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.

Ejercicio 2

Visitantes de dos días

El lunes visitaron la web {"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 con KeyError si falta; d.get(clave, defecto) nunca falla por ausencia.
  • items() devuelve pares y permite for 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

Evaluación 1

¿Qué devuelve {"a": 1}.get("b")?

Evaluación 2

¿Por qué {[1, 2]: "valor"} falla?

Evaluación 3

Si a = {1, 2, 3} y b = {2, 3, 4}, ¿qué contiene a & b?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.