Ir al contenido
wannacode

dict y set

En resumen: Un mapa (diccionario) guarda pares de clave y valor con claves únicas y encuentra rápido un valor por su clave, y un conjunto guarda valores únicos.

Problema

¿Quién es este usuario?

Los usuarios del sitio están en una lista. En cada inicio de sesión buscas el correo por el nombre de usuario:

def find_email(users, username):
for u in users:
if u.username == username:
return u.email
return None

Con tres usuarios es instantáneo. Con 2 000 000, cada inicio de sesión compara hasta 2 000 000 nombres, y hay miles de inicios de sesión por minuto. Una guía telefónica no se lee desde la primera página. Vas directo a la letra.

La idea

Diccionario: clave → valor

Quieres preguntar «¿cuál es el correo de garcia?» y obtener la respuesta al instante. La colección para esto se llama diccionario (mapa). Guarda pares: una clave y un valor. En Python el diccionario viene incorporado y se llama dict:

emails = {}
emails["garcia"] = "garcia@example.com"
emails["mlopez"] = "mlopez@example.com"
emails["fernandez"] = "fernandez@example.com"
print(emails["fernandez"])
print(emails.get("nadie"))
print("Usuarios:", len(emails))
Salida
fernandez@example.com
None
Usuarios: 3
  • {} es un diccionario vacío. emails[clave] = valor guarda un par, y emails[clave] devuelve el valor.
  • get(clave) también devuelve el valor, y para una clave que no está da None.
  • len(emails) es el número de pares. No necesitas un import.
  • No escribes tipos: un mismo diccionario puede tener cualquier clave y cualquier valor. Si quieres decir cuáles son, hay una anotación de tipo: emails: dict[str, str] = {}.

emails[clave] para una clave que no está no da None. Detiene el programa. También puedes escribir un diccionario de una vez, con pares clave: valor:

emails = {"garcia": "garcia@example.com", "mlopez": "mlopez@example.com"}
print(emails["garcia"])
try:
print(emails["nadie"])
except KeyError as e:
print(f"KeyError: {e}")
Salida
garcia@example.com
KeyError: 'nadie'

emails[clave] no recorre todos los pares. Encuentra el lugar de la clave casi al instante, sin importar cuántos haya. Cómo lo hace es la siguiente lección.

La idea

El almacén: cambios y comprobaciones

Las existencias de un almacén: artículo → cantidad.

stock = {}
stock["portátil"] = 4
stock["ratón"] = 25
stock["portátil"] = 3
print(stock["portátil"])
print("monitor" in stock)
print(stock.get("monitor", 0))
del stock["ratón"]
print(len(stock))
Salida
3
False
0
1
  • Las claves son únicas. El segundo stock["portátil"] = 3 no agrega una fila nueva. Reemplaza 4 por 3.
  • clave in stock pregunta si la clave está en el diccionario.
  • get(clave, 0) devuelve el valor, o 0 si la clave no está. Esto te protege de KeyError.
  • del stock[clave] quita el par.
Más precisamente, para curiosos
  • Los valores se pueden repetir: el portátil y el ratón pueden ser 3 los dos. Las claves no.
  • También puedes preguntar 3 in stock.values(). Pero eso recorre todos los valores, porque un diccionario es rápido solo por clave.

Pruébalo

¿Cuántos hay en el menú?

¿Qué imprimirá el programa?

menu = {}
menu["café"] = 2
menu["té"] = 2
menu["café"] = 3
print(len(menu), menu["café"])
Respuesta

La idea

Recorrido y orden

El precio de envío por ciudad. items() da los pares de clave y valor, y for city, price in separa cada uno en dos nombres:

delivery = {}
delivery["Sofia"] = 4.5
delivery["Plovdiv"] = 5.2
delivery["Varna"] = 6.0
delivery["Burgas"] = 6.0
delivery["Ruse"] = 5.8
for city, price in delivery.items():
print(f"{city}: {price:.2f} €")
Salida
Sofia: 4.50 €
Plovdiv: 5.20 €
Varna: 6.00 €
Burgas: 6.00 €
Ruse: 5.80 €

Las ciudades salen en el orden en que se agregaron. dict mantiene el orden en que se agregan, y desde Python 3.7 eso está prometido. :.2f imprime el precio con dos cifras después del punto decimal. Con las mismas asignaciones y print(delivery):

delivery = {}
delivery["Sofia"] = 4.5
delivery["Plovdiv"] = 5.2
delivery["Varna"] = 6.0
delivery["Burgas"] = 6.0
delivery["Ruse"] = 5.8
print(delivery)
Salida
{'Sofia': 4.5, 'Plovdiv': 5.2, 'Varna': 6.0, 'Burgas': 6.0, 'Ruse': 5.8}

Cuando necesitas el orden alfabético, sorted(delivery) da una lista ordenada de las claves. El diccionario no cambia:

delivery = {}
delivery["Sofia"] = 4.5
delivery["Plovdiv"] = 5.2
delivery["Varna"] = 6.0
delivery["Burgas"] = 6.0
delivery["Ruse"] = 5.8
for city in sorted(delivery):
print(f"{city}: {delivery[city]:.2f} €")
Salida
Burgas: 6.00 €
Plovdiv: 5.20 €
Ruse: 5.80 €
Sofia: 4.50 €
Varna: 6.00 €
Más precisamente, para curiosos
  • Si necesitas solo las claves, recorre delivery. Si necesitas solo los valores, recorre values():
for city in delivery:
print(city, end=" ")
print()
print(f"Media: {sum(delivery.values()) / len(delivery):.2f} €")
Salida
Sofia Plovdiv Varna Burgas Ruse
Media: 5.50 €
  • sum(...) suma los números, y len(delivery) es el número de pares.
  • El diccionario mantiene el orden en que se agregan, pero el set del final de la lección no.

La idea

Conteo

¿Cuántas veces se pidió cada artículo? La clave es el artículo, y el valor es la cantidad. En cada pedido tomas la cantidad anterior (o 0) y le sumas 1:

orders = "café té café cacao café té"
counts = {}
for item in orders.split(" "):
counts[item] = counts.get(item, 0) + 1
print(counts)
Salida
{'café': 3, 'té': 2, 'cacao': 1}

Este es el uso más común de un diccionario: contar palabras, votos, errores por tipo, ventas por ciudad. Los artículos salen en el orden en que aparecieron por primera vez.

orders = "café té café cacao café té"
counts = {}
for item in orders.split(" "):
counts[item] = counts.get(item, 0) + 1
print(counts)
Paso a paso, en palabras
  1. 6 pedidos y un diccionario vacío.
  2. «café» no está: get(item, 0) da 0, y la asignación añade la clave con 1.
  3. «té» no está: get(item, 0) da 0, y la asignación añade la clave con 1. El diccionario guarda el orden en que se añaden: la clave nueva es la última.
  4. «café» ya está: get da 1, y la asignación reemplaza el valor por 2.
  5. «cacao» no está: get(item, 0) da 0, y la asignación añade la clave con 1. El diccionario guarda el orden en que se añaden: la clave nueva es la última.
  6. «café» ya está: get da 2, y la asignación reemplaza el valor por 3.
  7. «té» ya está: get da 1, y la asignación reemplaza el valor por 2.
  8. Cada producto es una clave una sola vez, y el valor es cuántas veces se pidió.
Más precisamente, para curiosos
  • split(" ") de la lección sobre texto divide la línea en palabras.
  • Contar es tan común que collections tiene un diccionario listo para eso, Counter. Para una clave que no está, da 0, no KeyError:
from collections import Counter
orders = "café té café cacao café té"
counts = Counter(orders.split(" "))
print(counts)
print(counts["café"], counts["jugo"])
Salida
Counter({'café': 3, 'té': 2, 'cacao': 1})
3 0

Pruébalo

Encuentra el error

El programa debería contar los pedidos. En cambio, se detiene en el primero:
Error
KeyError: 'café'

¿En qué línea está el error?

Elige la línea con el error.

La idea

set: cada uno una vez

¿Cuántas personas distintas iniciaron sesión hoy en el sitio? Si alguien entra cinco veces, debe contarse una sola. Una colección que guarda cada valor una sola vez se llama conjunto:

visits = ["ana@example.com", "bruno@example.com", "ana@example.com", "vera@example.com", "bruno@example.com"]
visitors = set()
for email in visits:
visitors.add(email)
print("Visitas:", len(visits))
print("Visitantes únicos:", len(visitors))
print("vera@example.com" in visitors)
Salida
Visitas: 5
Visitantes únicos: 3
True
  • set() es un conjunto vacío. {} no lo es: es un diccionario vacío.
  • add sobre un valor que ya está no hace nada. En los dos casos devuelve None, así que no te dice si agregó algo:
seen = set()
print(seen.add("F-101"))
Salida
None
  • valor in visitors es tan rápido como in en un diccionario. Por dentro, un conjunto es como un diccionario en el que guardas solo las claves.
  • No hay índices. Un conjunto responde solo a la pregunta «¿está aquí?».
Más precisamente, para curiosos
  • Un conjunto no mantiene ningún orden. Con texto, el orden cambia incluso entre dos ejecuciones del mismo programa, porque Python calcula el hash del texto de forma distinta en cada ejecución. Por eso se imprime con sorted(). También puedes escribir un conjunto de una vez, entre {} con valores:
tags = {"java", "sql", "git", "java"}
print(len(tags))
print(sorted(tags))
backend = {"java", "sql", "docker"}
print(sorted(tags & backend))
print(sorted(tags | backend))
Salida
3
['git', 'java', 'sql']
['java', 'sql']
['docker', 'git', 'java', 'sql']
  • & deja solo los elementos que dos conjuntos tienen en común, y | los une. Los necesitarás en el ejercicio.

Pruébalo

Una factura repetida

El contador quiere ver los números de factura que se ingresaron dos veces. ¿Qué pregunta si el número ya está en el conjunto?
invoices = ["F-101", "F-102", "F-101", "F-103"]
seen = set()
for number in invoices:
    if number  seen:
        print("Factura repetida:", number)
    seen.add(number)

Ejercicio

Diccionarios en acción

Resuelve las tareas en tu editor.

1. Una tabla de usuarios. Una clase UserTable con los métodos add(username, email), get(username) y size(). Por dentro es un diccionario. Agrega garcia, mlopez y fernandez con correos de la forma garcia@example.com, y busca mlopez y nadie. En la siguiente lección también construirás tu propia tabla hash, sin dict.

Salida
mlopez: mlopez@example.com
nadie: no existe ese usuario
Usuarios: 3

2. Frecuencia de palabras. Lee una línea de texto, pásala a minúsculas y cuenta cada palabra. Imprime las palabras en orden alfabético.

Entrada
Café con leche y café sin azúcar y café
Salida
azúcar: 1
café: 3
con: 1
leche: 1
sin: 1
y: 2

3. Documentos por palabra clave. Una clase DocumentIndex con los métodos add_document(doc_id, *keywords) y search(keyword), que devuelve una lista con los números de los documentos que tienen esa palabra. Por dentro es un diccionario de listas: para cada palabra, una lista de documentos. Agrega el documento 1 con "POO" y "Java", el documento 2 con "SQL" y "bases de datos", y el documento 3 con "Java" y "colecciones". Busca "Java", "SQL" y "Python".

Salida
Java: [1, 3]
SQL: [2]
Python: []

4. Una red de contactos. Una clase Contact con nombre y correo, y una clase ContactNetwork con los métodos add_contact(name, email), add_connection(a, b) (la conexión va en los dos sentidos), friends_of(name), common(a, b) (amigos en común) y email_of(name). Guarda los contactos en un diccionario nombre → Contact, y las conexiones en un diccionario nombre → conjunto de nombres. friends_of y common devuelven una lista ordenada. Conecta a Juan con María y a Juan con Pedro.

Salida
Juan: ['María', 'Pedro']
María: ['Juan']
Amigos en común de María y Pedro: ['Juan']
Correo de Pedro: pedro@example.com

Más tareas

  • Un ID único. Una función generate_user_id(email) da a cada correo nuevo un número, U001, U002 y así sucesivamente, y para un correo que ya vio devuelve el mismo número. Las mayúsculas y las minúsculas del correo cuentan igual. En la siguiente lección hay una versión con una función hash.
  • Un catálogo de libros. La clave es el ISBN, por ejemplo 978-84-376-0494-7, y el valor es el título. Agrega libros, busca uno e imprímelos todos, ordenados por ISBN.
  • Un planificador de viajes. Para cada destino, guarda una lista de actividades: «París» → «la torre Eiffel», «el Louvre». Imprime el plan en el orden en que se agregaron los destinos. ¿Necesitas algo más que un diccionario simple?
  • Un diccionario en línea. Palabra → definición. Lee palabras hasta que llegue «fin». Para una palabra desconocida, imprime «No existe esa palabra.».
  • Notas por asignatura. Lee líneas como «Matemáticas 6», «Física 5», «Matemáticas 4», e imprime la nota media de cada asignatura, ordenadas por nombre.

Test

Ponte a prueba

1. ¿Cuándo es un diccionario (dict) mejor opción que una lista?
2. El diccionario menu ya tiene la clave "café" con el valor 2. ¿Qué hace menu["café"] = 3?
3. La clave "té" no está en menu. ¿Qué hacen menu["té"] y menu.get("té")?
4. Imprimes un diccionario. ¿En qué orden salen las claves?
5. ¿Qué devuelve add en un set si el valor ya está?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·