Pensamiento Computacional · Parcial modelo
Primer parcial de Pensamiento Computacional, resuelto paso a paso
Es un parcial modelo, escrito por nosotros. No es el parcial de ninguna cátedra ni de ninguna fecha: está armado con el temario y el formato de los que se toman, para que puedas practicar con algo parecido.
Algoritmos, datos y funciones, estructuras de control y estructuras de datos: trazar código a mano, completar funciones y encontrar errores en un programa que no anda.
6 ejercicios, 30 preguntas, 120 minutos. Se aprueba con 60 sobre 100.
Las consignas
Seis ejercicios, dos horas, como el de verdad. En el parcial real esto se rinde en papel y con libro cerrado: no hay computadora, no se puede probar el código. Así que la única forma de contestar es trazar el programa a mano, línea por línea, anotando al costado cuánto vale cada variable. Hacelo: es más rápido que adivinar y es lo que se corrige.
Cuando la respuesta es código, escribilo tal como iría en Python:
respetá mayúsculas y minúsculas, los paréntesis y los dos puntos. Cuando es
una lista, va con corchetes y comas —[2, 5, 8]—; cuando es
un valor especial, va la palabra sola y con la mayúscula que lleva:
None, True, False.
Sólo se puede resolver con lo visto en la materia: listas, tuplas,
diccionarios, for, while, if y
funciones. Nada de bibliotecas.
Ejercicio 1 — Trazar: texto, división entera y rebanadas 10 puntos
Mirá este programa y contestá sin ejecutarlo. palabra = "computadora" n = len(palabra) print(n // 4, n % 4) print(palabra[2:n-6])
- a) ¿Cuánto vale n?
- b) ¿Qué imprime la línea print(n // 4, n % 4)?
- c) ¿Qué imprime la línea print(palabra[2:n-6])?
- d) Si al final del programa se agregara la línea palabra[0] = "C" , ¿qué pasaría?
a) len cuenta caracteres, no letras distintas ni sílabas. Contalos de a uno: c-o-m-p-u-t-a-d-o-r-a son 11.
Control rápido sin contar con el dedo: "computa" son 7 y "dora" son 4. 7 + 4 = 11 ✓
b) Son las dos divisiones, que es lo que este ítem viene a preguntar:
11 % 4 = 3 (lo que sobra)
Sale 2 3. La opción 2.75 3 es la trampa: 11 / 4 sí da 2.75, pero acá hay dos barras. / devuelve un float y // devuelve un entero, y no son intercambiables.
Control: la cuenta tiene que cerrar como una división de la escuela primaria, 4 · 2 + 3 = 11 ✓
c) Primero se resuelve el índice: n − 6 = 11 − 6 = 5, así que la línea es palabra[2:5].
carácter: c o m p u t a …
Agarra desde la 2 hasta la 5 sin incluirla: 2, 3 y 4, o sea mpu. La opción mput es la de siempre, la de creer que el 5 entra.
Truco para no equivocarse nunca más: la cantidad de caracteres de una rebanada es la resta de los dos números. 5 − 2 = 3 caracteres. Si contaste cuatro, te pasaste.
d) Tira TypeError. Los strings en Python son inmutables: se pueden leer por posición, medir, cortar y recorrer, pero no se puede escribir sobre una posición.
Y ojo con la confusión de fondo, porque es la que después cuesta puntos en el ejercicio 3: lo que no se puede es modificar el texto que ya existe. Cambiar a qué apunta el nombre sí se puede, siempre:
Eso no modifica nada: fabrica un string nuevo y le mueve el nombre. Las listas, en cambio, sí se pueden modificar en el lugar — y de ahí sale todo el lío del ejercicio 3.
Ejercicio 2 — Trazar: for con range y for sobre un diccionario 10 puntos
Este programa acumula en la variable total. total = 0 for i in range(1, 8, 2): if i % 3 == 0: total = total + i else: total = total - 1 print(total) Y este otro hace algo parecido, pero sobre un diccionario. stock = {"clavos": 120, "tornillos": 45, "tuercas": 80} suma = 0 for p in stock: suma = suma + stock[p] print(suma)
- a) ¿Qué valores va tomando i en el primer programa?
- b) ¿Cuántas veces se ejecuta la línea total = total + i?
- c) ¿Qué imprime el primer programa?
- d) Si el range fuera range(1, 9, 2) en lugar de range(1, 8, 2), ¿qué valores tomaría i?
- e) En el segundo programa, ¿qué va tomando la variable p en cada vuelta?
- f) ¿Qué imprime el segundo programa?
a) range(1, 8, 2) arranca en 1, salta de a 2 y frena antes de 8: 1, 3, 5, 7. El 8 no aparece nunca, porque el segundo número dice dónde cortar, no hasta dónde llegar.
b) Esa línea está adentro del if i % 3 == 0, así que se ejecuta sólo cuando i es múltiplo de 3. De 1, 3, 5 y 7 el único múltiplo de 3 es el 3: una sola vez.
c) La tabla, vuelta por vuelta:
i = 3 → 3 % 3 = 0 → if → total = −1 + 3 = 2
i = 5 → 5 % 3 = 2 → else → total = 2 − 1 = 1
i = 7 → 7 % 3 = 1 → else → total = 1 − 1 = 0
Imprime 0.
Control por otro lado, sin seguir el orden: entra una vez al if sumando 3, y tres veces al else restando 1 cada vez. 3 − 3 = 0 ✓ Ese control es el que conviene hacer en el parcial, porque no depende de que hayas anotado bien cada paso intermedio.
d) Los mismos: 1, 3, 5, 7. Con paso 2 los valores son 1, 3, 5, 7, 9… y el 9 queda afuera igual, porque range nunca incluye el tope. Cambiar el 8 por el 9 no agrega nada.
La regla completa, que es lo que hay que llevarse: range(a, b, p) incluye a y llega hasta el último valor que sea estrictamente menor que b. Si querés que el 9 aparezca, el tope tiene que ser 10.
e) Las claves. Es el detalle de diccionarios que más se falla: for p in stock no recorre los valores, recorre los nombres. Por eso adentro del ciclo hace falta stock[p] para llegar al número.
Si quisieras los valores directamente iría for v in stock.values(), y si quisieras los dos a la vez, for clave, valor in stock.items().
f) Suma los tres valores:
Control: 120 + 80 = 200, más 45 son 245 ✓ La opción 3 es para quien sumó 1 por cada clave en vez de sumar el valor, y la opción 120 para quien leyó el primer valor y frenó ahí, sin recorrer el resto.
Un error parecido que conviene tener presente: si el suma = 0 estuviera adentro del ciclo, cada vuelta lo volvería a poner en cero y al final quedaría el último valor, 80 — no el primero.
Ejercicio 3 — Trazar: copiar una lista de listas 10 puntos
Este programa copia una matriz y la modifica. Contestá qué imprime cada print. matriz = [[1, 2], [3, 4]] copia = matriz.copy() copia[0][1] = 99 copia.append([5, 6]) print(matriz) print(copia)
- a) ¿Qué imprime print(matriz)?
- b) ¿Qué imprime print(copia)?
- c) ¿Por qué matriz cambió con la línea copia[0][1] = 99 pero no cambió con la línea copia.append([5, 6])?
- d) ¿Cuál de estos cambios haría que matriz quedara intacta en [[1, 2], [3, 4]]?
Este es el ejercicio del parcial: el que más se repite y el que más gente se lleva puesto. La respuesta intuitiva —"puse .copy(), entonces son independientes"— es la equivocada, y conviene entender exactamente por qué.
Qué hace cada línea. Al principio hay tres listas, no una: la de afuera y las dos filas. La de afuera no contiene a las filas, las apunta.
↓ ↓
[1, 2] [3, 4]
matriz.copy() fabrica una lista de afuera nueva… y le copia adentro las mismas dos flechas. Las filas siguen siendo dos, compartidas:
↓ ↓ ↓ ↓
[1, 2] [3, 4] ← las mismas dos
Eso se llama copia superficial: copia un solo nivel.
a) copia[0][1] = 99 no toca ninguna de las dos listas de afuera: entra a la fila 0 —que es una sola, compartida— y le cambia el segundo elemento. Entonces matriz queda [[1, 99], [3, 4]].
b) copia.append([5, 6]), en cambio, modifica la lista de afuera de copia, que sí es propia. Por eso el [5, 6] aparece sólo de un lado:
Fijate que el 99 también está acá: las dos comparten la fila modificada.
c) La regla, en una frase: lo que se copió es el primer nivel. Todo lo que esté más adentro sigue compartido. Por eso una línea con dos corchetes (copia[0][1]) se ve en las dos, y una que toca sólo el nivel de afuera (append, remove, copia[0] = [9, 9]) se ve en una sola.
El control que lo decide en dos segundos, y que sirve en cualquier ejercicio de este tipo:
matriz[0] is copia[0] → True (las de adentro son la misma)
d) Hay que copiar cada fila, no sólo la lista de afuera:
for fila in matriz:
copia.append(fila.copy())
Ahora sí copia[0][1] = 99 deja matriz en [[1, 2], [3, 4]].
Las otras dos opciones son la parte fina del ítem: matriz[:], list(matriz) y matriz.copy() hacen exactamente lo mismo. Las tres son copias superficiales. Cambiar una por otra no arregla nada — y creer que sí es justo el error que este ejercicio viene a buscar.
Ejercicio 4 — Completar: buscar con un while 20 puntos
La función primera_posicion tiene que devolver la posición de la PRIMERA aparición de buscado en lista, y −1 si no está. Completá los dos espacios punteados. No se pueden agregar líneas. def primera_posicion(lista, buscado): i = 0 while ......(1)...... and lista[i] != buscado: i = i + 1 if i == len(lista): return -1 return ......(2)......
- a) Escribí la condición que va en el espacio (1).
- b) Escribí la expresión que va en el espacio (2) (sólo la expresión, sin la palabra return).
- c) Con los espacios completos, ¿qué devuelve primera_posicion([4, 7, 7, 2], 7)?
- d) ¿Y qué devuelve primera_posicion([4, 7, 7, 2], 5)?
- e) Si en el espacio (1) se escribiera i <= len(lista) en lugar de la condición correcta, ¿qué pasaría al buscar un valor que no está en la lista?
a) La condición que falta es la que cuida el índice:
El ciclo tiene dos motivos para frenar, y por eso la condición tiene dos partes unidas con and: se sigue mientras queden elementos y todavía no apareció el buscado. En cuanto una de las dos falla, se corta.
El orden no es decorativo, y esto es lo que hay que entender del ejercicio. Python evalúa el and de izquierda a derecha y si la primera parte ya es False, la segunda ni la mira. Por eso cuando i llega a len(lista) —o sea, cuando se acabó la lista— el programa nunca ejecuta lista[i]. Si las dos partes estuvieran al revés:
intentaría leer lista[i] con i fuera de rango antes de chequear el rango, y ahí sí tira IndexError. Misma condición lógica, resultado distinto.
b) Cuando el while se corta sin haberse acabado la lista es porque encontró el valor, y en ese momento i es justo la posición. Así que va return i.
Y el if i == len(lista) de arriba es la otra puerta de salida: si i llegó al final, es que recorrió todo sin encontrarlo, y ahí devuelve −1.
c) Trazamos con lista = [4, 7, 7, 2] y buscado = 7:
i = 1 → 1 < 4 ✓ pero lista[1] = 7 → corta
i (= 1) ≠ len(lista) (= 4) → devuelve 1
Devuelve 1 y no 2: la consigna pide la primera aparición, y el ciclo se frena apenas la encuentra. El segundo 7 nunca se mira.
d) Buscando el 5, ningún elemento coincide, así que i llega hasta 4. Ahí i < len(lista) es False, el ciclo corta, y como i == len(lista) se dispara el return −1.
¿Por qué −1 y no 0? Porque 0 es una posición válida (la primera), así que no sirve para decir "no está". Devolver −1 es la convención justamente porque nunca puede confundirse con una posición real.
e) Tira IndexError. Con i <= len(lista), en la vuelta en que i vale 4 la primera parte da True, así que Python pasa a evaluar la segunda: lista[4] en una lista de cuatro elementos, cuyas posiciones válidas son 0, 1, 2 y 3. Se va de rango.
Es el error clásico de este tipo de ejercicio, y tiene una trampa adentro: con <= el programa anda perfecto mientras el valor esté, porque el ciclo corta antes de llegar al borde. Sólo se rompe en el caso feo, el de la búsqueda fallida — que es exactamente el que uno no prueba. La regla para no pensarlo nunca más: en una lista de n elementos el último índice es n − 1, así que la condición de un recorrido por índice es siempre i < len(lista).
Ejercicio 5 — Completar: armar una lista nueva 20 puntos
La función aprobadas recibe una lista de notas y tiene que devolver una lista NUEVA con las notas mayores o iguales que 4, en el mismo orden, sin modificar la lista que recibió. Completá los dos espacios punteados. No se pueden agregar líneas. def aprobadas(notas): nuevas = ......(1)...... for n in notas: if n >= 4: ......(2)...... return nuevas
- a) Escribí lo que va en el espacio (1).
- b) Escribí la línea que va en el espacio (2).
- c) ¿Qué devuelve aprobadas([7, 2, 4, 9, 3])? Escribilo con corchetes.
- d) Si en el espacio (2) se escribiera notas.append(n) en lugar del nombre correcto, ¿qué pasaría con aprobadas([7, 2, 4, 9, 3])?
- e) Si la última línea fuera print(nuevas) en lugar de return nuevas, ¿qué valor devolvería la función? Escribilo tal como se escribe en Python.
a) En el espacio (1) va []: la lista vacía donde se va a ir guardando el resultado. (list() es lo mismo escrito distinto.)
Lo importante no es qué se escribe sino dónde: el nuevas = [] está antes del for, y ahí tiene que quedarse. Si estuviera adentro del ciclo se volvería a vaciar en cada vuelta y la función devolvería, como mucho, la última nota aprobada. Es el error de "acumulador adentro del ciclo", y es especialmente feo porque no rompe nada: devuelve una lista, sólo que la equivocada.
b) En el espacio (2) va nuevas.append(n). Ojo con el nombre: se agrega a nuevas, no a notas.
También sirve nuevas = nuevas + [n], aunque es peor: arma una lista nueva entera en cada vuelta en lugar de agregar al final. Lo que no sirve es nuevas = n (le mueve el nombre a un número y se pierde todo lo acumulado) ni nuevas + [n] a secas, que calcula el resultado y lo tira, porque no lo guarda en ningún lado.
c) Se recorre la lista de a una nota:
n = 2 → 2 ≥ 4 ✗ → nuevas = [7]
n = 4 → 4 ≥ 4 ✓ → nuevas = [7, 4]
n = 9 → 9 ≥ 4 ✓ → nuevas = [7, 4, 9]
n = 3 → 3 ≥ 4 ✗ → nuevas = [7, 4, 9]
Devuelve [7, 4, 9]. Fijate en el 4: la consigna dice mayores o iguales, así que entra. Si el if dijera n > 4 quedaría [7, 9], y ese uno de diferencia es una nota aprobada que se pierde.
Control: la lista de entrada tiene 5 notas y dos son menores que 4, así que la respuesta tiene que tener 3 elementos ✓ Y como se va agregando al final, el orden original se respeta.
d) El for no termina nunca. Esta es la trampa del ejercicio, y no es intuitiva. El for va avanzando por la lista posición por posición; si adentro le agregás elementos a esa misma lista, el final se corre una casilla justo cuando el ciclo estaba por alcanzarlo. Como cada nota aprobada agrega otra nota aprobada, la lista crece más rápido de lo que el ciclo la recorre y el programa se cuelga.
La regla general, que también sirve para el otro lado: no modifiques una lista mientras la estás recorriendo. Ni agregando (no termina) ni borrando (se saltea elementos, porque al sacar uno todos los de atrás corren una posición y el ciclo ya pasó por ese lugar). Si necesitás una versión modificada, armás una lista nueva — que es justo lo que hace esta función.
e) Devolvería None.
print y return parecen lo mismo porque en la consola de Colab las dos "muestran" algo, y no lo son: print escribe en pantalla y sigue; return entrega el valor y termina la función. Una función que no ejecuta ningún return devuelve None.
Se nota apenas querés usar el resultado: len(aprobadas(notas)) con la versión de print tira TypeError, porque None no tiene longitud. La regla de la cátedra para no dudar: si la consigna dice "devuelva" va return; si dice "imprima" va print; si dice "pedirle al usuario" va input. Acá decía devolver.
Un ítem de regalo para el parcial. Si en lugar de una lista el espacio (1) tuviera una tupla vacía (), la línea (2) tiraría AttributeError: las tuplas no tienen append, justamente porque no se pueden modificar. Para ir acumulando hace falta una estructura mutable, y por eso acá va una lista.
Ejercicio 6 — Encontrar los errores y explicarlos 30 puntos
Estas dos funciones tendrían que contar cuántos elementos de una lista son múltiplos de k, pero el programa tiene cuatro errores: uno en la función auxiliar y tres en la principal. Encontralos, explicá cada uno y decí con qué se reemplaza. def es_multiplo(n, k): if n / k == 0: return True else: return False def contar_multiplos(lista, k): cuenta = 0 i = 0 while i <= len(lista): if es_multiplo(lista[i], k): cuenta = cuenta + 1 i = i + 1 print(cuenta)
- a) En es_multiplo hay un operador equivocado. ¿Cuál tiene que ir en lugar de la barra / ? Escribí sólo el símbolo.
- b) Tal como está escrita ahora, ¿qué devuelve es_multiplo(8, 4)?
- c) La condición del while está mal. Escribí la condición corregida (sólo la condición, sin la palabra while ni los dos puntos).
- d) La línea i = i + 1 también está mal. ¿Cuál es el problema?
- e) La última línea de contar_multiplos también está mal. ¿Qué palabra hay que escribir en lugar de print para que la función sirva? Escribí sólo la palabra.
- f) Ya corregidos los cuatro errores, ¿qué devuelve contar_multiplos([3, 8, 12, 7, 20], 4)?
Antes de los errores uno por uno, algo que conviene decir: tal como está, este programa ni siquiera llega a dar un resultado equivocado. Se cuelga. Y eso ya es una pista de por dónde buscar: cuando un programa no termina, el problema está en cómo avanza el ciclo.
Error 1 · en es_multiplo: / donde va %
a) Va el %:
Ser múltiplo significa que la división da resto cero, y el resto se pide con %. La barra / devuelve el cociente con decimales, que no dice nada sobre divisibilidad.
b) Con el código como está, es_multiplo(8, 4) calcula 8 / 4 = 2.0, compara 2.0 == 0 —que es False— y por lo tanto se va al else: devuelve False.
O sea que la función contesta que 8 no es múltiplo de 4. Y es peor que un error suelto: n / k == 0 sólo da True cuando n vale 0, así que la función devuelve False para absolutamente todo lo demás. Está rota siempre, no a veces.
Las tres divisiones, para no confundirlas nunca más:
8 // 4 = 2 (entero: cuántas veces entra)
8 % 4 = 0 (lo que sobra: esto es lo que se compara con 0)
Error 2 · la condición del while se va de rango
c) Va i < len(lista).
En una lista de n elementos las posiciones válidas van de 0 a n − 1. Con i <= len(lista) el ciclo intenta una vuelta de más, con i valiendo len(lista), y ahí lista[i] tira IndexError.
Error 3 · el i = i + 1 quedó adentro del if
d) Está adentro del if. Mirá la indentación: la línea que incrementa i está al mismo nivel que cuenta = cuenta + 1, o sea que sólo se ejecuta cuando el elemento es múltiplo. En cuanto aparece uno que no lo es, i se queda clavado, el while vuelve a mirar el mismo elemento, y así para siempre.
El arreglo es sacarlo un nivel de indentación, para que sea lo último del cuerpo del while y se ejecute en toda vuelta:
if es_multiplo(lista[i], k):
cuenta = cuenta + 1
i = i + 1 ← afuera del if
Y ojo con la opción de "ponerlo antes del if", que suena razonable: si i avanza antes de usar lista[i], el elemento de la posición 0 nunca se mira y el ciclo se pasa del final. En un while por índice el incremento va al final del cuerpo, después de haber usado el elemento.
Un detalle que cambia cómo se corrige a mano: este error es de indentación, no de escritura. La línea está bien escrita, está en el lugar equivocado. Por eso en el parcial, que se rinde en papel, hay que dejar los espacios bien marcados: si no se ve qué queda adentro del if, la respuesta es ambigua y descuenta igual.
Error 4 · print donde va return
e) Va return. Con print la función muestra el número en pantalla pero devuelve None, así que no se puede usar el resultado para nada: contar_multiplos(L, 4) + 1 tiraría TypeError.
La consigna dice "contar cuántos", no "mostrar cuántos". Va return.
Las dos funciones corregidas
if n % k == 0:
return True
else:
return False
def contar_multiplos(lista, k):
cuenta = 0
i = 0
while i < len(lista):
if es_multiplo(lista[i], k):
cuenta = cuenta + 1
i = i + 1
return cuenta
(El if/else con return True y return False se podría escribir en una línea, return n % k == 0, porque la comparación ya es un booleano. No es un error dejarlo largo, pero vale la pena saberlo.)
f) Con la lista [3, 8, 12, 7, 20] y k = 4:
7 % 4 = 3 ✗ 20 % 4 = 0 ✓
Los múltiplos de 4 son 8, 12 y 20: devuelve 3.
Control por otro lado: 8 = 4·2, 12 = 4·3 y 20 = 4·5, mientras que 3 y 7 no son 4 por ningún entero ✓
Lo que se lleva puntos en este ejercicio no es corregir, es explicar. La cátedra descuenta aunque el código final esté bien si no está escrito, error por error, qué estaba mal y por qué. Cuatro renglones alcanzan: "usa / en vez de %", "el <= se va de rango", "el i = i + 1 quedó adentro del if y el ciclo no termina", "imprime en vez de devolver".
Leerlo resuelto no es lo mismo que rendirlo. Acá lo podés hacer entero sin que te corrija nada hasta entregarlo, igual que en el aula, y recién ahí te dice la nota, cómo se resolvía cada ejercicio y a qué temas te conviene volver.
Rendir el primer parcial →