import numpy as np
# Clase A — 1º de Bachillerato
clase_a = np.array([
171, 173, 170, 172, 174, 171, 173, 172, 171, 173,
172, 174, 170, 173, 172, 171, 172, 173, 174, 172,
171, 172, 173, 170, 174, 172, 171, 173, 172, 174,
172, 171, 173, 172, 170, 173, 174, 172, 171, 172,
173, 172, 174, 171, 172, 173, 170, 172, 171, 173
])
# Clase B — 2º de la ESO
clase_b = np.array([
148, 168, 155, 172, 151, 165, 161, 170, 153, 167,
158, 173, 149, 162, 157, 169, 155, 164, 171, 150,
166, 154, 160, 174, 152, 163, 156, 168, 159, 172,
147, 165, 153, 170, 158, 162, 155, 167, 149, 173,
161, 156, 168, 152, 164, 170, 154, 159, 163, 169
])
# Clase C — Optativa de deportes mezclada
clase_c = np.array([
142, 156, 180, 148, 188, 162, 174, 139, 185, 153,
178, 144, 167, 191, 150, 172, 138, 183, 158, 176,
145, 169, 195, 141, 160, 186, 154, 173, 143, 179,
163, 147, 188, 155, 171, 140, 182, 158, 176, 149,
165, 193, 144, 170, 152, 184, 146, 161, 178, 157
])
Otro ejemplo
import numpy as np # NumPy es la librería de Python para cálculos matemáticos con listas/arrays.
# Alternativa sin instalar nada: import statistics (librería estándar de Python)
# Lista con 100 alturas en cm. En la vida real esto vendría de un archivo CSV o base de datos.
# Alternativa: pd.read_csv("alturas.csv")["altura"].tolist()
alturas =[172, 173, 154, 168, 175, 177, 168, 164, 167, 162, 173, 159,
182, 160, 161, 177, 154, 154, 167, 179, 179, 178, 172, 165,
173, 174, 165, 172, 166, 187, 179, 176, 171, 155, 163, 160,
187, 167, 167, 171, 168, 158, 170, 181, 174, 169, 176, 164,
175, 166, 167, 176, 171, 159, 166, 177, 159, 162, 167, 169,
161, 185, 165, 173, 171, 173, 172, 177, 164, 169, 162, 169,
181, 174, 169, 175, 174, 173, 185, 172, 166, 148, 159, 167,
163, 167, 164, 169, 173, 189, 169, 171, 177, 161, 168, 178, 170, 181, 158, 188]
# np.mean() suma todos los valores y divide entre el total. Igual que: sum(alturas)/len(alturas)
# Alternativa: statistics.mean(alturas)
media = np.mean(alturas)
mediana=np.median(alturas)
print(f'Media de altura: {media:.2f}')
print(f'Mediana de altura: {mediana:.2f}')
maximo=max(alturas)
minimo=min(alturas)
rango=maximo-minimo
print(f"El máximo es {maximo}, el mínimo {minimo} y el rango {rango}")
sigma=np.std(alturas)
print(f'Desviación típica: {sigma:.2f}')
print(np.std([2,4,6]))
# Tienda A — ventas muy estables (panadería de barrio, clientela fija)
tienda_a = np.array([198, 205, 201, 197, 203, 199, 202])
# Tienda B — ventas moderadamente variables (restaurante, depende del día)
tienda_b = np.array([150, 185, 210, 165, 230, 175, 195])
# Tienda C — ventas muy irregulares (tienda de souvenirs, turismo estacional)
tienda_c = np.array([80, 320, 45, 410, 95, 280, 170])
for nombre, datos in [("Tienda A", tienda_a), ("Tienda B", tienda_b), ("Tienda C", tienda_c)]:
print(f"{nombre} → Media: {np.mean(datos):.1f} € | σ = {np.std(datos):.1f} € | Datos: {datos}")
Ejemplo Outliers
import numpy as np # Para cálculos matemáticos con arrays
import pandas as pd # Para trabajar con tablas de datos (DataFrames)
import matplotlib.pyplot as plt # Para crear gráficos
# Alternativa moderna a matplotlib: import seaborn as sns (más bonito por defecto)
# Semilla para reproducibilidad (siempre obtendremos los mismos resultados)
# Alternativa: omitirla si quieres datos distintos cada vez que ejecutes el código
np.random.seed(42)
# Creamos 97 datos normales (media=50, desviacion tipica=8)
# loc = centro de la campana, scale = ancho, size = número de valores generados
# Alternativa con pandas: pd.Series(np.random.normal(50, 8, 97))
datos_normales = np.random.normal(loc=50, scale=8, size=97)
# Anadimos 3 outliers evidentes
# Son valores que claramente se salen del rango normal (50±8 → esperamos valores entre ~26 y ~74)
# Alternativa: outliers_manuales = np.array([datos_normales.mean() - 6*datos_normales.std(), ...])
outliers_manuales = np.array([5, 95, 102])
# Combinamos todo en un array
# np.concatenate une dos arrays como si pegases dos listas una detrás de otra
# Alternativa con listas puras: datos = datos_normales.tolist() + outliers_manuales.tolist()
datos = np.concatenate([datos_normales, outliers_manuales])
# len() cuenta el número total de elementos. Para un array NumPy también puedes usar datos.shape[0]
print(f'Total de datos: {len(datos)}')
# .min() y .max() son métodos de NumPy. Alternativa: np.min(datos), np.max(datos)
print(f'Minimo: {datos.min():.2f}') # :.2f → muestra solo 2 decimales
print(f'Maximo: {datos.max():.2f}')
# Calculamos los percentiles 25 y 75 (= Q1 y Q3)
# Percentil 25 significa: el valor por debajo del cual está el 25% de los datos
# Alternativa con pandas: Q1 = pd.Series(datos).quantile(0.25)
Q1 = np.percentile(datos, 25)
Q3 = np.percentile(datos, 75)
# Calculamos el IQR
# IQR = distancia entre el "centro" del 50% de los datos. Cuanto mayor, más dispersos están.
# A diferencia de la sigma, el IQR no se ve afectado por los outliers extremos.
IQR = Q3 - Q1
print(f'Q1 (percentil 25): {Q1:.2f}')
print(f'Q3 (percentil 75): {Q3:.2f}')
print(f'IQR = Q3 - Q1: {IQR:.2f}')
# Creamos un DataFrame de pandas
# Un DataFrame es como una tabla Excel: filas y columnas con nombre.
# Aquí tiene una sola columna llamada 'valor' con los 100 datos.
# Alternativa: df = pd.DataFrame(datos, columns=['valor'])
df = pd.DataFrame({'valor': datos})
# Funcion reutilizable para detectar outliers con IQR
# Recibe una Serie de pandas y un factor (por defecto 1.5, el estándar de Tukey).
# Aumentar el factor (ej: 3.0) hace la detección más permisiva (menos outliers detectados).
# Alternativa: sklearn.preprocessing.RobustScaler para normalizar eliminando outliers automáticamente
def detectar_outliers_iqr(serie, factor=1.5):
Q1 = serie.quantile(0.25) # equivale a np.percentile pero opera sobre Series de pandas
Q3 = serie.quantile(0.75)
IQR = Q3 - Q1
limite_inf = Q1 - factor * IQR # Por debajo de esto → outlier. Con factor=1.5: regla de Tukey
limite_sup = Q3 + factor * IQR # Por encima de esto → outlier
# El operador | es OR lógico: True si el valor es menor que el límite inferior O mayor que el superior
# Alternativa: mascara = ~serie.between(limite_inf, limite_sup)
mascara = (serie < limite_inf) | (serie > limite_sup)
return mascara, limite_inf, limite_sup # Devuelve 3 valores a la vez (tupla)
# Usamos la funcion
# Python permite recoger los 3 valores devueltos directamente en 3 variables
# mascara es una Serie de True/False con la misma longitud que df
mascara, lim_inf, lim_sup = detectar_outliers_iqr(df['valor'])
# Anadimos una columna al DataFrame
# df['nueva_columna'] = valores crea una nueva columna. Muy habitual en pandas.
# Alternativa: df = df.assign(es_outlier=mascara)
df['es_outlier'] = mascara
# Ver solo los outliers
# df[condicion] filtra las filas donde la condición es True. Igual que un WHERE en SQL.
# Alternativa más corta: df[df['es_outlier']] (True/False ya es suficiente, sin == True)
print(df[df['es_outlier'] == True])
# plt.subplots(1, 2) crea una figura con 1 fila y 2 columnas de gráficos (dos gráficos lado a lado)
# figsize=(12, 5) → ancho=12 pulgadas, alto=5 pulgadas
# Alternativa con seaborn: fig, axes = plt.subplots(1, 2) + sns.boxplot(..., ax=axes[0])
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# --- Grafico 1: Diagrama de caja (Boxplot) ---
ax1 = axes[0]
# patch_artist=True → rellena la caja con color (por defecto está vacía)
# boxprops, medianprops → diccionarios para personalizar el estilo visual
# Alternativa más sencilla: pd.Series(datos).plot.box(ax=ax1)
ax1.boxplot(datos, patch_artist=True,
boxprops=dict(facecolor='lightblue'),
medianprops=dict(color='red', linewidth=2))
# axhline dibuja una línea horizontal en y=valor. Muy útil para marcar umbrales.
# Alternativa: ax1.fill_betweenx para sombrear la zona válida en vez de marcar los límites
ax1.axhline(y=lim_inf, color='red', linestyle='--', label=f'Limite inf: {lim_inf:.1f}')
ax1.axhline(y=lim_sup, color='red', linestyle='--', label=f'Limite sup: {lim_sup:.1f}')
ax1.set_title('Diagrama de Caja')
ax1.legend() # Muestra la leyenda con los labels definidos arriba
# --- Grafico 2: Histograma ---
ax2 = axes[1]
# ~ es el operador NOT para arrays booleanos: invierte True↔False
# datos[~mascara] → solo los valores donde mascara es False (= los datos normales)
normales = datos[~mascara]
# mascara.values convierte la Serie de pandas a array NumPy para poder indexar datos con ella
# Alternativa: outs = df[df['es_outlier']]['valor'].values
outs = datos[mascara.values]
# Superponemos dos histogramas en el mismo eje: uno azul (normales) y uno rojo (outliers)
# bins=20 → divide el rango en 20 barras. Más bins = más detalle, menos bins = más suavizado
ax2.hist(normales, bins=20, color='steelblue', label='Datos normales')
ax2.hist(outs, bins=5, color='red', label=f'Outliers ({len(outs)})')
# axvline dibuja líneas verticales (igual que axhline pero en vertical) para marcar los límites
ax2.axvline(lim_inf, color='darkred', linestyle='--')
ax2.axvline(lim_sup, color='darkred', linestyle='--')
ax2.set_title('Histograma con Outliers marcados')
ax2.legend()
# tight_layout() ajusta automáticamente los márgenes para que los gráficos no se solapen
# Alternativa: plt.subplots_adjust(wspace=0.3) para controlar el espacio manualmente
plt.tight_layout()
plt.show() # Muestra la figura. En Jupyter Notebook no hace falta, se muestra automáticamente.
Ejercicio campana
Con estos datos:
[172, 173, 154, 168, 175, 177, 168, 164, 167, 162, 173, 159, 182, 160, 161, 177, 154, 154, 167, 179, 179, 178, 172, 165, 173, 174, 165, 172, 166, 187, 179, 176, 171, 155, 163, 160, 187, 167, 167, 171, 168, 158, 170, 181, 174, 169, 176, 164, 175, 166, 167, 176, 171, 159, 166, 177, 159, 162, 167, 169, 161, 185, 165, 173, 171, 173, 172, 177, 164, 169, 162, 169, 181, 174, 169, 175, 174, 173, 185, 172, 166, 148, 159, 167, 163, 167, 164, 169, 173, 189, 169, 171, 177, 161, 168, 178, 170, 181, 158, 188]
Calcula la media, la desviación típica y cuenta cuantos alumnos miden entre 162 y 178.
import numpy as np # NumPy es la librería de Python para cálculos matemáticos con listas/arrays.
# Alternativa sin instalar nada: import statistics (librería estándar de Python)
# Lista con 100 alturas en cm. En la vida real esto vendría de un archivo CSV o base de datos.
# Alternativa: pd.read_csv("alturas.csv")["altura"].tolist()
alturas=[172, 173, 154, 168, 175, 177, 168, 164, 167, 162, 173, 159, 182, 160, 161, 177, 154, 154, 167, 179, 179, 178, 172, 165, 173, 174, 165, 172, 166, 187, 179, 176, 171, 155, 163, 160, 187, 167, 167, 171, 168, 158, 170, 181, 174, 169, 176, 164, 175, 166, 167, 176, 171, 159, 166, 177, 159, 162, 167, 169, 161, 185, 165, 173, 171, 173, 172, 177, 164, 169, 162, 169, 181, 174, 169, 175, 174, 173, 185, 172, 166, 148, 159, 167, 163, 167, 164, 169, 173, 189, 169, 171, 177, 161, 168, 178, 170, 181, 158, 188]
# np.mean() suma todos los valores y divide entre el total. Igual que: sum(alturas)/len(alturas)
# Alternativa: statistics.mean(alturas)
media=np.mean(alturas)
# np.std() calcula la desviación típica (cuánto se dispersan los datos respecto a la media).
# ⚠️ Por defecto usa ddof=0 (población completa). Si fuera una muestra: np.std(alturas, ddof=1)
# Alternativa: statistics.stdev(alturas) <-- usa ddof=1 automáticamente
sigma=np.std(alturas)
# f-string: forma moderna de insertar variables dentro de un texto.
# Alternativa antigua: print("La media es: " + str(media) + " y la desviación típica es: " + str(sigma))
print(f"La media es: {media} y la desviación típica es: {sigma}")
# ¿Cuantos hay entre 1, 2 y 3 sigmas
# Contadores inicializados a 0. Irán sumando 1 por cada altura que caiga en cada rango.
sigma1=0
sigma2=0
sigma3=0
# Bucle clásico: recorre cada altura de la lista una por una.
# Alternativa más rápida para datos grandes: usar NumPy (ver abajo con arr = np.array())
for altura in alturas:
# Python permite encadenar comparaciones: a < x < b es lo mismo que x > a and x < b
# Rango ±1σ: contiene teóricamente el 68% de los datos en una distribución normal
if media+sigma >altura > media-sigma:
sigma1+=1 # += 1 es lo mismo que sigma1 = sigma1 + 1
# Rango ±2σ: contiene teóricamente el 95% de los datos
if media + sigma*2 > altura > media - sigma*2:
sigma2 += 1
# Rango ±3σ: contiene teóricamente el 99.7% de los datos
if media + sigma*3 > altura > media - sigma*3:
sigma3 += 1
# Imprime los tres conteos separados por espacio. Deberían acercarse a 68, 95 y 100.
print(sigma1,sigma2,sigma3)
# ── Segunda versión: más compacta con list comprehension ─────────────────────
# Hace exactamente lo mismo que el bucle for de arriba, pero en una sola línea.
# [x for x in lista if condicion] filtra la lista y len() cuenta cuántos pasaron el filtro.
# Alternativa NumPy (más eficiente): np.sum((arr >= media-sigma) & (arr <= media+sigma))
sigma1=len([altura for altura in alturas if media+sigma >altura > media-sigma])
sigma2=len([altura for altura in alturas if media + sigma*2 > altura > media - sigma*2])
sigma3=len([altura for altura in alturas if media + sigma*3 > altura > media - sigma*3])
print(sigma1,sigma2,sigma3)
# ── Tercera versión: genera datos aleatorios para verificar la regla 68-95-99.7 ──
# Sobreescribe la lista original con 1000 alturas simuladas siguiendo una distribución normal.
# loc=170 → media de 170cm, scale=8 → sigma de 8cm, size=1000 → genera 1000 valores
# ⚠️ Tras esta línea, media y sigma siguen siendo los de las 100 alturas reales, no de estos datos.
alturas = np.random.normal(loc=170, scale=8, size=1000) # Datos aleatorios con distribución normal
# Lista vacía donde se irán añadiendo los porcentajes para sigma 1, 2 y 3.
# Alternativa: sigmas = {f"sigma{i}": ... for i in range(1,4)} (diccionario más descriptivo)
sigmas=[]
# range(1,4) genera [1, 2, 3]. Para cada i, cuenta cuántas alturas caen dentro de ±i*sigma
# y divide entre 10 para convertirlo en porcentaje (1000 datos → dividir/10 = % directamente).
# ⚠️ Truco del /10: solo funciona porque hay exactamente 1000 datos. Para N genérico: /len(alturas)*100
for i in range(1,4):
sigmas.append(len([altura for altura in alturas if media+sigma*i >altura > media-sigma*i])/10)
# Debería imprimir algo cercano a [68.0, 95.0, 99.7] si los datos son suficientemente normales.
# Alternativa más clara: print(f"±1σ: {sigmas[0]}% ±2σ: {sigmas[1]}% ±3σ: {sigmas[2]}%")
print(sigmas)
Comprensión de listas
# ─────────────────────────────────────────────
# COMPRENSIÓN DE LISTAS (List Comprehensions)
# ─────────────────────────────────────────────
# Es una forma compacta de crear una lista nueva a partir de otra.
# Sintaxis básica:
#
# nueva_lista = [expresion for elemento in iterable]
#
# Equivale exactamente a:
#
# nueva_lista = []
# for elemento in iterable:
# nueva_lista.append(expresion)
# ─────────────────────────────────────────────
# TRANSFORMACIÓN: aplicar una operación a cada elemento
# ─────────────────────────────────────────────
lista = [1, 2, 3, 4, 5, 6]
# Por cada número de la lista, guardamos ese número multiplicado por 2
dobles = [numero * 2 for numero in lista]
print(dobles) # → [2, 4, 6, 8, 10, 12]
# 💡 EQUIVALENTE con for tradicional (hace exactamente lo mismo):
dobles = []
for numero in lista:
dobles.append(numero * 2)
# ─────────────────────────────────────────────
# TRANSFORMAR CADENAS
# ─────────────────────────────────────────────
alumnos = ["ana", "jUAN", "pep", "IU", "Eva"]
# .title() pone en mayúscula la primera letra de cada palabra y el resto en minúscula
alumnos_bien = [alumno.title() for alumno in alumnos]
print(alumnos_bien) # → ['Ana', 'Juan', 'Pep', 'Iu', 'Eva']
# 💡 ALTERNATIVAS a .title() para normalizar texto:
# "jUAN".upper() → "JUAN" todo mayúsculas
# "jUAN".lower() → "juan" todo minúsculas
# "jUAN".capitalize() → "Juan" solo la primera letra en mayúscula
# "jUAN".title() → "Juan" mayúscula al inicio de cada palabra
# [0:2] → slice con los dos primeros caracteres de cada nombre
alumnos_comienzos = [alumno[0:2] for alumno in alumnos]
print(alumnos_comienzos) # → ['an', 'jU', 'pe', 'IU', 'Ev']
# ─────────────────────────────────────────────
# USAR EL ELEMENTO COMO CANTIDAD
# ─────────────────────────────────────────────
lista = [2, 4, 5, 3]
# El número indica cuántas veces repetimos el carácter "*"
# "*" * 3 → "***"
asteriscos = ["*" * n for n in lista]
print(asteriscos) # → ['**', '****', '*****', '***']
# 💡 ALTERNATIVA: usar otro carácter o construir una barra de progreso
barras = ["█" * n for n in lista]
print(barras) # → ['██', '████', '█████', '███']
# ─────────────────────────────────────────────
# COMBINAR SLICE Y COMPRENSIÓN
# ─────────────────────────────────────────────
letras = "abcdefghijklmnopqrstuvwxyz"
# Para cada n de la lista, tomamos los primeros n caracteres del abecedario
# letras[0:2] → "ab" | letras[0:4] → "abcd" | letras[0:5] → "abcde"
comienzos = [letras[0:n] for n in lista]
print(comienzos) # → ['ab', 'abcd', 'abcde', 'abc']
# ─────────────────────────────────────────────
# COPIA DE UNA LISTA
# ─────────────────────────────────────────────
# Si la expresión es simplemente el propio elemento, obtenemos una copia
lista_igual = [n for n in lista]
print(lista_igual) # → [2, 4, 5, 3]
# 💡 ALTERNATIVA más directa para copiar una lista:
# lista_igual = lista.copy()
# lista_igual = lista[:]
# ─────────────────────────────────────────────
# FILTRADO: añadir una condición con 'if'
# ─────────────────────────────────────────────
# Sintaxis con filtro:
#
# nueva_lista = [expresion for elemento in iterable if condicion]
#
# Solo se incluyen los elementos que cumplen la condición.
palabras = ["patata", "bustrofedónico", "alubia",
"otorrinolaringólogo", "mesa", "supercalifragilísticoespialidoso"]
# Solo incluimos la palabra si su longitud (len) es mayor de 10
palabras_largas = [palabra for palabra in palabras if len(palabra) > 10]
print(palabras_largas) # → ['bustrofedónico', 'otorrinolaringólogo', 'supercalifragilísticoespialidoso']
# 💡 ALTERNATIVAS de filtrado:
palabras_cortas = [p for p in palabras if len(p) <= 5] # longitud ≤ 5
con_a = [p for p in palabras if p.startswith('a')] # que empiecen por 'a'
con_vocal_final = [p for p in palabras if p[-1] in 'aeiou'] # acaban en vocal
# ─────────────────────────────────────────────
# COMBINAR FILTRADO Y TRANSFORMACIÓN
# ─────────────────────────────────────────────
# La expresión y la condición pueden ser distintas:
# · la condición decide SI se incluye el elemento
# · la expresión decide QUÉ se guarda de ese elemento
# Objetivo: de una lista de nombres, quedarse solo con los de
# longitud PAR e invertirlos.
# ["Pep","Iu","Eva","Juan"] → longitud par: "Iu"(2), "Juan"(4) → invertidos: ["uI","nauJ"]
lista = ["Pep", "Iu", "Eva", "Juan"]
# Paso a paso:
# 1. if len(i) % 2 == 0 → filtra: solo "Iu" y "Juan" tienen longitud par
# 2. i[::-1] → transforma: invierte cada cadena seleccionada
par_invertidas = [i[::-1] for i in lista if len(i) % 2 == 0]
print(par_invertidas) # → ['uI', 'nauJ']
# 💡 EQUIVALENTE con for tradicional, más explicado:
par_invertidas = []
for i in lista:
if len(i) % 2 == 0: # condición: longitud par
par_invertidas.append(i[::-1]) # transformación: invertir
# 💡 ALTERNATIVA: guardar también el original para comparar
pares_con_original = [(i, i[::-1]) for i in lista if len(i) % 2 == 0]
print(pares_con_original) # → [('Iu', 'uI'), ('Juan', 'nauJ')]
Anatomía de una comprensión de lista
resultado = [ expresión for elemento in iterable if condición ]
│ │ │ │
│ │ │ └── (opcional) filtro:
│ │ │ solo los que cumplan esto
│ │ └─────────────── de dónde vienen los datos
│ └───────────────────────────── nombre temporal
└────────────────────────────────────────────── qué guardamos
Comparativa: for tradicional vs comprensión
Con for tradicional |
Con comprensión |
|---|---|
resultado = [] + for + append() |
Todo en una línea |
| Más fácil de leer al principio | Más compacto y expresivo |
| Fácil de depurar paso a paso | Ideal cuando la lógica es simple |
| Mejor si hay lógica compleja | Evitar si hay más de una condición |
Los tres sabores de la comprensión
# 1. Solo transformación
[expresion for x in lista]
# 2. Solo filtrado
[x for x in lista if condicion]
# 3. Transformación + filtrado
[expresion for x in lista if condicion]
💡 Regla de oro: si el
for+ifcabe en una línea corta y se lee de forma natural, usa comprensión. Si necesitas anidar dosfor, añadir variosifo la lógica es compleja, elfortradicional será más claro y fácil de mantener.
Datos anidados
# ─────────────────────────────────────────────
# DICCIONARIOS CON LISTAS DENTRO
# ─────────────────────────────────────────────
# Un valor de un diccionario puede ser cualquier tipo:
# un número, una cadena, una lista, otro diccionario…
alumno = {
'nombre': 'Ana',
'email': 'ana@ana.com',
'notas': [6, 7, 5, 8] # ← el valor es una lista
}
# Para acceder a la lista usamos la clave 'notas'
# y luego la recorremos con un for como cualquier otra lista
print(f"El alumno {alumno['nombre']} tiene las siguientes notas:")
for nota in alumno['notas']:
print(nota)
# 💡 ALTERNATIVA: calcular la media al vuelo
notas = alumno['notas']
print(f"Media: {sum(notas) / len(notas):.1f}") # :.1f → un decimal
# ─────────────────────────────────────────────
# DICCIONARIOS ANIDADOS (diccionario dentro de diccionario)
# ─────────────────────────────────────────────
# Un valor también puede ser otro diccionario.
# Para acceder a sus datos encadenamos corchetes: d['clave1']['clave2']
cliente = {
'nombre': 'Ana',
'email': 'ana@gmail.com',
'direccion': { # ← valor que es otro diccionario
'calle': 'Agla',
'numero': 6,
'cp': '08001',
'ciudad': 'Barcelona'
}
}
# Acceso en cadena: primero entramos en 'direccion', luego en 'calle' y 'numero'
print(f"El cliente {cliente['nombre']} vive en "
f"{cliente['direccion']['calle']} número {cliente['direccion']['numero']}")
# → El cliente Ana vive en Agla número 6
# 💡 ALTERNATIVA: extraer el sub-diccionario en una variable auxiliar
# para no repetir cliente['direccion'] varias veces
dir = cliente['direccion']
print(f"{dir['calle']}, {dir['numero']}, {dir['cp']} {dir['ciudad']}")
# ─────────────────────────────────────────────
# LISTA DE DICCIONARIOS
# ─────────────────────────────────────────────
# Patrón muy habitual: una lista donde cada elemento
# es un diccionario que representa un "registro" (alumno, producto, usuario…)
alumnos = [
{'nombre': 'Ana', 'email': 'ana@gmail.com'},
{'nombre': 'Pep', 'email': 'pep@pepe.com'},
{'nombre': 'Iu', 'email': 'iu@iu.com'}
]
# Recorrer la lista y acceder a una clave de cada diccionario
for alumno in alumnos:
print(alumno['nombre']) # → Ana / Pep / Iu
# Índice negativo -1: último elemento de la lista
print(alumnos[-1]['nombre']) # → Iu
# 💡 ALTERNATIVA: buscar un alumno concreto con next()
ana = next((a for a in alumnos if a['nombre'] == 'Ana'), None)
print(ana) # → {'nombre': 'Ana', 'email': 'ana@gmail.com'}
# · next() devuelve el primer resultado que cumpla la condición
# · el None al final evita un error si no se encuentra ninguno
# 💡 ALTERNATIVA: filtrar varios con comprensión de lista
emails = [a['email'] for a in alumnos]
print(emails) # → ['ana@gmail.com', 'pep@pepe.com', 'iu@iu.com']
# ─────────────────────────────────────────────
# LISTAS DE LISTAS (matriz / cuadrícula)
# ─────────────────────────────────────────────
# Una lista puede contener otras listas → se comporta como una tabla 2D.
# Se accede con dos índices: matriz[fila][columna]
cuadrado = [
[1, 2, 3], # fila 0
[4, 5, 6], # fila 1
[7, 8, 9] # fila 2
]
# Recorrer con for anidado: el primer for va fila a fila,
# el segundo va elemento a elemento dentro de cada fila
for linea in cuadrado:
print(linea) # imprime la fila entera como lista
for numero in linea:
print(numero) # imprime cada número por separado
# Acceso directo a un elemento: [fila][columna]
print(cuadrado[1][1]) # → 5 (fila 1, columna 1 → el centro del cuadrado)
print(cuadrado[0][2]) # → 3 (fila 0, columna 2)
print(cuadrado[2][0]) # → 7 (fila 2, columna 0)
# 💡 ALTERNATIVA: imprimir el cuadrado de forma más visual
for fila in cuadrado:
print(' '.join(str(n) for n in fila))
# → 1 2 3
# → 4 5 6
# → 7 8 9
# ─────────────────────────────────────────────
# ESTRUCTURA DE DATOS COMPLEJA (caso real)
# ─────────────────────────────────────────────
# En proyectos reales anidamos todos estos conceptos:
# diccionarios, listas y sub-diccionarios a varios niveles.
# La clave es ir "entrando" nivel a nivel con corchetes.
empresa = {
'nombre': 'TechCorp',
'departamentos': { # nivel 1: dict de departamentos
'Desarrollo': {
'jefe': 'María Ruiz',
'presupuesto': 150000,
'empleados': [ # nivel 2: lista de empleados
{
'id': 'E001',
'nombre': 'Ana García',
'salario': 35000,
'habilidades': ['Python', 'Django', 'SQL'],
'proyectos': ['WebApp', 'API REST'],
'notas_evaluacion': [8, 9, 8, 9],
},
{
'id': 'E002',
'nombre': 'Carlos López',
'salario': 32000,
'habilidades': ['JavaScript', 'React', 'CSS'],
'proyectos': ['WebApp', 'Dashboard'],
'notas_evaluacion': [7, 8, 7, 6],
},
],
},
'Marketing': {
'jefe': 'Pedro Sanz',
'presupuesto': 80000,
'empleados': [
{
'id': 'E003',
'nombre': 'Lucía Mora',
'salario': 28000,
'habilidades': ['PowerBI', 'Excel', 'Photoshop'],
'proyectos': ['Campaña Q1', 'Redes Sociales'],
'notas_evaluacion': [9, 9, 8, 9],
},
],
},
}
}
# ── Acceso profundo paso a paso ──────────────────────────────────
# Vamos entrando nivel a nivel hasta llegar a lo que queremos:
#
# empresa
# └─ ['departamentos'] → dict de departamentos
# └─ ['Desarrollo'] → dict del departamento
# └─ ['empleados'] → lista de empleados
# └─ [0] → primer empleado (Ana García)
# └─ ['notas_evaluacion'] → [8, 9, 8, 9]
notas = empresa['departamentos']['Desarrollo']['empleados'][0]['notas_evaluacion']
media = sum(notas) / len(notas)
print(media) # → 8.5
# 💡 ALTERNATIVAS Y USOS PRÁCTICOS sobre la estructura 'empresa'
# ── Listar todos los empleados de todos los departamentos
for nombre_depto, depto in empresa['departamentos'].items():
print(f"\n── {nombre_depto} (jefe: {depto['jefe']}) ──")
for emp in depto['empleados']:
print(f" {emp['id']} | {emp['nombre']} | {emp['salario']}€")
# ── Calcular la media de evaluación de cada empleado
for nombre_depto, depto in empresa['departamentos'].items():
for emp in depto['empleados']:
notas = emp['notas_evaluacion']
media = sum(notas) / len(notas)
print(f"{emp['nombre']}: media {media:.1f}")
# ── Acceso seguro con get() cuando no sabemos si la clave existe
salario = empresa['departamentos']['Desarrollo']['empleados'][0].get('bonus', 0)
print(f"Bonus: {salario}€") # → Bonus: 0€ (no existe la clave, devuelve 0)
# ─────────────────────────────────────────────
# FUNCIONES QUE BUSCAN DENTRO DE ESTRUCTURAS ANIDADAS
# ─────────────────────────────────────────────
# Estas funciones reciben el diccionario 'empresa' del ejercicio anterior
# y devuelven una lista con los empleados que coinciden con la búsqueda.
# Si no encuentran nada, devuelven una lista vacía [].
# ─────────────────────────────────────────────
# BUSCAR EMPLEADOS POR NOMBRE
# ─────────────────────────────────────────────
def buscar_empleado(empresa, nombre):
resultado = [] # lista donde iremos guardando los encontrados
departamentos = empresa['departamentos'] # extraemos el dict de departamentos
for departamento in departamentos.values(): # recorremos cada departamento
for empleado in departamento['empleados']: # recorremos cada empleado del departamento
# 'in' sobre una cadena comprueba si nombre está CONTENIDO en empleado['nombre']
# Por eso 'ía' encuentra 'Ana García' y 'María Ruiz'
# Es sensible a mayúsculas: 'ana' NO encontraría 'Ana'
if nombre in empleado['nombre']:
resultado.append(empleado) # añadimos el dict completo del empleado
return resultado # devuelve [] si no encontró nadie, o una lista de coincidencias
# Encuentra a Ana García y María Ruiz porque ambas contienen 'ía'
print(buscar_empleado(empresa, 'ía'))
# Devuelve [] porque no existe ningún 'Carlos González' (hay 'Carlos López')
print(buscar_empleado(empresa, 'Carlos González'))
# 💡 ALTERNATIVA: búsqueda sin distinguir mayúsculas/minúsculas
# Convirtiendo ambas cadenas a minúsculas antes de comparar
def buscar_empleado_v2(empresa, nombre):
resultado = []
for departamento in empresa['departamentos'].values():
for empleado in departamento['empleados']:
if nombre.lower() in empleado['nombre'].lower(): # .lower() → todo minúsculas
resultado.append(empleado)
return resultado
# Ahora 'ana' encuentra 'Ana García', 'ANA' también, etc.
print(buscar_empleado_v2(empresa, 'ana'))
# 💡 ALTERNATIVA: devolver solo el dato que interesa, no el dict entero
def buscar_nombres(empresa, nombre):
return [
emp['nombre'] # solo el nombre
for depto in empresa['departamentos'].values()
for emp in depto['empleados']
if nombre.lower() in emp['nombre'].lower() # búsqueda insensible
]
# Comprensión de lista con dos for anidados: más compacta, misma lógica
# ─────────────────────────────────────────────
# BUSCAR EMPLEADOS POR HABILIDAD
# ─────────────────────────────────────────────
def buscar_empleado_habilidades(empresa, habilidad):
resultado = []
departamentos = empresa['departamentos']
for departamento in departamentos.values():
for empleado in departamento['empleados']:
# Aquí 'in' comprueba si habilidad es un elemento de la LISTA
# empleado['habilidades'] es ['Python', 'Django', 'SQL'] etc.
# Esto busca coincidencia EXACTA: 'React' sí, 'react' NO
if habilidad in empleado['habilidades']:
resultado.append(empleado)
return resultado
# Encuentra a Carlos López porque tiene 'React' en su lista de habilidades
print(buscar_empleado_habilidades(empresa, 'React'))
# Devuelve [] porque nadie tiene 'C#' en sus habilidades
print(buscar_empleado_habilidades(empresa, 'C#'))
# 💡 ALTERNATIVA: búsqueda insensible a mayúsculas en la lista de habilidades
def buscar_empleado_habilidades_v2(empresa, habilidad):
resultado = []
for departamento in empresa['departamentos'].values():
for empleado in departamento['empleados']:
# Convertimos cada habilidad a minúsculas antes de comparar
habilidades_lower = [h.lower() for h in empleado['habilidades']]
if habilidad.lower() in habilidades_lower:
resultado.append(empleado)
return resultado
print(buscar_empleado_habilidades_v2(empresa, 'python')) # Encuentra a Ana García
# 💡 ALTERNATIVA: buscar empleados que tengan VARIAS habilidades a la vez
def buscar_por_varias_habilidades(empresa, habilidades_buscadas):
resultado = []
for departamento in empresa['departamentos'].values():
for empleado in departamento['empleados']:
# set y operador <= comprueban si un conjunto está contenido en otro
# {'Python','SQL'} <= {'Python','Django','SQL'} → True
if set(habilidades_buscadas) <= set(empleado['habilidades']):
resultado.append(empleado['nombre'])
return resultado
print(buscar_por_varias_habilidades(empresa, ['Python', 'SQL']))
# → ['Ana García']
# 💡 ALTERNATIVA: función genérica que busca por CUALQUIER campo
def buscar_por_campo(empresa, campo, valor):
resultado = []
for departamento in empresa['departamentos'].values():
for empleado in departamento['empleados']:
if empleado.get(campo) == valor: # get() evita error si el campo no existe
resultado.append(empleado['nombre'])
return resultado
print(buscar_por_campo(empresa, 'salario', 32000)) # → ['Carlos López']
Patrón general: «recorrer y filtrar»
def buscar_X(estructura, criterio):
resultado = [] # 1. empezamos con lista vacía
for nivel1 in estructura.values(): # 2. recorremos el nivel 1
for item in nivel1['coleccion']: # 3. recorremos el nivel 2
if criterio cumple condicion: # 4. comprobamos el criterio
resultado.append(item) # 5. guardamos el encontrado
return resultado
Diccionarios
# ─────────────────────────────────────────────
# DICCIONARIOS EN PYTHON
# ─────────────────────────────────────────────
# Un diccionario guarda pares clave: valor
# · Las claves son únicas (no puede haber dos iguales)
# · Los valores pueden ser de cualquier tipo
# · Se definen con llaves { }
# · A diferencia de las listas, NO se accede por posición
# sino por el nombre de la clave
producto = {
'referencia': 'SK789',
'precio': 200
}
print(producto) # → {'referencia': 'SK789', 'precio': 200}
clase = {'nombre': 'WEB', 'capacidad': 20}
print(clase) # → {'nombre': 'WEB', 'capacidad': 20}
# ─────────────────────────────────────────────
# LEER Y MODIFICAR VALORES
# ─────────────────────────────────────────────
# Acceder a un valor con su clave entre corchetes
print(clase['nombre']) # → WEB
print(clase['capacidad']) # → 20
# Modificar un valor existente (misma sintaxis que al leer)
clase['capacidad'] = 15
print(clase) # → {'nombre': 'WEB', 'capacidad': 15}
camisa = {
'talla': 'L',
'color': 'rojo',
'precio': 20,
'moneda': '€'
}
# f-string con acceso al diccionario dentro de las llaves { }
print(f"La talla de la camisa es {camisa['talla']}") # → La talla de la camisa es L
# ─────────────────────────────────────────────
# ACCESO SEGURO: get()
# ─────────────────────────────────────────────
# Si accedemos a una clave que NO existe con corchetes → ERROR (KeyError)
# print(camisa['stock']) ← esto lanzaría un KeyError y detendría el programa
# get(clave) devuelve el valor si existe, o None si no existe (sin error)
print(camisa.get('stock')) # → None
# get(clave, valor_por_defecto) devuelve el valor por defecto si no existe
print(camisa.get('stock', 0)) # → 0 ← mucho más útil en la práctica
# 💡 ALTERNATIVA: comprobar antes con 'in'
if 'stock' in camisa:
print(camisa['stock'])
else:
print("La clave 'stock' no existe")
# ─────────────────────────────────────────────
# AÑADIR CLAVES NUEVAS
# ─────────────────────────────────────────────
# Asignar a una clave que no existe → la crea automáticamente
camisa['stock'] = 20
print(camisa)
# → {'talla':'L','color':'rojo','precio':20,'moneda':'€','stock':20}
# ─────────────────────────────────────────────
# UPDATE: añadir o modificar varias claves a la vez
# ─────────────────────────────────────────────
# update() acepta argumentos con nombre (clave=valor)
camisa.update(almacen='Central', activo=True)
print(camisa)
# → {..., 'almacen': 'Central', 'activo': True}
# 💡 ALTERNATIVA: pasarle otro diccionario
# camisa.update({'almacen': 'Central', 'activo': True})
# 💡 ALTERNATIVA desde Python 3.9: operador |=
# camisa |= {'almacen': 'Central', 'activo': True}
# ─────────────────────────────────────────────
# VISTAS: keys(), values(), items()
# ─────────────────────────────────────────────
# Estas tres funciones devuelven "vistas" del diccionario.
# No son listas normales, pero se pueden recorrer con for
# y se actualizan automáticamente si el diccionario cambia.
claves = camisa.keys() # Todas las claves
valores = camisa.values() # Todos los valores
elementos = camisa.items() # Pares (clave, valor) como tuplas
print(claves) # → dict_keys(['talla', 'color', 'precio', ...])
print(valores) # → dict_values(['L', 'rojo', 20, ...])
print(elementos) # → dict_items([('talla','L'), ('color','rojo'), ...])
# 💡 Si necesitas una lista real puedes convertirlas:
# list(camisa.keys()) → ['talla', 'color', 'precio', ...]
# list(camisa.values()) → ['L', 'rojo', 20, ...]
# ─────────────────────────────────────────────
# ACCEDER CON UNA VARIABLE COMO CLAVE
# ─────────────────────────────────────────────
# La clave puede estar guardada en una variable, no tiene que ser literal
miclave = "stock"
print(camisa[miclave]) # → 20
miclave = "almacen"
print(camisa[miclave]) # → Central
# Esto es muy útil cuando la clave viene de una entrada del usuario
# o se calcula en tiempo de ejecución
# ─────────────────────────────────────────────
# RECORRER UN DICCIONARIO CON FOR
# ─────────────────────────────────────────────
# OPCIÓN 1: iterar solo por las claves y acceder al valor manualmente
for clave in camisa.keys():
print(clave, camisa[clave])
# OPCIÓN 2 (más elegante): iterar por clave y valor a la vez con items()
for clave, valor in camisa.items():
print(clave, valor)
# 💡 ALTERNATIVA: iterar directamente (sin .keys()) hace lo mismo que la opción 1
# for clave in camisa:
# print(clave, camisa[clave])
# 💡 ALTERNATIVA: crear una lista de pares formateados con comprensión de lista
# pares = [f"{k} → {v}" for k, v in camisa.items()]
# ─────────────────────────────────────────────
# ELIMINAR ELEMENTOS: pop() y popitem()
# ─────────────────────────────────────────────
# pop(clave): elimina la clave indicada y DEVUELVE su valor
elemento = camisa.pop('almacen')
print(elemento) # → Central (el valor que tenía 'almacen')
print(camisa) # → {...} sin 'almacen'
# 💡 pop() también acepta valor por defecto para evitar errores:
# camisa.pop('clave_inexistente', None) → devuelve None sin error
# popitem(): elimina y devuelve el ÚLTIMO par (clave, valor) insertado
# Devuelve una tupla → ('clave', valor)
elemento = camisa.popitem()
print(elemento) # → ('activo', True) (el último que se añadió)
print(camisa) # → {...} sin 'activo'
# 💡 ALTERNATIVA para eliminar sin recuperar el valor:
# del camisa['precio'] → borra la clave directamente
Listas y slicing
# ─────────────────────────────────────────────
# MODIFICAR LISTAS: ÍNDICES Y SLICES
# ─────────────────────────────────────────────
# Importación de un módulo propio (archivo iterar_cadenas.py)
# Por ahora no te preocupes por esto, lo veremos más adelante
from iterar_cadenas import posicion
lista = [1, 2, 3, 4, 5, 6, 7]
# Cambiar UN elemento por su índice (posición)
# El índice 3 corresponde al 4º elemento (se empieza a contar desde 0)
lista[3] = 11
print(lista) # → [1, 2, 3, 11, 5, 6, 7]
# SLICE (rebanada): modificar VARIOS elementos a la vez
# lista[4:7] selecciona desde el índice 4 hasta el 6 (el 7 no se incluye)
# Los reemplazamos por tres nuevos valores
lista[4:7] = [91, 92, 93]
print(lista) # → [1, 2, 3, 11, 91, 92, 93]
# Un slice puede reemplazarse por MENOS elementos de los que había
# Aquí sustituimos 2 elementos (índices 0 y 1) por solo 1
lista[0:2] = [1]
print(lista) # → [1, 3, 11, 91, 92, 93] ← la lista se encoge
# También puede reemplazarse por MÁS elementos de los que había
# Aquí sustituimos 2 elementos por 9 → la lista crece
lista[0:2] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(lista) # → [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 91, 92, 93]
# ─────────────────────────────────────────────
# EXTEND: añadir elementos de otro iterable
# ─────────────────────────────────────────────
listilla = [1, 2, 3]
otralista = [7, 77, 777]
tupla = (8, 88, 888)
cadena = "abc"
# extend() añade cada elemento del iterable uno a uno al final de la lista
listilla.extend(otralista) # Añade los elementos de una lista
print(listilla) # → [1, 2, 3, 7, 77, 777]
listilla.extend(tupla) # También funciona con tuplas
print(listilla) # → [1, 2, 3, 7, 77, 777, 8, 88, 888]
listilla.extend(cadena) # Con una cadena añade CADA LETRA por separado
print(listilla) # → [1, 2, 3, 7, 77, 777, 8, 88, 888, 'a', 'b', 'c']
# ─────────────────────────────────────────────
# EXTEND vs APPEND: diferencia importante
# ─────────────────────────────────────────────
lista = [1, 2, 3]
lista.extend([4, 5]) # extend: desglosa la lista y añade cada elemento
print(lista) # → [1, 2, 3, 4, 5]
lista = [1, 2, 3]
lista.append([4, 5]) # append: añade el objeto ENTERO como un único elemento
print(lista) # → [1, 2, 3, [4, 5]] ← ¡el último elemento es una lista!
# ─────────────────────────────────────────────
# LISTAS ANIDADAS (listas dentro de listas)
# ─────────────────────────────────────────────
# Cada alumno es una lista con su nombre y una tupla de notas
alumnos = [
["Ana", (1, 2, 3)],
["Pep", (4, 5, 6)],
["Iu", (7, 8)]
]
# Para acceder: alumnos[0] → ["Ana", (1,2,3)]
# alumnos[0][1] → (1, 2, 3)
# alumnos[0][1][0] → 1
# ─────────────────────────────────────────────
# MÉTODOS PARA ELIMINAR ELEMENTOS
# ─────────────────────────────────────────────
frutas = ["pera", "manzana", "kiwi", "pera"]
# 'in' comprueba si un elemento existe en la lista
if "pera" in frutas:
frutas.remove("pera") # remove() elimina la PRIMERA ocurrencia del valor
print(frutas) # → ['manzana', 'kiwi', 'pera'] ← queda una "pera"
# pop() elimina y DEVUELVE el último elemento (sin argumentos)
ultima_fruta = frutas.pop()
print(ultima_fruta) # → 'pera'
print(frutas) # → ['manzana', 'kiwi']
# pop(índice) elimina y devuelve el elemento en esa posición
primera_fruta = frutas.pop(0)
print(primera_fruta) # → 'manzana'
print(frutas) # → ['kiwi']
# ─────────────────────────────────────────────
# BUSCAR ELEMENTOS: index()
# ─────────────────────────────────────────────
lista = [2, 3, 4, 5, 4, 3, 2, 7, 8, 2]
# index(valor) devuelve la posición de la PRIMERA aparición del valor
posicion = lista.index(2)
print(posicion) # → 0
# index(valor, inicio) busca a partir de una posición concreta
# Así podemos encontrar la 2ª, 3ª... aparición del mismo valor
posicion = lista.index(2, posicion + 1) # Busca desde la posición 1 en adelante
print(posicion) # → 6
posicion = lista.index(2, posicion + 1) # Busca desde la posición 7 en adelante
print(posicion) # → 9
# ─────────────────────────────────────────────
# ORDENAR LISTAS: sort()
# ─────────────────────────────────────────────
lista = [2, 3, 4, 5, 4, 3, 2, 7, 8, 2]
lista.sort() # Orden ascendente (de menor a mayor)
print(lista) # → [2, 2, 2, 3, 3, 4, 4, 5, 7, 8]
lista.sort(reverse=True) # Orden descendente (de mayor a menor)
print(lista) # → [8, 7, 5, 4, 4, 3, 3, 2, 2, 2]
# sort(key=función): ordena según el valor que devuelve la función
# para cada elemento. Aquí los pares se quedan igual y los impares
# se "pesan" el doble, por lo que los pares van primero.
def mifuncion(num):
if num % 2 == 0:
return num # Par: se usa su valor real
else:
return num * 2 # Impar: se usa el doble (ocupa más "peso" al ordenar)
lista.sort(key=mifuncion)
print(lista) # → [2, 2, 2, 3, 4, 4, 3, 5, 7, 8] (pares primero)
# Ordenar cadenas por su longitud usando len como función clave
lista = ["Ana", "Iu", "Eva", "Pep", "Rosa", "Juan", "Roc"]
lista.sort(key=len) # len devuelve el número de caracteres de cada nombre
print(lista) # → ['Iu', 'Ana', 'Eva', 'Pep', 'Roc', 'Rosa', 'Juan']
# ─────────────────────────────────────────────
# COPIA DE LISTAS: referencia vs copia real
# ─────────────────────────────────────────────
notas = [6, 7, 3, 5]
# ⚠️ ASIGNACIÓN DIRECTA: NO crea una copia, ambas variables apuntan
# al MISMO objeto en memoria. Cambiar una cambia la otra.
copia = notas
copia[1] = 9
print(copia) # → [6, 9, 3, 5]
print(notas) # → [6, 9, 3, 5] ← ¡notas también cambió! Cuidado con esto.
notas = [6, 7, 3, 5]
# ✅ COPIA REAL con .copy(): crea un objeto nuevo e independiente
# Modificar 'copia' NO afecta a 'notas'
copia = notas.copy()
copia[1] = 9
print(copia) # → [6, 9, 3, 5]
print(notas) # → [6, 7, 3, 5] ← notas no ha cambiado ✓
# ─────────────────────────────────────────────
# SLICES (REBANADAS): acceder a partes de una cadena o lista
# ─────────────────────────────────────────────
# Sintaxis general: coleccion[inicio:fin:paso]
# · inicio: índice donde empieza la rebanada (se incluye)
# · fin: índice donde termina (NO se incluye)
# · paso: de cuánto en cuánto avanza (por defecto 1)
cadena = "Mi mamá me mima"
#índices: 0123456789...
# [inicio:fin] → caracteres desde el índice 2 hasta el 5 (el 6 no se incluye)
print(cadena[2:6]) # → ' mam'
# [:fin] → desde el principio hasta el índice 1 (el 2 no se incluye)
print(cadena[:2]) # → 'Mi'
# [inicio:] → desde el índice 5 hasta el final
print(cadena[5:]) # → 'má me mima'
# Índices NEGATIVOS: cuentan desde el final hacia atrás
# -1 es el último carácter, -2 el penúltimo, etc.
# [-3:] → los últimos 3 caracteres
print(cadena[-3:]) # → 'ima'
# ─────────────────────────────────────────────
# LOS MISMOS SLICES FUNCIONAN IGUAL CON LISTAS
# ─────────────────────────────────────────────
lista = ["Ana", "Iu", "Eva", "Pep", "Rosa", "Juan", "Roc"]
#índices: 0 1 2 3 4 5 6
print(lista[2:6]) # → ['Eva', 'Pep', 'Rosa', 'Juan'] (del índice 2 al 5)
print(lista[:2]) # → ['Ana', 'Iu'] (los 2 primeros)
print(lista[5:]) # → ['Juan', 'Roc'] (desde el índice 5 hasta el final)
print(lista[-3:]) # → ['Juan', 'Roc', ... ] (los 3 últimos)
# ─────────────────────────────────────────────
# EL TERCER PARÁMETRO: el PASO
# ─────────────────────────────────────────────
# [::] → sin inicio, sin fin, paso por defecto (1) → copia entera
print(lista[::]) # → ['Ana', 'Iu', 'Eva', 'Pep', 'Rosa', 'Juan', 'Roc']
# [::2] → coge un elemento sí, uno no (paso 2)
# índices 0, 2, 4, 6 → "Ana", "Eva", "Rosa", "Roc"
print(lista[::2]) # → ['Ana', 'Eva', 'Rosa', 'Roc']
# [::-1] → paso NEGATIVO: recorre la lista al revés
# sin inicio ni fin → la lista completa invertida
print(lista[::-1]) # → ['Roc', 'Juan', 'Rosa', 'Pep', 'Eva', 'Iu', 'Ana']
# [::-2] → al revés de dos en dos
# índices 6, 4, 2, 0 → "Roc", "Rosa", "Eva", "Ana"
print(lista[::-2]) # → ['Roc', 'Rosa', 'Eva', 'Ana']
# [6:2:-1] → hacia atrás desde el índice 6 hasta el 3 (el 2 no se incluye)
# índices 6, 5, 4, 3 → "Roc", "Juan", "Rosa", "Pep"
print(lista[6:2:-1]) # → ['Roc', 'Juan', 'Rosa', 'Pep']
Tuplas
# ─────────────────────────────────────────────
# TUPLAS Y LISTAS EN PYTHON
# ─────────────────────────────────────────────
# TUPLA: colección ordenada que NO se puede modificar (inmutable)
# Se define con paréntesis ( )
numeros = (1, 2, 3, 4)
# LISTA: colección ordenada que SÍ se puede modificar (mutable)
# Se define con corchetes [ ]
numeros_lista = [1, 2, 3, 4]
# También puedes crear una tupla SIN paréntesis, solo con comas
# Python la reconoce igualmente como tupla
masnumeros = 6, 7, 8, 9
print(numeros) # → (1, 2, 3, 4)
print(masnumeros) # → (6, 7, 8, 9)
# Recorrer una tupla con un bucle for (igual que con una lista)
for numero in numeros:
print(numero) # Imprime cada número en una línea distinta
# Las listas SÍ permiten cambiar sus elementos por índice
# El índice 0 es el primer elemento
numeros_lista[0] = 7 # Cambia el 1 por el 7 → [7, 2, 3, 4]
# numeros[0] = 7 ← Esto daría ERROR porque las tuplas son inmutables
# ─────────────────────────────────────────────
# FUNCIONES QUE DEVUELVEN MÚLTIPLES VALORES
# ─────────────────────────────────────────────
# Una función puede devolver varios valores separados por comas.
# En realidad, Python los empaqueta automáticamente en una tupla.
def estadistica(lista):
mayor = max(lista) # Valor más alto de la lista
menor = min(lista) # Valor más bajo de la lista
media = sum(lista) / len(lista) # Suma total dividida entre cantidad de elementos
return mayor, menor, media # Devuelve los tres valores como una tupla
# Llamamos a la función y guardamos el resultado (una tupla) en 'info'
info = estadistica([1, 2, 3, 4, 5])
print(info) # → (5, 1, 3.0) — imprime la tupla completa
# Podemos acceder a cada valor por su posición (índice)
print("mayor", info[0]) # → mayor 5
print("menor", info[1]) # → menor 1
print("media", info[2]) # → media 3.0
# DESEMPAQUETADO: asignar cada valor de la tupla a una variable distinta
# en una sola línea. El número de variables debe coincidir con los valores.
a, b, c = estadistica([1, 2, 3, 4, 5])
print(a) # → 5 (mayor)
print(b) # → 1 (menor)
print(c) # → 3.0 (media)
# ─────────────────────────────────────────────
# LISTAS DE TUPLAS
# ─────────────────────────────────────────────
# Una lista puede contener tuplas como elementos.
# Aquí cada tupla representa un punto con coordenadas (x, y).
puntos = [(1, 2), (3, 4), (5, 6)]
# OPCIÓN 1: recibir cada tupla completa y desempaquetarla dentro del bucle
for punto in puntos:
print(punto) # Imprime la tupla: (1, 2), (3, 4)...
x, y = punto # Desempaquetamos la tupla en dos variables
print(f"x vale {x} y vale {y}")
# OPCIÓN 2: desempaquetar directamente en la cabecera del for
# Es más compacto y hace exactamente lo mismo que la opción 1
for x, y in puntos:
print(f"x vale {x} y vale {y}")
Ejercicio cadena
# Nos piden una función que nos diga si una cadena
# tiene letras repetidas consecutivas.
#
# Ejemplos:
# letras_repetidas("hola") -> False
# letras_repetidas("sevilla") -> True
#
# En "sevilla" hay dos letras "l" seguidas.
cadena = "hola"
# Así NO funciona:
#
# for letra in cadena:
#
# Porque con este tipo de recorrido solo tenemos
# la letra actual, pero no sabemos fácilmente
# cuál es la siguiente letra para compararla.
# ---------------------------------------------------
# PRIMERA SOLUCIÓN
# ---------------------------------------------------
# Recorremos la cadena usando posiciones (índices)
# para poder comparar una letra con la siguiente.
def letras_repetidas(cadena):
# len(cadena) nos dice cuántas letras tiene la cadena.
#
# range(len(cadena)-1) genera números desde 0
# hasta la penúltima posición.
#
# Restamos 1 porque vamos a usar i+1 y no queremos
# salirnos del tamaño de la cadena.
for i in range(len(cadena) - 1):
# cadena[i] -> letra actual
# cadena[i + 1] -> letra siguiente
if cadena[i] == cadena[i + 1]:
# Si encontramos dos letras iguales seguidas,
# devolvemos True inmediatamente.
return True
# Si termina el bucle y no encontramos letras repetidas,
# devolvemos False.
return False
print(letras_repetidas("hola")) # False
print(letras_repetidas("sevilla")) # True
print(letras_repetidas("bliss")) # True
# ---------------------------------------------------
# SEGUNDA SOLUCIÓN
# ---------------------------------------------------
# En vez de usar posiciones, guardamos la letra anterior
# y la comparamos con la actual.
def letras_repetidas2(cadena):
# Variable donde guardaremos la letra anterior.
# Al principio está vacía porque todavía no hemos
# recorrido ninguna letra.
anterior = ""
# Recorremos la cadena letra por letra.
for letra in cadena:
# Comparamos la letra actual con la anterior.
if letra == anterior:
# Si son iguales, hay letras repetidas consecutivas.
return True
# Guardamos la letra actual como "anterior"
# para la siguiente vuelta del bucle.
anterior = letra
# Si terminamos el recorrido sin encontrar repeticiones,
# devolvemos False.
return False
print(letras_repetidas2("hola")) # False
print(letras_repetidas2("sevilla")) # True
print(letras_repetidas2("bliss")) # True