Proyecto Final — Fase 3: Evaluación, optimización y preparación de la defensa

Semana 15 — Examinar la solución obtenida: analizar su eficiencia en tiempo y espacio, medir antes de optimizar, aplicar una mejora justificada con las pruebas como red de seguridad, y construir la trazabilidad diseño→código→medición que sostiene la defensa oral

1. Qué tipo de clase es esta

La Semana 14 cerró con un programa que corre: módulos integrados por contrato, errores acotados a la frontera, ejecutado contra todos los casos de análisis de la Fase 1. Esta semana ese programa se pone bajo la lupa. No se introduce ningún concepto algorítmico nuevo —la materia prima sigue siendo el repertorio de las doce primeras semanas—; lo que cambia es la pregunta. Hasta ahora preguntamos si el programa es correcto; ahora preguntamos si además es bueno: eficiente, justificado y defendible.

Como las dos semanas anteriores, esta clase es un taller: el docente evalúa y optimiza sobre el caso modelo, y cada equipo traslada el método a su proyecto. El producto de hoy es el código final optimizado —con al menos una mejora medida y justificada— y la trazabilidad que permitirá defender cada decisión en la Semana 16.

La pregunta central de la semana. La Semana 14 preguntó «¿cómo traduzco un diseño correcto a un programa que sigue siendo correcto?». Esta semana cambia la pregunta: «¿cómo sé que mi solución es buena —y no solo que funciona—, y cómo lo demuestro?». Examinar una solución es medir su costo, encontrar dónde se va el tiempo de verdad, mejorar lo que conviene mejorar y poder justificar cada decisión con evidencia, no con intuición.

1.1. El ciclo de Pólya, ahora «Examinar la solución obtenida»

En el marco de George Pólya (How to Solve It, 1945), esta semana ocupa la cuarta y última fase: examinar la solución obtenida (la visión retrospectiva). Pólya sostenía que mirar atrás la solución terminada —preguntarse si el resultado es correcto, si podría obtenerse de otro modo, si el método sirve para otros problemas— es la fase que más consolida el aprendizaje y la que con más frecuencia se omite. Trasladado al proyecto, esa mirada retrospectiva tiene un instrumento preciso: medir. La figura sitúa la semana en el ciclo.

Las cuatro fases de Pólya desplegadas en las cuatro semanas del proyecto SEMANA 13 Fases 1 y 2 Comprender el problema Concebir un plan (análisis + diseño) SEMANA 14 Fase 3 Ejecutar el plan (codificación + integración) SEMANA 15 Fase 4 Examinar la solución obtenida (evaluación + optimización) SEMANA 16 Fase 4 (cont.) Comunicar y justificar (presentación + defensa oral) ▲ Estamos aquí donde el programa se vuelve evidencia

La fase anterior fue la del encuentro con la realidad; esta es la del juicio sobre lo construido. Aquí aparecen tres tareas que el programa funcionando no resuelve por sí solo: saber cuánto cuesta (análisis de eficiencia), saber dónde cuesta (profiling) y decidir qué conviene mejorar (optimización justificada). Las tres preparan la cuarta: poder explicarlo en la defensa.

1.2. Evaluar no es desconfiar del código

Un programa que pasa todos los casos de análisis ya demostró que es correcto. Evaluar no pone en duda esa corrección: la complementa con una pregunta que las pruebas no responden —¿a qué costo?—. Y la optimización que sigue se apoya, paradoja útil, en esas mismas pruebas: son la red que permite cambiar el cómo sin alterar el qué.

Conexión — Semanas 5, 7, 11 y 14. La eficiencia no es tema nuevo: la notación O grande (Semana 5, búsqueda lineal frente a binaria), el modelo de memoria y la pila de ejecución (Semana 7), el análisis de algoritmos iterativos y la lectura perezosa (Semana 11) son justamente las herramientas de hoy, ahora aplicadas al proyecto entero en lugar de a una función aislada. Y las pruebas de integración de la Semana 14 dejan de ser solo verificación: se vuelven la red de seguridad de la optimización.

2. Análisis de eficiencia del proyecto

El primer paso de la retrospectiva es analizar el costo sin ejecutar nada: razonar, función por función, cuántas operaciones realiza en términos del tamaño de su entrada (complejidad temporal) y cuánta memoria reserva (complejidad espacial). Es el análisis asintótico de la Semana 5 y 11, aplicado al caso modelo: el analizador de corpus textual que venimos construyendo desde la Semana 13.

Fijemos la notación del corpus: sea C el total de caracteres del corpus, t los tokens (palabras) de un archivo, T los tokens de todo el corpus, d las palabras distintas (el tamaño del vocabulario) y v el ancho de la ventana de concordancias. La tabla resume el análisis de cada función del diseño.

Función Tiempo Espacio Justificación
leer_tokens(ruta) O(c) O(c) Abre el archivo con with, lo lee y lo normaliza (lower + translate + split) en pasadas proporcionales a los c caracteres del archivo; la lista de tokens es proporcional al texto.
acumular_frecuencias(frecuencias, tokens) O(t) O(d) Un frecuencias.get(palabra, 0) + 1 por token; el acceso al diccionario es O(1) amortizado (Semana 10). La memoria crece con las palabras distintas, no con el total.
buscar_concordancias(...) O(t·v) O(a·v) Recorre los t tokens; por cada una de las a apariciones recorta y une una ventana de tamaño O(v). Con v pequeño, es en la práctica O(t).
ordenar_desc_por_valor(frec) O(d log d) O(d) sorted ordena los d pares (palabra, cantidad); el ordenamiento comparativo es Θ(d log d) en el caso general (Semana 6).
analizar_corpus(...) (orquestadora) O(C + d log d) O(cmáx + d) Recorre el corpus una sola vez (O(C)) y al final ordena el ranking (O(d log d)). El término dominante depende de qué tan grande sea el vocabulario frente al corpus.

El análisis temporal arroja una cota agregada de O(C + d log d): lineal en el tamaño del corpus, más el costo de ordenar el vocabulario al final. No hay ningún término cuadrático: el diseño de la Fase 1 lo evitó deliberadamente al elegir el diccionario para contar (un set o una lista habrían reintroducido recorridos innecesarios —ese matiz se retoma en la sección 4—).

2.1. La complejidad espacial: por qué el pico es O(cmáx + d) y no O(C + d)

La columna de espacio merece una lectura aparte, porque es donde una decisión de diseño de la Semana 13 paga dividendos. La orquestadora no carga todo el corpus en memoria: gracias al generador que rinde un par (ruta, tokens) por archivo (la lectura perezosa de la Semana 11), en cada instante hay en memoria los tokens de un solo archivo —el más grande, O(cmáx)— más el diccionario de frecuencias que crece con el vocabulario, O(d). Si en cambio se hubieran aplanado todos los archivos en una única lista de tokens, el pico habría sido O(C + d), proporcional al corpus completo.

Tiempo y espacio son ejes independientes. Un algoritmo puede ser barato en uno y caro en el otro. El generador no mejora la complejidad temporal (sigue recorriendo los mismos C caracteres), pero recorta drásticamente el espacio: cambia un pico proporcional a todo el corpus por uno proporcional al archivo más grande. Esta es la complejidad espacial en su forma más concreta: el pico de memoria que el sistema operativo debe reservar para que el programa no falle con corpus grandes.

2.2. El mismo análisis en cada dominio

El caso modelo es un corpus textual, pero el método —identificar la operación dominante y acotar su costo en tiempo y espacio— es el mismo para los cinco dominios. La tabla orienta dónde mirar en cada uno; son cotas analíticas orientativas, no medidas: cada equipo debe verificarlas sobre su propio diseño con las herramientas de la sección 3. En la notación, n es la cantidad de registros y m la cantidad de movimientos o eventos transaccionales.

Dominio Operación dominante típica Costo orientativo Dónde suele estar la mejora
A — Biblioteca Estado derivado (qué ejemplares están prestados) a partir de los préstamos; reportes ordenados. O(m) recorrer movimientos + O(n log n) ordenar reportes Búsquedas por clave natural con diccionario (O(1)) en vez de recorrer listas.
B — Depósito Stock actual derivado de los movimientos de entrada/salida; reportes. O(m) una pasada por movimientos + O(n log n) reportes Acumular el stock en un diccionario por clave de producto, no recalcular por consulta.
C — Horarios Backtracking: explorar asignaciones válidas evitando conflictos. O(bd) en el peor caso (b = opciones por paso, d = profundidad) La poda: descartar ramas inviables temprano reduce drásticamente los nodos explorados (ver recuadro).
D — Logs Consumo de un registro externo: leer y agregar por categoría. O(L) una pasada por las L líneas; espacio O(claves distintas) Lectura perezosa (línea a línea) para no cargar el archivo entero en memoria.
E — Turnos Detección de solapamientos entre turnos asignados. O(t²) comparando todos contra todos, o O(t log t) ordenando por horario Ordenar por hora de inicio y comparar solo con el turno previo, en vez de cada par.

Tres de los dominios (A, B y E) comparten un patrón que conviene nombrar en la defensa: el estado derivado (stock, disponibilidad, ocupación) no se almacena, se recalcula a partir de los registros transaccionales en una sola pasada O(m). Esa es la decisión que mantiene el costo lineal y evita inconsistencias —el mismo principio que en el caso modelo hace que la riqueza léxica se derive de las frecuencias y no se guarde por separado—.

El dominio C merece un análisis aparte. El backtracking de los horarios no es un algoritmo de costo polinómico como los demás: en el peor caso explora un árbol de decisiones de tamaño exponencial, O(bd). Por eso, para el dominio C el análisis de eficiencia no consiste en encontrar una cota ajustada (que sería desalentadora), sino en razonar sobre dos cosas: el tamaño del espacio de búsqueda y, sobre todo, el efecto de la poda —cuántas ramas se descartan antes de explorarlas—. Aquí el profiling tampoco mide constantes: mide cuántos nodos se visitan con poda y sin ella. La defensa del equipo C debe centrarse en justificar por qué su poda hace tratable un problema que, sin ella, no lo sería.

3. Medir antes de optimizar: profiling

El análisis de la sección 2 dice qué pasa cuando el corpus crece (el comportamiento asintótico). Pero no dice dónde se va el tiempo hoy, con el corpus real, donde mandan las constantes que la notación O grande esconde. Para eso no se razona: se mide. Y medir, casi siempre, desmiente la intuición.

El principio de Knuth. Donald Knuth, en su artículo de 1974 Structured Programming with go to Statements (ACM Computing Surveys), formuló la advertencia más citada de la disciplina: «premature optimization is the root of all evil». Su tesis no es «no optimices», sino: no optimices a ciegas. La mayor parte del tiempo de un programa se concentra en una fracción pequeña del código; gastar esfuerzo en el resto es trabajo perdido que además suele degradar la legibilidad. Primero medir, después decidir.

3.1. Dos instrumentos: el cronómetro y el perfilador

Hay dos herramientas estándar de la biblioteca de Python, con propósitos distintos:

El perfilador se usa así sobre el caso modelo —sin tocar el código del programa, solo envolviéndolo—:

import cProfile, pstats # --- Perfilar el pipeline completo sobre un corpus grande ------- perfil = cProfile.Profile() perfil.enable() analizar_corpus(rutas, termino, ventana) # el programa, sin modificar perfil.disable() # --- Ordenar por tiempo PROPIO de cada función y mostrar lo que pesa pstats.Stats(perfil).sort_stats("tottime").print_stats(8)

3.2. El resultado desmiente la intuición

¿Dónde supondría un equipo que su analizador gasta el tiempo? La respuesta intuitiva suele ser «en el ordenamiento» (la única operación con un término log) o «en el conteo» (el bucle que recorre todas las palabras). El perfilador dice otra cosa.

Medición verificada (corpus de tres archivos, ≈1,4 millones de caracteres, random.seed(42)). Perfilando el código de la Semana 14 tal cual, el costo se concentra en las operaciones de limpieza de cadenas de leer_tokens: str.translate sola se lleva el ~41 % del tiempo, str.lower el ~13 % y str.split el ~10 % —juntas, cerca del 64 %—. Los bucles a nivel Python (contar frecuencias y buscar concordancias) suman ~25 %. Y el sorted que la intuición señalaba como sospechoso resulta despreciable. El cuello de botella no estaba donde la intuición lo ponía. Cada número de esta guía se obtuvo ejecutando el código, no estimándolo.

¿Por qué str.translate —una operación implementada en C— es el costo dominante? Por una razón que el análisis asintótico no muestra: el corpus está en español y contiene caracteres anchos (el guion largo «—», las comillas tipográficas, los puntos suspensivos «…»). Por la forma en que Python representa las cadenas (PEP 393), basta con que aparezca uno de esos caracteres para que toda la cadena pase a una representación más ancha, y sobre esa representación str.translate pierde su camino rápido y se vuelve mucho más lento. El ordenamiento, en cambio, opera solo sobre las d palabras distintas —un conjunto mucho menor—. La lección es general y transferible a los cinco dominios: la operación que procesa más datos, no la que más «suena», suele ser el cuello de botella —y a veces el costo depende de cómo están representados esos datos, no del algoritmo—.

4. Una optimización justificada

Con el perfilador señalando la limpieza de cadenas de leer_tokens, la optimización tiene una dirección clara. La consigna de la Fase 3 pide al menos una mejora documentada y justificada; aquí «justificada» significa exactamente esto: respaldada por una medición previa que muestra que vale la pena, y por una medición posterior que confirma que sirvió.

4.1. La mejora sobre el cuello de botella real

La versión de la Semana 14 limpia todo el texto antes de separarlo: aplica lower y translate sobre la cadena completa —el millón de caracteres— y recién entonces hace split. Ese translate sobre el texto entero es justamente el cuello de botella que mostró el perfilador.

# --- Versión original (Semana 14): limpia TODO el texto, luego separa def leer_tokens(ruta): """Lee un archivo de texto y devuelve sus palabras normalizadas. Pre: ruta es la ruta a un archivo de texto codificado en UTF-8. Pos: devuelve una lista de tokens en minúsculas, sin signos de puntuación. Propaga la excepción si el archivo no existe. """ sin_puntuacion = str.maketrans("", "", string.punctuation) with open(ruta, encoding="utf-8") as archivo: texto = archivo.read() texto = texto.lower().translate(sin_puntuacion) tokens = texto.split() return tokens

La idea de la mejora es invertir el orden: separar primero con split y limpiar después solo los bordes de cada palabra con strip. Así el costoso translate sobre el texto completo desaparece: en vez de recorrer el millón de caracteres, se tocan apenas los pocos signos pegados al inicio o al final de cada token. Y todo se hace con tres métodos de cadena que ya se conocen —lower, split y strip—, sin módulos ni herramientas nuevas.

# --- Versión optimizada: separa PRIMERO, limpia los bordes después --- def leer_tokens(ruta): """Lee un archivo de texto y devuelve sus palabras normalizadas. Pre: ruta es la ruta a un archivo de texto codificado en UTF-8. Pos: devuelve la MISMA lista de tokens que la versión original (cuando los signos están en los bordes de las palabras). """ with open(ruta, encoding="utf-8") as archivo: texto = archivo.read() tokens = [] for palabra in texto.lower().split(): # separar PRIMERO palabra = palabra.strip(string.punctuation) # limpiar solo los bordes if palabra: tokens.append(palabra) return tokens

La medición confirma que la mejora vale la pena, y muestra una distinción importante entre optimizar una parte y optimizar el todo:

Medición (corpus de 3 archivos, ≈1,36 M caracteres) Original (S14) Optimizada (split + strip) Aceleración
Normalización sola (leer_tokens)0,1999 s0,0520 s3,85×
Programa completo (analizar_corpus)0,2485 s0,0909 s2,73×

La normalización por sí sola mejora 3,85×; el programa completo, 2,73×. La diferencia es esperable: como la limpieza era ~64 % del tiempo, acelerarla casi 4 veces deja al descubierto el 36 % restante, que no cambió. Es la cara empírica de una idea simple: acelerar una parte mejora el todo solo en la medida en que esa parte pesaba.

Las pruebas de la Semana 14 como red de seguridad. ¿Cómo se sabe que la versión optimizada no rompió nada? Porque produce los mismos tokens que la original, y eso se verifica con una prueba: leer_tokens_original(ruta) == leer_tokens_optimizada(ruta) para cada archivo de prueba. Verificado sobre el corpus, las dos versiones dan tokens idénticos, y el programa completo arroja el mismo ranking, la misma riqueza y las mismas concordancias. Esa igualdad es la prueba de regresión que autoriza el cambio. Optimizar sin esa red es cambiar el cómo a ciegas, con riesgo de alterar el qué.
Qué significa «la misma lista» (y por qué importa medir sobre datos representativos). Las dos versiones coinciden mientras los signos estén en los bordes de las palabras —el caso normal en un texto con espacios—. Difieren en un caso preciso: la puntuación interna sin espacios. Sobre "U.S.A", la versión original (que borra en todo el texto) da usa, mientras que la optimizada (que solo limpia bordes) da u.s.a. Por eso la prueba de regresión no es un trámite: es lo que verifica que esa suposición —«los signos están en los bordes»— se cumple en los datos reales del dominio. Y explica, de paso, por qué el problema no se veía en una prueba pequeña: sobre el corpus de 32 palabras de la Semana 14 —corto y casi sin caracteres anchos— translate es instantáneo; el costo solo aparece al medir sobre un corpus representativo.

4.2. La «optimización elegante» que el profiling desautoriza

Conviene contrastar la mejora anterior con la tentación de optimizar lo que la intuición creía caro. El reporte solo muestra el top 5, pero ordenar_desc_por_valor ordena todo el vocabulario y luego recorta con [:5]. Existe una mejora algorítmica genuina —seleccionar solo los 5 mayores en vez de ordenar los d, que la biblioteca estándar ofrece como heapq.nlargest, cambiando O(d log d) por O(d log 5)—. Pero el profiling de la sección 3 ya dijo que el ordenamiento es una fracción despreciable del tiempo: mejorarlo casi no mueve la aguja del programa. Es el caso de Knuth en estado puro —una optimización correcta sobre algo que no es el cuello de botella—.

La decisión se documenta, se aplique o no. Un equipo puede legítimamente posponer esta mejora (no es el cuello de botella, y el simple sorted es más claro) o aplicarla si prevé vocabularios enormes. Lo que no es legítimo es elegir sin medir. La defensa oral no premia haber optimizado todo, sino haber sabido qué valía la pena optimizar y por qué: en este proyecto, la limpieza de cadenas sí (era el 64 %), el ordenamiento no (era despreciable).
Conexión — Semanas 10 y 11: por qué el diseño eligió un diccionario (y la ley de Heaps). El análisis de la sección 2 mencionó que contar palabras distintas con una lista habría sido un error. Vale cuantificarlo: verificar «¿ya vi esta palabra?» con palabra in lista cuesta O(d) por consulta, de modo que construir el vocabulario así es O(t·d) —entre O(t) y O(t²)—. ¿Dónde cae en ese rango? Lo decide la ley de Heaps (Heaps, 1978): en un texto natural el vocabulario crece de forma sublineal con la extensión, aproximadamente d ∝ tβ con β ≈ 0,4–0,6. Es decir: ni O(t) (vocabulario fijo) ni O(t²) (todas distintas), sino un punto intermedio que empeora con corpus grandes. El diccionario sortea todo esto: las palabras distintas ya son sus claves y len(frecuencias) las cuenta en O(1). La decisión de estructura de la Fase 1 era, vista desde la eficiencia, exactamente la correcta.

5. Preparación de la defensa oral

Todo lo anterior converge aquí. La defensa oral de la Semana 16 no es un resumen del código: es la demostración de que el equipo entiende y puede justificar cada decisión. La materia prima de esa defensa es la trazabilidad que se viene construyendo desde la Semana 13: problema → análisis → diseño → código → medición.

5.1. La revisión final del código

Antes de preparar qué decir, conviene asegurar que el código que se va a mostrar está presentable. Esta revisión —la actividad de cierre del taller— recorre, una última vez, las convenciones que el curso sostuvo desde el comienzo. No es maquillaje: un código coherente y legible es más fácil de defender, porque cada decisión queda a la vista.

Checklist de revisión final del código.

Este recorrido no introduce nada nuevo: reúne, en una sola lista verificable, las convenciones de las Semanas 1 a 14. Que el código final las cumpla es la señal de que el equipo no solo resolvió el problema, sino que lo hizo con el oficio que la materia se propuso formar.

5.2. Una estructura para la presentación

Una presentación de 15–20 minutos se sostiene mejor si sigue, precisamente, las fases de Pólya que estructuraron el proyecto. Cada bloque responde una pregunta:

Bloque Pregunta que responde Apoyo (de qué semana)
El problema ¿Qué entra, qué sale, qué transformación ocurre? Enunciado acotado (S13)
El análisis ¿Qué casos consideramos, incluidos los límite y extremos? Casos de análisis (S13)
El diseño ¿Por qué estas estructuras y estos algoritmos? Decisiones justificadas (S13)
La implementación ¿Cómo se integran los módulos y se manejan los errores? Código integrado (S14)
La evaluación ¿Cuánto cuesta, dónde, qué optimizamos y por qué? Análisis + profiling (S15)
La IA generativa ¿Cómo la usamos y cómo verificamos lo que produjo? Registro de IA (S13–S15)

5.3. Argumentar decisiones algorítmicas

El núcleo de la defensa es poder responder, sobre cualquier decisión, dos preguntas: «¿por qué así?» y «¿qué pasaría si fuera de otra manera?». Una decisión bien defendida no se justifica por gusto, sino por consecuencia. Tres ejemplos tomados de este mismo caso modelo:

Ensayo de preguntas conceptuales (computadoras, datos y SO). La defensa incluye preguntas que conectan el proyecto con los temas holísticos del curso. Conviene ensayar respuestas como: ¿por qué el acceso a un diccionario es O(1) (tabla de dispersión, Semana 10)? ¿por qué el traceback de un error conserva toda la cadena de llamadas (la pila de ejecución, Semana 9)? ¿qué hace el sistema operativo cuando open no encuentra un archivo (Semana 14)? ¿por qué un mismo algoritmo cuesta distinto según cómo se representan los datos en memoria (Semana 7)? Saber el qué del proyecto es la mitad; el por qué que lo conecta con la máquina es la otra.
El ensayo entre pares (rehearsal). La sesión práctica de hoy es una presentación preliminar ante otros equipos. Su valor no es el aplauso sino las preguntas incómodas: cada «¿y por qué no lo hicieron de otra forma?» que un compañero formula es una pregunta menos que sorprenderá en la defensa real. Ensayar en voz alta también revela los huecos de la propia comprensión: si no se puede explicar una decisión, todavía no se la entendió del todo.

6. Revisión final del uso crítico de IA generativa

El informe final incluye el análisis crítico del uso de IA generativa, apoyado en el registro_ia.md que el equipo viene completando desde la Semana 13. Cerrarlo es responder, con honestidad, tres preguntas por cada interacción relevante: qué se pidió, qué se obtuvo y, sobre todo, qué se verificó o corrigió.

La responsabilidad intelectual es indelegable. El episodio de str.translate de la sección 3 es un caso de estudio perfecto, y no por casualidad: tanto una herramienta de IA como un manual pueden recomendar con total seguridad «usá str.translate, es lo más rápido para limpiar texto» —un consejo que, medido sobre el corpus real en español, resulta ser el cuello de botella—. La frontera ética no está en usar o no la herramienta, sino en quién responde por el resultado: el código que se entrega y se defiende es del equipo, lo haya sugerido quien lo haya sugerido. Medir, verificar sobre datos representativos y poder explicar el porqué es lo que convierte una sugerencia ajena en una decisión propia. Esa es, exactamente, la competencia que la materia evalúa.

Un buen análisis crítico no se mide por la cantidad de IA usada ni por su ausencia, sino por la lucidez con que el equipo distingue lo que la herramienta resolvió bien, lo que resolvió mal y cómo lo detectaron. Un registro que solo dice «la IA escribió esta función y funcionó» es pobre; uno que dice «la IA propuso esta optimización, la medimos, no rendía sobre nuestro corpus y entendimos por qué» demuestra el pensamiento crítico que se busca formar.

7. Visión de conjunto

La eficiencia conecta el programa con los niveles holísticos del curso de un modo directo. La complejidad espacial de la sección 2 no es una abstracción: es el pico de memoria que el programa le pide al sistema operativo, la misma memoria que en la arquitectura de Von Neumann comparten datos e instrucciones. Razonar el espacio es razonar cuánta de esa memoria física necesita el proceso para no fallar —y por eso el generador de la Semana 11, que mantiene el pico en O(cmáx) en vez de O(C), es una decisión de arquitectura tanto como de algoritmo—.

Por qué el mismo algoritmo cuesta distinto. El caso de str.translate —rápido sobre texto angosto, lento sobre texto ancho— ilustra la pila completa del sistema computacional: hardware → SO → lenguaje → programa. El mismo trabajo lógico —quitar la puntuación— cuesta muy distinto según cómo se haga (recorrer todo el texto o solo los bordes de cada palabra) y, sobre todo, según cómo se represente la cadena en memoria: un byte por carácter o varios, según PEP 393, cambia qué atajos puede tomar el intérprete. La eficiencia de un programa no vive solo en su notación O grande: vive también en cómo el lenguaje y la máquina materializan cada operación sobre los datos tal como están representados.

8. Síntesis y mirada hacia adelante

Esta semana el programa que corría se volvió un programa examinado: con su costo en tiempo y espacio analizado, su cuello de botella medido, una mejora aplicada y justificada con evidencia, y la trazabilidad lista para la defensa. El producto a entregar es el código final optimizado, el informe escrito —con el análisis de complejidad y el análisis crítico del uso de IA— y la presentación preparada.

8.1. Dos ideas para llevarse

Primera. Medir antes de optimizar. La intuición sobre dónde se va el tiempo casi siempre se equivoca; el perfilador no. Optimizar lo que no es el cuello de botella es trabajo perdido, y medir sobre datos no representativos engaña: el costo de str.translate, invisible en una prueba pequeña, solo se reveló al perfilar sobre un corpus español real.
Segunda. Una optimización sin una prueba que la respalde es una apuesta. La igualdad versión_original(x) == versión_optimizada(x) es lo que convierte un cambio arriesgado en una mejora segura: las pruebas de la Fase 2 son la red que hace posible la optimización de la Fase 3.

Lo que se conectó hacia atrás

Lo que se prepara hacia adelante

Una idea que recorre todo

Una solución buena no es solo una que funciona: es una que su autor entiende lo bastante como para decir cuánto cuesta, dónde, y por qué decidió lo que decidió. Medir, justificar y poder explicar son la misma actitud vista en tres momentos —y es esa actitud, más que cualquier línea de código, la que distingue a quien resuelve problemas de quien solo escribe programas—.


Proyecto Final — Fase 3: Evaluación, optimización y preparación de la defensa
Semana 15 — Desarrollo de Contenidos — Fundamentos de Programación — Carrera de Informática — FIUBA