FT Introducción a la Programación Gerardo M. Sarria M. - Mario Julián Mora DR A Borrador de 3 de agosto de 2012 FT DR A Este libro fue creado usando LATEX. Ningún animal fue maltratado durante el desarrollo de este escrito. c 2012 - Gerardo M. Sarria M. y Mario Julián Mora Copyright FT DR A Dedicado a las todas las hamburguesas y las gaseosas del mundo... DR A FT Índice de figuras Índice de cuadros Índice de algoritmos FT Índice general 7 9 11 13 13 14 16 2. Noción de Sistema 2.1. Sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2. Observación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 24 27 3. Noción de Estado 3.1. Estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 29 37 4. Noción de Abstracción 4.1. Abstracción de Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 39 52 5. Noción de Condición 5.1. Condición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 53 63 6. Noción de Repetición 6.1. Iteración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2. Recursión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 65 74 83 DR A 1. Introducción 1.1. Sobre este libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Sobre los algoritmos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3. Para los profesores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Índice general 7. Noción de Abstracción de Datos 87 7.1. Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 7.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 111 DR A FT Bibliografı́a 6 FT Índice de figuras Sistema Solar . . . . . . . . . . . . . . . . . . . . . . . Cajero electrónico . . . . . . . . . . . . . . . . . . . . Wiimote y consola Wii . . . . . . . . . . . . . . . . . . Algunos sistemas y un modelo computacional de ellos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 20 21 23 3.1. 3.2. 3.3. 3.4. 3.5. 3.6. Mundo de la Tortuga . . . . . . . . . . . . . . . . . . . Cambios de estado en el Mundo de la Tortuga . . . . . Estado inicial y final para dibujar una persona . . . . Problema de sacar la tortuga del laberinto . . . . . . . Solución al problema de sacar la tortuga del laberinto Problema de llevar la tortuga donde su mamá . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 31 33 36 36 38 DR A 2.1. 2.2. 2.3. 2.4. 4.1. Resultado de aplicar el algoritmo para dibujar una casa cuatro veces . . . . 43 4.2. Resultado esperado de aplicar el algoritmo para dibujar una casa cuatro veces 44 5.1. 5.2. 5.3. 5.4. Estado inicial del problema de mayorı́a de edad . . . . . Los dos posibles casos del problema de mayorı́a de edad Flujo de ejecución de la función esMayorDeEdad() . . . Tablero de tiro al blanco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 56 57 58 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 69 75 77 78 78 79 79 81 82 7.1. Juego Ahorcado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 6.1. Flujo de ejecución de la función adivinar() . . 6.2. Posiciones del juego Triqui . . . . . . . . . . . . 6.3. Fractal . . . . . . . . . . . . . . . . . . . . . . . 6.4. Flujo de ejecución de la función factorial(5) 6.5. Torres de Hanoi - Configuración inicial . . . . . 6.6. Torres de Hanoi - Configuración final . . . . . . 6.7. Torres de Hanoi - Configuración intermedia 1 . 6.8. Torres de Hanoi - Configuración intermedia 2 . 6.9. Torres de Hanoi - Proceso completo para n = 4 6.10. Triángulo de Sierpinski . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 ÍNDICE DE FIGURAS Saludo Inicial en el juego Ahorcado Etapas del ahorcado . . . . . . . . Estados finales del juego ahorcado Buscaminas en Windows 7 . . . . . Ventana del juego de las elecciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 . 96 . 99 . 103 . 109 DR A FT 7.2. 7.3. 7.4. 7.5. 7.6. 8 FT Índice de cuadros 3.1. Operaciones básicas en el Mundo de la Tortuga . . . . . . . . . . . . . . . . 3.2. Operaciones de colores en el Mundo de la Tortuga . . . . . . . . . . . . . . 33 37 4.1. 4.2. 4.3. 4.4. . . . . 46 48 49 50 5.1. Operaciones y constantes clásicas de un sistema Booleano . . . . . . . . . . 5.2. Operaciones básicas de un sistema de números aleatorios . . . . . . . . . . . 5.3. Operaciones básicas de un sistema de cadenas de texto . . . . . . . . . . . . 54 59 62 6.1. Operaciones básicas de entrada y salida . . . . . . . . . . . . . . . . . . . . 6.2. Operaciones básicas del sistema Tkinter . . . . . . . . . . . . . . . . . . . . 6.3. Operaciones para dibujar en un Canvas de Tkinter . . . . . . . . . . . . . . 69 81 84 7.1. 7.2. 7.3. 7.4. 7.5. 90 91 94 94 96 DR A Operaciones de posicionamiento y orientación en el Mundo de la Tortuga Operaciones y constantes clásicas de un sistema matemático . . . . . . . . Porcentaje de notas de un curso Introducción a la Programación . . . . . Operación de escritura en el Mundo de la Tortuga . . . . . . . . . . . . . Operaciones Operaciones Operaciones Operaciones Operaciones básicas básicas básicas básicas básicas del sistema de listas . . . . . . . . . . . . . . . . . . . y algunas constantes del sistema Pygame . . . . . . . del módulo de fuentes en el sistema Pygame . . . . . del módulo de superficies en el sistema Pygame . . . del módulo de rutas de archivos del sistema operativo . . . . . 9 DR A FT ÍNDICE DE CUADROS 10 FT Índice de algoritmos DR A 1.1. Carga del sistema matemático . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Carga del sistema de la Tortuga . . . . . . . . . . . . . . . . . . . . . . . . . 3.1. Dibuja una persona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Sale del laberinto cuadrado . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1. Dibuja las extremidades de una persona . . . . . . . . . . . . . . . . . . . . . 4.2. Dibuja el tronco de una persona . . . . . . . . . . . . . . . . . . . . . . . . . 4.3. Dibuja la cabeza de una persona . . . . . . . . . . . . . . . . . . . . . . . . . 4.4. Dibuja completamente una persona . . . . . . . . . . . . . . . . . . . . . . . 4.5. Dibuja una casa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6. Dibuja cuatro casas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.7. Dibuja una casa con posición . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.8. Dibuja cuatro casas especificando las posiciones . . . . . . . . . . . . . . . . 4.9. Dibuja la letra A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.10. Retorna la nota final del curso . . . . . . . . . . . . . . . . . . . . . . . . . . 4.11. Escribe la nota final del curso . . . . . . . . . . . . . . . . . . . . . . . . . . 4.12. Retorna la longitud de la hipotenusa de un triángulo rectángulo . . . . . . . 4.13. Retorna el dı́a del Domingo de Pascua . . . . . . . . . . . . . . . . . . . . . . 5.1. Retorna si un número es mayor o igual a 18 . . . . . . . . . . . . . . . . . . . 5.2. Retorna el puntaje dado un disparo . . . . . . . . . . . . . . . . . . . . . . . 5.3. Simula un juego de tiro al blanco . . . . . . . . . . . . . . . . . . . . . . . . . 5.4. Escribe correctamente la fecha del domingo de Pascua . . . . . . . . . . . . . 5.5. Retorna el medio de transporte para ir a un sitio de acuerdo a unas condiciones 6.1. Juego de adivinar un número, sin ciclo . . . . . . . . . . . . . . . . . . . . . 6.2. Juego de adivinar un número, con ciclo . . . . . . . . . . . . . . . . . . . . . 6.3. Juego de Triqui incompleto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4. Desglose de dinero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.5. Imprime la lista de número primos entre 1 y n . . . . . . . . . . . . . . . . . 6.6. Recibe lı́neas de código y quita los comentarios . . . . . . . . . . . . . . . . . 6.7. Halla el factorial de un número n . . . . . . . . . . . . . . . . . . . . . . . . 6.8. Imprime el proceso para solucionar las torres de hanoi de n discos . . . . . . 6.9. Dibuja el triángulo de Sierpinski . . . . . . . . . . . . . . . . . . . . . . . . . 7.1. Juego ahorcado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 15 34 35 40 40 41 41 42 43 45 46 47 49 49 50 51 56 59 60 61 63 66 67 70 71 72 74 76 80 83 89 11 ÍNDICE DE ALGORITMOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . pistas . que no . . . . . 93 95 97 98 100 101 102 104 105 106 107 DR A FT 7.2. Pantalla de inicio del juego ahorcado . . . . . . . . . . . . . . . . . 7.3. Dibuja/Actualiza la pantalla principal del juego ahorcado . . . . . . 7.4. Retorna la letra que el usuario presione en el juego ahorcado . . . . 7.5. Termina el juego ahorcado . . . . . . . . . . . . . . . . . . . . . . . 7.6. Simulador de elecciones . . . . . . . . . . . . . . . . . . . . . . . . . 7.7. Retorna un candidato en el juego de elecciones . . . . . . . . . . . . 7.8. Ordena las listas de candidato y votos en el juego de elecciones . . . 7.9. Juego buscaminas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.10. Crea y retorna una matriz de dimensiones ancho × alto . . . . . . . 7.11. Llena el tablero del buscaminas con minas y pistas . . . . . . . . . . 7.12. Modificación del algoritmo para llenar el tablero del buscaminas con 7.13. Función que determina si se han descubierto todas las ubicaciones tienen mina . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 108 FT 1 Introducción DR A La programación se ha convertido en una ciencia básica. Su importancia dentro de todas las ingenierı́as se ha vuelto notoria dado que cualquiera de las corrientes de ingenierı́a tiene componentes que involucran nociones de programación: problemas que pueden ser resueltos siguiendo una serie de pasos (noción de algoritmo), variables que deben ser tomadas en cuenta al realizar observaciones, datos que deben ser procesados, e incluso el mismo uso del computador. En las actividades cotidianas de los adolescentes actuales también podemos ver nociones de programación: comprar un reproductor mp3, descargar una canción de internet (de las que sı́ se pueden descargar sin romper ninguna ley) y copiarla en el reproductor, practicar un deporte, etc. Mucho más en actividades más cercanas a las profesiones de ingenierı́a: construcción de una vı́a, transporte de mercancı́a, inter-conectar una red de celulares, instalar un computador/servidor con un sistema operativo como linux, entre otros. 1.1. Sobre este libro La mayorı́a de libros de introducción a la programación usan la metodologı́a de enseñanza de programación teach-a-languague, es decir, mediante un lenguaje de programación. Esta metodologı́a presume que si el estudiante aprende un lenguaje de programación entonces aprende a programar. Nosotros pensamos que esta metodologı́a ya no es apropiada para la actual generación de estudiantes. Por esta razón proponemos una nueva metodologı́a y como creemos en los métodos formales, este libro está basado en las nociones de programación en vez de herramientas y lenguajes. En este escrito usamos la idea del paradigma del paracaidista [1] utilizado en especificación de modelos formales de sistemas. Vamos a pasar por muchas nociones, empezando por la de sistema, la noción de observación, la de estado, abstracción, condición, iteración, recursión, abstracción de datos y terminando en la noción de persistencia. En este libro no enseñamos cómo se escribe una lı́nea de código, esperamos que eso se aprenda de una manera natural. Sabemos que es necesario usar un lenguaje de programación para practicar, de manera que usaremos Python (como veremos en la siguiente sección) ya que tenemos confianza en que mejora el proceso de enseñanza, que es el cuarto principio en un curso inicial a la programación [6]. Sin embargo, como veremos, el uso de Python no es el núcleo 13 1 Introducción FT 1.2. Sobre los algoritmos Los algoritmos mostrados en este documento siguen la sintaxis del lenguaje de programación Python. De esta manera si se quieren probar los programas se debe instalar el compilador del lenguaje, que puede ser descargado gratis de la página web de Python (http://www.python.org/). Las funciones y procedimientos que se definan tendrán la documentación permitida por el lenguaje. Esta se especifica entre comillas debajo de la declaración de la función o procedimiento (en color verde). Para probar los algoritmos que usan operaciones de distintos sistemas es necesario cargar dichos sistemas. Para cargar el sistema matemático de Python (math), por ejemplo, antes del código es necesario agregar una lı́nea fundamental para cargar el sistema, la cual se puede ver en el algoritmo 1.1. DR A Noción del libro porque las nociones de programación son aquellas que estarán en la vida diaria de los estudiantes por siempre. El profesor Edsger W. Dijkstra dijo alguna vez: “Las nociones son fundamentales en toda actividad de programación. Ellas son tan fundamentales que no serán divididas en conceptos más primitivos.”. Entonces para resaltarlas, las nociones de programación podrán verse marcadas al lado del texto, como una nota al margen. Los cuadros que se muestran en este documento describen solo unas cuantas operaciones (a veces incompletas) que pueden aplicarse y que pertenecen a algunos sistemas desarrollados para Python. Sin embargo con ellos no pretendemos que los estudiantes tomen este libro como un manual de referencia de las librerı́as del lenguaje, por el contrario, deseamos que los estudiantes se motiven a consultar y estudiar más las librerı́as de Python (en su documentación en lı́nea en http://www.python.org/doc/) de manera que sus programas contengan mejores caracterı́sticas. El lector que desee un libro técnico sobre Python puede ver [3] ó [4]. # Se carga el sistema matemático import math Algoritmo 1.1: Carga del sistema matemático Por otro lado, para ejecutar los programas en el contexto del Mundo de la Tortuga se debe hacer tal y como se carga el sistema matemático antes de la aplicación de cualquier operación. Adicionalmente, para que el resultado de la ejecución de los algoritmos 14 1.2 Sobre los algoritmos sea exacto a aquellos que se muestra en este libro, se deben aplicar algunas operaciones inmediatamente después de la carga del sistema, que se pueden ver en el algoritmo 1.2. FT # Se carga el sistema Mundo de la Tortuga import turtle # Operacion para cambiar el titulo de la ventana turtle.title("El Mundo de la Tortuga") # Operacion para cambiar el tamaño de la ventana turtle.setup(500,500) # Operacion para cambiar la forma de la Tortuga turtle.shape("turtle") # Operacion para reiniciar el sistema turtle.reset() Algoritmo 1.2: Carga del sistema de la Tortuga DR A Puede notarse que para aplicar las operaciones de los sistemas es necesario anteponer el nombre del sistema (turtle en este caso). Los sistemas que se usarán como ejemplo en este libro se listan a continuación: turtle – Sistema de la Tortuga math – Sistema matemático random – Sistema de números aleatorios string – Sistema de cadenas de texto Tkinter – Sistema de interfaces gráficas pygame – Sistema de creación de juegos os – Sistema de manejo de operaciones del sistema operativo Adicionalmente en aquellos problemas donde se tiene un estado inicial determinado, es necesario cargar dicho estado inicial como en la segunda lı́nea de los algoritmos anteriores cambiando el nombre del sistema (math y turtle, respectivamente) por el nombre del archivo sin la extensión pyc. Todos los archivos deben estár guardados en la misma carpeta. En la página web del libro están enumerados los estados iniciales de los ejemplos y ejercicios que los requieran. 15 1 Introducción 1.3. Para los profesores FT En [5] propusimos una metodologı́a para la enseñanza de la programación. En ella nosotros concebimos un curso de Introducción a la Programación como una secuencia de sesiones de clase. Consideramos la duración de un curso como un perı́odo de 16 semanas, por lo que contamos con 32 sesiones de 2 horas. Tomamos entonces dos o más sesiones para cada noción incluyendo teorı́a y práctica (esperamos que más práctica que teorı́a). A continuación detallamos un poco la idea que proponemos con cada noción: Sistema Nosotros pensamos la noción de sistema como un conjunto de elementos interactivos en un dominio muy especı́fico. No hay que profundizar en la definición de la palabra sino en la aplicación del concepto. Es bueno empezar con ejemplos que todos los estudiantes conocen como el sistema solar, el sistema digestivo y el sistema económico. Luego se puede mover a los sistemas actuales y populares (con los que el estudiante convive diariamente) como los cajeros automáticos, la televisión, las consolas de juego y los computadores, por supuesto. DR A La idea en esta noción es mostrar que todo lo que nos rodea es un sistema y la mayorı́a (sino todos) de sistemas son modelables. Los programas de computador son finalmente modelos computacionales de sistemas reales. Los estudiantes deben identificar los elementos de los sistemas y describir sus interacciones. Es una buena idea pedir a los estudiantes que vacı́en sus bolsillos para encontrar una gran cantidad de sistemas que sirven como material para la clase: iPods, celulares, tarjetas de crédito, CDs,/DVDs, memorias USB, etc. Adicionalmente, en la página web http://www.howstuffworks.com/ se encuentra una cantidad suficiente de artı́culos de los dispositivos y sistemas recientemente nombrados los cuales pueden motivar a los estudiantes. Se pueden emplear dos sesiones en esta noción. Observación Una vez los estudiantes entienden un sistema, deben ser capaces de describirlo. En este punto nos interesamos en los elementos y no en el sistema como un todo. Siguiendo el paradigma del paracaidista bajamos un poco de manera que nos acerquemos al sistema y podamos preguntarles a los estudiantes qué es estático y qué cambia en él. Lo que se mantiene estático lo llamaremos “una constante” y los elementos que cambian serán llamados “variables”. Adicionalmente, para cada elemento detectamos los valores que puede tener. Este conjunto de valores define el tipo de dato del elemento. Esta noción no necesita mas de dos sesiones para ser entendida. 16 1.3 Para los profesores FT Estado Bajando un poco más en el sistema, se hace que los estudiantes noten que algunas veces unas variables tienen un valor y algunas veces ellas tienen otro valor. La valuación de todas las variables define el estado del sistema. Los estudiantes deben identificar cómo cambia el sistema de un estado a otro, de manera que se den cuenta que algunas operaciones tienen que ejecutarse para cambiar los valores de las variables (y consecuentemente cambiar el estado). Aquı́ es donde la definición del concepto de algoritmo surge: la secuencia de operaciones que lleva un estado a otro. Las operaciones que se usan aquı́ pueden ser descritas en notación Python para que los estudiantes se familiaricen con el lenguaje. En este punto las operaciones son consideradas cajas negras con entradas y salidas. Como Python es un lenguaje de programación tipado implı́cito, esta caracterı́stica evita el ruido que incorpora la declaración de tipos de datos en los llamados a funciones. DR A Motivamos a los profesores a que se armen de ayudas pedagógicas para que los estudiantes entiendan muy bien esta noción, la cual es vital para el éxito del aprendizaje. Cambiar el modo de pensar de una persona hacia un pensamiento sistémico (i.e. pensar todo el tiempo en términos de secuencia de pasos, algoritmos) es muy difı́cil. Usar el Mundo de la Tortuga, el robot LightBot (http://armorgames.com/play/2205) ó un Lego MindStorm, son algunas alternativas. Cuatro sesiones son una buena cantidad para esta noción. Además, este es un buen punto para hacer una primera evaluación en el curso. Abstracción Entre mas complejos se vuelven los problemas, más largos se vuelven los programas. De esta manera el estudiante siente la necesidad de tener una forma de programar con menos lı́neas de código. Ası́ se les enseña a organizar varias operaciones en una sola. Las cajas negras se vuelven cajas blancas. Los estudiantes ahora pueden construir sus propias operaciones llamando las básicas. Con las nuevas operaciones ellos pueden construir otras y ası́ sucesivamente. La noción de niveles de abstracción llega de una manera natural. Otras cuatro sesiones pueden usarse en esta noción. Condición Si el estado del sistema es desconocido el estudiante siente la necesidad de tener una estructura de control para considerar varios casos. Esto hace que se introduzca la noción de condición. Sin embargo, para entender mejor el mecanismo de esta noción se tienen que empezar por hacer ejercicios de los fundamentos básicos de la lógica proposicional: tablas de verdad y conectivos lógicos. Tal como las nociones anteriores esto debe hacerse usando expresiones cercanas al conocimiento de los estudiantes. Como las expresiones lógicas son elementos muy abstractos se deben utilizar cuatro sesiones para esta noción. El final de esta noción es otro buen punto para hacer una 17 1 Introducción evaluación. Iteración Aunque pensamos que la recursión es la forma más simple de repetición ya que no cambia el estado de las variables, parece ser más natural para los estudiantes aplicar otra estructura de repetición que si cambie las variables. Ella es llamada iteración. FT En esta noción es bueno quedarse un buen tiempo (ocho sesiones) ya que las nociones son introducidas gradualmente, y con la inclusión de una nueva noción se continúa usando y practicando las anteriores. Además, los estudiantes ahora tienen todas las herramientas para solucionar la mayorı́a de problemas. De allı́ que al finalizar esta noción se pueda hacer otra evaluación. Recursión Como dijimos arriba, nosotros creemos que la recursión es la forma básica de repetición en programación. Sin embargo esta noción puede ser muy difı́cil de entender. Por esta razón consideramos que ella puede ser opcional. DR A Nosotros simplificamos la idea considerando solo la recursión numérica y enseñando esta noción como lo que es: un llamado a función. Sabemos que se debe enseñar todo sobre el caso base y las reglas para reducir los otros casos, pero tal como las otras nociones, debe hacerse con ejemplos de la vida real que los estudiantes conozcan, y usando herramientas gráficas. Se podrı́an usar dos o tres sesiones para practicar la recursión. Abstracción de Datos Esta noción viene con la necesidad de agrupar una gran cantidad de información. Se puede pedir a los estudiantes, por ejemplo, que se calcule el promedio de todas las clases que han tomado. Si el número de clases es poco entonces no hay problema, pero si el estudiante ha tomado siete o más clases, manejar esta cantidad de variables es inconveniente (imagine la cantidad de variables de entrada a las funciones). De todas las estructuras de datos hemos escogido listas por dos razones: son estructuras de datos básicas y su mecanismo de uso en Python es muy cómodo. Usamos las últimas ocho sesiones para esta noción y se puede hacer la cuarta y última evaluación. Persistencia Al igual que recursión, dejamos esta noción como opcional ya que ella nace de la necesidad de mantener los datos incluso después de ejecutar los programas. Se debe enseñar entonces un poco sobre sistemas de archivos (sin entrar mucho en detalle sobre los sistemas operativos) y dispositivos de almacenamiento fı́sico como discos duros o memorias USB. Aquı́ también se podrı́an usar dos o tres sesiones. 18 2.1. Sistema FT 2 Noción de Sistema DR A Un sistema es un conjunto de elementos que interactúan en un dominio especı́fico y cumplen ciertas propiedades. Dichos elementos se relacionan entre ellos y actúan con un propósito especı́fico. El resultado del comportamiento y conectividad de los elementos describe el objetivo del sistema. Figura 2.1: Sistema Solar Ejemplos de sistemas pueden verse en todos los ámbitos. Entre los más conocidos tenemos el sistema solar, el sistema digestivo y el sistema económico. La figura 2.11 muestra el sistema solar. En este sistema el conjunto de elementos está compuesto por el Sol, los ocho 1 Imagen tomada de http://www.ifir.edu.ar/planetario/nssolar.htm/ 19 Sistema 2 Noción de Sistema FT planetas, los cinco planetas enanos y otra cantidad de astros. Todos estos elementos se relacionan entre sı́ por medio de la gravedad del Sol, la cual ocasiona que los planetas y astros se muevan en una órbita casi circular sobre el denominado plano eclı́ptico. Existen otros múltiples ejemplos de sistemas que tal vez ignoramos debido a que hacen parte de nuestra vida cotidiana y no llevan en su nombre la palabra sistema. Ejemplos de ellos son los cajeros automáticos, la televisión, las consolas de juego y los computadores. Los ejemplos 2.1 y 2.2 muestran algunos de estos sistemas. DR A Ejemplo 2.1 La figura 2.22 muestra un ejemplo de cajero automático. Muy a grosso modo un cajero automático es una terminal que acepta peticiones y se comunica, por medio de una red de datos, con los computadores de un banco para atender dichas peticiones. Figura 2.2: Cajero electrónico Los elementos que componen el cajero son: los botones, el teclado del cajero, el lector de tarjetas, la ranura de depósitos, la pantalla, el parlante, el dispensador de dinero y la impresora de recibos. Una de tantas interacciones que tiene un cajero electrónico es la siguiente: un usuario realiza una petición por intermedio de una tarjeta que es introducida al lector de tarjetas. Algunos cajeros, una vez acepta la tarjeta, pide a través de la pantalla y/o el parlante 2 Imagen modificada de http://www.howstuffworks.com/ 20 2.1 Sistema FT que introduzca la clave (otros cajeros la piden justo antes de realizar la transacción). Por medio los botones y el teclado se introduce la clave y se selecciona los datos apropiados del menú para efectuar la transacción deseada. Si es un retiro, entonces el cajero podrı́a proveer el dinero por medio del dispensador o podrı́a enviar un mensaje por la pantalla y/o el parlante diciendo que no se pudo efectuar la operación (falta de dinero o alguna otra razón); si es una consulta simplemente se muestran los datos en la pantalla; y si es una consignación se abre la ranura de depósitos para entregar el dinero. Una vez termina cualquiera de las transacciones anteriores, el cajero imprime un recibo y termina la interacción. ? ? ? DR A Ejemplo 2.2 Wii (figura 2.33 ) es una consola de juego lanzada en 2006 por la empresa de videojuegos Nintendo. Como toda consola de juego el Wii es un computador de entretenimiento interactivo que produce una señal de video, la cual puede ser interpretada por un televisor o monitor. Figura 2.3: Wiimote y consola Wii Al igual que un computador actual, el Wii cuenta con un procesador central, un procesador de video, 88 Mb de memoria interna, una memoria flash de 512 Mb (no cuenta con 3 Imagen tomada de http://wii.com/. 21 2 Noción de Sistema FT disco duro), tarjetas de video y sonido, y un lector de discos, entre otras cosas. Sin embargo, el Wii es reconocido por el Wii Remote, también conocido como Wiimote. Este dispositivo es el control principal de la consola. El Wiimote cuenta con un sistema de detección de movimiento en el espacio que permite manipular objetos en la pantalla, un apuntador óptico y una serie de botones para interactuar con el usuario. El funcionamiento del Wii es relativamente sencillo ya que la clave de la consola de Nintendo se centra en el control. El sistema cuenta con un barra –llamada sensor bar – que emite luz infraroja y que es detectada por el Wiimote. De esta manera el sistema determina la posición y orientación relativa en 3D del control. El Wiimote se conecta a la consola usando Bluetooth (la tecnologı́a abierta para el intercambio de datos entre dispositivos electrónicos en distancias cortas). Este diseño permite a los usuarios controlar los juegos usando movimientos corporales y las presiones de botones tradicionales. ? ? ? DR A Incluso si dejamos a un lado la electrónica, podemos ver ejemplos de sistemas mucho más simples como por ejemplo un banco (sin la parte automatizada), una sala de cine, un club social, una biblioteca, un teléfono y una tienda de música. Todos estos son sistemas y en cada uno de ellos se pueden identificar sus elementos e interacciones. De esta manera podemos construir modelos de dichos sistemas. Un modelo es la representación de un objeto o sistema. Los modelos pueden ser fı́sicos (e.g. un carro de juguete que represente un carro real) o no fı́sicos (e.g. una ecuación matemática que represente el movimiento del carro), pero todos tienen en común que sirven para hacer simulaciones y probar propiedades en los sistemas. Existe un tipo de modelo no fı́sico llamado modelo computacional. Este es un modelo abstracto que se desarrolla en computadores y es denominado comúnmente como programa. En la actualidad es muy frecuente el uso de dispositivos computacionales como PCs, iPads y Blackberrys. Todos ellos pueden ejecutar programas que sirven para una tarea especı́fica. Sin embargo, si se mira bien dichos programas, puede verse que son representaciones de sistemas reales. El lector que esté familiarizado con programas como Skype, puede notar que son modelos de un sistema telefónico; aquel que esté familiarizado con iTunes, podrá ver que es un modelo de una tienda de música; Kindle es un modelo de una biblioteca; Windows Media Player es un modelo de una sala de cine; y Facebook es un modelo de un club social. En la figura 2.44 pueden verse otros ejemplos de sistemas reales y un modelo computacional de cada uno de ellos. Modelo Programa 4 Imágenes tomadas de http://recursos.cnice.mec.es/biosfera/alumno/1ESO/planeta_habitado/ index.htm (Tierra), http://www.olympiaph.com.ph/crm/typewriters.html (máquina de escribir), y http://www.revivesoundproductions.com/djseminar.html (dj) 22 FT 2.1 Sistema DR A (a) La Tierra y Google Earth (b) Una Máquina de Escribir y MSWord (c) Un DJ y VirtualDJ Figura 2.4: Algunos sistemas y un modelo computacional de ellos 23 2 Noción de Sistema 2.2. Ejemplo 2.3 Las siguientes tablas listan las constantes y variables de los sistemas: una sala de cine, una tienda de barrio y un rompecabezas. DR A Variable Al conocer los sistemas, existen muchas propiedades y caracterı́sticas interesantes que podemos observar de ellos. Las descripciones que hagamos de los sistemas nos permiten identificar los aspectos estáticos y dinámicos de ellos. Si tomamos un carro, por ejemplo, y observamos detenidamente sus caracterı́sticas, podemos encontrar que algunas de ellas cambian a través del tiempo, mientras que otras se quedan estáticas: la velocidad cambia, el kilometraje cambia, el nivel de gasolina cambia, el estado del motor (encendido o apagado) cambia y su posición geográfica cambia, mientras que la placa se mantiene, el color del carro se mantiene (en términos generales) y la forma se mantiene (si no se choca). Muchas otras cosas del carro cambian y muchas otras se mantienen igual a través del tiempo, sin embargo, en primera instancia, estas son las mas notorias. Aquellas caracterı́sticas que se mantienen igual en un sistema son llamadas constantes. Las constantes tienen un papel muy importante en los sistemas dado que están completamente fijas y, por lo tanto, sirven como referentes. Por otro lado, aquellos componentes que cambian de un sistema son denominados variables. En el ejemplo 2.3 se muestran algunas constantes y variables de algunos sistemas. FT Constante Observación Una sala de cine: Constantes Número de Sillas Pantalla Variables Número de Asistentes Pelı́cula Configuración de los Asistentes Un rompecabezas: Constantes Número de fichas Tamaño Forma de las fichas Una tienda de barrio: 24 Variables Ubicación de las fichas 2.2 Observación Variables Cantidad de productos Dinero Número de clientes Organización de los productos Precio de los productos FT Constantes El local El vendedor(*) (*) Aunque el vendedor puede variar se puede decir que siempre hay un vendedor. ? ? ? DR A Como se pudo notar en el ejemplo anterior, es normal que en la mayorı́a de sistemas existan más variables que constantes. A nivel general, las variables son conjuntos lógicos de atributos, es decir, sı́mbolos o nombres simbólicos dados a una cantidad de información. Esta información pertenece a un dominio de valores, esto es, una asociación de datos que se clasifican de la misma manera (e.g. números, letras, objetos). Estos dominios que determinan los posibles valores de las variables son llamados tipos de datos 5 . En el ejemplo del carro tenemos que las variables observadas son la velocidad, el kilometraje, el nivel de gasolina, estado del motor y la posición geográfica. Para encontrar los tipos de datos para estas variables hacemos el siguiente análisis: La velocidad del carro es un valor numérico que varia dependiendo de la marca y el modelo del carro, sin embargo tiene dos lı́mites, uno inferior y uno superior. El lı́mite inferior es cero ya que lo mı́nimo que el carro puede hacer es quedarse quieto. Por otro lado, el lı́mite superior es una constante k, la cual es definida en el tiempo de diseño del carro. Un Renault Logan, por ejemplo, tiene una velocidad máxima de 185 km/h. Por lo anterior esta variable tiene el dominio de valores en el rango [0, k], y entonces su tipo de dato es Natural. El kilometraje es también un valor numérico que comienza en 0 (cero) cuando el carro es nuevo y nunca ha sido manejado. Este valor se incrementa a medida que el carro es usado y jamás decrece. De allı́ que el dominio de valores es el de los números reales positivos, luego su tipo de dato es Real. El nivel de gasolina es otro valor numérico similar a la velocidad. Tiene un lı́mite inferior (cuando el carro no tiene nada de gasolina) y un lı́mite superior (cuando el carro está “tanqueado”). La diferencia entre este dominio de valores y el de la 5 Aunque las constantes no cambian su valor, dicho valor pertenece a un dominio especı́fico y por lo tanto podemos decir que las constantes tienen también un tipo de dato. 25 Tipo de Datos 2 Noción de Sistema velocidad está en que el dominio del nivel de gasolina representa un porcentaje, esto es, la variable tiene un valor entre 0 y 100. Por lo anterior el tipo de dato de la variable nivel de gasolina es Real. FT El estado del motor tiene uno de dos valores posibles: encendido o apagado. Esto quiere decir que si preguntan ¿está encendido el carro?, en la observación podemos contestar Verdadero o Falso. Por esto su tipo de dato es Booleano6 . La posición geográfica del carro es un poco más complicada ya que involucra dos ejes. Estos ejes son comúnmente llamados Latitud (posición con respecto al norte o sur del ecuador) y Longitud (posición con respecto al oriente u occidente del primer meridiano ó Greenwich). Ellos tienen sus valores en unidades de medida angular (UMA), es decir, grados, minutos y segundos. Entonces el tipo de datos para la posición geográfica es UMA × UMA. Ejemplo 2.4 Supongamos que tenemos los siguientes juegos: un buscaminas, una sopa de letras y un ahorcado, ¿cuáles son sus variables y tipos de datos correspondientes? DR A Un buscaminas: Variable Número de minas Ubicación de la mina i Número de marcas Ubicación de la marca j Número de espacios destapados Espacio destapado k Tipo de Dato Entero Entero × Entero Entero Entero × Entero Entero Entero × Entero Una sopa de letras: Variable Número de filas Número de columnas Número de palabras Letra de la ubicación i, j Palabras encontradas 6 Tipo de Dato Entero Entero Entero Caracter Lista de palabras El conjunto de valores Booleanos está compuesto por dos elementos 1 (Verdadero) y 0 (Falso). Este conjunto es la base fundamental del Álgebra Lógica de Boole, desarrollada por el matemático británico George Boole en 1854. 26 2.3 Ejercicios Un ahorcado: Tipo de Dato Lista de palabras Entero Caracter Entero Entero Entero Booleano FT Variable Palabras Número total de letras de la palabra Letra correspondiente a la ubicación i Número de letras descubiertas de la palabra Número total de partes del muñeco Número de partes del muñeco dibujadas Se ahorcó ? ? ? 2.3. Ejercicios 2.1 De los siguientes sistemas escoja cinco (5) y describa sus elementos e interacciones: DVD Player Casino Kinect Pizzerı́a Celular iPod Supermercado Estación Espacial Dispensador de Gaseosas Discoteca DR A Carro Gobierno Televisor MIO Monopoly Partido de Fútbol Universidad Minicomponente Laboratorio de Fı́sica Su Casa 2.2 ¿Cuáles de los anteriores sistemas tienen un modelo computacional? Provea una descripción del programa. 2.3 Tome cinco sistemas de los enumerados anteriormente (diferentes de los escogidos en el primer ejercicio) y muestre cuáles de sus elementos son constantes y cuáles son variables. 2.4 De los siguientes juegos determine sus variables y sus constantes, con sus respectivos tipos de datos: blackjack, triqui, pinball. 27 DR A FT 2 Noción de Sistema 28 3.1. Estado FT 3 Noción de Estado Las variables de un sistema cambian su valor a través del tiempo. Cada vez que una variable cambia su valor, el estado en el cual se encuentra sistema cambia. Un estado de un sistema es una configuración única de los elementos que componen dicho sistema. El estado del sistema está definido entonces por la valuación de todas sus variables. Para ejemplificar la noción de estado vamos a introducir un sistema muy simple llamado el mundo de la tortuga: DR A La tortuga vive en un mundo de dos dimensiones en el cual puede moverse. Ella siempre inicia en el centro del mundo. Este mundo es ilimitado y el centro de él está en la posición 0,0. La tortuga puede dibujar mientras se mueve y sus movimientos pueden ser: ir en lı́nea recta hacia adelante ó hacia atrás una distancia determinada, cambiar su orientación (a la izquierda o la derecha k grados), moverse en un cı́rculo y escribir un texto. Adicionalmente la tortuga puede desplazarse sin dibujar a una posición x, y y se le puede preguntar su posición y orientación. En la figura 3.1 puede verse el mundo de la tortuga. En esta figura la tortuga ha dibujado una casita. Para llegar a este estado del sistema, se ha debido pasar por varios estados intermedios donde se ha cambiado el valor de las variables del sistema. En la figura 3.2 se detallan los diferentes estados. A continuación se explica en detalle los cambios de estado de la figura 3.2. 1. Todo comienza en el estado inicial (Figura 3.2a), donde la tortuga está en el centro del mundo (posición x = 0, y = 0) y su orientación o sentido es cero (θ = 0). 2. La tortuga se mueve luego 50 unidades hacia adelante (Figura 3.2b). Su posición ha cambiado a x = 50, y = 0 y su orientación queda igual. 3. Después la tortuga cambia su orientación a θ = 90, pero su posición permance igual en x = 50, y = 0 (Figura 3.2c). 4. Acto seguido (Figura 3.2d) la tortuga se desplaza 100 unidades hacia adelante quedando en x = 50, y = 100. Su orientación se mantiene en θ = 90. 29 Estado FT 3 Noción de Estado DR A Figura 3.1: Mundo de la Tortuga 5. Ahora la tortuga rota noventa grados a la izquierda sin desplazarse cambiando su orientación a θ = 180 (Figura 3.2e). 6. Una vez más la tortuga se mueve 100 unidades hacia adelante modificando su posición a x = −50 y y = 100 (Figura 3.2f). 7. De nuevo rota noventa grados a la izquierda sin trasladarse y su orientación es ahora θ = 270 (Figura 3.2g). 8. Otra vez viaja la tortuga hacia adelante 100 unidades (Figura 3.2h). Su nueva posición es x = −50 y y = 0. 9. Luego la tortuga gira noventa grados a la izquierda volviendo a su orientación inicial θ = 0 (Figura 3.2i). 10. La tortuga termina el cuadrado yendo hacia adelante 50 unidades y finalizando en la misma posición inicial x = 0, y = 0 (Figura 3.2j). 11. Para dibujar el techo de la casa, la tortuga primero se desplaza sin dibujar cincuenta unidades en el eje horizontal x = 50 (Figura 3.2k). 30 (b) Estado 2 (e) Estado 5 (f) Estado 6 (c) Estado 3 (d) Estado 4 (g) Estado 7 (h) Estado 8 DR A (a) Estado Inicial FT 3.1 Estado (i) Estado 9 (j) Estado 10 (k) Estado 11 (l) Estado 12 (m) Estado 13 (n) Estado 14 (ñ) Estado 15 (o) Estado Final Figura 3.2: Cambios de estado en el Mundo de la Tortuga 31 3 Noción de Estado 12. Después se mueve cien unidades en el eje vertical y = 100 (Figura 3.2l) también sin dibujar. FT 13. Como el techo tiene dos lı́neas diagonales, la tortuga tiene que rotar 135 grados (Figura 3.2m) y cambia su orientación a θ = 135. 14. Dibuja la primera lı́nea desplazandose 70.7 unidades1 (Figura 3.2n). Su posición cambia a x = 0 y y = 150 15. Posteriormente la tortuga rota noventa grados a la izquierda, dejando θ = 215 (Figura 3.2ñ). 16. Finalmente la tortuga se mueve otras 70.7 unidades llegando al estado final x = −50, y = 100 y θ = 215 (Figura 3.2o). DR A Algoritmo Los anteriores son los pasos para llevar el sistema del estado inicial al estado final. De manera general, el conjunto ordenado de pasos que llevan un sistema de un estado a otro es comúnmente llamado algoritmo. Los pasos que se ejecutan en un algoritmo realizan operaciones que modifican las variables del sistema y, por ende, el estado del mismo. Las operaciones son acciones que pueden efectuarse dentro del sistema y son definidas en el tiempo de creación del sistema. Para el caso del mundo de la tortuga, el conjunto núcleo de operaciones2 se encuentra en el cuadro 3.1. Comentario Ejemplo 3.1 Dados los estados inicial y final en la figura 3.3, ¿cuáles son las operaciones necesarias para llevar al sistema del estado inicial al estado final? El algoritmo 3.1 muestra los pasos para llevar el sistema de un estado al otro. Las lı́neas rojas que comienzan con el sı́mbolo ’#’ son denominados comentarios. Los comentarios son anotaciones en algoritmos que sirven para documentar el código y hacerlo más fácil de entender, pero que no se tienen en cuenta a la hora de ejecutar el algoritmo. 1 ? ? ? Este valor es determinado fácilmente aplicando el teorema de Pitágoras: En todo triángulo rectángulo el cuadrado de la hipotenusa es igual a la suma de los√cuadrados de los catetos. Como los catetos de este triángulo miden 50 unidades se tiene entonces d = 502 + 502 = 70,7. 2 Existen muchas más acciones que se irán introduciendo a medida que se necesiten. 32 3.1 Estado left(a) penup() pendown() position() heading() undo() clear() home() reset() Acción Avanza u pixeles (dibuja dependiendo del estado del lápiz) En la posición actual realiza una porción del un cı́rculo de radio r (la porción y dirección dibujada depende del parámetro a, 360 para todo) A partir de la orientación actual, rota la tortuga a grados en sentido contrario a las manecillas del reloj Levanta el lápiz (cualquier movimiento no dibujará su recorrido) Baja el lápiz (cualquier movimiento dibujará su recorrido) Reporta el valor de la posición de la tortuga Reporta el valor de la orientación de la tortuga Deshace la última acción ejecutada por la tortuga Borra todo lo dibujado por la tortuga (no mueve la tortuga) Lleva la tortuga al centro del mundo (no borra lo dibujado) Reinicia todo el sistema y posiciona la tortuga en la posición x = 0 y y = 0, con orientación θ = 0 FT Operación forward(u) circle(r,a) DR A Cuadro 3.1: Operaciones básicas en el Mundo de la Tortuga Figura 3.3: Estado inicial y final para dibujar una persona 33 3 Noción de Estado # Dibuja la cabeza turtle.pendown() turtle.circle(50,360) # Dibuja el cuerpo turtle.left(270) turtle.forward(200) FT # Se posiciona para dibujar la cabeza turtle.penup() turtle.left(90) turtle.forward(100) turtle.left(270) # Dibuja la pierna izquierda turtle.left(45) turtle.forward(100) DR A # Se posiciona para dibujar la pierna derecha turtle.penup() turtle.left(180) turtle.forward(100) turtle.left(90) # Dibuja la pierna derecha turtle.pendown() turtle.forward(100) # Se posiciona para dibujar los brazos turtle.penup() turtle.left(180) turtle.forward(100) turtle.left(45) turtle.forward(150) turtle.left(90) turtle.forward(75) turtle.left(180) # Dibuja los dos brazos turtle.pendown() turtle.forward(150) Algoritmo 3.1: Dibuja una persona Cuando se tiene un sistema cuyo estado actual no es el deseado, se dice que se tiene un 34 3.1 Estado problema. La solución a un problema es una serie de pasos (ejecuciones de operaciones) que llevan del estado en que están las cosas en el sistema al estado que se desean, es decir, un algoritmo. # Se cambia el color del lápiz turtle.pencolor("red") DR A # Sale del laberinto turtle.forward(350) turtle.left(90) turtle.forward(150) turtle.left(90) turtle.forward(50) turtle.left(90) turtle.forward(100) turtle.left(270) turtle.forward(200) turtle.left(270) turtle.forward(150) turtle.left(270) turtle.forward(100) turtle.left(90) turtle.forward(50) turtle.left(90) turtle.forward(150) turtle.left(270) turtle.forward(100) turtle.left(270) turtle.forward(300) turtle.left(90) turtle.forward(50) FT Ejemplo 3.2 La tortuga Junior se encuentra atrapada en el laberinto de la figura 3.4. Resuelva el problema de sacar a Junior del laberinto. Inicialmente la tortuga se encuentra en la posición (-175,175). Los lados del cuadrado externo del laberinto tienen una longitud de 400 pixeles, la esquina superior izquierda está en (-200,200) y el ancho de los pasadizos del laberinto es de 50 pixeles. Problema Algoritmo 3.2: Sale del laberinto cuadrado El algoritmo 3.2 muestra los pasos para resolver el problema. Si se aplica dicho algoritmo en el estado inicial del problema, se obtendrá como resultado el estado en la figura 3.5. ? ? ? 35 FT 3 Noción de Estado DR A Figura 3.4: Problema de sacar la tortuga del laberinto Figura 3.5: Solución al problema de sacar la tortuga del laberinto 36 3.2 Ejercicios El algoritmo 3.2 usa una nueva operación llamada pencolor(c) para cambiar el color del lápiz con el cual dibuja la tortuga. Otras operaciones similares para hacer más vistoso el mundo están en la tabla 3.2. fillcolor(c) begin fill() end fill() bgcolor(c) Acción Cambia el color del lápiz a c (c debe ser una cadena de texto, e.g. ”red”, ”orange”, ”green”) Cambia el color de rellenado de las figuras a c (c debe ser una cadena de texto, e.g. ”red”, ”orange”, ”green”) Las figuras cerradas que dibuje la tortuga serán rellenas con el color definido en la operación fillcolor(c). Para que las figuras se coloreen, se debe llamar a end fill() después de dibujar Termina el proceso de rellenar las figuras cerradas Cambia el color de fondo de la ventana a c (c debe ser una cadena de texto, e.g. ”red”, ”orange”, ”green”) FT Operación pencolor(c) Cuadro 3.2: Operaciones de colores en el Mundo de la Tortuga Ejercicios DR A 3.2. 3.1 Haga un algoritmo que ponga a la tortuga a dibujar su nombre y apellido. 3.2 Mejore el algoritmo de dibujar una persona. Agrégue los ojos, la nariz, la boca, camiseta, pantalón, manos y pies. Use colores. 3.3 Resuelva el problema de llevar a la tortuga Junior (tortuga negra) donde su mamá (tortuga roja). El laberinto del problema se puede ver en la figura 3.6. La tortuga mamá se encuentra en el centro del mundo (0,0) mientras que Junior se encuentra en (-150,180). Los semi-cı́rculos que rodean a la mamá son concéntricos y si se enumeraran desde el más interno hasta el más externo, el semi-cı́rculo 1 tiene un radio de 33 pixeles, el semi-cı́rculo 2 tiene un radio de 60 pixeles, el semi-cı́rculo 3 tiene un radio de 90 pixeles, el semi-cı́rculo 4 tiene un radio de 120 pixeles y el semi-cı́rculo 5 tiene un radio de 150 pixeles. 3.4 Desarrolle un algoritmo para que la tortuga construya una sopa de letras. 37 DR A FT 3 Noción de Estado Figura 3.6: Problema de llevar la tortuga donde su mamá 38 4.1. Abstracción de Control FT 4 Noción de Abstracción DR A Normalmente, a medida que se van resolviendo problemas más complicados, la cantidad de operaciones básicas que deben realizarse y la cantidad de datos a procesar es mayor. Adicionalmente, muchas veces el algoritmo que resulta no es lo suficientemente intuitivo como para entenderlo fácilmente, por el contrario, se puede ver como una lista de comandos cuyo propósito es indefinido. Por ejemplo, si tomamos el algoritmo 3.1 (ejemplo 3.1), eliminamos los comentarios y omitimos el propósito del algoritmo, una persona solo con mirar el código tendrı́a dificultad en encontrar su objetivo. Esto se debe a que el nivel de abstracción del algoritmo es bajo. Abstracción es el proceso en el que datos y algoritmos son agrupados de acuerdo a una semántica particular, de manera que se reduzcan los detalles. El proceso de abstracción puede aplicarse a operaciones o a datos. En este capı́tulo veremos la abstracción que involucra algoritmos, llamada abstracción de control. En el capı́tulo 7 se verá la abstracción de datos. El nivel de abstracción de un sistema define la cantidad de detalles a tener en cuenta al realizar el modelo computacional del sistema. El mundo de la tortuga, por ejemplo, tiene un alto nivel de abstracción ya que las operaciones básicas son claras en cuanto a su propósito y el programador sabe que si ejecuta una operación, el resultado se verá gráficamente, no importa el computador en el que se ejecute. Si el nivel de abstracción del mundo de la tortuga fuera bajo, el programador tendrı́a que lidiar con operaciones del procesador y la tarjeta de video del computador en el que quiera ejecutar el programa, es decir, rutinas propias de la máquina. En el ejemplo 4.1 se muestra cómo aumentar el nivel de abstracción del algoritmo 3.1. Ejemplo 4.1 Si queremos dibujar una persona, podemos dividir el dibujo en las partes del cuerpo: cabeza, tronco, y extremidades, y definimos cada una de estas partes como una operación diferente. Las nuevas operaciones para dibujar las extremidades, el tronco y la cabeza están definidas en los algoritmos 4.1, 4.2 y 4.3. 39 Abstracción 4 Noción de Abstracción FT def dibujarExtremidades(): "Dibuja las extremidades de una persona" # Dibuja la pierna izquierda turtle.left(45) turtle.forward(100) # Se posiciona para dibujar la pierna derecha turtle.penup() turtle.left(180) turtle.forward(100) turtle.left(90) # Dibuja la pierna derecha turtle.pendown() turtle.forward(100) DR A # Se posiciona para dibujar los brazos turtle.penup() turtle.left(180) turtle.forward(100) turtle.left(45) turtle.forward(150) turtle.left(90) turtle.forward(75) turtle.left(180) # Dibuja los dos brazos turtle.pendown() turtle.forward(150) Algoritmo 4.1: Dibuja las extremidades de una persona def dibujarTronco(): "Dibuja el tronco de una persona" # Dibuja el cuerpo turtle.left(270) turtle.forward(200) Algoritmo 4.2: Dibuja el tronco de una persona 40 4.1 Abstracción de Control # Dibuja la cabeza turtle.pendown() turtle.circle(50,360) FT def dibujarCabeza(): "Dibuja la cabeza de una persona" # Se posiciona para dibujar la cabeza turtle.penup() turtle.left(90) turtle.forward(100) turtle.left(270) Algoritmo 4.3: Dibuja la cabeza de una persona El algoritmo para dibujar una persona se reduce ahora a cargar y aplicar las operaciones recién creadas, como en el algoritmo 4.4. Note que si a dicho algoritmo le quitamos los comentarios, aún ası́ el usuario que lea el algoritmo tiene una idea de cuál es su propósito solo con echarle un vistazo. DR A # Dibuja la cabeza dibujarCabeza() # Dibuja el cuerpo dibujarTronco() # Dibuja las extremidades dibujarExtremidades() Algoritmo 4.4: Dibuja completamente una persona ? ? ? En los algoritmos 4.3, 4.2, y 4.1 se han definido las operaciones dibujarExtremidades(), dibujarTronco() y dibujarCabeza(). La definición de una operación especifica un identificador (que va a ser usado para aplicar la operación) y el cuerpo de dicha operación. La especificación del cuerpo de una operación es denominada implementación de la operación. En el algoritmo 4.4 se usan las operaciones previamente definidas. Cuando una operación es usada de esta manera, se dice que se hace una aplicación de la operación. Aplicar una operación es, finalmente, hacer el llamado a la operación. Ejemplo 4.2 Supongamos que queremos dibujar un conjunto de casas. La manera mas sencilla es crear 41 Definición Implementación Aplicación 4 Noción de Abstracción def dibujarCasa(): "Dibuja una casa" # Dibuja la pared turtle.pendown() turtle.left(270) turtle.forward(100) turtle.left(270) turtle.forward(100) turtle.left(270) turtle.forward(100) turtle.left(270) turtle.forward(100) DR A # Dibuja el techo turtle.left(135) turtle.forward(70.7) turtle.left(90) turtle.forward(70.7)) FT una operación para dibujar una sola casa y luego ejecutar dicha operación tantas veces como número de casas se quiera. El algoritmo 4.5 define una operación para dibujar una casa. # Dibuja la puerta y termina turtle.penup() turtle.left(45) turtle.forward(100) turtle.left(90) turtle.forward(40) turtle.left(90) turtle.pendown() turtle.forward(30) turtle.left(270) turtle.forward(20) turtle.left(270) turtle.forward(30) turtle.left(90) turtle.penup() turtle.forward(40) turtle.left(90) turtle.forward(100) Algoritmo 4.5: Dibuja una casa Luego ejecutamos la operación para dibujar una casa varias veces, como en el algoritmo 4.6, en el cual se dibujan cuatro casas. 42 4.1 Abstracción de Control FT # Dibuja cuatro casas dibujarCasa() dibujarCasa() dibujarCasa() dibujarCasa() Algoritmo 4.6: Dibuja cuatro casas ? ? ? DR A Podemos ver que el nivel de abstracción del algoritmo 4.5 puede aumentarse separando las partes de la casa y definiéndolas como operaciones separadas. Por otra parte, el lector que pruebe el algoritmo 4.6 puede darse cuenta que el resultado que se obtiene no es el esperado. La figura 4.1 muestra lo que la tortuga dibuja después de aplicado el algoritmo. Figura 4.1: Resultado de aplicar el algoritmo para dibujar una casa cuatro veces Es claro que la tortuga dibuja cuatro casas, pero no es el resultado esperado; el resultado que se quisiera es el que se muestra en la figura 4.2. 43 FT 4 Noción de Abstracción DR A Figura 4.2: Resultado esperado de aplicar el algoritmo para dibujar una casa cuatro veces Parámetro Argumento El problema que ocurre es que la posición y la orientación inicial de la tortuga en cada ejecución de la operación no es la correcta. Por lo tanto, debemos darle a la operación estos datos para que la tortuga pueda dibujar las casas como deseamos. En el algoritmo 4.7 se define la operación con la posición en x y en y como datos de entrada. Cuando se definen operaciones, a los datos de entrada de ellas se les llama parámetros. Un parámetro de una operación es entonces, un tipo especial de variable que provee datos necesarios que pueden ser usados en la operación. Con la nueva definición de la operación para dibujar una casa, se puede hacer un algoritmo más adecuado para dibujar cuatro casas y que, de hecho, queden como en la figura 4.2. El resultado es el algoritmo 4.8. Los valores que se proveen como datos de entrada a la ejecución de una operación son llamados los argumentos de la operación. En el algoritmo 4.8, por ejemplo, −100 y 0 son los argumentos de la primera aplicación de la operación dibujarCasa(). En el algoritmo 4.7 se incorporan nuevas operaciones del mundo de la tortuga. Estas operaciones sirven para posicionar y orientar la tortuga en un lugar particular del mundo sin necesidad de llevarla hasta allá con left() y forward(). Estas y otras operaciones están en el cuadro 4.1. 44 4.1 Abstracción de Control # Dibuja la pared turtle.pendown() turtle.left(270) turtle.forward(100) turtle.left(270) turtle.forward(100) turtle.left(270) turtle.forward(100) turtle.left(270) turtle.forward(100) DR A # Dibuja el techo turtle.left(135) turtle.forward(70.7) turtle.left(90) turtle.forward(70.7)) FT def dibujarCasa(x,y): "Dibuja una casa dada una posicion" # Se posiciona donde quiere dibujar la casa y orienta a la # tortuga en 0 grados turtle.penup() turtle.setx(x) turtle.sety(y) turtle.seth(0) # Dibuja la puerta y termina turtle.penup() turtle.left(45) turtle.forward(100) turtle.left(90) turtle.forward(40) turtle.left(90) turtle.pendown() turtle.forward(30) turtle.left(270) turtle.forward(20) turtle.left(270) turtle.forward(30) turtle.left(90) turtle.penup() turtle.forward(40) turtle.left(90) turtle.forward(100) Algoritmo 4.7: Dibuja una casa con posición 45 4 Noción de Abstracción FT # Dibuja cuatro casas especificando su posicion dibujarCasa(-100,0) dibujarCasa(0,0) dibujarCasa(100,0) dibujarCasa(200,0) Algoritmo 4.8: Dibuja cuatro casas especificando las posiciones Operación goto(x,y) setx(x) sety(y) DR A seth(h) xcor() ycor() Acción Traslada la tortuga a la posición x = x, y = y. La orientación permanece igual Traslada la tortuga a la coordenada x = x sin cambiar la coordenada y. La orientación permanece igual Traslada la tortuga a la coordenada y = y sin cambiar la coordenada x. La orientación permanece igual Orienta la tortuga al ángulo a = h Reporta el valor de la posición de la tortuga en el eje x Reporta el valor de la posición de la tortuga en el eje y Cuadro 4.1: Operaciones de posicionamiento y orientación en el Mundo de la Tortuga Ejemplo 4.3 Ahora queremos que la tortuga escriba la letra ’A’ pero en diferentes escalas, es decir, con un tamaño que depende de un valor de entrada. Para esto se necesita definir una operación que tenga un parámetro escala cuyo dominio de valores se encuentre entre 1 y 100. Este parámetro será usado para determinar las longitudes de las lı́neas que componen la letra ’A’ y los ángulos que debe usar la tortuga para hacer el dibujo. El algoritmo 4.9 resuelve este problema. Se asume que la letra a una escala 100 ocupa la mayor parte de la ventana y el dibujo de la letra más pequeña tiene una escala 1. Como la ventana es de 500 × 500 pixeles, entonces la tortuga se moverá inicialmente a una posición determinada por la escala y un factor de −2,3. Ası́, si la escala es 100, la posición inicial será (−230, −230), mientras que si la escala es 1, la posición será (−2,3, −2,3). Como puede verse para hallar las longitudes de las lı́neas diagonales se usa (de nuevo) el teorema de Pitágoras, y para hallar el ángulo que debe tomar la tortuga para dibujar las diagonales se usa la función trigonométrica arcoseno. Estas operaciones necesarias en este problema (y en algunos siguientes) hacen parte de un sistema matemático; algunas de las 46 4.1 Abstracción de Control operaciones más usadas de este sistema se encuentran en el cuadro 4.2. FT def letraAescala(escala): "Dibuja la letra 'A' con una escala dada" # Se posiciona para dibujar turtle.penup() turtle.setx(-2.3*escala) turtle.sety(-2.3*escala) turtle.seth(0) # Calcula los valores necesarios para la letra, a partir de # la escala longitud = math.sqrt((2.3*escala * 2.3*escala) + \ (4.6*escala * 4.6*escala)) angulo = (math.asin(4.6*escala/longitud)*360)/(2*math.pi) # Dibuja la primera diagonal turtle.pendown() turtle.left(angulo) turtle.forward(longitud) DR A # Dibuja la segunda diagonal turtle.seth(0) turtle.left(360-angulo) turtle.forward(longitud) # Se posiciona para dibujar la linea intermedia turtle.left(180) turtle.penup() turtle.forward(longitud/3) # Dibuja la linea intermedia turtle.seth(180) turtle.pendown() linea = turtle.xcor()*2 turtle.forward(linea) # Mueve la tortuga a una posicion que permita ver el resultado turtle.penup() turtle.goto(-235,235) turtle.seth(0) Algoritmo 4.9: Dibuja la letra A ? ? ? 47 4 Noción de Abstracción Operación pow(x,y) sqrt(x) log(x,b) sin(v) cos(v) tan(v) asin(v) acos(v) atan(v) degrees(a) radians(a) Acción Retorna Retorna Retorna Retorna Retorna Retorna Retorna Retorna Retorna Retorna Retorna Constante pi e Valor El valor matemático π = 3,141592... El valor matemático e = 2,718281... x elevado a la y la raiz cuadrada de x el logaritmo base b de x el seno de a, en radianes el coseno de a, en radianes la tangente de a, en radianes el arcoseno de a, en radianes el arcocoseno de a, en radianes la arcotangente de a, en radianes la conversión del ángulo a de radianes a grados la conversión del ángulo a de grados a radianes DR A Alcance FT Declaración El lector puede notar que en el algoritmo 4.9 se han usado unas variables longitud, angulo y linea. Estas variables se dice que son locales ya que son declaradas (usadas por primera vez o por lo menos anunciadas de que existen) dentro del contexto de la operación definida. Por fuera del contexto (en el caso de las variables longitud, angulo y linea, por fuera de la operación letraAescala()), dichas variables no son visibles o accesibles. Por otro lado, las variables globales son aquellas que pueden ser usadas en cualquier contexto, es decir, en cualquier parte del código del modelo computacional. Por lo tanto, el contexto en el que son declaradas las variables define su alcance, esto es, la visibilidad o accesibilidad de las variables en diferentes partes del modelo computacional. Cuadro 4.2: Operaciones y constantes clásicas de un sistema matemático Función Los valores de las variables locales longitud, ángulo y linea son obtenidos al aplicar las operaciones sqrt(), asin() y xcor(), respectivamente. Estas operaciones en su definición han hecho aplicaciones de otras operaciones y cálculos internos, y al final ellas (sqrt(), asin() y xcor()) reportan un valor, el cual es retornado a la operación donde fueron aplicadas (en este caso letraAescala()) y se asignan a las variables longitud, angulo y linea. Cuando una operación reporta un valor que es retornado al final de su definición, a dicha operación se le denomina función. Ejemplo 4.4 El curso de Introducción a la programación se evalúa con tres exámenes parciales, tareas 48 4.1 Abstracción de Control y un proyecto. El cuadro 4.3 muestra los porcentajes que tienen estos elementos en la nota final del curso. Porcentaje 25 % 20 % 20 % 15 % 20 % FT Evaluación Examen Parcial 1 Examen Parcial 2 Examen Parcial 3 Tareas Proyecto Cuadro 4.3: Porcentaje de notas de un curso Introducción a la Programación Si quisiéramos que la tortuga escribiera la nota final del curso dados los datos de las evaluaciones, primero tenemos que hacer la función que retorne la nota final y luego hacer que la tortuga escriba este valor reportado. La función está definida en el algoritmo 4.10. DR A def notaFinal(p1, p2, p3, t, pry): "Funcion que haya la nota final del curso" nota = (p1*0.25) + (p2*0.2) + (p3*0.2) + (t*0.15) + (pry*0.2) return nota Algoritmo 4.10: Retorna la nota final del curso Esta función puede usarse entonces en un algoritmo que haga dibujar a la tortuga la nota final, como el algoritmo 4.11. def escribirNotaFinal(): "Escribe la nota final de un curso" # Halla el dia del domingo de Pascua valor = notaFinal(4.5, 4.0, 5.0, 4.0, 5.0) # Escribe la nota y termina turtle.write(valor, False, align="center", \ font=("Arial",32,"normal")) turtle.penup() turtle.goto(-235,235) Algoritmo 4.11: Escribe la nota final del curso ? ? ? En el algoritmo 4.11 se usa una operación para escritura. Esta operación está descrita 49 4 Noción de Abstracción en el cuadro 4.4. Acción Escribe en la posición actual el texto t. El parámetro m es un valor lógico (True o False) que especifica si la tortuga se desplazará cuando escriba el texto. El parámetro a define la alineación del texto (“left”, “center”, o “right”). El parámetro f es una estructura que describe la fuente del texto (esta estructura se codifica entre paréntesis con los datos separados por comas, ası́: (nombre, tamaño, tipo), e.g. (“Arial”,18,“italic”)) FT Operación write(t,m,a,f) Cuadro 4.4: Operación de escritura en el Mundo de la Tortuga Procedimiento DR A Composición Las operaciones que no reportan un valor y, por lo tanto, no es retornado a la operación donde se aplica se les llama procedimientos. Todas las operaciones que se han definido hasta el momento, excepto notaFinal(), son procedimientos. Las funciones, dado que retornan un valor que puede asignarse a una variable y, por lo tanto, puede usarse en otras operaciones, pueden aplicarse como argumento de otra aplicación. Este proceso es conocido como composición de funciones. El ejemplo 4.5 muestra un algoritmo con composición de funciones. Ejemplo 4.5 Supongamos que queremos construir la función que retorna la longitud de la hipotenusa de un triángulo rectángulo, dadas las longitudes de los dos catetos (esto nos sirve de ayuda en varios ejemplos vistos). Para resolver este problema se usa el teorema de pitágoras. El algoritmo 4.12 muestra su uso de dos maneras diferentes. def longitudHipotenusa1(cateto1, cateto2): "Retorna la longitud de la hipotenusa de un triangulo rectangulo" valor1 = math.pow(cateto1,2) valor2 = math.pow(cateto2,2) hipotenusa = math.sqrt(valor1+valor2) return hipotenusa def longitudHipotenusa2(cateto1, cateto2): "Retorna la longitud de la hipotenusa de un triangulo rectangulo" return math.sqrt(math.pow(cateto1,2)+math.pow(cateto2,2)) Algoritmo 4.12: Retorna la longitud de la hipotenusa de un triángulo rectángulo Las dos funciones retornan exactamente lo mismo. La primera función declara dos variables locales que contendrán el valor resultado de elevar al cuadrado las longitudes de 50 4.1 Abstracción de Control FT los dos catetos y luego aplica la función de raı́z cuadrada con la suma de las dos variables como argumento. El resultado de la función raı́z cuadrada se asigna a una última variable local, cuyo valor será retornado por la función. La segunda función se ahorra la declaración de las variables aplicando directamente la función de raı́z cuadrada con el resultado de la función de elevar al cuadrado los catetos y sumarlos, es decir, componiendo las funciones. ? ? ? DR A Ejemplo 4.6 A principio de cada año una de las preguntas más frecuentes entre nosotros es ¿en qué fecha cae Semana Santa? La tortuga Sabelotodo encontró que una fórmula para calcular la fecha del Domingo de Pascua en los años 1982–2048, inclusive, es ası́1 : sea a = ano %19, b = ano %4, c = ano %7, d = (19a + 24) %30, e = (2b + 4c + 6d + 5) %7, entonces el dı́a del Domingo de Pascua es Marzo 22 + d + e. Esta fórmula la podemos usar para crear la función Pascua. Esta función tiene como parámetro un año y retorna el dı́a del Domingo de Pascua de ese año. El algoritmo 4.13 muestra la función. def Pascua(ano): "Funcion que halla el día del domingo de pascua" a = ano%19 b = ano%4 c = ano%7 d = (19*a + 24)%30 e = (2*b + 4*c + 6*d + 5)%7 dia = 22 + d + e return dia Algoritmo 4.13: Retorna el dı́a del Domingo de Pascua Usando esta función, podemos poner a la tortuga a escribir la fecha del Domingo de Pascua (o toda la Semana Santa) con un algoritmo similar al algoritmo 4.11. ? ? ? Si el lector prueba el algoritmo 4.13, aplicándolo con diferentes años como argumento, podrá notar que en algunas ocasiones se obtendrán resultados inconsistentes cronológicamente. Por ejemplo, la función Pascua con un argumento 2000 retorna 54. Sin embargo no 1 La operación módulo ’ %’ halla el residuo de la división entera entre dos números. 51 4 Noción de Abstracción 4.2. Ejercicios FT existe la fecha Marzo 54 de 2000, Marzo tiene solo 31 dı́as y el dı́a siguiente es Abril 1. De esta manera, es necesario considerar los casos para los que la fecha está en Marzo y aquellos donde la fecha está en Abril. La estructura de control para tener dichas consideraciones se verá en el capı́tulo 5. 4.1 Mejore los procedimientos 4.1, 4.2 y 4.3, creándoles parámetros que especifiquen las posiciones donde van a ser dibujados. 4.2 Baje el nivel de abstracción del dibujo de una persona, desarrollando los procedimientos y funciones para dibujar todas partes del cuerpo humano. Por ejemplo, en vez de dibujarCabeza(), puede construir las operaciones dibujarOjos(), dibujarNariz(), dibujarBoca(), dibujarOrejas(), etc., con sus respectivos parámetros de posición y otros parámetros que se deseen (e.g. dibujarOjos() puede tener además un parámetro que especifique el color de los ojos). DR A 4.3 Desarrolle un procedimiento para que la tortuga escriba su nombre en diferentes escalas. 4.4 Se quiere que la tortuga dibuje una pequeña ciudad con un carro en ella. Cree entonces un procedimiento que lo haga y que permita dibujar el carro en diferentes calles de ella. Adicionalmente el procedimiento debe tener la capacidad de hacer zoom en cualquier parte de la pequeña ciudad (piense en un Google Maps – http://maps. google.com/ – muy muy sencillo). 4.5 Escriba una función con dos parámetros correspondientes a la base y la altura de un triángulo isósceles y haga que la tortuga dibuje el triángulo. La función debe retornar el valor del área del triángulo. 4.6 Desarrolle un algoritmo para que la tortuga construya un sudoku y luego defina las operaciones para resolver cualquier sudoku. 52 5.1. Condición FT 5 Noción de Condición Cuando el estado en que se encuentra un sistema se desconoce, no podemos garantizar que un algoritmo desarrollado llegue al estado deseado. Lo anterior debido a que no tenemos forma de saber qué operaciones deben realizarse a partir de dicho estado desconocido para lograr llegar al estado final. Sin embargo si consideráramos todos los casos posibles, entonces sı́ garantizarı́amos llegar al estado final. Para diferenciar un caso de otro es necesario que el estado del sistema cumpla una condición. Una condición es: una relación lógica entre variables, ó DR A una función que retorna un valor de tipo Booleano. Una relación lógica entre variables se construye utilizando operadores de relación. Los operadores de relación entre variables son ==, <, >, <=, >=, ! =, <>. Por ejemplo, si tenemos la siguiente relación: x >= 5 esta relación es verdadera en el caso en que la variable x sea asignada con valores mayores o iguales a 5. De lo contrario la relación es falsa. La semántica de los operadores de relación es como sigue: el operador < significa menor que. el operador > significa mayor que. el operador == significa igual que. el operador <= significa menor o igual que. el operador >= significa mayor o igual que. los operadores ! = y <> significan distinto de. 53 Condición 5 Noción de Condición Operación p and q Acción Retorna True cuando los valores de ambas variables p y q son True. De lo contrario retorna False. Esto se ve mas claramente en la siguiente tabla de verdad: p or q q True False True False Retorna True cuando el valor de cualquiera de las dos variables p y q es True. De lo contrario retorna False. Esto se ve mas claramente en la siguiente tabla de verdad: q True False True False DR A p True True False False not p p or q True True True False Retorna True cuando el valor de la variable p es False. De lo contrario retorna False. Esto se ve mas claramente en la siguiente tabla de verdad: p True False Constante True False p and q True False False False FT p True True False False not p False True Valor El valor Booleano 1 El valor Booleano 0 Cuadro 5.1: Operaciones y constantes clásicas de un sistema Booleano De otro lado, la implementación de funciones que retornan valores de tipo Booleano se logra combinando constantes lógicas (True y False), variables lógicas (i.e. con tipo de dato 54 5.1 Condición Booleano) y operaciones lógicas. Las operaciones lógicas son aquellas que al ser aplicadas producen un valor Booleano. Algunas de estas operaciones se pueden ver en el cuadro 5.1. Un condicional es una sentencia que evalúa una condición y de acuerdo al resultado de dicha evaluación, ejecuta o no, un conjunto de operaciones. DR A FT Ejemplo 5.1 Supongamos que la tortuga Junior tiene dos opciones para divertirse esta noche. Una opción es ir a una discoteca mientras que la otra es ir a comer helado. La decisión depende de la edad que tenga Junior dado que para entrar a las discotecas se requiere tener mas de 18 años. La figura 5.1 muestra el estado inicial del problema, con Junior al frente de ambos edificios. Condicional Figura 5.1: Estado inicial del problema de mayorı́a de edad El algoritmo 5.1 desarrolla la función esMayorDeEdad() la cual recibe como parámetro un número natural que representa la edad y retorna True o False dependiendo si el argumento pasado a la función en su aplicación es mayor o igual a 18, o no. Para hacer que Junior vaya a la discoteca, debemos entonces aplicar la función esMayorDeEdad() del algoritmo 5.1 con un número mayor o igual a 18 como argumento. Esto hace que la función retorne True y el estado final del sistema sea el de la figura 5.2a. Por otro lado, si aplicamos la función con un número menor a 18, ella retornará False (ya que la condición no se cumple (edad >= 18), la variable mayor no se modifica y se queda 55 5 Noción de Condición con el valor que se le dio en su declaración, es decir, False) y el estado final del sistema será el de la figura 5.2b. FT def esMayorDeEdad(edad): "Funcion que retorna si es mayor de edad o no" mayor = False if edad >= 18: mayor = True return mayor DR A Algoritmo 5.1: Retorna si un número es mayor o igual a 18 (a) Solución 1 (b) Solución 2 Figura 5.2: Los dos posibles casos del problema de mayorı́a de edad Flujo de Ejecución ? ? ? En el ejemplo 5.1 puede notarse que en algunos casos se ejecutan unas operaciones y en otros se ejecutan otras operaciones. El orden de ejecución de las operaciones de un programa determina el flujo de ejecución del programa. La figura 5.3 da una explicación gráfica del flujo de la función esMayorDeEdad(). La asignación mayor = False siempre se 56 5.1 Condición FT ejecuta. La condición edad >= 18 es evaluada y de acuerdo al resultado se puede ir por uno de dos caminos: si el resultado de la condición es True entonces se ejecuta la asignación mayor = True y luego se retorna la variable mayor (es decir se retorna True); si el resultado de la condición es False entonces simplemente se retorna la variable mayor, que en este caso tiene asignado el valor original False. edad mayor = False ¿edad >= 18? Cierto DR A mayor = True Falso return mayor Figura 5.3: Flujo de ejecución de la función esMayorDeEdad() Ejemplo 5.2 Supongamos que la tortuga Junior ha dibujado un tablero de tiro al blanco para jugar un rato. El tablero que ha dibujado se muestra en la figura 5.4. Como puede apreciarse, un tablero de tiro al blanco consiste en un número fijo de circunferencias concéntricas separadas por una distancia usualmente constante. En este caso particular, el tablero tiene ocho circunferencias concéntricas cada una separada 25 unidades de la siguiente. La circunferencia más pequeña (negra) tiene un radio de 25 unidades. El juego consiste en disparar al tablero y de acuerdo al lugar donde ocurrió el disparo, se obtiene un puntaje. Este puntaje es acumulado hasta que se acaben los disparos. El jugador que más puntaje obtenga al final será el ganador. 57 FT 5 Noción de Condición DR A Figura 5.4: Tablero de tiro al blanco Lo primero que debemos hacer es construir una función que, dado un disparo, retorne el puntaje correspondiente. Un disparo se puede ver como una posición en el mundo, es decir, una posición en el eje x y una posición en el eje y. De allı́ que si hallamos la distancia desde ese punto hasta el origen (0,0), podrı́amos saber entre qué par de cı́rculos cayó el disparo y, por lo tanto, saber el puntaje asociado. Entonces la distancia podemos conocerla aplicando una de las funciones para hallar la hipotenusa del ejemplo 4.5. Luego comparamos el valor obtenido en la aplicación con cada una de las distancias de las circunferencias y de esta manera conocemos el puntaje. El algoritmo 5.2 muestra la función. Vemos que si se aplica esta función con un disparo en la posición (100,100), el puntaje √ obtenido es 300 ya que la distancia calculada ( 1002 + 1002 = 141,42) se encuentra entre 125 y 150. Podemos notar además que si se calcula una distancia mayor a 200, el puntaje retornado es cero; de allı́ que se puede asumir que el disparo no le pegó al tablero. Ahora hacemos el procedimiento para simular un juego. Para realizar la simulación usamos un sistema de números aleatorios. Este sistema nos permite asumir disparos al azar, es decir, no tenemos que inventarnos unos números y cada vez que ejecutemos la simulación obtendremos disparos diferentes (un sistema inicial desconocido). Las operaciones del sistema de números aleatorios se encuentra en el cuadro 5.2. El procedimiento para la simulación se encuentra en el algoritmo 5.3. Primero se hallan los disparos utilizando la función randint(), luego se hallan los puntajes y con ellos se 58 5.1 Condición randint(a,b) Acción Retorna el siguiente número real aleatorio en el rango [0,0, 1,0) Retorna número aleatorio en la lista de números entre i y f con intervalos de p. Por ejemplo, si queremos un número aleatorio par entre 0 y 10, le enviamos los argumentos a la función: i=0, f=10, p = 2 (la lista de números entre 0 y 10 con intervalos de 2 es {0, 2, 4, 6, 8, 10}) Retorna un número entero aleatorio n de manera que a ≤ n ≤ b FT Operación random() randrange(i,f,p) Cuadro 5.2: Operaciones básicas de un sistema de números aleatorios encuentra el ganador. DR A def puntajeXdisparo(x,y): "Funcion que retorna el puntaje dado el disparo" distancia = longitudHipotenusa2(x,y) puntaje = 0 if distancia <= 25: puntaje = 1000 if distancia > 25 and distancia <= 50: puntaje = 800 if distancia > 50 and distancia <= 75: puntaje = 600 if distancia > 75 and distancia <= 100: puntaje = 500 if distancia > 100 and distancia <= 125: puntaje = 400 if distancia > 125 and distancia <= 150: puntaje = 300 if distancia > 150 and distancia <= 175: puntaje = 200 if distancia > 175 and distancia <= 200: puntaje = 100 return puntaje Algoritmo 5.2: Retorna el puntaje dado un disparo En el algoritmo 5.3 pueden notarse algunas cosas. El juego consiste en tres disparos que se representan con seis variables (la posición en x y y de cada disparo). Con estas seis variables se calculan los tres puntales obtenidos en los tres disparos, que se suman para tener el puntaje total. Lo anterior se realiza para los dos jugadores. Puede verse que todas 59 5 Noción de Condición FT las variables (excepto las de puntaje total) son asignadas dos veces, esto está bien ya que una vez se usan las variables para realizar los cálculos, su valor deja de ser necesario. Por otro lado, cada posición de un disparo se tiene después de aplicar la función randint() con argumentos 0 y 250; como la distancia máxima para obtener un punto es 200, este valor (250) permite simular un jugador que realiza un tiro malo. def tiroAlBlanco(): "Procedimiento que simula un juego de tiro al blanco" # Jugador 1 disparo1x = random.randint(0,250) disparo1y = random.randint(0,250) disparo2x = random.randint(0,250) disparo2y = random.randint(0,250) disparo3x = random.randint(0,250) disparo3y = random.randint(0,250) puntaje1 = puntajeXdisparo(disparo1x,disparo1y) puntaje2 = puntajeXdisparo(disparo2x,disparo2y) puntaje3 = puntajeXdisparo(disparo3x,disparo3y) puntajeTotalJugador1 = puntaje1 + puntaje2 + puntaje3 DR A # Jugador 2 disparo1x = random.randint(0,250) disparo1y = random.randint(0,250) disparo2x = random.randint(0,250) disparo2y = random.randint(0,250) disparo3x = random.randint(0,250) disparo3y = random.randint(0,250) puntaje1 = puntajeXdisparo(disparo1x,disparo1y) puntaje2 = puntajeXdisparo(disparo2x,disparo2y) puntaje3 = puntajeXdisparo(disparo3x,disparo3y) puntajeTotalJugador2 = puntaje1 + puntaje2 + puntaje3 # Ganador if puntajeTotalJugador1 > puntajeTotalJugador2: turtle.write("El ganador es el jugador 1") if puntajeTotalJugador1 < puntajeTotalJugador2: turtle.write("El ganador es el jugador 2") if puntajeTotalJugador1 == puntajeTotalJugador2: turtle.write("El juego quedo empatado") Algoritmo 5.3: Simula un juego de tiro al blanco ? ? ? En algunas ocasiones solo se tiene una posible condición en el sistema que se está mode- 60 5.1 Condición lando. Por esto si la condición se cumple entonces el flujo de ejecución del programa va por un camino, pero si no se cumple el flujo debe ir por otro. En estos casos, se puede hacer uso del complemento opcional de un condicional (if) llamada la condición por defecto (else). El ejemplo 5.3 muestra el uso de la condición por defecto. FT Ejemplo 5.3 Retomando el ejemplo 4.6, si quisiéramos que la tortuga escribiera la fecha cronológicamente bien (que no escriba Marzo 54 de 2000, por ejemplo) entonces después de aplicar la función Pascua() debemos tener en cuenta los casos. Si el número retornado es menor o igual que 31, entonces la tortuga debe escribir la fecha en Marzo, de lo contrario debe escribir la fecha en Abril. El algoritmo 5.4 muestra el código para la escritura correcta de la fecha del domingo de Pascua de cualquier año. Es claro que si el dı́a no es menor o igual a 31 entonces debe ser mayor a 31 (es la única otra opción), por lo tanto se puede utilizar la condición por defecto, es decir, no hay necesidad de especificar “if dı́a > 31:”. Adicionalmente, para escribir la fecha correcta es necesario utilizar unas operaciones para manejo de cadenas de texto. El cuadro 5.3 describe algunas operaciones básicas de cadenas de texto. Condición por Defecto DR A def PascuaMejorado(ano): "Escribe el día del domingo de Pascua de acuerdo a un ano" # Halla el día del domingo de Pascua dia = Pascua(ano) # Si el dia pertenece a Marzo o a Abril if(dia <= 31): fecha = "Marzo " + str(dia) + " de " + str(ano) else: dia = dia - 31 fecha = "Abril " + str(dia) + " de " + str(ano) # Escribe la fecha turtle.write(fecha, False, align="center", font=("Arial",32,"normal")) turtle.penup() turtle.goto(-235,235) Algoritmo 5.4: Escribe correctamente la fecha del domingo de Pascua ? ? ? 61 5 Noción de Condición len(w) find(w,s) capitalize(w) w1 + w2 w * n c in w Acción Retorna una cadena de texto que contiene una representación imprimible del objeto w Retorna el número de caracteres de la cadena w Retorna la posición más cercana al inicio donde se encuentre la subcadena s dentro de la cadena w. Si la subcadena no se encuentra en w, retorna -1 Retorna la cadena w en mayúsculas Retorna la concatenación de las cadenas w1 y w2 Retorna n copias de la cadena w concatenadas Retorna un booleano que indica si el caracter c se encuentra en la cadena w FT Operación str(w) Cuadro 5.3: Operaciones básicas de un sistema de cadenas de texto DR A Ejemplo 5.4 Supongamos que la tortuga Junior debe ir a un sitio particular de la ciudad. Las opciones de transporte son: Bus, Taxi ó Carro Particular. Si Junior necesitase llegar antes de 15 minutos y tuviera $30000, entonces Junior escoge mejor irse en Taxi, sin embargo si tuviera un poco mas de tiempo e igual cantidad de dinero, serı́a indiferente el medio de transporte. Si tuviera 1 hora o más para llegar a su destino, la tortuga preferirı́a ahorrar el dinero e irse en Bus. Lo anterior se calcula solo si Junior no tuviera a nadie que lo lleve en Carro Particular. Para resolver este problema es necesario tener en cuenta todas las condiciones necesarias para cada opción de transporte. El algoritmo 5.5 muestra una forma de solucionar el problema. La función transporte() tiene tres parámetros: dinero, tiempo y carro. El dinero y el tiempo son entonces variables de tipo entero, mientras que carro es una variable de tipo Booleano. Por comodidad, el transporte por defecto será “Carro Particular” (la variable local transporte se declara con este valor). Si el argumento que se manda para el parámetro carro en la aplicación de esta función es True entonces simplemente se retorna el transporte por defecto. De lo contrario, se debe verificar el tiempo: si es mayor o igual a una hora entonces el transporte será Bus, sino tenemos que consultar el dinero: si se tienen los $30000 pero no hay problema con el tiempo (la tortuga necesita llegar entre 15 y 60 minutos) entonces se toma el Bus, sin embargo si hay prisa (15 minutos o menos) se tiene que tomar Taxi. 62 ? ? ? 5.2 Ejercicios return transporte FT def transporte(dinero, tiempo, carro): "Decide el medio de transporte para ir a un sitio" transporte = "Carro Particular" if not carro: if tiempo >= 60: transporte = "Bus" else: if dinero == 30000: if tiempo > 15 and tiempo < 60: transporte = "Bus" else: transporte = "Taxi" else: transporte = "Bus" Algoritmo 5.5: Retorna el medio de transporte para ir a un sitio de acuerdo a unas condiciones Ejercicios DR A 5.2. 5.1 Dado el estado inicial del problema de mayorı́a de edad (ejemplo 5.1) y la función esMayorDeEdad(), escriba el algoritmo necesario para que la tortuga vaya al sitio adecuado dependiendo de la edad que tiene, deje una lı́nea punteada por el camino que recorre y se abran las puertas del sitio, como en la figura 5.2. 5.2 Modifique los algoritmos necesarios del ejemplo 5.2 para que gráficamente se muestren los disparos cuando se realicen (i.e. en las posiciones retornadas por la operación del sistema aleatorio). 5.3 La fórmula de Pascua del ejemplo 4.6 funciona para cualquiera año en el rango de 1900–2099 excepto para 1954, 1981, 2049 y 2076. Para estos 4 años la fórmula produce una fecha que está una semana después. Escriba la función Pascua2() con las modificaciones necesarias para que la fórmula funcione para todos los años. 5.4 Un año es bisiesto si es divisible por 4, a menos que sea un año que no es divisible por 400 (1800 y 1900 no son bisiestos, mientras que 1600 y 2000 sı́ lo son). Escriba la función Bisiesto() que retorne True si el año que le entra es bisiesto, y retorne False de lo contrario. 63 5 Noción de Condición 5.5 Escriba la función FechaValida() que tenga tres parámetros correspondientes a una fecha (dı́a, mes y año), y retorne True si la fecha es válida, y False de lo contrario. Por ejemplo hdı́a: 24, mes: 5, año: 1962i es válida, pero hdı́a: 31, mes: 9, año: 2000i no lo es (Septiembre solo tiene 30 dı́as). FT 5.6 Los dı́as del año pueden numerarse del 1 al 365 (ó 366). Este número puede ser calculado en tres pasos usando un sistema de números enteros: a) N umeroDia = 31(mes − 1) + dia b) si el mes está después de Febrero, reste (4mes + 23)/10 c) si es un año bisiesto y después de Febrero 29, sume 1 DR A Escriba la función NumeroDia() que tome una fecha (igual que el problema anterior, es decir, representada en tres entradas dı́a, mes y año), verifique que es una fecha válida (usando la función del problema anterior), y calcule el número asociado al dı́a del año. 64 FT 6 Noción de Repetición DR A En la mayorı́a de sistemas existen operaciones que se repiten una y otra vez. En el sistema solar, por ejemplo, se ve que la traslación de los planetas se repite, al igual que la rotación de la Tierra; en un sistema de producción las máquinas repiten un proceso de fabricación; en la fórmula 1 los carros repiten un recorrido un número partı́cular de veces. Todas las repeticiones mencionadas se hacen hasta que el sistema llega un estado deseado (algunas de ellas son tantas que su cantidad podrı́a entenderse como infinito). En modelos computacionales las repeticiones pueden hacerse de manera explı́cita o implı́cita, es decir, con estructuras que controlan el flujo de la ejecución ó con aplicaciones circulares de funciones que vayan reduciendo las entradas hasta un caso base. Una repetición explı́cita es comúnmente llamada iteración mientras que la aplicación circular de una función (repetición implı́cita) es denominada recursión. A continuación se verá cada una de ellas. 6.1. Iteración Un ciclo es una secuencia de aplicación de operaciones que se especifican una sola vez pero son ejecutadas un número determinado de veces de manera sucesiva. Cada repetición de la secuencia es llamada una iteración. El estado en el que queda el sistema después de una iteración resulta ser el estado inicial de la siguiente iteración. Las iteraciones terminan cuando se llega a un estado particular, es decir, se continua la ejecución del ciclo mientras se cumpla una condición. Por ejemplo, la Tierra seguirá dando vueltas alrededor del Sol mientras el sistema solar se encuentre en un estado en el que el Sol exista, i.e. mientras se cumpla la condición de que el Sol siga ahı́; los corredores de la fórmula 1 dan vueltas mientras el sistema se encuentre en un estado en el que los corredores no hayan dado un número particular de vueltas, i.e. mientras se cumpla la condición de que el número de vueltas de los corredores sea menor a un número k. En modelos computacionales funciona igual. Supongamos que queremos crear un juego muy simple de adivinar un número del 1 al 100. El jugador que quiere adivinar el número solo tiene cinco oportunidades de adivinar. Cada vez que el jugador intenta adivinar se le da una pista que consiste en decirle si el número a adivinar es menor o mayor al número que acaba de decir. El algoritmo 6.1 codifica el juego anterior. 65 Ciclo Iteración 6 Noción de Repetición DR A FT def adivinar(n): "Procedimiento para jugar a adivinar un numero" # Primera oportunidad numero = input("Cual es el numero?") if numero == n: print("Adivino!!!") else: if numero > n: print("El numero es menor") else: print("El numero es mayor") # Segunda oportunidad numero = input("Cual es el numero?") if numero == n: print("Adivino!!!") else: if numero > n: print("El numero es menor") else: print("El numero es mayor") # Tercera oportunidad numero = input("Cual es el numero?") if numero == n: print("Adivino!!!") else: if numero > n: print("El numero es menor") else: print("El numero es mayor") # Cuarta oportunidad numero = input("Cual es el numero?") if numero == n: print("Adivino!!!") else: if numero > n: print("El numero es menor") else: print("El numero es mayor") # Quinta oportunidad numero = input("Cual es el numero?") if numero == n: print("Adivino!!!") else: print("Perdio, el numero era" + str(n)) Algoritmo 6.1: Juego de adivinar un número, sin ciclo 66 6.1 Iteración FT Si se aprecia bien el algoritmo de adivinar un número, puede detallarse que una porción del código se repite varias veces haciendo que el código se torne aburrido y sin elegancia. Adicionalmente, si el juego se extiende un poco, por ejemplo si el número de intentos se incrementa, la implementación de este algoritmo se vuelve inmanejable. Por esta razón es más conveniente utilizar un ciclo en este problema. El algoritmo 6.2 es equivalente al anterior algoritmo solo que utilizando ciclos. Utilizamos una variable temporal llamada oportunidad para saber cuantas veces se ha repetido el ciclo, y mientras dicha variable sea menor o igual a cinco repetimos el proceso de pedir un número y saber si lo adivinó o no. Es de notar que cuando se adivina el número se actualiza dicha variable temporal a cinco para que el ciclo termine sin realizar el resto de iteraciones. DR A def adivinar(n): "Procedimiento para jugar a adivinar un numero" oportunidad = 1 while oportunidad <= 5: numero = input("Cual es el numero?") if numero == n: print("Adivino!!!") oportunidad = 5 else: if oportunidad == 5: print("Perdio, el numero era" + str(n)) else: if numero > n: print("El numero es menor") else: print("El numero es mayor") oportunidad = oportunidad + 1 Algoritmo 6.2: Juego de adivinar un número, con ciclo Tal como puede verse en los anteriores algoritmos, para solicitar un dato al usuario e imprimir información en pantalla, pueden usarse tres operaciones básicas de entrada y salida, las cuales pueden verse en el cuadro 6.1. El flujo de ejecución del algoritmo 6.2 (figura 6.1) muestra la semántica de un ciclo: el cuerpo de un ciclo se ejecuta repetidamente mientras la condición sea cierta. Una vez la condición sea falsa el ciclo termina. Cabe anotar que la condición de un ciclo, en este caso, se prueba al comienzo del ciclo (llamado ciclo while ), lo que hace que si inicialmente la condición es falsa, no se ejecute nada del cuerpo del ciclo.1 1 Existen otros tres tipos de ciclo: (1) aquel en el que la condición se prueba al final del ciclo, lo que 67 Ciclo while 6 Noción de Repetición n adivinar(n) FT oportunidad = 1 ¿oportunidad <= 5? Falso Cierto numero = input() ¿numero == n? Cierto print() oportunidad = 5 Falso ¿oportunidad == 5? DR A Cierto Falso print() ¿numero > n? Cierto print() Falso print() oportunidad = oportunidad + 1 Figura 6.1: Flujo de ejecución de la función adivinar() garantiza que el cuerpo del ciclo se ejecute por lo menos una vez (ciclo do-while), (2) aquel en el que no se prueba una condición sino que se especifica explı́citamente cuántas iteraciones se harán en el ciclo (ciclo for) y (3) aquel en el que se hace una iteración sobre los elementos de una lista (ciclo for-each). 68 6.1 Iteración Operación input(t) raw input(t) FT print(t) Acción Solicita al usuario un dato por medio del texto t y retorna lo que el usuario entre con el tipo de dato apropiado Solicita al usuario un dato por medio del texto t, pero retorna lo que el usuario entre como una cadena de texto Imprime en pantalla el texto t Cuadro 6.1: Operaciones básicas de entrada y salida Los siguientes ejemplos muestran otros modelos computacionales que usan ciclos y las operaciones del cuadro 6.1. DR A Ejemplo 6.1 El juego Triqui (Tic-Tac-Toe en inglés) consiste en una grilla de 3 × 3, donde dos jugadores X y O se turnan colocando su marca en una posición vacı́a de la grilla. Cuando un jugador logra colocar tres de sus marcas en lı́nea (vertical, horizontal o diagonal) gana el juego. Luego, cada jugador lo que debe hacer es escoger una posición y marcarla, y esto se debe hacer mientras el juego no haya terminado (i.e. no haya un ganador o el tablero no esté lleno). p1 p2 p3 p4 p5 p6 p7 p8 p9 Figura 6.2: Posiciones del juego Triqui El algoritmo 6.3 muestra el código incompleto del juego. Dicho algoritmo está dividido en tres partes: la inicialización de las variables locales, el juego como tal y la finalización donde se conoce si hubo un ganador o quedó empatado el juego. En la primera parte puede verse que el juego de Triqui se considera como un conjunto de nueve posiciones (p1 . . . p9) como en la figura 6.2. Cada posición, inicialmente con la cadena vacı́a, puede contener el sı́mbolo del jugador (X ó O). Como el juego es una secuencia de turnos, donde cada jugador realiza En el capı́tulo 7 se verá el ciclo for-each. 69 6 Noción de Repetición FT def JuegoTriqui(): "Programa para jugar Triqui" # Inicializa las variables locales jugador = "X" p1 = p2 = p3 = p4 = p5 = p6 = p7 = p8 = p9 = "" DR A # Juego while not TriquiTerminado(p1,p2,p3,p4,p5,p6,p7,p8,p9): jugada = input(jugador + ", escribe tu posicion") if jugada == 1: p1 = jugador elif jugada == 2: p2 = jugador elif jugada == 3: p3 = jugador elif jugada == 4: p4 = jugador elif jugada == 5: p5 = jugador elif jugada == 6: p6 = jugador elif jugada == 7: p7 = jugador elif jugada == 8: p8 = jugador elif jugada == 9: p9 = jugador ImprimirTriqui(p1,p2,p3,p4,p5,p6,p7,p8,p9) if jugador == "X": jugador = "O" else: jugador = "X" # Final del Juego y Ganador ganador = QuienGanoTriqui(p1,p2,p3,p4,p5,p6,p7,p8,p9) if ganador == "X": print("Gano la X") elif ganador == "0": print("Gano la O") else: print("Empatado") Algoritmo 6.3: Juego de Triqui incompleto 70 6.1 Iteración FT la misma acción (decide en qué posición escribe su sı́mbolo) hasta que termine el juego, esto se representa con segunda parte del algoritmo: mientras que el juego no está terminado (la negación de lo que retorna la función TriquiTerminado()) se pide al jugador la posición donde decide colocar su sı́mbolo, se actualiza el tablero cambiando los valores de las variables locales, se imprime (con la función ImprimirTriqui()) y se cambia el turno. En la última parte se determina quién gana el juego con la función QuienGanoTriqui() y se imprime el resultado. ? ? ? Ejemplo 6.2 En un negocio de venta de productos al detal (e.g. una tienda de barrio, un supermercado o restaurante) un cliente paga por un producto y si el dinero que entrega es mayor al precio del producto, la empresa debe devolverle al cliente la cantidad excedente de dinero (el cambio). Dicha cantidad debe ser desglosada en una denominación que sea factible dadas las restricciones de billetes y monedas que existen, por ejemplo, si el excedente es 12500, pero solo se tienen billetes de 10000, entonces no es posible dar el cambio, pero si se tienen billetes de 10000, de 2000 y monedas de 500, entonces el cambio es factible de dar. DR A def desglose(): "Procedimiento que imprime el desglose de una cantidad de dinero" producto = 17500 dinero = input("Con cuanto va a pagar?") cambio = dinero - producto print("Su cambio es:") denominacion = 50000 while cambio > 0: cantidad = cambio/denominacion if cantidad > 0: print(str(cantidad) + "de" + str(denominacion)) cambio = cambio%denominacion denominación = siguienteDenominacion(denominacion) Algoritmo 6.4: Desglose de dinero El algoritmo 6.4 es un procedimiento que pide al cliente el dinero para comprar un producto e imprime la cantidad de billetes y monedas que debe dar como cambio. Se asume un solo producto con un costo fijo de 17500 e igualmente se asume la función siguienteDenominacion() la cual, dado un número que representa una denominación de dinero, retorna la siguiente denominación en orden descendente, i.e. si se le pasa como argumento (un billete de) 50000, retornará (un billete de) 20000 (es decir, la siguiente 71 6 Noción de Repetición denominación), si se le pasa 20000, retornará 10000, y ası́ sucesivamente.2 ? ? ? FT Ejemplo 6.3 Supongamos que queremos imprimir la lista de los números primos del 1 al n. Para hacer esto debemos hacer un ciclo desde 1 hasta n imprimiendo el número i (1 ≤ i ≤ n) en cada iteración. Sin embargo i se debe imprimir solo si es primo y esto lo sabemos si dicho número es divisible solo entre 1 y él mismo, es decir, si el residuo de la división entre i y todos los números entre 2 y i − 1 es diferente de cero. Para esto último debemos hacer otro ciclo. El algoritmo 6.5 muestra el anterior procedimiento. def primos(n): "Procedimiento que imprime los números primos de 1 a n" i = 1 while i < n: esprimo = True j = 2 while j <= i-1: if i%j == 0: esprimo = False j = j + 1 if esprimo == True: print(i) i = i + 1 DR A Ciclos Anidados Existen algunos problemas cuya solución requiere que en el cuerpo del ciclo, es decir, en medio de las operaciones que se quieren repetir, hayan otros ciclos. Estos son comúnmente llamados ciclos anidados. Algoritmo 6.5: Imprime la lista de número primos entre 1 y n ? ? ? Es normal que en algunos problemas el objetivo que se quiere lograr al realizar un ciclo se cumpla antes de terminar el ciclo, es decir, cuando todavı́a la condición es cierta. Para 2 Es de notar que en el algoritmo 6.4 el desglose comienza desde la denominación más alta hasta la mas pequeña. Esta es la forma más simple de hacerlo, pero no es la única. Un estudiante puede afirmar que se podrı́a dar cambio de 12500 solo con monedas de 500 y no como se mencionó arriba. Lo importante aquı́ es saber que el problema de encontrar todos los posibles desgloses de dinero es un problema combinatorio, y por lo tanto, muy difı́cil de resolver. 72 6.1 Iteración Rompimiento FT estos casos resulta ineficiente continuar realizando iteraciones ya que lo que se pretendı́a ya se ha cumplido. Hay dos posibles soluciones para estos problemas: (1) forzar manualmente a que se incumpla la condición del ciclo, ó (2) usar una instrucción de rompimiento. Un rompimiento 3 es una estructura de control que altera la forma de proceder de una iteración, causando que el ciclo más interno en el que se encuentra el rompimiento se termine inmediatamente cuando es ejecutado. De igual manera, en algunos ciclos resulta útil que en algún punto de la iteración el flujo de ejecución se mueva a la siguiente iteración sin continuar más allá en el cuerpo de la actual iteración. Estas estructuras de control son llamadas continuaciones. En el ejemplo 6.4 se muestra una situación común donde el uso de los rompimientos y continuaciones es adecuado. DR A Ejemplo 6.4 Supongamos que necesitamos hacer un interpretador on-line del lenguaje de programación Python. Esta es una tarea bastante laboriosa y requiere un conocimiento muy avanzado, por lo que queremos empezar simplemente quitando los comentarios, ya que estos, por definición, son solo anotaciones y siempre son ignorados por los interpretadores. Para esto, entonces, debemos pedir al usuario que vaya ingresando lı́nea por lı́nea, el código del programa que está desarrollando, y cada vez que el usuario escribe una lı́nea, revisar el primer carácter de dicha lı́nea y si es un sı́mbolo “#” significa que esa lı́nea es un comentario y no debe ser interpretada. El algoritmo 6.6 muestra este procedimiento. Puede notarse que la condición del ciclo es la constante True, esto significa que la condición siempre será cierta y por lo tanto el número de iteraciones está indeterminado. Lo anterior es necesario ya que de antemano no se sabe cuántas lı́neas de código va a ingresar el usuario. Una vez se obtiene la lı́nea de código (un texto), se pregunta si el primer caracter de dicho texto es el sı́mbolo “#” y si la respuesta es afirmativa no hay necesidad de seguir con el resto del cuerpo del ciclo sino que simplemente se vuelve a pedir una lı́nea de código. Si la respuesta es negativa se revisa si lo que hizo fue presionar enter en cuyo caso se revisa si se ha presionado enter dos veces seguidas y se termina la interpretación, es decir, se interrumpe el ciclo. Si no pasa lo anterior, entonces se aplica el procedimiento para interpretar el código que se ha introducido, el cual no será implementado aquı́. 3 ? ? ? Hasta el dı́a de hoy existe una fuerte discusión sobre si usar rompimientos es una buena o mala práctica de programación. Nosotros pensamos que todo depende del uso que se haga de la instrucción, e invitamos a los lectores a desarrollar varios ejemplos usándola y leer el famoso artı́culo de Donald Knuth, “Structured Programming with go to Statements” [2] para sacar sus propias conclusiones. 73 Continuación 6 Noción de Repetición FT def quitarComentarios(): "Procedimiento que recibe lineas codigo en python" "las envia a interpretar, excepto los comentarios" print("Comienzo de la Interpretacion") enter = 1 # Ciclo para capturar todas las lineas while True: # Pide una linea de codigo al usuario linea = raw_input(">>> ") # Verifica si la linea es un comentario if string.find(linea,"#") == 1: continue DR A # Verifica si termina el codigo if linea == "": if enter == 2: print("Fin de la Interpretacion") break else: enter = enter + 1 # Envia la linea a interpretar y continua else: Interpretar(linea) enter = 1 Algoritmo 6.6: Recibe lı́neas de código y quita los comentarios 6.2. Recursión A diferencia de la repetición por medio de iteración, la repetición por medio de recursión no cambia el estado del sistema en cada repetición. Lo que hace es abstraer el código a repetir y luego repite la aplicación de dicha abstracción reduciendo los argumentos en cada repetición hasta cumplir una condición. De esta manera la recursión hace que el problema que se está resolviendo se divida en dos o mas piezas conceptuales (i.e. idea matemática de dividir y conquistar), cada una de ellas siendo una versión mas pequeña del problema original. Los fractales son ejemplos reales de la idea de recursión. En la figura 6.34 puede verse un helecho, el cual está constituido de helechos con una medida más pequeña, que a su vez 4 Imagen tomada de http://www.fresh-paper.com/ 74 6.2 Recursión DR A FT están constituidos de helechos mas pequeños, y ası́ sucesivamente. Figura 6.3: Fractal Como en recursión el conjunto de operaciones que se quiere repetir es abstraı́do en un procedimiento o función, la forma de hacer repetición es aplicando dicho procedimiento o función dentro de la misma abstracción. De allı́ que la noción de recursión sea descrita como una operación definida en términos de sı́ misma. Por ejemplo, encontrar el factorial de un número n es igual a encontrar el factorial del número anterior, n − 1, y multiplicar su resultado por n, es decir, f act(n) = f act(n − 1) × n. Sin embargo, si desarrollamos la recursión del factorial ası́ como está: f act(n) = n × 75 Recursión 6 Noción de Repetición f act(n − 1), podemos ver que es infinita, es decir, nunca para: f act(n) = n × f act(n − 1) f act(n − 1) = (n − 1) × f act(n − 2) FT f act(n − 2) = (n − 2) × f act(n − 3) .. . Para evitar esto es necesario garantizar que en algún punto la recursión se detenga. Ese punto donde la recursión para es llamado el caso base. El caso base de la función factorial es el factorial de cero, ya que esta función no está definida para número negativos. Entonces si decimos que f act(0) = 1, vemos que la función general (también llamada paso de reducción) funciona hasta que llega a este punto, donde se termina la recursión. La implementación completa de la función factorial está en el algoritmo 6.7. DR A def factorial(n): "Funcion que halla el factorial de un numero" if n == 0: return 1 else: return n * factorial(n-1) Algoritmo 6.7: Halla el factorial de un número n Seguir el flujo de ejecución de un algoritmo recursivo no es algo intuitivo ya que es necesario estar muy atento al nivel de recursión en el que se encuentra la ejecución. El nivel de recursión está dado por el número de aplicaciones del paso de reducción: cada vez que se aplica el paso de reducción se aumenta el nivel de recursión hasta que se llega al caso base, una vez allı́ se comienza a devolver en los niveles hasta volver a la primera aplicación (nivel cero). La figura 6.4 muestra gráficamente el flujo de la aplicación de la función factorial del algoritmo 6.7 para n = 5. Cada cuadro de la figura representa una aplicación de la función factorial(); cuando se hace la primera aplicación (nivel 0), n es igual a 5, luego la condición es falsa y el flujo de ejecución se va por el paso de reducción (return 5*factorial(n-1)). Como el resultado de factorial(5-1) no se conoce, esto hace que se haga una nueva aplicación de la función y, por lo tanto, aumente el nivel de recursión (nivel 1). En esta nueva aplicación n es igual a 4, entonces la condición es falsa y, con factorial(4-1) desconocido, de nuevo el flujo se va por el paso de reducción, se hace una nueva aplicación de la función (nivel 2). Esto se repite tres veces mas hasta que en la aplicación de la función n sea igual a cero; en este caso la condición es cierta y entonces el 76 6.2 Recursión n=5 factorial(5) nivel 0 ¿n == 0? Cierto return 1 Falso return 5 * factorial(n-1) n=4 factorial(4) nivel 1 ¿n == 0? Cierto return 1 Falso return 4 * factorial(n-1) retorna 24 n=3 factorial(3) nivel 2 ¿n == 0? Cierto return 1 FT retorna 120 Falso return 3 * factorial(n-1) retorna 6 n=2 DR A factorial(2) nivel 3 ¿n == 0? Cierto return 1 Falso return 2 * factorial(n-1) retorna 2 n=1 factorial(1) nivel 4 ¿n == 0? Cierto return 1 Falso return 1 * factorial(n-1) retorna 1 n=0 ¿n == 0? Cierto return 1 factorial(0) nivel 5 Falso return n * factorial(n-1) retorna 1 Figura 6.4: Flujo de ejecución de la función factorial(5) 77 6 Noción de Repetición FT flujo de ejecución cambia y la función, en el nivel 5 de recursión, retorna 1. Este cambio en el flujo hace que se empiece a conocer el resultado de las aplicaciones de las función: en el nivel 4, factorial(1-1) es igual a 1, de allı́ que la función factorial en el nivel 4 retorne 1 × 1 = 1; este resultado hace que se conozca factorial(2-1) y se retorne 2 × 1 = 2 en el nivel 3; y ası́ sucesivamente hasta volver al nivel 0 donde se retornará el resultado final. A continuación se muestran otros ejemplos de algoritmos recursivos. DR A Ejemplo 6.5 Cuenta la leyenda que en un templo de Hanoi, bajo la cúpula que señala el centro del mundo, hay una bandeja de bronce con tres largas agujas. Al crear el mundo, Dios colocó en una de ellas sesenta y cuatro discos de oro, cada uno de ellos más pequeño que el anterior hasta llegar al de la cima. Dı́a y noche, incesantemente, los monjes transfieren discos de una aguja a otra siguiendo las inmutables leyes de Dios, que dicen que debe moverse cada vez el disco superior de los ensartados en una aguja a otra y que bajo él no puede haber un disco de menor radio. Cuando los sesenta y cuatro discos pasen de la primera aguja a otra, todos los creyentes se convertirán en polvo y el mundo desaparecerá con un estallido.5 El objetivo es modelar computacionalmente este problema. La situación inicial del problema para cuatro discos está en la figura 6.5. Figura 6.5: Torres de Hanoi - Configuración inicial Y se desea pasar a la situación final de la figura 6.6. Figura 6.6: Torres de Hanoi - Configuración final 5 La leyenda fue inventada por De Parville en 1884, en “Mathematical Recreations and Essays”, un libro de pasatiempos matemáticos. La ambientación era diferente: el templo estaba en Benarés y el dios era Brahma. 78 6.2 Recursión FT Aunque sólo se puede tocar el disco superior de un montón, se debe pensar en el disco del fondo. Ese disco debe pasar de la primera aguja a la tercera, y para que eso sea posible, se debe alcanzar la configuración de la figura 6.7. Figura 6.7: Torres de Hanoi - Configuración intermedia 1 DR A Sólo en ese caso se puede pasar el disco más grande a la tercera aguja, es decir, alcanzar la configuración de la figura 6.8. Figura 6.8: Torres de Hanoi - Configuración intermedia 2 Está claro que el disco más grande no se va a mover ya de esa aguja, pues es su destino final. La pregunta que surge entonces es: ¿Cómo se han pasado los tres discos superiores a la segunda aguja? Pues simple, pasar una pila de tres discos de una aguja a otra no es más que el problema de las torres de Hanoi para una torre de tres discos. De este punto lo que falta por hacer es mover la pila de tres discos de la segunda aguja a la tercera, y eso, nuevamente, es el problema de la torres de Hanoi para tres discos. Ahı́ aparece la recursión. Resolver el problema de las torres de Hanoi con n discos requiere entonces: resolver el problema de las torres de Hanoi con n − 1 discos, aunque pasándolos de la aguja inicial a la aguja libre; mover el último disco de la aguja en que estaba inicialmente a la aguja de destino; y resolver el problema de las torres de Hanoi con n − 1 discos de la aguja libre a la aguja final. Hay un caso trivial o caso base: el problema de la torres de Hanoi para un solo disco (basta con mover el disco de la aguja en la que esté insertado a la aguja final). 79 6 Noción de Repetición Los parámetros que necesita el procedimiento son el número de discos que vamos a mover, la aguja origen y la aguja destino. Se identificará cada aguja con un número. El procedimiento se encuentra en el algoritmo 6.8. FT def torresHanoi(n, inicial, final): "Procedimiento para resolver el problema de las torres de Haoi" if n == 1: print("Mover el disco superior de la aguja " + str(inicial)) print("a la aguja " + str(final)) else: # Determinar cual es la aguja libre if inicial != 1 and final != 1: libre = 1 else: if inicial != 2 and final != 2: libre = 2 else: libre = 3 # Primer subproblema: mover n-1 discos de inicial a libre torresHanoi(n-1, inicial, libre) DR A print("Mover el disco superior de la aguja " + str(inicial)) print("a la aguja " + str(final)) # Segundo subproblema: mover n-1 discos de libre a final torresHanoi(n-1, libre, final) Algoritmo 6.8: Imprime el proceso para solucionar las torres de hanoi de n discos Ahora, para resolver el problema con n = 4 se hace el llamado al procedimiento torresHanoi(4,1,3). El programa imprimirá las órdenes correspondientes a los movimientos de la figura 6.9. Interfaz Gráfica ? ? ? La solución para el problema de las torres de Hanoi está bien, pero no muestra gráficamente el proceso, es decir, solo describe textualmente los pasos para resolver el problema. Para los siguientes problemas vamos a introducir un sistema para desarrollar interfaces gráficas llamado Tkinter6 . Una interfaz gráfica es un espacio de interacción entre una persona y un programa con imágenes en vez de comandos de texto. Algunas operaciones del 6 El sistema Tkinter (“Tk Interface”) es la interfaz estándar de Python para el sistema gráfico Tk GUI toolkit. 80 6.2 Recursión 2) 3) 4) 5) 6) 7) 8) 9) 10) 11) 12) 13) 14) 15) 16) FT 1) Figura 6.9: Torres de Hanoi - Proceso completo para n = 4 sistema de interfaces gráficas Tkinter pueden verse en el cuadro 6.2. Acción Crea y retorna la ventana principal de un programa Crea y retorna un nuevo botón en la ventana v con texto t y que realiza la acción a cuando se oprime Crea y retorna una nueva etiqueta en la ventana v con texto t Crea y retorna un nuevo campo de texto en la ventana v para un número de caracteres c Crea y retorna un nuevo campo de dibujo en la ventana v de ancho w y alto h Toma el widget y lo hace visible Hace que la ventana se quede esperando los eventos que puedan ocurrir hasta que se cierre la ventana DR A Operación Tk() Button(v,text=t,command=a) Label(v,text=t) Text(v,width=c) Canvas(v,width=w,height=h) pack() mainloop() Cuadro 6.2: Operaciones básicas del sistema Tkinter Ejemplo 6.6 El Triángulo de Sierpinski es un fractal creado por Waclaw Sierpiński en 1915. El concepto del Triángulo de Sierpinski es muy simple: Se toma un triángulo (usualmente, pero no necesariamente, equilátero) como la primera imagen de la figura 6.10; se conectan los puntos medios de cada lado para formar cuatro triángulos separados y se corta el triángulo del centro, como en la segunda imagen de la figura; para cada uno de los tres triángulos 81 6 Noción de Repetición FT restantes se realiza el mismo proceso como en la tercera imagen de la figura; y se repite el proceso indefinidamente construyendo la imagen final de la figura. ... Figura 6.10: Triángulo de Sierpinski DR A La construcción de este fractal muestra claramente una recursión que puede ser implementada como la función sierpinski() del algoritmo 6.9. La idea de este algoritmo se centra en que el nivel de recursión en el que se encuentre el proceso determina el número de triángulos a dibujar: si estamos en un nivel inicial (cero) solo se dibuja un triángulo, en el nivel uno se dibujan tres triángulos, en el nivel dos se dibuja nueve y ası́ sucesivamente. Lo importante a notar es que cada triángulo que se va a dibujar tiene una posición particular y un tamaño especı́fico, y entre más alto sea el nivel, la posición cambiará de manera monótona y dicho tamaño es más pequeño (el tamaño de un lado de cualquiera de los tres triángulos es la mitad del tamaño de un lado del triángulo superior). Los parámetros del procedimiento sierpinski() incluyen el canvas y las coordenadas x y y donde será dibujado el triángulo, el tama~ no del triángulo a dibujar y el nivel en el que se encuentra la recursión. En el nivel cero simplemente se dibuja un triángulo usando el procedimiento para crear polı́gonos (véase el cuadro 6.3); en cualquier otro nivel se realizan tres llamados recursivos, correspondientes a los tres triángulos que se deben dibujar dentro del triángulo superior. El procedimiento gui-sierpinski() en el algoritmo 6.9 recibe como parámetro el nivel de profundidad del Triángulo de Sierpinski. Su proceso es simple: primero crea una ventana y se la asigna a la variable v; entonces dentro de dicha ventana crea un canvas (asignado a la variable miCanvas) de tamaño 500×500 donde se dibujará el Triángulo de Sierpinski y se hace visible con la operación miCanvas.pack(); luego se hace el llamado al procedimiento para construir y dibujar el triángulo enviándo como argumento el canvas recién creado, la posición dentro del canvas (100,400), el tamaño del triángulo más grande y el nivel de profundidad; finalmente se aplica la operación para dejar a la ventana v lista para cualquier 82 6.3 Ejercicios evento. FT def sierpinski(canvas, x, y, tamano, nivel): "Procedimiento que dibuja el triangulo de sierpinski" if (nivel == 0): canvas.create_polygon(x, y, x+tamano, y, x+tamano/2, y-tamano*math.sqrt(3)/2, outline="black", fill="orange") else: sierpinski(canvas, x, y, tamano/2, nivel-1) sierpinski(canvas, x+tamano/2, y, tamano/2, nivel-1) sierpinski(canvas, x+tamano/4, y-tamano*math.sqrt(3)/4, tamano/2, nivel-1) DR A def gui-sierpinski(nivel): "Procedimiento que dibuja la ventana del triangulo de sierpinski" v = Tkinter.Tk() miCanvas = Tkinter.Canvas(v, width=500, height=500) miCanvas.pack() sierpinski(miCanvas, 100, 400, 300, nivel) root.mainloop() Algoritmo 6.9: Dibuja el triángulo de Sierpinski Puede intuirse entonces que si se quisiera crear un Triángulo de Sierpinski con una profundidad 4, se deberı́a hacer la aplicación del procedimiento gui-sierpinski(5). 6.3. ? ? ? Ejercicios 6.1 Complete el juego de Triqui con las operaciones que no están implementadas: TriquiTerminado() recibe como parámetros las nueve posiciones de un tablero de Triqui y retorna True si alguno de los dos jugadores ganó ó si el tablero ya está completo y ninguno de los jugadores ganó, de lo contrario retorna False. ImprimirTriqui() recibe como parámetros las nueve posiciones de un tablero de Triqui e imprime el tablero de Triqui de una manera visualmente correcta (i.e. como se ve normalmente el juego). 83 6 Noción de Repetición Operación create arc(x0,y0,x1,y1) create bitmap(x,y,bitmap=b) create oval(x0,y0,x1,y1) FT create image(x,y,image=i) create line(x0,y0,x1,y1,...,xn,yn) create polygon(x0,y0,x1,y1,...,xn,yn) create rectangle(x0,y0,x1,y1) DR A create text(x,y,text=t) Acción Construye un arco desde el punto (x0,y0) hasta el punto (x1,y1) Muestra el mapa de bits b en el punto (x,y) Muestra la imagen i en el punto (x,y) Construye una lı́nea que va por la serie de puntos (x0,y0), (x1,y1), . . . , (xn,yn) Construye el óvalo que calza en el rectángulo definido por los puntos (x0,y0) y (x1,y1) Construye un polı́gono cuya geometrı́a es especificada por los vértices definidos con los puntos (x0,y0), (x1,y1), . . . , (xn,yn) Construye el rectángulo definido por los puntos (x0,y0) y (x1,y1) Muestra el texto t en el punto (x,y) Cuadro 6.3: Operaciones para dibujar en un Canvas de Tkinter QuienGanoTriqui() recibe como parámetros las nueve posiciones de un tablero de Triqui y retorna una cadena de texto solo con el caracter correspondiente al ganador del juego (‘‘X’’ o ‘‘O’’). Si no hubo ganador, la función debe retornar un sı́mbolo diferente. 6.2 El juego de Triqui del algoritmo 6.3 tiene algunos problemas, por ejemplo un jugador puede seleccionar una posición donde ya habı́a un sı́mbolo, ó si un jugador no ingresa un número entre 1 y 9 (las posiciones del tablero), pierde el turno. Corrija estos y otros problemas que tiene el juego. 6.3 El algoritmo 6.4 tiene dos problemas, (1) si se paga con una cantidad menor al costo del producto, no se da cambio, pero tampoco se especifica que le falta dinero y (2) la función siguienteDenominacion() no está implementada. Solucione estos problemas. 6.4 Asuma que en el problema del desglose de dinero se tiene una cantidad limitada de billetes y monedas. Modifique el algoritmo 6.4 para que se tenga en cuenta el número 84 6.3 Ejercicios de billetes y monedas y, llegado el caso, imprima que no es posible dar el cambio debido a que no se cuenta con las denominaciones necesarias. FT 6.5 Asuma que el algoritmo solución al problema del desglose va a ser usado en un restaurante. Complete el algoritmo para que muestre un menú de comida, le pida al usuario seleccionar la comida que quiera (pueden ser varios platos), luego le pase la cuenta y muestre el cambio dado lo que el cliente dio de dinero. 6.6 Mejore el algoritmo del punto anterior (6.5) haciendo que haya un número n de clientes, los cuales ven el menú, escogen la comida y pagan. Este nuevo algoritmo debe retornar la cantidad de dinero que pagaron todos los clientes del restaurante. 6.7 Muestre el flujo de ejecución de la aplicación del procedimiento torresHanoi(5,1,3). 6.8 Pruebe la función torresHanoi() haciendo el llamado con los 64 discos de la leyenda, es decir torresHanoi(64,1,3). ¿Cuántos movimientos deben hacer los monjes? DR A 6.9 De manera general ¿cuántos movimientos son necesarios para resolver el problema con n discos? Para responder esta pregunta escriba una función NumMovimientosHanoi() que reciba un número y devuelva el número de movimientos necesarios para resolver el problema de las torres de Hanoi con ese número de discos (se debe utilizar la función torresHanoi()). 6.10 Modifique el algoritmo 6.8 para mostrar gráficamente el proceso de solución de las Torres de Hanoi. Puede usar el Mundo de la Tortuga o el sistema Tkinter. 85 DR A FT 6 Noción de Repetición 86 FT 7 Noción de Abstracción de Datos En el capı́tulo 4 se describió la abstracción como el proceso en el que datos y algoritmos son agrupados dentro de una categorı́a que los conecta. En este capı́tulo veremos la abstracción que involucra datos. 7.1. Listas DR A La abstracción de datos, al igual que la abstracción de control, permite “ocultar” información de manera que se presenten solo los datos relevantes a un contexto particular. De esta forma se hace visible al usuario solo una interfaz de los datos mientras que los datos como tal se vuelven privados y son accedidos solo a través de dicha interfaz. Las interfaces, en general, son herramientas computacionales que sirven como punto de interacción entre componentes, los cuales pueden ser de bajo nivel como los sistemas operativos que permiten interactuar piezas de hardware con programas, o de más alto nivel como gráficas (que brevemente se describieron en el capı́tulo anterior) y estructuras de datos. Una estructura de datos es un grupo de piezas de información reunidas bajo un mismo nombre. Su propósito es organizar eficientemente dicha información de manera que operaciones como búsqueda y ordenamiento se realicen de la mejor manera posible y se conserven las propiedades matemáticas que puedan existir en esas operaciones. Las estructuras de datos comprenden, entre muchos otras, arreglos, registros, conjuntos, pilas, colas, árboles, grafos y tablas hash, pero quizás las más usadas sean las listas. Una lista es una secuencia de datos llamados elementos los cuales pueden ser accedidos directamente por medio de un ı́ndice. Para una lista X, el primer elemento tiene ı́ndice cero, es decir es el elemento X[0], el segundo elemento tiene ı́ndice uno, o sea que es el X[1], y ası́ sucesivamente hasta el último elemento cuyo ı́ndice es igual al tamaño de la lista menos uno, es decir que si n es el número de elementos de la lista, el último elemento será X[n − 1]. Para ejemplificar la noción de lista y la necesidad de contar con ella, consideremos un juego simple como el ahorcado. Este juego consiste en dos jugadores, uno de ellos piensa en una palabra y dibuja lı́neas que representan las letras de dicha palabra, y el otro jugador debe descifrar cuál es esa palabra secreta. En cada turno el primer jugador dice una letra y si esta letra pertenece a la palabra secreta el otro jugador escribirá la letra sobre la correspondiente lı́nea, pero si la letra no pertenece a la palabra el segundo jugador 87 Interfaz Estructura de Datos Lista 7 Noción de Abstracción de Datos dibujará una parte de un hombre ahorcado. Si el primer jugador puede adivinar la palabra secreta antes que se dibuje por completo el ahorcado, este gana, de lo contrario pierde. La figura 7.1 muestra un juego de ahorcado: el primer jugador ha acertado 5 letras y fallado igual número de veces, por esto el dibujo del ahorcado está muy cerca de terminarse. FT Letras Falladas: U, S, E, T, L, B, V, K, Q RA A DR A PRO ON Figura 7.1: Juego Ahorcado Es de notar que ahorcado es posible convertirlo en un juego de un jugador contra el computador ya que el segundo jugador que se nombró arriba solo se limita a interactuar con las jugadas del primer jugador, es decir, solo selecciona una palabra y, cada vez que el primer jugador dice una letra, verifica dicha letra y realiza la acción correspondiente: escribir la letra en el espacio adecuado ó dibujar una parte del cuerpo del ahorcado. La palabra seleccionada por el segundo jugador debe ser parte de un conjunto de palabras que conoce, lo suficientemente grande para que se pueda jugar una y otra vez cambiando la palabra secreta y que ası́ el primer jugador no sepa cuál es de manera anticipada. Si usáramos una variable para cada palabra, se tendrı́a una cantidad absurda e inmanejable de variables, y si a eso le adicionamos las variables necesarias para conocer cuáles son las letras que ya se han probado, se complica aún mas el algoritmo. Por esto son indispensables unas estructuras de datos que reúnan y organicen las palabras y las letras sin necesidad de crear dicha cantidad de variables. El procedimiento para jugar ahorcado se encuentra en el algoritmo 7.1. Se cuenta con tres listas listaPalabras, letrasAcertadas y letrasFalladas. El propósito de listaPalabras es intuitivo (aunque deberı́a tener muchos mas elementos para hacer el juego más diver- 88 7.1 Listas # Dibuja la ventana inicial ventana = saludoInicial() FT def juegoAhorcado(): "Procedimiento para un juego de ahorcado" listaPalabras = ["abstraccion", "ciclo", "declaracion", "estado", "flujo", "implementacion", "grafica", "lista", "modelo", "parametro", "recursion", "sistema"] indice = random.randint(0, len(listaPalabras)-1) palabraSecreta = listaPalabras[indice] letrasAcertadas = [] letrasFalladas = [] juegoAcabado = False # Ciclo del juego while not juegoAcabado: # Dibuja/Actualiza el tablero dibujarTablero(ventana, palabraSecreta, letrasAcertadas, letrasFalladas) DR A # Pide una letra al jugador letra = pedirLetra(ventana) # Procede de acuerdo a la letra dada letrasAcertadas.append(letra) if letra in palabraSecreta: # Chequea si encontro todas las letras num = 0 completoPalabra = True while num < len(palabraSecreta): if not palabraSecreta[num] in letrasAcertadas: completoPalabra = False break num = num + 1 if completoPalabra: acabarJuego(ventana, True) juegoAcabado = True else: # Chequea si se ha ahorcado if len(letrasFalladas) == 10: acabarJuego(ventana, False) juegoAcabado = True Algoritmo 7.1: Juego ahorcado 89 7 Noción de Abstracción de Datos Acción Agrega el elemento x al final de la lista Inserta el elemento x en la posición i de la lista Borra el primer elemento de la lista que tenga valor x Retorna y borra el elemento de la lista cuya posición es i Retorna el tamaño de la lista l Retorna un booleano que indica si el elemento x se encuentra en la lista l Retorna una lista con los números del cero al n-1 como elementos DR A Operación append(x) insert(i,x) remove(x) pop(i) len(l) x in l FT tido). Las otras dos listas sirven para llevar un registro de las letras que el jugador va diciendo, en una van las letras que efectivamente se encuentran en la palabra secreta y en la otra aquellas que no. Al inicio del juego se toma una palabra de manera aleatoria de la lista listaPalabras y se asigna a la variable palabraSecreta. Luego se da un saludo inicial (en teorı́a se debe mostrar una splash screen 1 ) con un procedimiento no implementado aquı́ llamado saludoInicial(), y después arranca el ciclo del juego: primero dibuja o actualiza el tablero con el procedimiento dibujarTablero(), el cual debe mostrar una imagen como la figura 7.1 dada la palabra secreta y las listas de registro de letras; acto seguido le pide una letra al jugador con la función pedirLetra(); por último, de acuerdo a la letra dada se realiza el proceso correspondiente, es decir, agregar la letra a la lista letrasAcertadas o a la lista letrasFalladas y, si es el caso, terminar el juego ya sea porque encontró todas las letras de la palabra secreta o porque falló diez letras y se terminó de dibujar el ahorcado. En el algoritmo 7.1 puede verse el uso de algunas operaciones para manejar listas como append y in. Estas y otras operaciones básicas del sistema de listas se pueden ver en el cuadro 7.1. range(n) Cuadro 7.1: Operaciones básicas del sistema de listas Las operaciones saludoInicial(), dibujarTablero(), pedirLetra() y acabarJuego() aplicadas en el algoritmo 7.1 realizan procesos gráficos, por lo que pueden ser implementadas usando el sistema TkInter. Sin embargo, dado que ahorcado es un juego, podemos usar un sistema especializado en la construcción de juegos. De allı́ que vamos a introducir pygame, un sistema para el desarrollo de juegos. 1 Una splash screen o pantalla de inicio, es una imagen que se muestra al inicio de un programa para presentar el logo y/o tı́tulo del programa, y además para notificar al usuario que el programa se está cargando. 90 7.1 Listas FT Pygame2 es una librerı́a diseñada para construir software multimedia. Cuenta con operaciones para manejo de video y sonido que permiten desarrollar video juegos. De igual manera, pygame tiene funciones para manejar gráficos 2D, música, entradas, fuentes, detección de colisiones, gráficos 3D con OpenGL, joysticks, pelı́culas, etc. Las operaciones básicas y algunas constantes de pygame se pueden ver en el cuadro 7.2. Operación init() quit() display.set mode(r) display.update() event.get() mouse.get pos() DR A font.SysFont(f,t) draw.rect(s,c,r,w) Acción Inicializa todo el sistema pygame Termina el sistema Inicializa una ventana para trabajar y retorna un display (dispositivo de salida). Su parámetro r representa la resolución de la ventana (una pareja (w, h) donde w es el ancho y h es el alto de la ventana) Actualiza la ventana Retorna una lista con los eventos que recibe el sistema Retorna una pareja, i.e. lista de dos elementos, con la posición (x, y) de la ventana donde se hizo clic con el mouse Retorna la fuente con tipo f y tamaño t para ser usada Dibuja un rectángulo en la superficie s con color c, anchura del borde w (w=0 significa que el rectángulo estará lleno del color c) y cuyos puntos se encuentran en las posiciones dadas por los elementos de la lista r Carga y retorna la imagen contenida en el archivo f Toma la imagen i y la escala a las nuevas dimensiones (w, h) Carga el archivo de sonido w Interpreta el archivo de sonido cargado previamente image.load(f) transform.scale(i,(w,h)) mixer.music.load(w) mixer.music.play() Constantes QUIT KEYUP KEYDOWN MOUSEMOTION MOUSEBUTTONUP MOUSEBUTTONDOWN K RETURN K SPACE K A3 Cuadro 7.2: Operaciones básicas y algunas constantes del sistema Pygame 2 3 http://www.pygame.org/ Las constantes para todos los demás botones del teclado tienen la misma forma: K <tecla>. 91 7 Noción de Abstracción de Datos A continuación, en el ejemplo 7.1, se desarrollan las funciones y procedimientos no implementados del procedimiento juegoAhorcado() en el algoritmo 7.1, usando operaciones del sistema pygame. FT Ejemplo 7.1 DR A La función saludoInicial() (algoritmo 7.2) comienza inicializando el sistema pygame y construyendo la ventana principal del programa (que puede verse en la figura 7.2). Después de esto selecciona la fuente con la que se escribirá en esta función, que en este caso es “Arial” de tamaño 30. Acto seguido asigna las variables que contienen las frases que se van a imprimir en la pantalla. Dichas frases deben ser renderizadas, es decir, se deben generar imágenes a partir de unos modelos, que en este caso son texto. La renderización se produce aplicando la operación render() del módulo de fuentes del sistema (las operaciones de este módulo pueden verse en el cuadro 7.3). Luego comienza un ciclo infinito en el cual primero se borra completamente la pantalla, se se muestran las frases renderizadas y se espera a que el usuario realice un evento. Si el evento resulta ser oprimir una tecla y esa tecla es RETURN o ENTER, entonces el ciclo se rompe con la instrucción break y se termina la función retornando la ventana principal del programa. Figura 7.2: Saludo Inicial en el juego Ahorcado Una ventana en el sistema pygame es una superficie (surface en inglés) que sirve para almacenar imágenes. Dichas imágenes deben ser transferidas a la superficie como mapas de bits (de allı́ que el texto deba ser renderizado para poder ser mostrado, como se mencionó anteriormente). La posición donde se quiere transferir la imagen es una coordenada (x, y) donde los valores de x y y van de (0, 0) (la esquina superior izquierda) a (w, h), la 92 7.1 Listas esquina inferior derecha. En el cuadro 7.4 pueden detallarse dos operaciones de superficies en pygame. FT def saludoInicial(): "Funcion que inicializa el sistema grafico" "y muestra la ventana inicial" # Inicializa el sistema pygame.init() ventana = pygame.display.set_mode((640, 480)) # Selecciona el tipo de fuente fuente = pygame.font.SysFont("arial", 30) # Construye las frases titulo = fuente.render("JUEGO AHORCADO", True, (0,0,0)) subtitulo = fuente.render("Presione enter para empezar", True,(0,0,0)) DR A # Ciclo para que el usuario lea el saludo de inicio presionaEnter = False while True: # Muestra la informacion ventana.fill((255,255,255)) ventana.blit(titulo,(180,180)) ventana.blit(subtitulo,(140,240)) # Maneja el evento de presionar una tecla for e in pygame.event.get(): if e.type == pygame.KEYDOWN: if e.key == pygame.K_RETURN: presionaEnter = True if presionaEnter: break # Actualiza el sistema pygame.display.update() return ventana Algoritmo 7.2: Pantalla de inicio del juego ahorcado Por otro lado, los eventos que ocurren en un programa de pygame tienen un tipo y una llave (o nombre). El tipo de los eventos identifica la acción que el usuario realiza, por ejemplo, el tipo QUIT para salir, KEYDOWN y KEYUP para presionar o soltar un botón del teclado, y MOUSEMOTION, MOUSEBUTTONUP y MOUSEBUTTONDOWN para mover el mouse, 93 7 Noción de Abstracción de Datos size(t) set underline(b) set bold(b) set italic(b) Acción Crea una nueva superficie donde se imprime el texto t con color c (una tipleta que representa el color en formato RGB). El argumento a es un booleano que especifica si el texto llevará antialias Retorna las dimensiones necesarias (ancho,alto) para imprimir el texto t De acuerdo al argumento booleano b, subraya el texto a imprimir De acuerdo al argumento booleano b, pone en negrilla el texto a imprimir De acuerdo al argumento booleano b, pone el texto a imprimir en itálica FT Operación render(t,a,c) Cuadro 7.3: Operaciones básicas del módulo de fuentes en el sistema Pygame Acción Dibuja la imagen s en la posición d de la superficie Llena la superficie con el color c DR A Operación blit(s,d) fill(c) Cuadro 7.4: Operaciones básicas del módulo de superficies en el sistema Pygame Ciclo for-each presionar un botón o soltarlo. La llave del tipo determina exactamente la fuente del evento, por ejemplo, K RETURN para la tecla RETURN o ENTER del teclado, K A para la tecla A, y ası́ sucesivamente. Lo eventos que van sucediendo son almacenados temporalmente en una lista, la cual es retornada por la función pygame.event.get(). Esta lista puede recorrerse, es decir, conocer sus elementos, usando un ciclo while, sin embargo, cómo se dijo en el capı́tulo 6, existe otro tipo de ciclo que realiza iteraciones sobre los elementos de una lista, llamado ciclo for-each . Este tipo de ciclo tiene dos particularidades: (1) en cada iteración una variable definida en el ciclo toma el valor de un elemento de la lista en el orden en que se encuentran los elementos en la lista, y (2) no se tienen que manejar los ı́ndices de los elementos de la lista. Estas caracterı́sticas hacen valioso este tipo de ciclo ya que el programador no se preocupa por los ı́ndices de la lista los cuales pueden llevar fácilmente a un error en el programa (e.g. tratar de acceder un elemento inexistente). El procedimiento dibujarTablero() que se muestra en el algoritmo 7.3, tiene como parámetros la ventana donde se dibujará, la palabra secreta y las listas de letras acertadas y letras falladas. Como el tamaño de las letras que se van encontrando de la palabra secreta y el tamaño de las letras falladas son distintos, primero se crean dos fuentes. Luego se 94 7.1 Listas FT def dibujarTablero(ventana, palabraSecreta, letrasAcertadas, letrasFalladas): "Procedimiento que dibuja/actualiza el tablero del juego" # Selecciona los tipos de fuente fuente1 = pygame.font.SysFont("arial", 35) fuente2 = pygame.font.SysFont("arial", 25) # Crea la frase que va a mostrar dadas las letras acertadas palabraAMostrar = "" for letra in palabraSecreta: if letra in letrasAcertadas: palabraAMostrar = palabraAMostrar + \ string.capitalize(letra) + " " else: palabraAMostrar = palabraAMostrar + "_" + " " # Borra completamente la ventana ventana.fill((255,255,255)) DR A # Dibuja el estado del ahorcado dadas las letras falladas if len(letrasFalladas) > 0: ventana.blit(pygame.image.load(os.path.join("ahorcado" + \ str(len(letrasFalladas)) + \ ".jpg")),(80,90)) # Muestra la frase "Letras Falladas" ventana.blit(fuente2.render("Letras Falladas:",True,(0,0,0)), \ (330,100)) # Muestra las letras falladas i=0 if letra in letrasFalladas: ventana.blit(fuente2.render(string.capitalize(letra),True, \ (0,0,0)),(330+i,150)) i = i + 30 # Dibuja la frase actual ventana.blit(fuente1.render(palabraAMostrar,True,(0,0,0)), \ (100,380)) # Actualiza el sistema pygame.display.update() Algoritmo 7.3: Dibuja/Actualiza la pantalla principal del juego ahorcado 95 7 Noción de Abstracción de Datos (a) (b) (c) (d) FT construye la palabra que se va a mostrar recorriendo cada una de las letras de la palabra secreta y adicionándolas (en mayúscula) a una cadena de caracteres si la letra se encuentra en la lista de letras acertadas. Si la letra no se encuentra en la lista entonces se adiciona una lı́nea de subrayado. Acto seguido se limpia la pantalla y se dibuja el estado del ahorcado. Este estado es dibujado cargando una imagen previamente hecha cuyo nombre es ahorcadoX.jpg donde X es un número entre 1 y 10 (estas imágenes, que pueden verse en la figura 7.3, se encuentran como archivos en el sistema operativo y son cargadas en el programa por medio del módulo de rutas de archivos del sistema os cuyas operaciones básicas pueden verse en el cuadro 7.54 ). Después se muestran las letras fallas y el estado actual de la palabra secreta. Por último se actualiza el sistema. (e) (f) (g) (h) (i) (j) DR A Figura 7.3: Etapas del ahorcado Operación path.exists(p) path.basename(p) path.dirname(p) path.join(p1,p2,p3,...) Acción Retorna true si la ruta p existe, false de lo contrario Retorna el nombre base de la ruta p (e.g. si el sistema operativo actual es linux y p es /usr/local/bin/programa entonces el resultado de aplicar esta función es programa) Retorna el nombre del directorio de la ruta p (e.g. si el sistema operativo actual es windows y p es C:\windows\temp\programa entonces el resultado de aplicar esta función es C:\windows\temp) Junta las rutas de archivos p1,p2,... de manera adecuada (e.g. si el sistema operativo actual es windows, p1 es C: y p2 es temp, entonces el resultado de aplicar esta función es C:\temp) Cuadro 7.5: Operaciones básicas del módulo de rutas de archivos del sistema operativo 4 En el capı́tulo ?? se profundizará un poco en el sistema os y se usará el módulo de manejo de archivos y directorios del sistema operativo. 96 7.1 Listas FT Las letras que el jugador quiere probar en el juego las solicita simplemente presionando la tecla correspondiente. En el algoritmo 7.4 se muestra el proceso para hacer esto. Se define un ciclo infinito para esperar indefinidamente hasta que el usuario realice algún evento. Si el evento realizado es presionar una tecla, se verifica si esa tecla corresponde a una letra y si esto es cierto entonces se retorna la letra como una cadena de caracteres. De lo contrario, es decir si la tecla no es una letra, hacemos la aplicación de la operación para interpretar un sonido, que en este caso es un archivo de tipo wav que contiene un beep. DR A def pedirLetra(ventana): "Funcion que retorna la letra que el usuario ha ingresado" # Ciclo para que el usuario ingrese la letra while True: for e in pygame.event.get(): if e.type == pygame.KEYDOWN: # Si presiona una tecla que corresponde a una letra if e.key == pygame.K_a: return "a" if e.key == pygame.K_b: return "b" if e.key == pygame.K_c: return "c" # ... aqui van los condicionales para las demas letras if e.key == pygame.K_x: return "x" if e.key == pygame.K_y: return "y" if e.key == pygame.K_z: return "z" # Si presiona una tecla que no es una letra suena beep else: pygame.mixer.music.load("beep.wav") pygame.mixer.music.play() # Actualiza el sistema pygame.display.update() Algoritmo 7.4: Retorna la letra que el usuario presione en el juego ahorcado Finalmente el algoritmo 7.5 para acabar el juego simplemente dibuja dos rectángulos rellenos en la ventana y escribe en ellos las frases de ganador o perdedor. Mientras tanto espera a que el jugador presione alguna tecla y cuando esto ocurre termina el sistema. Un 97 7 Noción de Abstracción de Datos ejemplo del juego con los dos posibles finales puede verse en la figura 7.4. FT def acabarJuego(ventana, resultado): "Procedimiento que termina el juego ahorcado" # Selecciona la fuente y construye las frases finales fuente = pygame.font.SysFont("arial", 40) gano = fuente.render("Gano :-)", True, (255,0,0)) perdio = fuente.render("Perdio :-(", True, (255,0,0)) DR A # Ciclo para que el usuario vea el resultado y termine acabo = False while True: # Si gano if resultado == True: pygame.draw.rect(ventana,(255,0,0),(335,240,215,85),5) ventana.blit(gano,(355,260)) # Si perdio else: pygame.draw.rect(ventana,(255,0,0),(335,240,240,85),5) ventana.blit(perdio,(355,260)) # Revisa los eventos que suceden for e in pygame.event.get(): if e.type == KEYDOWN: acabo = False # Actualiza el sistema pygame.display.update() # Si presiono una tecla termina el sistema if acabo: pygame.quit() break Algoritmo 7.5: Termina el juego ahorcado ? ? ? Ejemplo 7.2 Supongamos que queremos crear un juego que simule las elecciones para presidente. El juego debe recibir el voto de cada uno de los n ciudadanos del paı́s y luego contar el número de votos para cada candidato de manera que se sepa quién es el presidente electo. La tarjeta o tarjetón electoral es un documento donde se encuentra la lista de candidatos y el voto el blanco. Para votar, un ciudadano simplemente marca alguno de los candidatos o la casilla del voto en blanco. Cuando se realiza el escrutinio, se cuentan cuántos votos se marcaron para cada candidato y el que haya sacado la mayor cantidad es el ganador. La información anterior provee los datos necesarios para construir el algoritmo 7.6 el 98 (a) Ganó el juego ahorcado FT 7.1 Listas (b) Perdió el juego ahorcado Figura 7.4: Estados finales del juego ahorcado DR A cual simula las elecciones. Se tienen dos listas: la lista de los nombres de los candidatos y la lista de los votos. En la lista de votos se irán contabilizando los votos que cada candidato obtenga. Las listas listaNombres y listaVotos son correspondientes, es decir, el candidato 1, el cual es el elemento 1 de listaNombres, tiene el número de votos del primer elemento de listaVotos, el número de votos del candidato 2 (elemento 2 de la lista) es el número que se encuentra en la segunda posición de la lista de votos, y ası́ sucesivamente. Es de notarse que en la lista de candidatos se tiene como último elemento “voto nulo”, esto es necesario para contabilizar este tipo de voto aunque no sea un candidato como tal. La posición donde se encuentra el voto nulo se almacena en la variable posVotoNulo para su uso en la contabilidad de dicho voto. La ventana se crea en la función comienzoElecciones(), la cual se deja como ejercicio para el lector. El ciclo para hacer las votaciones hace n iteraciones, siendo n un parámetro del procedimiento. En cada iteración se obtiene el nombre del candidato por el que se vota mediante la aplicación de la función sufragar(). Este nombre retornado por sufragar() se busca en listaNombres y si existe se le agrega un voto a su elemento correspondiente. Si el nombre no existe en la lista, entonces se contabiliza en el último elemento de la lista de votos, haciendo entender que fue un voto nulo. Una vez termina el ciclo, se debe ordenar la lista de acuerdo a los votos que recibieron cada uno de los candidatos. Esto se hace en la función ordenar(). Luego de esta aplicación, en la primera posición de listaNombres queda el nombre del ganador de las elecciones, el cual es mostrado en el procedimiento mostrarGanador(), que también se deja como 99 7 Noción de Abstracción de Datos ejercicio para el lector. FT def elecciones(n): "Procedimiento para simular unas elecciones de presidente" listaNombres = ["candidato1", "candidato2", "candidato3", "candidato4", "candidato5", "en blanco", "voto nulo"] listaVotos = [0, 0, 0, 0, 0, 0, 0] posVotoNulo = len(listaNombres) - 1 # Dibuja la tarjeta electoral ventana = comienzoElecciones() DR A # Comienzan las votaciones: Ciclo de n iteraciones i = 1 while i < n: # Se obtiene el nombre del candidato por el que se vota voto = sufragar(ventana, listaNombres) # Se busca el nombre dado en la lista de candidatos # y se le agrega un voto j = 0 while j < len(listaNombres) - 1: if voto == listaNombres[j]: listaVotos[j] = listaVotos[j] + 1 break j = j + 1 # Si el nombre no esta en la lista es un voto nulo if j == len(listaNombres): listaVotos[posVotoNulo] = listaVotos[posVotoNulo] + 1 i = i + 1 # Escrutinio: se ordena la lista de candidatos por orden de votos listaNombres = ordenar(listaNombres, listaVotos) # Resultado mostrarGanador(ventana, listaNombres, listaVotos) Algoritmo 7.6: Simulador de elecciones La función sufragar() (algoritmo 7.7) recibe como parámetros la ventana donde se harán las votaciones y la lista de candidatos. En ella se declara una variable donde se almacenará el nombre del candidato elegido y luego, en un ciclo infinito, se maneja el evento de presionar un botón del mouse. Cuando este evento sucede se obtiene la posición donde se hizo clic y de acuerdo a ella se determina el candidato por el cual se quiere votar. Una vez se haya hecho clic, el ciclo se interrumpe y se retorna el candidato elegido. Si 100 7.1 Listas la acción de hacer clic se hace fuera de los rectángulos que limitan las imágenes de los candidatos, se considera que el voto fue nulo y la variable elegido es retornada con su valor por defecto, es decir, una cadena vacı́a. FT def sufragar(ventana, listaNombres): "Funcion que retorna el candidato por el cual se ha votado" # Declaracion de la variable que tendrá el nombre del candidato elegido = "" DR A # Comienzan las votaciones: Ciclo de n iteraciones haceClic = False while True: # Maneja el evento de hacer clic for e in pygame.event.get(): if e.type == pygame.MOUSEBUTTONDOWN: haceClic = True pos = pygame.mouse.get_pos() if pos[0] >= 22 and pos[0] <= 122 and \ pos[1] >= 80 and pos[1] <= 200: elegido = listaNombres[0] if pos[0] >= 142 and pos[0] <= 242 and \ pos[1] >= 80 and pos[1] <= 200: elegido = listaNombres[1] if pos[0] >= 262 and pos[0] <= 362 and \ pos[1] >= 80 and pos[1] <= 200: elegido = listaNombres[2] if pos[0] >= 382 and pos[0] <= 482 and \ pos[1] >= 80 and pos[1] <= 200: elegido = listaNombres[3] if pos[0] >= 502 and pos[0] <= 602 and \ pos[1] >= 80 and pos[1] <= 200: elegido = listaNombres[4] if pos[0] >= 622 and pos[0] <= 722 and \ pos[1] >= 80 and pos[1] <= 200: elegido = listaNombres[5] if haceClic: break # Actualiza el sistema pygame.display.update() return elegido Algoritmo 7.7: Retorna un candidato en el juego de elecciones El algoritmo 7.8 ordena las listas de candidatos y votos por número de votos obtenidos. 101 7 Noción de Abstracción de Datos FT Esto lo logra realizando dos ciclos anidados. El ciclo externo recorre las listas desde el primer elemento hasta el último, encontrando el menor elemento y ubicándolo en la posición adecuada según el orden que se desea. El menor elemento se encuentra realizando el ciclo interno, desde la posición donde se encuentra el recorrido de las listas hasta el final, buscando saber cuál es la posición del elemento con menos valor. def ordenar(listaNombres, listaVotos): "Funcion que ordena las listas por numero de votos" # Ciclo que recorre las listas contador1 = 0 while contador1 < len(listaNombres): contador2 = contador1 posMenor = contador1 # Ciclo que encuentra la posicion del elemento menor while contador2 < len(listaNombres): if listaVotos[posMenor] > listaVotos[contador2]: posMenor = contador2 contador2 = contador2 + 1 DR A # Ubica el menor en la posicion adecuada según el orden nombreTemp = listaNombres.pop(posMenor) listaNombres.insert(contador1, nombreTemp) votoTemp = listaVotos.pop(posMenor) listaVotos.insert(contador1, votoTemp) contador1 = contador1 + 1 return listaNombres,listaVotos Algoritmo 7.8: Ordena las listas de candidato y votos en el juego de elecciones Ordenamiento Matriz ? ? ? Ordenar una lista es organizar cada elemento de ella en un orden particular. En el algoritmo 7.8 se utilizó el método Selection Sort, el cual encuentra el mı́nimo valor en la lista, lo intercambia por el valor de la primera posición y repite este proceso en el resto de la lista (empezando en la segunda posición y avanzando una posición cada iteración). Existen muchos otros algoritmos para ordenar una lista. Se deja como ejercicio para el lector investigar e implementar algunos de esos algoritmos. Los elementos que tienen las listas pueden ser de cualquier tipo de datos. Esto significa que los elementos podrı́an ser también listas. Cuando se tiene una lista de listas se dice que se tiene una matriz . La cantidad de niveles de anidamiento de las listas determina el 102 7.1 Listas número de dimensiones que tiene la matriz. En el ejemplo 7.3 se trabajará con listas de listas, es decir, matrices de dos dimensiones. DR A FT Ejemplo 7.3 Buscaminas es un juego muy popular cuyo objetivo es “limpiar” un campo minado sin detonar ninguna mina. El campo minado es una grilla de n × m ubicaciones, en las cuales puede o no haber una mina. Este es un juego de un solo jugador, quien debe explorar el campo minado haciendo clic en las ubicaciones de manera estratégica para no detonar una mina. Cuando se limpia (se hace clic en) una ubicación que no tiene una mina, esta revela una pista acerca de las ubicaciones adyacentes. La pista es un número, el cual hace referencia a la cantidad de minas que hay alrededor de dicha ubicación. El juego termina cuando se hace clic en una mina ó cuando se ha limpiado la totalidad de ubicaciones que no tienen mina. En la figura 7.5 puede verse un ejemplo del buscaminas para Windows 7. Figura 7.5: Buscaminas en Windows 7 El procedimiento para jugar buscaminas se encuentra en el algoritmo 7.9. La idea detrás de este procedimiento es contar con dos matrices, una (tableroOculto) en la que se encuentra el tablero completo, es decir, se conoce plenamente la ubicación de las minas y, 103 7 Noción de Abstracción de Datos por lo tanto, las pistas. La otra matriz (tableroVisible) representa el tablero tal y como se ve en el juego, por lo que inicia vacı́a y a medida que se escoge una ubicación, se va revelando el contenido del tablero. FT def buscaminas(ancho, alto, numMinas): "Procedimiento para jugar buscaminas" tableroVisible = construirMatriz(ancho, alto) tableroOculto = llenarTablero(ancho, alto, numMinas) juegoAcabado = False # Dibuja la ventana inicial ventana = saludoInicial() # Ciclo del juego while not juegoAcabado: # Dibuja/Actualiza el tablero dibujarTablero(ventana, tableroVisible) # Pide hacer clic en una posicion x,y = obtenerUbicacion(ventana) DR A # Procede de acuerdo a la ubicacion tableroVisible[x][y] = tableroOculto[x][y] # Si detono una mina (perdio) if tableroOculto[x][y] == 'M': acabarJuego(ventana, False) juegoAcabado = True else: # Si no detono una mina pero completo el tablero (gano) if tableroLleno(tableroVisible): acabarJuego(ventana, True) juegoAcabado = True Algoritmo 7.9: Juego buscaminas La parte gráfica de este algoritmo se maneja de manera similar al algoritmo 7.1 del ejemplo del juego ahorcado, simplemente cambian algunos parámetros e implementaciones de las funciones y procedimientos saludoInicial(), dibujarTablero(), obtenerUbicacion() (que en el ahorcado se llamaba pedirLetra()) y acabarJuego(). El proceso para obtener el dato ingresado por el usuario es, quizás, el que más cambia dado que ahora dicho dato es una coordenada que representa la ubicación que quiere explorar en el tablero. Todas estas funciones se dejan como ejercicio para implementar. Para trabajar con el tablero primero debe construirse la matriz. El proceso es bastante simple y puede verse en el algoritmo 7.10. Se tienen dos ciclos anidados, el primero se 104 7.1 Listas encarga de crear las sublistas y el segundo de ingresar la cantidad apropiada de elementos a cada subsista. A medida que las sublistas van quedando construidas, se van adicionando una lista inicialmente vacı́a, creando ası́ la matriz, que es retornada al final. for i in range(alto): listatemp = [] for j in range(ancho): listatemp.append(' ') matriz.append(temp) return matriz FT def construirMatriz(ancho, alto): "Funcion que crea e inicializa con ' ' una matriz de anchoxalto" matriz = [] Algoritmo 7.10: Crea y retorna una matriz de dimensiones ancho × alto DR A Una vez el tablero ha sido construido, se debe llenar con las minas y pistas. Este proceso se realiza en el algoritmo 7.11. Primero se crea la matriz que representa el tablero. Luego esta matriz se empieza a llenar con las minas (sı́mbolo ’M’). Las posiciones de las minas son halladas de manera aleatoria y poniendo mucho cuidado de no repetir ninguna. La forma de hallar y poner las pistas es relativamente simple: se recorre la matriz un elemento a la vez y en cada ubicación se pregunta si dicha ubicación contiene una mina. Si esto es cierto, entonces se continua con la siguiente ubicación, pero si es falso entonces se revisa cada uno de los ocho lugares adyacentes a la ubicación actual preguntando por una mina. Cada vez que una mina sea encontrada en una posición adyacente, una variable pista es aumentada una unidad en su valor, que inicialmente es cero. Ası́, si se tienen tres minas alrededor, por ejemplo, entonces el valor de la variable terminará siendo tres y ese número irá en la ubicación correspondiente de la matriz. Si el lector pone mucha atención en la parte de las pistas del algoritmo 7.11 puede darse cuenta que hay problemas en ella. Supongamos que estamos en la posición (0,0) de la matriz, es decir, recién iniciaron los dos ciclos, y en ella no hay una mina. Como este lugar en la matriz es la esquina superior izquierda, esto quiere decir que hay que revisar en las posiciones (0,1), (1,1) y (1,0), que son las únicas ubicaciones adyacentes a (0,0). Sin embargo, nótese que en el algoritmo la revisión de minas se hace sobre ocho ubicaciones adyacentes, luego ¿qué va a pasar en las cinco posiciones inexistentes que se están revisando? Sale un error porque efectivamente no existen. Entonces hay que modificar 105 7 Noción de Abstracción de Datos el algoritmo para evitar estos problemas en los bordes de la matriz. FT def llenarTablero(ancho, alto, numMinas): "Funcion que llena el tablero del buscaminas con minas y pistas" # Primero se crea la matriz que servira de tablero tableroOculto = construirMatriz(ancho, alto) # Se llena el tablero con una cantidad numMinas de minas ('M') i = 0 while i < numMinas: dirx = random.randint(0, ancho - 1) diry = random.randint(0, alto - 1) if tableroOculto[dirx][diry] != 'M': tableroOculto[dirx][diry] = 'M' i = i + 1 DR A # Se recorre el tablero poniendo las pistas for i in range(alto): for j in range(ancho): if tableroOculto[i][j] != 'M': pista = 0 if tableroOculto[i+1][j] == 'M': pista = pista + 1 if tableroOculto[i-1][j] == 'M': pista = pista + 1 if tableroOculto[i][j+1] == 'M': pista = pista + 1 if tableroOculto[i][j-1] == 'M': pista = pista + 1 if tableroOculto[i+1][j+1] == 'M': pista = pista + 1 if tableroOculto[i+1][j-1] == 'M': pista = pista + 1 if tableroOculto[i-1][j+1] == 'M': pista = pista + 1 if tableroOculto[i-1][j-1] == 'M': pista = pista + 1 tableroOculto[i][j] = pista return tableroOculto Algoritmo 7.11: Llena el tablero del buscaminas con minas y pistas El algoritmo 7.12 es una modificación de la parte de las pistas del algoritmo 7.11. En él 106 7.1 Listas antes de revisar la ubicación se pregunta si dicha ubicación existe en la matriz. DR A FT # Se recorre el tablero poniendo las pistas for i in range(alto): for j in range(ancho): if tableroOculto[i][j] != 'M': pista = 0 if i < alto - 1: if tableroOculto[i+1][j] == 'M': pista = pista + 1 if i > 0: if tableroOculto[i-1][j] == 'M': pista = pista + 1 if j < ancho - 1: if tableroOculto[i][j+1] == 'M': pista = pista + 1 if j > 0: if tableroOculto[i][j-1] == 'M': pista = pista + 1 if i < alto - 1 and j < ancho - 1: if tableroOculto[i+1][j+1] == 'M': pista = pista + 1 if i < alto - 1 and j > 0: if tableroOculto[i+1][j-1] == 'M': pista = pista + 1 if i > 0 and j < ancho - 1: if tableroOculto[i-1][j+1] == 'M': pista = pista + 1 if i > 0 and j > 0: if tableroOculto[i-1][j-1] == 'M': pista = pista + 1 tableroOculto[i][j] = pista Algoritmo 7.12: Modificación del algoritmo para llenar el tablero del buscaminas con pistas En el juego, para saber si el éste acabó se deben realizar dos tareas: preguntar si en la ubicación que el jugador escogió hay una mina y revisar si todo el tablero ha sido explorado. La primera tarea es muy sencilla de hacer, solo se pregunta si en la ubicación actual hay un caracter ’M’. La segunda tarea requiere hacer un recorrido por todo la matriz tableroVisible buscando una ubicación que tenga el valor inicial ’ ’ y cuya ubicación correspondiente en tableroOculto no tenga una mina. Si no existe dicha ubicación, entonces se hay llenado todo el tablero y el jugador ganó. El algoritmo 7.13 muestra esta 107 7 Noción de Abstracción de Datos segunda tarea. FT def tableroLleno(tableroVisible, tableroOculto): "Funcion que revisa si se han descubierto todas las ubicaciones" "sin mina" lleno = True for i in range(len(tableroVisible)): for j in range(len(tableroVisible[0])): if tableroVisible[i][j] == ' ' and \ tableroOculto[i][j] != 'M': lleno = False return lleno Algoritmo 7.13: Función que determina si se han descubierto todas las ubicaciones que no tienen mina ? ? ? Ejercicios DR A 7.2. 7.1 La operación in para listas, revisa si un elemento se encuentra en una lista. Ası́, si escribimos if letra in letrasFalladas, lo que queremos saber es si el elemento letra se encuentra en la lista letrasFalladas. Desarrolle, sin usar dicha operación in, la función buscarElemento() que tome como argumentos una lista y un elemento, y retorne True si el elemento efectivamente se encuentre en la lista, y False de lo contrario. 7.2 En el juego Ahorcado visto en este capı́tulo se pueden repetir las letras falladas y esto hace que se acumule la misma letra en la lista de letras falladas y, por lo tanto, se dibuje un elemento más del ahorcado. Corrija esto haciendo que el programa muestre una aviso al jugador diciendo, cuando se repite una letra fallada, que ya se escogió dicha letra y la pida de nuevo sin anexarla de nuevo a la lista y sin dibujar un elemento más del ahorcado. 7.3 La última letra que se presiona y el dibujo completo en el juego ahorcado no se alcanza a mostrar cuando termina el juego. Modifique los algoritmos necesarios para que efectivamente se muestre la última letra presionada y se dibuje completamente el ahorcado (cuando pierde). 7.4 Complete el ejemplo 7.2 de las elecciones desarrollando la función comienzoElecciones() y el procedimiento mostrarGanador(). Para que los algoritmos aquı́ mostrados fun- 108 7.2 Ejercicios FT cionen correctamente, la ventana creada y retornada en comienzoElecciones() debe tener unas dimensiones de 750 × 350 y verse como la figura 7.6. DR A Figura 7.6: Ventana del juego de las elecciones Es muy importante que en la ventana creada en comienzoElecciones() las fotos de los candidatos estén en unos cuadros de tamaño 100 × 120 y las posiciones de sus esquinas superior-izquierda sean: (22,80), (142,80), (262,80), (382,80), (502,80), y (622,80). 7.5 La función ordenamiento del ejemplo 7.2 retorna las listas en orden ascendente, es decir, de menor a mayor, de acuerdo a los votos obtenidos. Desarrolle la función invertirLista() para tomar estas listas y retornarlas en orden invertido (i.e. de mayor a menor). 7.6 Investigue e implemente en el juego de las elecciones los siguientes métodos de ordenamiento: Bubble Sort, Insertion Sort y Shell Sort. 7.7 Desarrolle todas las funciones y procedimientos gráficos del juego buscaminas. 7.8 El juego triqui del ejemplo 6.1 puede desarrollarse con una matriz y funciones de pygame. Modifique las funciones necesarias para hacerlo. 109 DR A FT 7 Noción de Abstracción de Datos 110 FT Bibliografı́a [1] Jean-Raymond Abrial. Guidelines to formal system studies. MATISSE project, November 2000. [2] Donald E. Knuth. Structured programming with go to statements. ACM Computing Surveys, 6(4):261–301, December 1974. [3] Guido Van Rossum and Fred L Drake Jr. An Introduction to Python. Network Theory Ltd, 2011. [4] Guido Van Rossum and Fred L Drake Jr. The Python Language Reference Manual. Network Theory Ltd, 2011. DR A [5] Gerardo Sarria. Introduction to programming for engineers following the parachute paradigm. In 39th ASEE/IEEE Frontiers in Education. IEEE, San Antonio, TX, USA, 18–21 October 2009. [6] G. Michael Schneider. The introductory programming course in computer science: ten principles. In Papers of the SIGCSE/CSA technical symposium on Computer science education, SIGCSE ’78, pages 107–114, New York, NY, USA, 1978. ACM. 111