Listas y tuplas en Python — ejercicios con recortes, un resumen de clase y aprobados contra suspensos (y la tupla de seis elementos que solo tenía uno)
Quería guardar mis asignaturas en una tupla y, de momento, solo tenía una: Python. Escribí asignaturas = ("Python"), pedí cuántos elementos tenía y el programa respondió 6. Seis. Una sola asignatura, seis elementos. No había error ni aviso; simplemente, mi «tupla» no era una tupla. Era un string con unos paréntesis alrededor, y len estaba contando las letras.
Las secuencias tienen un puñado de trampas así: silenciosas, sin excepciones, y con una salida que casi tiene sentido. Hoy practicamos con tres ejercicios de dificultad creciente, tres fragmentos para depurar y un chuletario para llevarte.
Estos ejercicios usan la teoría de listas y tuplas y repasan lo de la práctica. Puedes hacerlos sin ellas, pero te costará más.
Tabla de Contenidos
Cómo usar este artículo
- Lee el enunciado de un nivel y no mires la solución todavía.
- Escribe tu versión, ejecútala y compara tu salida con la esperada.
- Si te atascas, mira solo la 🔵 pista y vuelve a intentarlo.
- Cuando termines, compara con la solución y fíjate en por qué está hecha así, no solo en que dé la misma salida.
Ningún ejercicio usa input(). Los datos van fijos en el código para que tu salida sea idéntica a la esperada.
Nivel 1 · Básico: recortes de una lista de notas
Enunciado. Con la lista notas = [6.5, 8.0, 5.5, 9.5, 7.0, 4.5, 10.0], escribe un programa que muestre, usando solo índices y slices (sin bucles): la primera nota, la última, las tres primeras, las dos últimas, todas menos la primera y la última, una de cada dos empezando por la primera y la lista al revés.
Salida esperada:
Primera: 6.5 Última: 10.0 Tres primeras: [6.5, 8.0, 5.5] Dos últimas: [4.5, 10.0] Sin la primera ni la última: [8.0, 5.5, 9.5, 7.0, 4.5] Una de cada dos: [6.5, 5.5, 7.0, 10.0] Al revés: [10.0, 4.5, 7.0, 9.5, 5.5, 8.0, 6.5]
La última nota se alcanza con el índice -1. Para «las dos últimas» deja el fin del slice vacío ([-2:]) y para quitar la primera y la última usa [1:-1]. La lista al revés es [::-1] y «una de cada dos» es [::2].
Nivel 2 · Intermedio: el resumen de una clase
Enunciado. Escribe una función resumen(notas) que devuelva una tupla con la nota mínima, la máxima, la media y cuántas notas están aprobadas (5 o más). No uses min, max ni sum: calcula todo con un solo recorrido de la lista. Después, desempaqueta la tupla en cuatro variables y muestra el resumen con los mismos datos del Nivel 1.
Salida esperada:
Mínima: 4.5 Máxima: 10.0 Media: 7.29 Aprobadas: 6 de 7
Un solo recorrido significa un solo for. Dentro irás actualizando cuatro variables: dos candidatos (mínima y máxima, que empiezan en la primera nota), un acumulador para el total y un contador para las aprobadas. Al final divide el total entre len(notas) y devuelve las cuatro cosas separadas por comas.
Nivel 3 · Desafío: aprobados contra suspensos
Enunciado. Tenemos los alumnos como lista de tuplas (nombre, nota):
ALUMNOS = [("Nora", 7.0), ("Pablo", 3.0), ("Elena", 9.5), ("Hugo", 4.5), ("Sara", 6.5), ("Tomás", 8.0)]
CORTE = 5.0
Escribe separar(alumnos, corte), que devuelva dos listas de nombres: los aprobados (nota igual o mayor que el corte) y los suspensos, en el orden en que aparecen. Escribe también primer_suspenso(alumnos, corte), que devuelva la posición del primer suspenso (o -1 si no hay). Muestra después los dos grupos, el primer suspenso con su tupla y los tres últimos alumnos de la lista. Para construir las listas no puedes usar métodos: solo recorrido, concatenación (+) y el elemento neutro [].
Salida esperada:
Aprobados: ['Nora', 'Elena', 'Sara', 'Tomás']
Suspensos: ['Pablo', 'Hugo']
Primer suspenso en la posición 1 -> ('Pablo', 3.0)
Los tres últimos: [('Hugo', 4.5), ('Sara', 6.5), ('Tomás', 8.0)]
Empieza con las dos listas vacías, recorre for nombre, nota in alumnos y, según la nota, haz aprobados = aprobados + [nombre] o lo mismo con los suspensos (fíjate en los corchetes: se concatenan dos listas). Para la posición, enumerate y un return dentro del bucle. Los tres últimos son un slice con índice negativo.
¿Qué falla aquí?
Tres fragmentos con un fallo cada uno. Antes de leer la explicación, haz dos cosas: predice si da error y, si no lo da, qué imprime.
Fragmento 1
hay_suspenso.py
def hay_suspenso(notas):
for nota in notas:
if nota < 5:
return True
else:
return False
print(hay_suspenso([7.5, 4.0, 9.0]))
*Piénsalo antes de seguir: la lista tiene un 4.0 en la segunda posición. ¿Qué imprime python3 hay_suspenso.py?*
Explicación. No hay ningún error, pero la respuesta es incorrecta:
False
La función se decide con el primer elemento. El 7.5 no es suspenso, entra en el else y el return False termina la función en la primera vuelta; el 4.0 ni se llega a mirar.
Poner el return False de una búsqueda en el else de dentro del bucle. Una búsqueda solo puede decir «no» cuando ha mirado todos los elementos.
Arreglo. Quita el else y deja el return False fuera del bucle, a la altura del for:
def hay_suspenso(notas):
for nota in notas:
if nota < 5:
return True
return False
print(hay_suspenso([7.5, 4.0, 9.0]))
True
En una búsqueda, el return positivo va dentro del if y el negativo, fuera del bucle.
Fragmento 2
asignaturas.py
asignaturas = ("Python")
print(len(asignaturas))
for asignatura in asignaturas:
print(asignatura, end=" ")
print()
¿Da error? ¿Qué imprime? Quiero una tupla con una sola asignatura.
Explicación. Tampoco da error, y por eso es traicionero:
6 P y t h o n
("Python") no es una tupla: es el string "Python" entre paréntesis, que Python trata como una expresión cualquiera. Por eso len da 6 y el bucle recorre las letras una a una. Lo que construye una tupla es la coma.
Olvidar la coma en una tupla de un solo elemento. Con dos o más elementos las comas ya aparecen solas; con uno hay que ponerla a propósito.
Arreglo. Añade la coma después del único elemento:
asignaturas = ("Python",)
print(len(asignaturas))
for asignatura in asignaturas:
print(asignatura, end=" ")
print()
1 Python
Si un valor «no se comporta como una tupla», pregúntale a Python con type(...). Si dice <class 'str'>, falta la coma.
Fragmento 3
ultimas.py
notas = [6.5, 8.0, 5.5, 9.5, 7.0, 4.5, 10.0]
print("Las dos últimas:", notas[-2:-1])
¿Da error? ¿Qué imprime? Quiero ver las dos últimas notas.
Explicación. Se ejecuta sin errores, pero falta una nota:
Las dos últimas: [4.5]
En un slice el fin no se incluye. [-2:-1] empieza en el penúltimo elemento y se detiene justo antes del último, así que solo deja pasar el 4.5. El 10.0 se queda fuera.
Usar -1 como fin para llegar hasta el último elemento. Como el fin no se incluye, ese último queda fuera.
Arreglo. Para llegar hasta el final se deja el fin vacío:
notas = [6.5, 8.0, 5.5, 9.5, 7.0, 4.5, 10.0]
print("Las dos últimas:", notas[-2:])
Las dos últimas: [4.5, 10.0]
Si dudas, cuenta: notas[-2:] debe tener 2 elementos. Si te salen 1, te falta el final.
Soluciones
Mira esta sección solo cuando hayas intentado el ejercicio. Si tu código es distinto pero da la misma salida y cumple los requisitos, también es una solución válida.
Solución del Nivel 1
recortes.py
notas = [6.5, 8.0, 5.5, 9.5, 7.0, 4.5, 10.0]
print("Primera:", notas[0])
print("Última:", notas[-1])
print("Tres primeras:", notas[:3])
print("Dos últimas:", notas[-2:])
print("Sin la primera ni la última:", notas[1:-1])
print("Una de cada dos:", notas[::2])
print("Al revés:", notas[::-1])
Por qué está hecho así:
- No hace falta ningún bucle: cada línea es un acceso directo.
notas[0]ynotas[-1]son el primer y el último elemento; los dos son un valor, mientras que los slices devuelven una lista, y por eso unos salen sin corchetes y los otros con ellos. notas[:3]empieza en el principio y se detiene antes del índice 3: las posiciones 0, 1 y 2.notas[-2:]deja el fin vacío para llegar hasta el final. Ese es el truco para «las últimas n».notas[1:-1]quita un elemento por cada extremo: empieza en el 1 y termina antes del último.notas[::2]va de dos en dos desde el principio (posiciones 0, 2, 4 y 6) ynotas[::-1]recorre con salto negativo: la lista dada la vuelta, sin modificar la original.
Solución del Nivel 2
resumen.py
APROBADO = 5.0
def resumen(notas):
"""Devuelve (mínima, máxima, media, aprobadas) con un solo recorrido."""
minima = notas[0]
maxima = notas[0]
total = 0
aprobadas = 0
for nota in notas:
if nota < minima:
minima = nota
if nota > maxima:
maxima = nota
total += nota
if nota >= APROBADO:
aprobadas += 1
return minima, maxima, total / len(notas), aprobadas
notas = [6.5, 8.0, 5.5, 9.5, 7.0, 4.5, 10.0]
minima, maxima, media, aprobadas = resumen(notas)
print(f"Mínima: {minima}")
print(f"Máxima: {maxima}")
print(f"Media: {media:.2f}")
print(f"Aprobadas: {aprobadas} de {len(notas)}")
Por qué está hecho así:
- Con un único
forcalculamos cuatro cosas. Cada una es un esquema distinto (dos de extremo, un acumulador y un contador selectivo) y no se estorban porque usan variables diferentes. minimaymaximaarrancan ennotas[0], el primer elemento. Si hubiéramos iniciadominimaen0, el mínimo saldría siempre 0, aunque nadie lo hubiera sacado (la historia de la práctica).- Los dos
ifde los extremos van uno detrás de otro, no conelif: una nota puede ser a la vez el nuevo mínimo y el nuevo máximo (por ejemplo, la primera de todas). return minima, maxima, total / len(notas), aprobadasempaqueta los cuatro valores en una tupla sin escribir paréntesis, y la línea siguiente la desempaqueta en cuatro variables.- Los números cuadran: 6.5 + 8.0 + 5.5 + 9.5 + 7.0 + 4.5 + 10.0 = 51.0, y 51.0 / 7 ≈ 7.2857, que
:.2fmuestra como 7.29. Todas aprueban menos el 4.5.
Solución del Nivel 3
separar.py
ALUMNOS = [
("Nora", 7.0),
("Pablo", 3.0),
("Elena", 9.5),
("Hugo", 4.5),
("Sara", 6.5),
("Tomás", 8.0),
]
CORTE = 5.0
def separar(alumnos, corte):
"""Devuelve dos listas de nombres: aprobados y suspensos."""
aprobados = []
suspensos = []
for nombre, nota in alumnos:
if nota >= corte:
aprobados = aprobados + [nombre]
else:
suspensos = suspensos + [nombre]
return aprobados, suspensos
def primer_suspenso(alumnos, corte):
"""Posición del primer suspenso, o -1 si no hay ninguno."""
for pos, alumno in enumerate(alumnos):
if alumno[1] < corte:
return pos
return -1
aprobados, suspensos = separar(ALUMNOS, CORTE)
print("Aprobados:", aprobados)
print("Suspensos:", suspensos)
pos = primer_suspenso(ALUMNOS, CORTE)
print("Primer suspenso en la posición", pos, "->", ALUMNOS[pos])
print("Los tres últimos:", ALUMNOS[-3:])
Por qué está hecho así:
aprobados = []ysuspensos = []son el elemento neutro de la concatenación. Cada vuelta del bucle crea una lista nueva más larga, y por eso se vuelve a asignar a la misma variable.- Se concatena
[nombre]y nonombre:+solo une secuencias del mismo tipo, yaprobados + nombre(lista más string) daría unTypeError. - Un solo recorrido decide a cuál de las dos listas va cada alumno: es un recorrido selectivo con dos ramas (
ifyelse), porque aquí todos los elementos se tratan, solo que de forma distinta. separardevuelve dos listas conreturn aprobados, suspensos(una tupla) y el programa las desempaqueta en dos variables.primer_suspensoes el esquema de búsqueda de siempre: elreturn posva dentro delify elreturn -1fuera del bucle. Conenumeratetenemos a la vez la posición y el alumno.ALUMNOS[pos]solo es seguro porque en este programa hay suspensos. Siposvaliera-1, el índice daría el último alumno en lugar de un error; en un programa real habría que comprobarlo antes.ALUMNOS[-3:]son los tres últimos: un slice con índice negativo y el fin vacío.
Míralo paso a paso en Python Tutor
El fallo del fragmento 1 se ve mejor en vivo. Python Tutor ejecuta el código línea a línea y dibuja los marcos de las funciones y sus variables. Elige Python 3 y pega este programa, que llama a la función con la misma lista en dos órdenes distintos:
def hay_suspenso(notas):
for nota in notas:
if nota < 5:
return True
else:
return False
print(hay_suspenso([7.5, 4.0, 9.0]))
print(hay_suspenso([4.0, 7.5, 9.0]))
Salida esperada:
False True
Qué observar, paso a paso:
- En la primera llamada, la lista es
[7.5, 4.0, 9.0]. Dentro dehay_suspenso,notavale7.5en la primera vuelta. - El
if nota < 5es falso, así que se salta alelse. Y ahí está el problema: elreturn Falsese ejecuta en esa misma vuelta. El marco de la función desaparece y devuelveFalsesin haber visto el4.0. - Compruébalo contando las vueltas:
notasolo llega a valer7.5. Las posiciones 1 y 2 nunca se visitan. - En la segunda llamada, la lista empieza por
4.0. La primera vuelta ya cumple elif, ejecutareturn Truey la salida esTrue. - Las dos listas tienen un suspenso, pero el resultado depende solo de qué elemento va primero. Eso es lo que delata un
returnmal colocado en un bucle.
Chuletario: Listas y tuplas
Lo esencial en 30 segundos
- Tres secuencias:
str(comillas),tuple(paréntesis) ylist(corchetes). La lista se puede modificar; las otras dos, no. - Tupla de un elemento:
(10,). Sin la coma no es una tupla. - Índices: desde 0; el último es
len(s) - 1o-1. - Slice:
s[inicio:fin:salto]. El inicio se incluye, el fin no.s[::-1]invierte. - **
for item in slee;for pos in range(len(s))** permite modificar y conocer la posición. - Búsqueda:
returndentro delif; el «no está» fuera del bucle. - Extremos: el candidato inicial es el primer elemento, no un valor inventado.
Sintaxis
| Quiero… | Código | Resultado |
|---|---|---|
| El último elemento | notas[-1] | 10.0 |
| Los tres primeros | notas[:3] | [6.5, 8.0, 5.5] |
| Los dos últimos | notas[-2:] | [4.5, 10.0] |
| Quitar el primero y el último | notas[1:-1] | Los del medio |
| Un elemento de cada dos | notas[::2] | Posiciones 0, 2, 4… |
| La secuencia al revés | notas[::-1] | Una copia invertida |
| Unir / repetir | a + b / [0] * 5 | Nueva secuencia |
| Pasar de un tipo a otro | list(t) / tuple(l) | Nueva secuencia |
| Empaquetar / desempaquetar | t = a, b / a, b = t | Tupla / variables |
| Saber si un valor está | valor in notas | True o False |
| Posición y elemento a la vez | for pos, item in enumerate(s): | Parejas (pos, item) |
| Posición de un valor | notas.index(5.5) | Error si no está |
| Contar, sumar, extremos | len, sum, max, min | Atajos de los esquemas |
Plantillas de los ejercicios
Recorrido con acumulador y contador (Nivel 2):
total = 0
aprobadas = 0
for nota in notas:
total += nota # acumulador
if nota >= 5:
aprobadas += 1 # contador selectivo
Máximo / mínimo (Nivel 2):
candidato = notas[0] # el primer elemento, no 0
for nota in notas:
if nota > candidato:
candidato = nota
Separar en dos listas (Nivel 3):
buenos = []
malos = []
for nombre, nota in alumnos:
if nota >= 5:
buenos = buenos + [nombre]
else:
malos = malos + [nombre]
Búsqueda de posición (Nivel 3):
for pos, item in enumerate(secuencia):
if item == buscado:
return pos # dentro del if
return -1 # fuera del bucle
Errores que más se repiten
| Síntoma | Causa probable |
|---|---|
IndexError: list index out of range | Índice igual o mayor que len(s), o lista vacía con s[0] |
TypeError: 'tuple' object does not support item assignment | Intentas cambiar un elemento de una tupla o de un string |
TypeError: can only concatenate list (not "tuple") to list | + entre tipos distintos: convierte uno de los dos |
ValueError: too many values to unpack | Menos variables a la izquierda que elementos a la derecha |
len() da más de lo esperado en una «tupla» | Falta la coma: ("a") es un string |
| Un slice devuelve un elemento menos | El fin no se incluye: deja el fin vacío para llegar al final |
| La búsqueda siempre decide con el primer elemento | return False dentro del bucle (en el else) |
| El mínimo o el máximo es un valor que no está en los datos | Candidato inicial inventado: usa s[0] |
La lista no cambia tras un for | Modificas la variable del for: recorre por índice |
ValueError: x is not in list | .index() de un valor ausente: comprueba antes con in |
Antes de entregar, pregúntate
- ¿Mis slices incluyen lo que quiero? Recuerda que el fin no se incluye.
- En mi búsqueda, ¿el
returnde «no encontrado» está fuera del bucle? - ¿He iniciado cada candidato (máximo, mínimo) con el primer elemento y he pensado qué pasa con una lista vacía?
- Si tengo que cambiar la lista, ¿recorro por índice en lugar de por elemento?
Si quieres repasar el tema desde la base, vuelve a la teoría de listas y tuplas.
Sigue aprendiendoTodos los artículos de Python¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
