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.

Cómo usar este artículo

  1. Lee el enunciado de un nivel y no mires la solución todavía.
  2. Escribe tu versión, ejecútala y compara tu salida con la esperada.
  3. Si te atascas, mira solo la 🔵 pista y vuelve a intentarlo.
  4. 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] y notas[-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) y notas[::-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 for calculamos 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.
  • minima y maxima arrancan en notas[0], el primer elemento. Si hubiéramos iniciado minima en 0, el mínimo saldría siempre 0, aunque nadie lo hubiera sacado (la historia de la práctica).
  • Los dos if de los extremos van uno detrás de otro, no con elif: 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), aprobadas empaqueta 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 :.2f muestra 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 = [] y suspensos = [] 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 no nombre: + solo une secuencias del mismo tipo, y aprobados + nombre (lista más string) daría un TypeError.
  • Un solo recorrido decide a cuál de las dos listas va cada alumno: es un recorrido selectivo con dos ramas (if y else), porque aquí todos los elementos se tratan, solo que de forma distinta.
  • separar devuelve dos listas con return aprobados, suspensos (una tupla) y el programa las desempaqueta en dos variables.
  • primer_suspenso es el esquema de búsqueda de siempre: el return pos va dentro del if y el return -1 fuera del bucle. Con enumerate tenemos a la vez la posición y el alumno.
  • ALUMNOS[pos] solo es seguro porque en este programa hay suspensos. Si pos valiera -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:

  1. En la primera llamada, la lista es [7.5, 4.0, 9.0]. Dentro de hay_suspenso, nota vale 7.5 en la primera vuelta.
  2. El if nota < 5 es falso, así que se salta al else. Y ahí está el problema: el return False se ejecuta en esa misma vuelta. El marco de la función desaparece y devuelve False sin haber visto el 4.0.
  3. Compruébalo contando las vueltas: nota solo llega a valer 7.5. Las posiciones 1 y 2 nunca se visitan.
  4. En la segunda llamada, la lista empieza por 4.0. La primera vuelta ya cumple el if, ejecuta return True y la salida es True.
  5. Las dos listas tienen un suspenso, pero el resultado depende solo de qué elemento va primero. Eso es lo que delata un return mal colocado en un bucle.

Chuletario: Listas y tuplas

Lo esencial en 30 segundos

  • Tres secuencias: str (comillas), tuple (paréntesis) y list (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) - 1 o -1.
  • Slice: s[inicio:fin:salto]. El inicio se incluye, el fin no. s[::-1] invierte.
  • **for item in s lee; for pos in range(len(s))** permite modificar y conocer la posición.
  • Búsqueda: return dentro del if; el «no está» fuera del bucle.
  • Extremos: el candidato inicial es el primer elemento, no un valor inventado.

Sintaxis

Quiero…CódigoResultado
El último elementonotas[-1]10.0
Los tres primerosnotas[:3][6.5, 8.0, 5.5]
Los dos últimosnotas[-2:][4.5, 10.0]
Quitar el primero y el últimonotas[1:-1]Los del medio
Un elemento de cada dosnotas[::2]Posiciones 0, 2, 4…
La secuencia al revésnotas[::-1]Una copia invertida
Unir / repetira + b / [0] * 5Nueva secuencia
Pasar de un tipo a otrolist(t) / tuple(l)Nueva secuencia
Empaquetar / desempaquetart = a, b / a, b = tTupla / variables
Saber si un valor estávalor in notasTrue o False
Posición y elemento a la vezfor pos, item in enumerate(s):Parejas (pos, item)
Posición de un valornotas.index(5.5)Error si no está
Contar, sumar, extremoslen, sum, max, minAtajos 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íntomaCausa 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 assignmentIntentas 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 unpackMenos 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 menosEl fin no se incluye: deja el fin vacío para llegar al final
La búsqueda siempre decide con el primer elementoreturn False dentro del bucle (en el else)
El mínimo o el máximo es un valor que no está en los datosCandidato inicial inventado: usa s[0]
La lista no cambia tras un forModificas 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

  1. ¿Mis slices incluyen lo que quiero? Recuerda que el fin no se incluye.
  2. En mi búsqueda, ¿el return de «no encontrado» está fuera del bucle?
  3. ¿He iniciado cada candidato (máximo, mínimo) con el primer elemento y he pensado qué pasa con una lista vacía?
  4. 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 →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *