Está en la página 1de 199

Algoritmos y Programacin I Con lenguaje Python

31 de agosto de 2009

Contenidos
1. Algunos conceptos bsicos 1.1. Computadoras y programas . . . . . . . . . . . . . . . . 1.2. El mito de la mquina todopoderosa . . . . . . . . . . . 1.3. Cmo darle instrucciones a la mquina usando Python 1.4. Para que las funciones nos devuelvan un resultado . . 1.5. Una instruccin un poco ms compleja: el ciclo denido 1.5.1. Ayuda desde el intrprete . . . . . . . . . . . . . 1.6. Construir mdulos . . . . . . . . . . . . . . . . . . . . . 1.7. La forma de un programa Python . . . . . . . . . . . . . 1.8. Estado y computacin . . . . . . . . . . . . . . . . . . . 1.9. Depuracin de programas . . . . . . . . . . . . . . . . . 1.10. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7 8 9 12 12 14 14 16 16 18 18 19 19 20 22 22 23 24 25 25 26 27 29 31 33 34 36 39 39 40 40 41 44 3

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

2. Programas sencillos 2.1. Construccin de programas . . . . . . . . . . . . . . . . . . . . 2.2. Un programa sencillo: convertir una longitud expresada en el sistema mtrico . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3. Piezas de un programa Python . . . . . . . . . . . . . . . . . . 2.3.1. Nombres . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.2. Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . 2.4. No slo de nmeros viven los programas . . . . . . . . . . . . 2.5. Instrucciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.6. Ciclos denidos . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.7. Una gua para el diseo . . . . . . . . . . . . . . . . . . . . . . 2.8. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Funciones 3.1. Cmo usar una funcin en un programa . 3.2. Ms sobre los resultados de las funciones 3.3. Un ejemplo completo . . . . . . . . . . . . 3.4. Devolver mltiples resultados . . . . . . . 4. Decisiones 4.1. Expresiones booleanas . . . . . . . . 4.1.1. Expresiones de comparacin 4.1.2. Operadores lgicos . . . . . . 4.2. Comparaciones simples . . . . . . . 4.3. Mltiples decisiones consecutivas .

. . . . . sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . ingls al . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

CONTENIDOS 4.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 47 48 48 50 51 53 55 55 56 56 57 58 59 62 67 69 69 69 70 70 71 72 72 73 75 79 80 83 83 84 84 84 86 86 86 90 93 93 94

5. Ms sobre ciclos 5.1. Ciclos indenidos . . . 5.2. Ciclo interactivo . . . . 5.3. Ciclo con centinela . . 5.4. Cmo romper un ciclo 5.5. Ejercicios . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

6. Cadenas de caracteres 6.1. Operaciones con cadenas . . . . . . . . . . . . . . . . . . . . . . . 6.2. Una operacin para recorrer todos los caracteres de una cadena 6.3. Acceder a una posicin de la cadena . . . . . . . . . . . . . . . . 6.4. Segmentos de cadenas . . . . . . . . . . . . . . . . . . . . . . . . 6.5. Las cadenas son inmutables . . . . . . . . . . . . . . . . . . . . . 6.6. Procesamiento sencillo de cadenas . . . . . . . . . . . . . . . . . 6.7. Nuestro primer juego . . . . . . . . . . . . . . . . . . . . . . . . . 6.8. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7. Tuplas y listas 7.1. Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1. Elementos y segmentos de tuplas . . . . . . . . . . . . 7.1.2. Las tuplas son inmutables . . . . . . . . . . . . . . . . 7.1.3. Longitud de tuplas, tuplas vacas, tuplas unitarias . . 7.1.4. Empaquetado y desempaquetado de tuplas . . . . . . 7.2. Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.1. Longitud de la lista. Elementos y segmentos de listas 7.2.2. Cmo mutar listas . . . . . . . . . . . . . . . . . . . . 7.2.3. Cmo buscar dentro de las listas . . . . . . . . . . . . 7.3. Ordenar listas . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4. Listas y cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . 8. Algoritmos de bsqueda 8.1. El problema de la bsqueda . . . . . . . . . . . . . . . 8.1.1. Cuntas comparaciones hace este programa? 8.2. Cmo programar la bsqueda lineal a mano . . . . . 8.3. Bsqueda lineal . . . . . . . . . . . . . . . . . . . . . . 8.3.1. Cuntas comparaciones hace este programa? 8.4. Buscar sobre una lista ordenada . . . . . . . . . . . . . 8.5. Bsqueda binaria . . . . . . . . . . . . . . . . . . . . . 8.5.1. Cuntas comparaciones hace este programa?

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

9. Diccionarios 9.1. Qu es un diccionario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2. Utilizando diccionarios en Python . . . . . . . . . . . . . . . . . . . . . . . . . . .

CONTENIDOS 10. Contratos 10.1. Pre y Postcondiciones . . . . . . . . . . . . . . . . . . . 10.1.1. Precondiciones . . . . . . . . . . . . . . . . . . 10.1.2. Postcondiciones . . . . . . . . . . . . . . . . . . 10.1.3. Aseveraciones . . . . . . . . . . . . . . . . . . . 10.1.4. Ejemplos . . . . . . . . . . . . . . . . . . . . . . 10.2. Invariantes de ciclo . . . . . . . . . . . . . . . . . . . . 10.2.1. Comprobacin de invariantes desde el cdigo 10.3. Mutabilidad e Inmutabilidad . . . . . . . . . . . . . . 10.3.1. Parmetros mutables e inmutables . . . . . . . 10.4. Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5. Apndice . . . . . . . . . . . . . . . . . . . . . . . . . . 11. Manejo de archivos 11.1. Cerrar un archivo . . . . . . . . . . . . . 11.2. Ejemplo de procesamiento de archivos . 11.3. Modo de apertura de los archivos . . . . 11.4. Escribir en un archivo . . . . . . . . . . 11.5. Agregar informacin a un archivo . . . 11.6. Manipular un archivo en forma binaria 11.7. Persistencia de datos . . . . . . . . . . . 11.7.1. Persistencia en archivos CSV . . 11.7.2. Persistencia en archivos binarios 11.8. Directorios . . . . . . . . . . . . . . . . . 11.9. Resumen . . . . . . . . . . . . . . . . . . 11.10. pndice . . . . . . . . . . . . . . . . . . A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5 97 97 97 97 98 98 99 100 100 101 102 103 105 105 106 106 107 108 108 108 109 109 109 110 112 115 115 115 116 117 118 119 119 120 121 122 123 124 127 127 127 129 130

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

12. Manejo de errores y excepciones 12.1. Errores . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2. Excepciones . . . . . . . . . . . . . . . . . . . . . . . . 12.2.1. Manejo de excepciones . . . . . . . . . . . . . . 12.2.2. Procesamiento y propagacin de excepciones . 12.2.3. Acceso a informacin de contexto . . . . . . . 12.3. Validaciones . . . . . . . . . . . . . . . . . . . . . . . . 12.3.1. Comprobaciones por contenido . . . . . . . . . 12.3.2. Entrada del usuario . . . . . . . . . . . . . . . 12.3.3. Comprobaciones por tipo . . . . . . . . . . . . 12.3.4. Comprobaciones por caractersticas . . . . . . 12.4. Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . 12.5. Apndice . . . . . . . . . . . . . . . . . . . . . . . . . . 13. Procesamiento de archivos 13.1. Corte de control . . . . 13.1.1. Ejemplo . . . . 13.2. Apareo . . . . . . . . . 13.3. Resumen . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

CONTENIDOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 131 132 133 135 136 137 139 141 145 145 146 147 149 151 151 153 153 156 167 167 168 169 174 175 175 177 179 179 181 183 184 184 189 189 191 191 194 195 195 197 198 199

14. Objetos 14.1. Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.2. Cmo denimos nuevos tipos: clases . . . . . . . . . . . . . . . . 14.2.1. Denicin de una clase . . . . . . . . . . . . . . . . . . . 14.2.2. Una prueba poco robusta . . . . . . . . . . . . . . . . . . 14.2.3. Un mtodo para mostrar objetos . . . . . . . . . . . . . . 14.2.4. Una prueba robusta . . . . . . . . . . . . . . . . . . . . . 14.3. Denir relaciones de orden . . . . . . . . . . . . . . . . . . . . . 14.4. Denir relaciones de orden dentro de los elementos de una clase 15. Ms sobre objetos: polimorsmo y herencia 15.1. Polimorsmo . . . . . . . . . . . . . . . 15.1.1. Sobrecarga . . . . . . . . . . . . . 15.2. Herencia . . . . . . . . . . . . . . . . . . 15.2.1. La clase de las guras . . . . . . 16. Referencias - Listas enlazadas 16.1. Referencias . . . . . . . . . . . . . . . 16.2. Listas enlazadas . . . . . . . . . . . . 16.2.1. Una clase sencilla de vagones 16.2.2. La clase ListaEnlazada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17. Tipos abstractos de datos: Pilas y colas 17.1. Pilas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.1.1. Pilas representadas por listas . . . . . . . . . 17.1.2. Uso de pila: calculadora cientca . . . . . . 17.1.3. Cunto cuestan los mtodos? . . . . . . . . 17.2. Colas . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.2.1. Colas implementadas sobre listas . . . . . . . 17.2.2. Colas implementadas sobre listas enlazadas 18. Funciones y su modelo de ejecucin 18.1. La pila de ejecucin de las funciones . . . . . 18.2. Pasaje de parmetros . . . . . . . . . . . . . . 18.3. Devolucin de resultados . . . . . . . . . . . 18.4. La recursin y cmo puede ser que funcione 18.5. Algunos ejemplos con nmeros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19. Ordenar listas 19.1. Ordenamiento por seleccin . . . . . . . . . . . 19.1.1. Cunto cuesta ordenar por seleccin? . 19.2. Ordenamiento por insercin . . . . . . . . . . . 19.2.1. Cunto cuesta ordenar por insercin? . 20. Algunos ordenamientos recursivos 20.1. Ordenamiento por mezcla, o Mergesort 20.2. Cunto cuesta el Mergesort? . . . . . . . 20.3. Ordenamiento rpido o Quicksort . . . 20.4. Cunto cuesta el Quicksort? . . . . . . . . . . . . . . . . . . .

Unidad 1

Algunos conceptos bsicos


En este captulo hablaremos de lo que es un programa de computadora e introduciremos unos cuantos conceptos referidos a la programacin y a la ejecucin de programas. Utilizaremos en todo momento el lenguaje de programacin Python para ilustrar esos conceptos.

1.1.

Computadoras y programas

Prcticamente todos ustedes usan permanentemente computadoras: para mandar correos electrnicos, navegar por Internet, chatear, jugar, escribir textos. Las computadoras se usan para actividades tan dismiles como predecir las condiciones meteorolgicas de la prxima semana, guardar historias clnicas, disear aviones, llevar la contabilidad de las empresas o controlar una fbrica. Y lo interesante aqu (y lo que hace apasionante a esta carrera) es que el mismo aparato sirve para realizar todas estas actividades: uno no cambia de computadora cuando se cansa de chatear y quiere jugar al solitario. Muchos denen una computadora moderna como una mquina que almacena y manipula informacin bajo el control de un programa que puede cambiar. Aparecen ac dos conceptos que son claves: por un lado se habla de una mquina que almacena informacin, y por el otro lado, esta mquina est controlada por un programa que puede cambiar. Una calculadora sencilla, de esas que slo tienen 10 teclas para los dgitos, una tecla para cada una de las 4 operaciones, un signo igual, encendido y CLEAR, tambin es una mquina que almacena informacin y que est controlada por un programa. Pero lo que diferencia a esta calculadora de una computadora es que en la calculadora el programa no puede cambiar. Un programa de computadora es un conjunto de instrucciones paso a paso que le indican a una computadora cmo realizar una tarea dada, y en cada momento uno puede elegir ejecutar un programa de acuerdo a la tarea que quiere realizar. Las instrucciones se deben escribir en un lenguaje que nuestra computadora entienda. Los lenguajes de programacin son precisamente eso: lenguajes diseados especialmente para dar rdenes a una computadora, de manera exacta y no ambigua. Sera muy agradable poderle dar las rdenes a la computadora en castellano, pero el problema del castellano, y de las lenguas habladas en general, es su ambigedad: Si alguien nos dice Compr el collar sin monedas, no sabremos si nos pide que compremos el collar que no tiene monedas, o que compremos un collar y que no usemos monedas para la compra. Habr que preguntarle a quien nos da la orden cul es la interpretacin correcta. Pero tales dudas no pueden aparecer cuando se le dan rdenes a una computadora. Este curso va a tratar precisamente de cmo se escriben programas para hacer que una 7

Unidad 1. Algunos conceptos bsicos

computadora realice una determinada tarea. Vamos a usar un lenguaje especco (Python) porque es sencillo y elegante, pero ste no ser un curso de Python sino un curso de programacin.

1.2.

El mito de la mquina todopoderosa

Muchas veces la gente se imagina que con la computadora se puede hacer cualquier cosa, que no hay tareas imposibles de realizar. Ms an, se imaginan que si bien hubo cosas que eran imposibles de realizar hace 50 aos, ya no lo son ms, o no lo sern dentro de algunos aos, cuando las computadoras crezcan en poder (memoria, velocidad), y la computadora se vuelva una mquina todopoderosa. Sin embargo eso no es as: existen algunos problemas, llamados no computables que nunca podrn ser resueltos por una computadora digital, por ms poderosa que sta sea. La computabilidad es la rama de la computacin que se ocupa de estudiar qu tareas son computables y qu tareas no lo son. De la mano del mito anterior, viene el mito del lenguaje todopoderoso: hay problemas que son no computables porque en realidad se utiliza algn lenguaje que no es el apropiado. En realidad todas las computadoras pueden resolver los mismos problemas, y eso es independiente del lenguaje de programacin que se use. Las soluciones a los problemas computables se pueden escribir en cualquier lenguaje de programacin. Eso no signica que no haya lenguajes ms adecuados que otros para la resolucin de determinados problemas, pero la adecuacin est relacionada con temas tales como la elegancia, la velocidad, la facilidad para describir un problema de manera simple, etc., nunca con la capacidad de resolucin. Los problemas no computables no son los nicos escollos que se le presentan a la computacin. Hay otros problemas que si bien son computables demandan para su resolucin un esfuerzo enorme en tiempo y en memoria. Estos problemas se llaman intratables. El anlisis de algoritmos se ocupa de separar los problemas tratables de los intratables, encontrar la solucin ms barata para resolver un problema dado, y en el caso de los intratables, resolverlos de manera aproximada: no encontramos la verdadera solucin porque no nos alcanzan los recursos para eso, pero encontramos una solucin bastante buena y que nos insume muchos menos recursos (el orden de las respuestas de Google a una bsqueda es un buen ejemplo de una solucin aproximada pero no necesariamente ptima). En este curso trabajaremos con problemas no slo computables sino tambin tratables. Y aprenderemos a medir los recursos que nos demanda una solucin, y empezaremos a buscar la solucin menos demandante en cada caso particular. Algunos ejemplos de los problemas que encararemos y de sus soluciones: Problema 1.1. Dado un nmero N se quiere calcular N 33 . Una solucin posible, por supuesto, es hacer el producto N N . . . N , que involucra 32 multiplicaciones. Otra solucin, mucho ms eciente es: Calcular N N . Al resultado anterior mutiplicarlo por s mismo con lo cual ya disponemos de N 4 . Al resultado anterior mutiplicarlo por s mismo con lo cual ya disponemos de N 8 . Al resultado anterior mutiplicarlo por s mismo con lo cual ya disponemos de N 16 .

1.3. Cmo darle instrucciones a la mquina usando Python Al resultado anterior mutiplicarlo por s mismo con lo cual ya disponemos de N 32 .

Al resultado anterior mutiplicarlo por N con lo cual conseguimos el resultado deseado con slo 6 multiplicaciones. Cada una de estas soluciones representa un algoritmo, es decir un mtodo de clculo, diferente. Para un mismo problema puede haber algoritmos diferentes que lo resuelven, cada uno con un costo distinto en trminos de recursos computacionales involucrados. Problema 1.2. Tenemos que permitir la actualizacin y consulta de una gua telefnica. Para este problema no hay una solucin nica: hay muchas y cada una est relacionada con un contexto de uso. De qu gua estamos hablando: la gua de una pequea ocina, un pequeo pueblo, una gran ciudad, la gua de la Argentina? Y en cada caso de qu tipo de consulta estamos hablando: hay que imprimir un listado una vez por mes con la gua completa, se trata de una consulta en lnea, etc.? Para cada contexto hay una solucin diferente, con los datos guardados en una estructura de datos apropiada, y con diferentes algoritmos para la actualizacin y la consulta.

1.3.

Cmo darle instrucciones a la mquina usando Python

El lenguaje Python nos provee de un intrprete, es decir un programa que interpreta las rdenes que le damos a medida que las escribimos. Para orientarnos, el intrprete presenta una lnea de comandos (los comandos son las rdenes) que identica al comienzo con los smbolos >>>, y que llamaremos prompt. En esta lnea, a continuacin del prompt podemos escribir diferentes rdenes. Algunas rdenes sencillas, por ejemplo, permiten utilizar la lnea de comandos como una calculadora simple con nmeros enteros. Para esto escribimos la expresin que queremos resolver en el prompt y presionamos la tecla <ENTER>. El intrprete de Python responde el resultado de la operacin en la lnea siguiente, sin prompt, y luego nos presenta nuevamente el cursor para escribir la siguiente orden. >>> 2+3 5 >>> Python permite utilizar las operaciones +, -, *, / (divisin entera), y ** (potenciacin). La sintaxis es la convencional (valores intercalados con operaciones), y se pueden usar parntesis para modicar el orden de asociacin natural de las operaciones (potenciacin, producto/divisin, suma/resta). >>> 35 >>> 23 >>> 35 >>> 2 >>> 5*7 2+3*7 (2+3)*7 10/5 5**2

10 25 >>>

Unidad 1. Algunos conceptos bsicos

Otra orden sencilla de Python permite indicarle al intrprete que escriba o imprima por pantalla una palabra o frase, que llamaremos cadena de texto. >>> print Hola Hola >>> print Como estan? Como estan? >>> print "Bienvenidos y bienvenidas a este curso!" Bienvenidos y bienvenidas a este curso! >>> print es una instruccin de Python: aqulla que le indica a la mquina que debe imprimir un texto en pantalla, que deber ser ingresado entre comillas simples o dobles " indistintamente. Ya veremos con qu otras instrucciones viene equipado Python. Pero ya dijimos que como programadores debamos ser capaces de escribir nuevas instrucciones para la computadora. Los programas de correo electrnico, navegacin por Internet, chat, juegos, escritura de textos o prediccin de las condiciones meteorolgicas de los prximos das no son ms que grandes instrucciones que se le da a la mquina, escritas por uno o muchos programadores. Llamaremos funcin a una instruccin escrita por un programador. Si queremos escribir una funcin (que llamaremos holaMar) que escribe en una lnea el texto Hola Marta! y en la lnea siguiente el texto Estoy programando en Python., lo que debemos hacer es ingresar el siguiente conjunto de lneas en Python: >>> def holaMar(): print "Hola Marta!" print "Estoy programando en Python." >>> def holaMar(): le indica a Python que estamos escribiendo una instruccin cuyo nombre es holaMar. Por qu se ponen esos dos parntesis? Lo veremos dentro de unos prrafos. La sangra con la que se escriben las dos instrucciones print le indican a Python que estamos escribiendo el cuerpo (es decir las instrucciones que la componen) de la funcin en cuestin. Las dos teclas <ENTER> que tecleamos despus de ingresar el texto "Estoy programando en Python." le indican a Python que se acab el cuerpo de la funcin (y por eso aparece nuevamente el cursor). Si ahora queremos que la mquina ejecute la instruccin holaMar, debemos escribir holaMar() a continuacin del cursor de Python: >>> holaMar() Hola Marta! Estoy programando en Python. >>>

1.3. Cmo darle instrucciones a la mquina usando Python

11

Se dice que estamos invocando a la funcin holaMar. Al invocar una funcin, se ejecutan las instrucciones que habamos escrito en su cuerpo, una a continuacin de la otra. Nuestro amigo Pablo seguramente se pondr celoso porque escribimos una funcin que la saluda a Marta, y nos pedir que escribamos una funcin que lo salude a l. Y as procederemos entonces: >>> def holaPab(): print "Hola Pablo!" print "Estoy programando en Python." >>> Pero, si para cada amigo que quiere que lo saludemos debemos que escribir una funcin distinta, parecera que la computadora no es una gran solucin. A continuacin veremos, sin embargo, que podemos llegar a escribir una nica funcin que se personalice en cada invocacin, para saludar a quien querramos. Para eso estn precisamente los parntesis. Las funciones tienen partes variables, llamadas parmetros, que se ponen dentro de los parntesis. Escribimos por ejemplo una funcin hola general que nos sirva para saludar a cualquiera, de la siguiente manera: >>> def hola(alguien): print "Hola", alguien,"!" print "Estoy programando en Python." >>> En este caso, alguien es un parmetro cuyo valor ser reemplazado por un texto (nombre en este caso) en cada invocacin. Por ejemplo, podemos invocarla dos veces, para saludar a Ana y a Juan: >>> hola("Ana") Hola Ana ! Estoy programando en Python. >>> hola("Juan") Hola Juan ! Estoy programando en Python. >>> Problema 1.3.1. Escribir un programa que calcule el cuadrado de un nmero dado. Solucin. Esto tiene una solucin muy sencilla: def cuad1(num): print num*num Para invocarlo, deberemos hacer: >>> cuad1(5) 25 >>>

12

Unidad 1. Algunos conceptos bsicos

Problema 1.3.2. Permitir que el usuario ingrese el valor a elevar al cuadrado Solucin. Para esto utilizaremos una nueva funcin input que permite leer valores ingresados por el usuario. def cuad2(): n = input("Ingrese un nmero: ") cuad1(n) La ejecucin del programa ser la siguiente: >>> cuad2() Ingrese un numero: 5 25 >>>

1.4.

Para que las funciones nos devuelvan un resultado

Las funciones que vimos hasta ahora muestran mensajes, pero no hemos visto funciones que se comporten como las funciones que conocemos, las de la matemtica, que se usan para calcular resultados. Queremos tambin poder hacer cosas del estilo y = f (x) en nuestros programas. Para ello introduciremos la instruccin return <expresion > que indica cul es el valor que tiene que devolver nuestra funcin. En este ejemplo escribimos una funcin que eleva al cuadrado un nmero. >>> def cuadrado (x): ... cua = x * x ... return cua ... >>> y = cuadrado (5) >>> y 25 >>>

1.5.

Una instruccin un poco ms compleja: el ciclo denido

Problema 1.5.1. Ahora que sabemos construir una funcin que calcula el cuadrado de un nmero, nos piden que imprimamos los cuadrados de los nmeros del 2 al 8. Solucin. Por supuesto que podemos hacer: >>> def cuad3(): print cuadrado(2) print cuadrado(3) print cuadrado(4) print cuadrado(5)

1.5. Una instruccin un poco ms compleja: el ciclo denido print cuadrado(6) print cuadrado(7) print cuadrado(8) >>> cuad3() 4 9 16 25 36 49 64 >>>

13

Se puede hacer algo mejor que esto? No con lo que sabemos de Python hasta el momento. Para resolver este tipo de problema (repetir un clculo para los valores contenidos en un intervalo dado) de manera eciente, introducimos el concepto de ciclo denido, que tiene la siguiente forma: for x in range(n1, n2): <hacer algo con x> Esta instruccin se lee como: Generar la secuencia de valores enteros del intervalo [n1, n2 ), y Para cada uno de los valores enteros que toma x en el intervalo generado, se debe hacer lo indicado por <hacer algo con x>. Vemos cmo, usando esta construccin, el problema anterior se puede resolver de manera ms compacta: Solucin. En nuestro caso lo que hay que hacer es invocar a la instruccin print cuadrado(x) que usa la funcin denida en 1.4 para calcular el cuadrado de x, y luego imprime el resultado, cuando x toma los valores 2, 3, . . . , 8. La instruccin que describe qu tipo de repeticiones se deben realizar es el encabezado del ciclo, y las instrucciones que describen la accin que se repite se llaman cuerpo del ciclo. No nos olvidemos que en nuestro ejemplo el ciclo debe recorrer todos los valores enteros entre 2 y 8, por lo tanto: for x in range(2, 9): ser el encabezado del ciclo y print cuadrado(x) ser el cuerpo del ciclo. Vemos entonces cmo resulta el ciclo completo (con su ejecucin): >>> for x in range(2, 9): print cuadrado(x) 4 9

14 16 25 36 49 64 >>>

Unidad 1. Algunos conceptos bsicos

Todas las instrucciones que describen el cuerpo del ciclo deben tener una sangra mayor que el encabezado del ciclo.

1.5.1.

Ayuda desde el intrprete

El intrprete de python nos provee una ayuda en lnea, es decir, nos puede dar la documentacin de una funcin, intruccin, etc, para obtenerla llamamos a help(). Por ejemplo podemos pedir help(input) y nos dara la documentacin de esa funcin, para obtener la documentacin de las instrucciones las debemos poner entre comillas, es decir help(return), de la misma forma se puede pedir ayuda sobre variable o valores. Otra forma de obtener ayuda es mediante dir(variable), que nos va a listar todas las funciones que tiene asociadas esa variable. Por ejemplo, mostramos las funciones asociadas a una cadena. >>> dir("hola") [__add__, __class__, __contains__, __delattr__, __doc__, __eq__, __ge__, __getattribute__, __getitem__, __getnewargs__, __getslice__, __gt__, __hash__, __init__, __le__, __len__, __lt__, __mod__, __mul__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __rmod__, __rmul__, __setattr__, __str__, capitalize, center, count, decode, encode, endswith, expandtabs, find, index, isalnum, isalpha, isdigit, islower, isspace, istitle, isupper, join, ljust, lower, lstrip, partition, replace, rfind, rindex, rjust, rpartition, rsplit, rstrip, split, splitlines, startswith, strip, swapcase, title, translate, upper, zfill] Como se puede ver son muchas las funciones asociadas a las cadenas, pero no necesitamos conocerlas todas, a medida que avancemos veremos algunas.

1.6.

Construir mdulos

Si escribimos nuestras funciones frente al cursor de Python como hasta ahora, perdemos todas las deniciones cuando salimos de Python. Para conservar los programas que vamos escribiendo, debemos usar algn editor de texto y guardarlos con la extensin .py. La extensin py indica que se trata de un mdulo Python, es decir un archivo separado que debe ser ejecutado por Python. Vamos a escribir nuestro primer mdulo, para lo cual resolveremos el siguiente problema: Problema 1.6.1. Escribir en Python un programa que haga lo siguiente: Despliega un mensaje de bienvenida por pantalla.

1.6. Construir mdulos Le pide al usuario que introduzca dos nmeros enteros n1 y n2. Imprime el cuadrado de todos los nmeros enteros del intervalo [n1, n2). Despliega un mensaje de despedida por pantalla. Solucin. # ARCHIVO: cuad.py # Un programa sencillo, para calcular cuadrados de nmeros def main(): print "Se calcularn cuadrados de nmeros" n1 = input("Ingrese un nmero entero: ") n2 = input("Ingrese otro nmero entero: ") for x in range(n1, n2): print x*x print "Es todo por ahora" main()

15

Este archivo se debe guardar como cuad.py. Si queremos ejecutarlo deberemos iniciar Python y luego importarlo. Lo ejecutaremos con valores 5 y 8 de la siguiente manera: >>> import cuad Se calcularn cuadrados de nmeros Ingrese un nmero entero: 5 Ingrese otro nmero entero: 8 25 36 49 Es todo por ahora >>> La orden import cuad le indica a Python que debe traer a la memoria el mdulo cuad.py tal como lo habamos guardado y ejecutar su contenido. Esto en este caso implica crear la funcin main de cuad (la llamaremos cuad.main) a partir de la denicin que est en el archivo, e iniciar su ejecucin inmediatamente, dado que se encuentra con la invocacin main(). Una vez importado el mdulo, cuad.main queda en memoria, y se puede volver a invocar sin necesidad de importar nuevamente: >>> cuad.main() Se calcularn cuadrados de nmeros Ingrese un nmero entero: 3 Ingrese otro nmero entero: 5

16

Unidad 1. Algunos conceptos bsicos

9 16 Es todo por ahora >>>

1.7.

La forma de un programa Python


print "Se calcularn cuadrados de nmeros"

La primera instruccin de cuad.main es


5

que lo que hace es mostrar un mensaje por pantalla. Las instrucciones segunda y tercera
7 8

n1 = input("Ingrese un nmero entero: ") n2 = input("Ingrese otro nmero entero: ") son instrucciones de entrada: se despliega el texto que est entre comillas y se espera que el usuario tipee un valor numrico y oprima la tecla <ENTER>. Cmo hacer para que los valores que tipea el usuario se recuerden a lo largo de todo el programa? Al valor ingresado se le dar un nombre, de la misma manera que a otros valores calculados durante la ejecucin. Aparece el concepto de variables de un programa: una variable se usa para darle un nombre a un valor dado y poder de esa manera referirnos al mismo a lo largo del programa. En estas dos instrucciones, n1 y n2 sern los nombres con los que se mencionarn el primer y el segundo entero tipeados por el usuario. En el ejemplo de la ltima corrida, se asociar el valor 3 con la variable n1 y el valor 5 con la variable n2. Luego de leer esos valores, se procede a ejecutar el ciclo

9 10

for x in range(n1, n2): print x*x Si el valor asociado con n1 es 3, y el valor asociado con n2 es 5, se asociar a x sucesivamente con los valores 3 y 4, y en cada caso se ejecutar el cuerpo del ciclo indicado (mostrar en pantalla los valores de los cuadrados de 3 y 4). Finalmente, cuando se terminan las repeticiones indicadas en el ciclo, se ejecuta la instruccin print "Es todo por ahora" que, como ustedes ya saben muestra el mensaje Es todo por ahora por pantalla.

1.8.

Estado y computacin

A lo largo de la ejecucin de un programa las variables pueden cambiar el valor con el que estn asociadas. En un momento dado uno puede detenerse a observar a qu valor se reere cada una de las variables del programa. Esa foto que indica en un momento dado a qu valor hace referencia cada una de las variables se denomina estado. Tambin hablaremos del estado de una variable para indicar a qu valor est asociada esa variable, y usaremos la notacin n 13 para describir el estado de la variable n (e indicar que est asociada al nmero 13).

1.8. Estado y computacin

17

A medida que las variables cambian de valores a los que se reeren, el programa va cambiando de estado. La sucesin de todos los estados por los que pasa el programa en una ejecucin dada se denomina computacin.

Para ejemplicar estos conceptos veamos qu sucede cuando se ejecuta el programa cuad:

Instruccin print "Se calcularn cuadrados de nmeros"

n1 = input("Ingrese un nmero entero: ")

n2 = input("Ingrese otro nmero entero: ")

for x in range(n1, n2): print x*x

Qu sucede Se despliega el texto Se calcularn cuadrados de nmeros en la pantalla. Se despliega el texto Ingrese un nmero entero: en la pantalla y el programa se queda esperando que el usuario ingrese un nmero. Ac supondremos que el usuario ingresa el nmero 3 y luego oprime la tecla <ENTER>. Se asocia el nmero 3 con la variable n1. Se despliega el texto Ingrese otro nmero entero: en la pantalla y el programa se queda esperando que el usuario ingrese un nmero. Ac supondremos que el usuario ingresa el nmero 5 y luego oprime la tecla <ENTER>. Se asocia el nmero 5 con la variable n2. Se asocia el primer nmero de [n1, n2 ) con la variable x y se ejecuta el cuerpo del ciclo:

Estado

n1 3

n1 3

n1 3 n2 5 n1 3 n2 5 x3

18

Unidad 1. Algunos conceptos bsicos Instruccin print x*x Qu sucede Se imprime por pantalla el valor de x * x (9) Se asocia el segundo nmero de [n1, n2 ) con x y se ejecuta el cuerpo del ciclo: Se imprime por pantalla el valor de x * x (16) Como no quedan ms valores sin tratar en [n1, n2 ), se sale del ciclo. Se despliega por pantalla el mensaje Es todo por ahora Estado n1 3 n2 5 x3 n1 3 n2 5 x4 n1 3 n2 5 x4 n1 3 n2 5 x4 n1 3 n2 5 x4

for x in range(n1, n2):

print x*x

for x in range(n1, n2):

print "Es todo por ahora"

1.9.

Depuracin de programas

Una manera de seguir la evolucin del estado es insertar instrucciones de impresin en sitios crticos del programa. Esto nos ser de utilidad para detectar errores y tambin para comprender cmo funcionan determinadas instrucciones.

1.10.

Ejercicios

Ejercicio 1.1. Corran tres veces el programa cuad con valores de entrada (3,5), (3,3) y (5,3) respectivamente. Qu sucede en cada caso? Ejercicio 1.2. Inserten instrucciones de depuracin que permitan ver el valor asociado con la variable x en el cuerpo del ciclo for y despus que se sale de tal ciclo. Vuelvan a correr tres veces el programa cuad con valores de entrada (3,5), (3,3) y (5,3) respectivamente, y expliquen lo que sucede. Ejercicio 1.3. La salida del programa cuad es poco informativa. Escriban un programa nom_cuad que ponga el nmero junto a su cuadrado. Ejecuten el programa nuevo. Ejercicio 1.4. Si la salida sigue siendo poco informativa sganla mejorando.

Unidad 2

Programas sencillos
En este captulo empezaremos a resolver problemas sencillos, y a programarlos en Python.

2.1.

Construccin de programas

Cuando nos piden que hagamos un programa debemos seguir una cierta cantidad de pasos para asegurarnos de que tendremos xito en la tarea. La accin irreexiva (me piden algo, me siento frente a la computadora y escribo rpidamente y sin pensarlo lo que me parece que es la solucin) no constituye una actitud profesional (e ingenieril) de resolucin de problemas. Toda construccin tiene que seguir una metodologa, un protocolo de desarrollo, dado. Existen muchas metodologas para construir programas, pero en este curso aplicaremos una metodologa sencilla, que es adecuada para la construccin de programas pequeos, y que se puede resumir en los siguientes pasos: 1. Analizar el problema. Entender profundamente cul es el problema que se trata de resolver, incluyendo el contexto en el cual se usar. Documentar el anlisis. 2. Especicar la solucin. ste es el punto en el cual se describe qu debe hacer nuestro programa, sin importarnos el cmo. En el caso de los problemas sencillos que abordaremos, deberemos decidir cules son los datos de entrada que se nos proveen, cules son las salidas que debemos producir, y cul es la relacin entre todos ellos. Documentar la especicacin. 3. Disear la solucin. ste es el punto en el cul atacamos el cmo vamos a resolver el problema, cules son los algoritmos y las estructuras de datos que usaremos. Analizamos posibles variantes, y las decisiones las tomamos usando como dato de la realidad el contexto en el que se aplicar la solucin, y los costos asociados a cada diseo. Documentar el diseo. 4. Implementar el diseo. Traducir a un lenguaje de programacin (en nuestro caso, y por el momento, Python) el diseo que elegimos en el punto anterior. 19

20

Unidad 2. Programas sencillos Documentar la implementacin. 5. Probar el programa. Disear un conjunto de pruebas para probar cada una de sus partes por separado, y tambin la correcta integracin entre ellas. Utilizar el depurador como instrumento para descubir dnde se producen ciertos errores. Documentar las pruebas. 6. Mantener el programa. Realizar los cambios en respuesta a nuevas demandas. Documentar los cambios.

2.2.

Un programa sencillo: convertir una longitud expresada en el sistema ingls al sistema mtrico

Qu hacemos si, al estar leyendo un artculo en una revista norteamericana que contiene informacin de longitudes expresadas en millas, pies y pulgadas, decidimos escribir un programa para convertir estas longitudes al sistema mtrico decimal? Sigamos la gua de la seccin anterior: 1. Anlisis del problema. En este caso el problema es sencillo: nos dan un valor expresado en millas, pies y pulgadas y queremos transformarlo en un valor en el sistema mtrico decimal. Sin embargo hay varias respuestas posibles, porque no hemos jado en qu unidad queremos el resultado. Supongamos que decidimos que queremos expresar todo en metros. 2. Especicacin. Debemos establecer la relacin entre los datos de entrada y los datos de salida. Ante todo debemos averiguar los valores para la conversin de las unidades bsicas. Buscando en Internet encontramos la siguiente tabla: A lo largo de todo el curso usaremos punto decimal, en lugar de coma decimal, para representar valores no enteros, dado que esa es la notacin que utiliza Python. 1 milla = 1.609344 km 1 pie = 30.48 cm 1 pulgada = 2.54 cm Esta tabla no traduce las longitudes a metros. La manipulamos para llevar todo a metros: 1 milla = 1609.344 m 1 pie = 0.3048 m 1 pulgada = 0.0254 m Si una longitud se expresa como L millas, F pies y P pulgadas, su conversin a metros se calcular como M = 1609.344 * L + 0.3048 * F + 0.0254 * P. Hemos especicado el problema. Pasamos entonces a la prxima etapa.

2.2. Un programa sencillo: convertir una longitud expresada en el sistema ingls al sistema mtrico 21 3. Diseo. La estructura de este programa es sencilla: leer los datos de entrada, calcular la solucin, mostrar el resultado, o Entrada-Clculo-Salida. Antes de escribir el programa, escribiremos en pseudocdigo (un castellano preciso que se usa para describir lo que hace un programa) una descripcin del mismo: Leer cuntas millas tiene la longitud dada (y referenciarlo con la variable millas) Leer cuntos pies tiene la longitud dada (y referenciarlo con la variable pies) Leer cuntas pulgadas tiene la longitud dada (y referenciarlo con la variable pulgadas) Calcular metros = 1609.344 * millas + 0.3048 * pies + 0.0254 * pulgadas Mostrar por pantalla metros

4. Implementacin. Ahora s estamos en condiciones de traducir este pseudocdigo a Python: # ametrico.py # Convierte medidas inglesas a sistema metrico # Autores: ... def main(): print "Convierte medidas inglesas a sistema metrico" millas = input("Cuntas millas?: ") pies = input("Y cuntos pies?: ") pulgadas = input("Y cuntas pulgadas?: ") metros = 1609.344 * millas + 0.3048 * pies + 0.0254 * pulgadas print "La longitud es de ", metros, " metros" main() Guardaremos este programa como ametrico.py. 5. Prueba. Probaremos el programa para valores para los que conocemos la solucin: 1 milla, 0 pies, 0 pulgadas. 0 millas, 1 pie, 0 pulgada. 0 millas, 0 pies, 1 pulgada. La prueba la documentaremos con la sesin de Python correspondiente a las tres invocaciones a ametrico.py.

22

Unidad 2. Programas sencillos

En la seccin anterior hicimos hincapi en la necesidad de documentar todo el proceso de desarrollo. En este ejemplo la documentacin completa del proceso lo constituye todo lo escrito en esta seccin.

Cuando entreguen un ejercicio, debern presentar el desarrollo completo con todas las etapas, desde el anlisis hasta las pruebas (y el mantenimiento, si hubo cambios).

2.3.

Piezas de un programa Python

Para poder empezar a programar en Python tienen que conocer los elementos que constituyen un programa en dicho lenguaje y las reglas para construirlos. Cuando empezamos a hablar en un idioma extranjero es posible que nos entiendan pese a que cometamos errores. No sucede lo mismo con los lenguajes de programacin: la computadora no nos entender si nos desviamos un poco de alguna de las reglas.

2.3.1.

Nombres

Ya hemos visto que se usan nombres para denominar a los programas (ametrico) y para denominar a las funciones dentro de un mdulo (main). Cuando queremos dar nombres a valores usamos variables (millas, pies, pulgadas, metros). Todos esos nombres se llaman identicadores y Python tiene reglas sobre qu es un identicador vlido y qu no lo es. Un identicador comienza con una letra o con guin bajo (_) y luego sigue con una secuencia de letras, nmeros y guiones bajos. Los espacios no estn permitidos dentro de los identicadores. Los siguientes son todos identicadores vlidos de Python: hola hola12t _hola Hola Python distingue maysculas de minsculas, as que Hola es un identicador y hola es otro identicador. Por convencin, no usaremos identicadores que empiezan con mayscula. Los siguientes son todos identicadores invlidos de Python: 8hola hola a12t hola\% Hola*9 Python reserva 31 palabras para describir la estructura del programa, y no permite que se usen como identicadores. Cuando en un programa nos encontramos con que un nombre no es admitido pese a que su formato es vlido, seguramente se trata de una de las palabras de esta lista, a la que llamaremos de palabras reservadas. La lista completa de las palabras reservadas de Python aparecen en el cuadro 2.1.

2.3. Piezas de un programa Python and as assert break class continue def del elif else except exec finally for from global if import in is lambda not or pass print raise return try while with yield

23

Cuadro 2.1: Las palabras reservadas de Python. Estas palabras no pueden ser usadas como identicadores.

2.3.2.

Expresiones

Una expresin es una porcin de cdigo Python que produce o calcula un valor (resultado). Un valor es una expresin (de hecho es la expresin ms sencilla). Por ejemplo el resultado de la expresin 111 es precisamente el nmero 111. Una variable es una expresin, y el valor que produce es el que tiene asociado en el estado (si x 5 en el estado, entonces el resultado de la expresin x es el nmero 5). Usamos operaciones para combinar expresiones y construir expresiones ms complejas: Si x es como antes, x + 1 es una expresin cuyo resultado es 6. Si en el estado millas 1, pies 0 y pulgadas 0, entonces 1609.344 * millas + 0.3048 * pies + 0.0254 * pulgadas es una expresin cuyo resultado es 1609.344. La exponenciacin se representa con el smbolo **. Por ejemplo, x**3 signica x3 . Se pueden usar parntesis para indicar un orden de evaluacin: ((b * b) - (4 * a * c)) / (2 * a). Igual que en la matemtica, si no hay parntesis en la expresin primero se agrupan las exponenciaciones, luego los productos y cocientes, y luego las sumas y restas. Sin embargo, hay que tener cuidado con lo que sucede con los cocientes, porque si x e y son nmeros enteros, entonces x / y se calcula como la divisin entera entre x e y: Si x se reere al valor 12 e y se reere al valor 9 entonces x / y se reere al valor 1. Si x e y son nmeros enteros, entonces x % y se calcula como el resto de la divisin entera entre x e y: Si x se reere al valor 12 e y se reere al valor 9 entonces x % y se reere al valor 3. Los nmeros pueden ser tanto enteros (111, -24), como reales (12.5, 12.0, -12.5). Dentro de la computadora se representan de manera diferente, y se comportan de manera diferente frente a las operaciones. Conocemos tambin dos expresiones muy particulares:

24

Unidad 2. Programas sencillos input, que devuelve el valor ingresado por teclado tal como se lo digita (en particular sirve para ingresar valores numricos). raw_input, que devuelve lo ingresado por teclado como si fuera un texto.

Ejercicio 2.1. Aplicando las reglas matemticas de asociatividad, decidan cules de las siguientes expresiones son iguales entre s: (i) ((b * b) - (4 * a * c)) / (2 * a), (ii) (b * b - 4 * a * c) / (2 * a), (iii) b * b - 4 * a * c / 2 * a, (iv) (b * b) - (4 * a * c / 2 * a) (v) 1 / 2 * b (vi) b / 2. En Python hagan lo siguiente: Denle a a, b y c los valores 10, 100 y 1000 respectivamente y evalen las expresiones del punto anterior. En Python hagan lo siguiente: Denle a a, b y c los valores 10.0, 100.0 y 1000.0 respectivamente y evalen las expresiones del punto anterior.

2.4.

No slo de nmeros viven los programas

No slo tendremos expresiones numricas en un programa Python. Recuerden el programa que se us para saludar a muchos amigos: >>> def hola(alguien): print "Hola", alguien, "!" print "Estoy programando en Python." >>> Para invocar a ese programa y hacer que saludara a Ana haba que escribir hola("Ana"). La variable alguien en dicha invocacin queda ligada a un valor que es una cadena de caracteres (letras, dgitos, smbolos, etc.): Ana. Python usa tambin una notacin con comillas simples para referirse a las cadenas de caracteres, y habla de Ana. Como en la seccin anterior, veremos las reglas de qu constituyen expresiones con caracteres: Un valor tambin ac es una expresin. Por ejemplo el resultado de la expresin Ana es precisamente Ana. Una variable es una expresin, y el valor que produce es el que tiene asociado en el estado (si amiga Ana en el estado, entonces el resultado de la expresin amiga es la cadena Ana). Usamos operaciones para combinar expresiones y construir expresiones ms complejas, pero atencin con qu operaciones estn permitidas sobre cadenas:

2.5. Instrucciones

25

El signo + no representa la suma sino la concatenacin de cadenas: Si amiga es como antes, amiga + Perez es una expresin cuyo valor es AnaPerez. No se pueden sumar cadenas ms nmeros >>> amiga="Ana" >>> amiga+Perez AnaPerez >>> amiga+3 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: cannot concatenate str and int objects >>> El signo * se usa para indicar cuntas veces se repite una cadena: amiga * 3 es una expresin cuyo valor es AnaAnaAna. No se pueden multiplicar cadenas entre s >>> amiga * 3 AnaAnaAna >>> amiga * amiga Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: cant multiply sequence by non-int of type str

2.5.

Instrucciones

Las instrucciones son las rdenes que entiende Python. Ya hemos usado varias instrucciones: hemos mostrado valores por pantalla mediante la instruccin print, hemos retornado valores de una funcin mediante la instruccin return, hemos asociado valores con variables y hemos usado un ciclo para repetir un clculo.

2.6.

Ciclos denidos

Hemos ya usado la instruccin for x in range(n1, n2): print x*x en el programa que calcula cuadrados de enteros en un rango. Este ciclo se llama denido porque de entrada, y una vez ledos n1 y n2, se sabe exactamente cuntas veces se ejecutar el cuerpo y qu valores tomar x. Un ciclo denido es de la forma

26

Unidad 2. Programas sencillos

for <variable> in <secuencia de valores>: <cuerpo>

En nuestro ejemplo la secuencia de valores es el intervalo de enteros [n1, n1+1, ..., n2-1] y la variable es x. La secuencia de valores se puede indicar como: range(n). Establece como secuencia de valores a [0, 1, ..., n-1]. range(n1, n2). Establece como secuencia de valores a [n1, n1+1, ..., n2-1]. Se puede denir a mano una secuencia entre corchetes: for x in [1, 3, 9, 27]: print x*x imprimir los cuadrados de los nmeros 1, 3, 9 y 27.

2.7.

Una gua para el diseo

En su artculo How to program it, Simon Thompson plantea algunas preguntas a sus alumnos que son muy tiles para la etapa de diseo: Han visto este problema antes, aunque sea de manera ligeramente diferente? Conocen un problema relacionado? Conocen un programa que puede ser til? Fjense en la especicacin. Traten de encontrar un problema que les resulte familiar y que tenga la misma especicacin o una parecida. Ac hay un problema relacionado con el que ustedes tienen y que ya fue resuelto. Lo pueden usar? Pueden usar sus resultados? Pueden usar sus mtodos? Pueden agregarle alguna parte auxiliar a ese programa del que ya disponen? Si no pueden resolver el problema propuesto, traten de resolver uno relacionado. Pueden imaginarse uno relacionado que sea ms fcil de resolver? Uno ms general? Uno ms especco? Un problema anlogo? Pueden resolver una parte del problema? Pueden sacar algo til de los datos de entrada? Pueden pensar qu informacin es til para calcular las salidas? De qu manera se pueden manipular las entradas y las salidas de modo tal que estn ms cerca unas de las otras? Usaron todos los datos de entrada? Usaron las condiciones especiales sobre los datos de entrada que aparecen en el enunciado? Han tenido en cuenta todos los requisitos que se enuncian en la especicacin?

2.8. Ejercicios

27

2.8.

Ejercicios

Ejercicio 2.2. Escriban un ciclo denido para imprimir por pantalla todos los nmeros entre 10 y 20. Ejercicio 2.3. Escriban un ciclo denido que salude por pantalla a sus cinco mejores amigos/as. Ejercicio 2.4. Escriban un programa que use un ciclo denido con rango numrico, que pregunte los nombres de sus cinco mejores amigos/as, y los salude. Ejercicio 2.5. Escriban un programa que use un ciclo denido con rango numrico, que pregunte los nombres de sus seis mejores amigos/as, y los salude. Ejercicio 2.6. Escriban un programa que use un ciclo denido con rango numrico, que averigue a cuntos amigos quieren saludar, les pregunte los nombres de esos amigos/as, y los salude.

28

Unidad 2. Programas sencillos

Unidad 3

Funciones
En la primera unidad vimos que el programador puede denir nuevas instrucciones, que llamamos funciones. En particular lo aplicamos a la construccin de una funcin llamada hola que salude a todos a quienes queramos saludar: >>> def hola(alguien): ... print "Hola ", alguien,"!" ... print "Estoy programando en Python." ... >>> Dijimos en esa ocasin que las funciones tienen partes variables, llamadas parmetros, que se asocian a un valor distinto en cada invocacin. El valor con el que se asocia un parmetro se llama argumento. En nuestro caso la invocamos dos veces, para saludar a Ana y a Juan, haciendo que alguien se asocie al valor Ana en la primera llamada y al valor Juan en la segunda: >>> hola("Ana") Hola Ana ! Estoy programando en Python. >>> hola("Juan") Hola Juan ! Estoy programando en Python. >>> Una funcin puede tener ninguno, uno o ms parmetros. La funcin hola tiene un parmetro. Ya vimos tambin ejemplos de funciones sin parmetros: >>> def holaPab(): print "Hola Pablo!" print "Estoy programando en Python." >>> En el caso de tener ms de un parmetro, stos se separan entre s por comas, y en la invocacin tambin se separan por comas los argumentos. A continuacin se dene una funcin print_asegundos (horas, minutos, segundos) con tres parmetros (horas, minutos y segundos) que imprime por pantalla la transformacin a segundos de una medida de tiempo expresada en horas, minutos y segundos: 29

30

Unidad 3. Funciones

>>> def print_asegundos (horas, minutos, segundos): ... """ Transforma en segundos una medida de tiempo expresada en ... horas, minutos y segundos """ ... segsal = 3600 * horas + 60 * minutos + segundos # regla de transformaci ... print "Son", segsal, "segundos" ... >>> print_asegundos (1, 10, 10) Son 4210 segundos >>>

Contar con funciones es de gran utilidad, ya que nos permite ir armando una biblioteca de instrucciones con problemas que vamos resolviendo, y que se pueden reutilizar en la resolucin de nuevos problemas (como partes de un problema ms grande, por ejemplo) tal como lo sugiere Thompson en How to program it. Sin embargo, ms til que tener una biblioteca donde los resultados se imprimen por pantalla, es contar con una biblioteca donde los resultados se devuelven, para que la gente que usa esas funciones manipule esos resultados a voluntad: los imprima, los use para realizar clculos ms complejos, etc.

>>> def calc_asegundos (horas, minutos, segundos): ... """ Transforma en segundos una medida de tiempo expresada en ... horas, minutos y segundos """ ... segsal = 3600 * horas + 60 * minutos + segundos # regla de transformacio ... return segsal ... >>> print calc_asegundos (1, 10, 10) 4210 >>> print "Son", calc_asegundos (1, 10, 10), "segundos" Son 4210 segundos >>> y = calc_asegundos(1, 10, 10) >>> z = calc_asegundos(2, 20, 20) >>> y+z 12630 >>>

Ejercicio 3.1. Escriban una funcin rep_hola que reciba como parmetro un nmero entero n y escriba por pantalla el mensaje Hola n veces. Invquenla. Ejercicio 3.2. Escriban una funcin rep_hola que reciba como parmetro un nmero entero n y retorne la cadena formada por n concatenaciones de Hola. Invquenla. Ejercicio 3.3. Escriban una funcin rep_saludo que reciba como parmetro un nmero entero n y una cadena saludo y escriba por pantalla el valor de saludo n veces. Invquenla. Ejercicio 3.4. Escriban una funcin rep_saludo que reciba como parmetro un nmero entero n y una cadena saludo retorne el valor de n concatenaciones de saludo. Invquenla

3.1. Cmo usar una funcin en un programa

31

3.1.

Cmo usar una funcin en un programa

Una funcin es til porque nos permite repetir la misma instruccin (puede que con argumentos distintos) todas las veces que las necesitemos en un programa. Para utilizar la funcin anterior, escribiremos un programa que pide tres duraciones, y en los tres casos las transforma a segundos y las muestra por pantalla. 1. Anlisis: El programa debe pedir tres duraciones expresadas en hs, minutos y segundos, y las tiene que mostrar en pantalla expresadas en segundos. 2. Especicacin: Entradas: Tres duraciones ledas de teclado y expresadas en horas, minutos y segundos. Salidas: Mostrar por pantalla cada una de las duraciones ingresadas, convertidas a segundos. Para cada juego de datos de entrada (h, m, s) se obtiene entonces 3600 * h + 60 * m + s, y se muestra ese resultado por pantalla. 3. Diseo: Se tienen que leer tres conjuntos de datos y para cada conjunto hacer lo mismo, se trata entonces de un programa con estructura de ciclo denido de tres pasos: repetir 3 veces: <hacer cosas> El cuerpo del ciclo ( <hacer cosas>) tiene la estructura Entrada-Clculo-Salida. En pseudocdigo: Leer cuntas horas tiene el tiempo dado (y referenciarlo con la variable hs) Leer cuntos minutos tiene tiene el tiempo dado (y referenciarlo con la variable min) Leer cuntos segundos tiene el tiempo dado (y referenciarlo con la variable seg) Mostrar por pantalla 3600 * hs + 60 * min + seg Pero convertir y mostrar por pantalla es exactamente lo que hace nuestra funcin print_asegundos, por lo que podemos hacer que el cuerpo del ciclo se disee como: Leer cuntas horas tiene la duracin dada (y referenciarlo con la variable hs) Leer cuntos minutos tiene tiene la duracin dada (y referenciarlo con la variable min)

32

Unidad 3. Funciones Leer cuntas segundos tiene la duracin dada (y referenciarlo con la variable seg) Invocar la funcin print_asegundos(hs, min, seg) El pseudocdigo queda: repetir 3 veces: Leer cuntas horas tiene la duracin dada (y referenciarlo con la variable hs) Leer cuntos minutos tiene la duracin dada (y referenciarlo con la variable min) Leer cuntos segundos tiene la duracin dada (y referenciarlo con la variable seg) Invocar la funcin print_asegundos(hs, min, seg) 4. Implementacin: Resulta el siguiente programa Python:

# Programa tres_tiempos.py # Lee tres tiempos expresados en hs, min y seg # y usa print_asegundos para mostrar en pantalla la conversin a segundo def print_asegundos (horas, minutos, segundos): segsal = 3600 * horas + 60 * minutos + segundos print "Son",segsal, "segundos" def main(): for x in range(3): hs = raw_input("Cuantas horas?: ") min = raw_input("Cuantos minutos?: ") seg = raw_input("Cuantos segundos?: ") print_asegundos(hs, min, seg)

main()

5. Prueba: Probamos el programa con las ternas (1,0,0), (0,1,0) y (0,0,1): >>> import tres_tiempos Cuantas horas?: 1 Cuantos minutos?: 0 Cuantos segundos?: 0 Son 3600 segundos

3.2. Ms sobre los resultados de las funciones Cuantas horas?: 0 Cuantos minutos?: 1 Cuantos segundos?: 0 Son 60 segundos Cuantas horas?: 0 Cuantos minutos?: 0 Cuantos segundos?: 1 Son 1 segundos >>> Ejercicio 3.5. Resolver el problema anterior usando ahora la funcin calc_asegundos.

33

3.2.

Ms sobre los resultados de las funciones

Ya hemos visto cmo hacer para que las funciones que se comporten como las funciones que conocemos, las de la matemtica, que se usan para calcular resultados. Veremos ahora varias cuestiones a tener en cuenta al escribir funciones. Para ello volvemos a escribir una funcin que eleva al cuadrado un nmero. >>> def cuadrado (x): ... cua = x * x ... return cua ... >>> y = cuadrado (5) >>> y 25 >>> Por qu no usamos dentro del programa el valor cua calculado dentro de la funcin? >>> def cuadrado (x): ... cua = x * x ... return cua ... >>> cua Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name cua is not defined >>> Las variables y los parmetros que se declaran dentro de una funcin no existen fuera de ella, no se los conoce. Fuera de la funcin se puede ver slo el valor que retorna y es por eso que es necesario introducir la instruccin return. Para qu hay que introducir un return en la funcin? No alcanza con el valor que se calcula dentro de la misma para que se considere que la funcin retorna un valor? En Python no alcanza (hay otros lenguajes en los que se considera que el ltimo valor calculado en una funcin es el valor de retorno de la misma).

34

Unidad 3. Funciones

>>> def cuadrado (x): ... cua = x * x ... >>> y = cuadrado (5) >>> y >>> Cuando se invoca la funcin cuadrado mediante la instruccin y = cuadrado (5) lo que sucede es lo siguiente: Se invoca a cuadrado con el argumento 5, y se ejecuta el cuerpo de la funcin. El valor que retorna la funcin se asocia con la variable y.

3.3.

Un ejemplo completo

Un usuario nos plantea su problema: necesita que se facture el uso de un telfono. Nos informar la tarifa por segundo, cuntas comunicaciones se realizaron, la duracin de cada comunicacin expresada en horas, minutos y segundos. Como resultado deberemos informar la duracin en segundos de cada comunicacin y su costo. Aplicaremos los pasos aprendidos: 1. Anlisis: Cuntas tarifas distintas se usan? Una sola (f). Cuntas comunicaciones se realizaron? La cantidad de comunicaciones (n) se informa cuando se inicia el programa. En qu formato vienen las duraciones de las comunicaciones? Vienen como ternas (h, m, s). Qu se hace con esas ternas? Se convierten a segundos y se calcula el costo de cada comunicacin multiplicando el tiempo por la tarifa. 2. Especicacin: Entradas: Una tarifa f expresada en pesos/segundo. Una cantidad n de llamadas telefnicas. n duraciones de llamadas ledas de teclado y expresadas en horas, minutos y segundos. Salidas: Mostrar por pantalla las n duraciones ingresadas, convertidas a segundos, y su costo. Para cada juego de datos de entrada (h, m, s) se imprimen 3600 * h + 60 * m + s, f * (3600 * h + 60 * m + s) . 3. Diseo:

3.3. Un ejemplo completo

35

Siguiendo a Thompson, buscamos un programa que haga algo anlogo, y vemos si se lo puede modicar para resolver nuestro problema. tres_tiempos se parece bastante a lo que necesitamos. Veamos las diferencias entre sus especicaciones.
Diferencias tarifador debe leer el valor de la tarifa (f) En tres_tiempos se conoce la cantidad de ternas (3), en tarifador la cantidad de ternas (n) es un dato que hay que ingresar. El cuerpo del ciclo de tres_tiempos lee una terna y llama a print_asegundos que calcula, imprime y no retorna valores Si hacemos lo mismo en tarifador no podremos calcular el costo de la comunicacin. Debemos cambiar print_asegundos por una modicacin que llamaremos asegundos que slo calcule el valor transformado y lo retorne. En main se har el clculo del costo y la impresin de los resultados. tres_tiempos tarifador leer el valor de f leer el valor de n repetir n veces: <hacer otras cosas> El cuerpo del ciclo: Leer el valor de hs Leer el valor de min Leer el valor de seg Asignar segcalc = asegundos(hs, min, seg) Calcular costo = segcalc * f Mostrar por pantalla segsal y costo asegundos (horas, minutos, segundos): segsal = 3600*horas+60*minutos+segundos Retornar segsal repetir 3 veces: <hacer cosas>

El cuerpo del ciclo: Leer el valor de hs Leer el valor de min Leer el valor de seg Invocar print_asegundos(hs, min, seg)

print_asegundos (horas, minutos, segundos): segsal = 3600*horas+60*minutos+segundos Mostrar segsal por pantalla

4. Implementacin: El siguiente es el programa resultante: # # # # # # Programa tarifador.py Se factura el uso de un telefono. Se informa la tarifa por segundo, cuantas comunicaciones se realizaron, la duracion de cada comunicacion expresada en horas, minutos y segundos. Como resultado se informa la duracion en segundos de cada comunicacion y su costo.

def asegundos (horas, minutos, segundos): segsal = 3600 * horas + 60 * minutos + segundos return segsal def main(): f = input("Cuanto cuesta 1 segundo de comunicacion?: ") n = input("Cuantas comunicaciones hubo?: ") for x in range(n): hs = input("Cuantas horas?: ") min = input("Cuantos minutos?: ") seg = input("Cuantos segundos?: ") segcalc = asegundos(hs, min, seg) costo = segcalc * f print "Duracion: ", segcalc, "segundos. Costo: ",costo, "$."

main() 5. Prueba: Lo probamos con una tarifa de 0.40$ el segundo y tres ternas de (1,0,0), (0,1,0) y (0,0,1). sta es la corrida: >>> import tarifador Cuanto cuesta 1 segundo de comunicacion?: 0.40 Cuantas comunicaciones hubo?: 3 Cuantas horas?: 1

36

Unidad 3. Funciones Cuantos minutos?: 0 Cuantos segundos?: 0 Duracion: 3600 segundos. Costo: 1440.0 $. Cuantas horas?: 0 Cuantos minutos?: 1 Cuantos segundos?: 0 Duracion: 60 segundos. Costo: 24.0 $. Cuantas horas?: 0 Cuantos minutos?: 0 Cuantos segundos?: 1 Duracion: 1 segundos. Costo: 0.4 $. >>> 6. Mantenimiento: Ejercicio 3.6. Hay que corregir el programa para que: Los centavos se escriban con dos decimales. Informe adems cul fue el total facturado en la corrida.

3.4.

Devolver mltiples resultados

Ahora nos piden que escribamos una funcin que dada una duracin en segundos sin fracciones (representada por un nmero entero) calcule la misma duracin en horas, minutos y segundos. La especicacin es sencilla: La cantidad de horas es la duracin informada en segundos dividida por 3600 (divisin entera). La cantidad de minutos es el resto de la divisin del paso 1, dividido por 60 (divisin entera). La cantidad de segundos es el resto de la divisin del paso 2. Observen que si la duracin no se informa como un nmero entero, todas las operaciones que se indican ms arriba carecen de sentido. Cmo hacemos para devolver ms de un valor? En realidad lo que se espera de esta funcin es que devuelva una terna de valores: si ya calculamos hs, min y seg, lo que debemos retornar es la terna (hs, min, seg): >>> def aHsMinSeg (x): ... """ dada una duracion en segundos sin fracciones ... (la funcion debe invocarse con numeros enteros) ... se la convierte a horas, minutos y segundos """ ... hs = x / 3600 ... min = (x % 3600) / 60 ... seg = (x % 3600 ) % 60 ... return (hs, min, seg)

3.4. Devolver mltiples resultados ... >>> >>> Son >>> >>> Son >>>

37

(h, m, s) = aHsMinSeg(3661) print "Son",h,"horas",m,"minutos",s,"segundos" 1 horas 1 minutos 1 segundos (h, m, s) = aHsMinSeg(3661.0) # aca violamos la especificacion print "Son",h,"horas",m,"minutos",s,"segundos" # y esto es lo que pasa: 1.0169444444444444 horas 1.0166666666666666 minutos 1.0 segundos Cuando la funcin debe retornar mltiples resultados se empaquetan todos juntos en una n-upla del tamao adecuado.

Respecto de la variable que har referencia al resultado de la invocacin, se podr usar tanto una n-upla de variables como en el ejemplo anterior, en cuyo caso podremos nombrar en forma separada cada uno de los resultados, o bien se podr usar una sola variable, en cuyo caso se considerar que el resultado tiene un solo nombre y la forma de una n-upla: >>> t=aHsMinSeg(3661) >>> print t (1, 1, 1) >>> Si se usa una n-upla de variables para referirse a un resultado, la cantidad de variables tiene que coincidir con la cantidad de valores que se retornan: >>> (x,y)=aHsMinSeg(3661) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: too many values to unpack >>> (x,y,w,z)=aHsMinSeg(3661) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: need more than 3 values to unpack >>>

38

Unidad 3. Funciones

Unidad 4

Decisiones
Nos plantean el siguiente problema: Problema 4.1. Debemos leer un nmero y, si el nmero es positivo, debemos escribir en pantalla el cartel Numero positivo. Especicamos nuestra solucin: Se deber leer un nmero x. Si x > 0 se escribe el cartel Numero positivo. Diseamos nuestra solucin: 1. Desplegar un mensaje y leer x. 2. Si x > 0 a) Imprimir Numero positivo Es claro que la primera lnea se puede traducir como x = input("Ingrese un numero: ") Sin embargo, con las instrucciones que vimos hasta ahora no podemos tomar el tipo de decisiones que nos planteamos a partir de la segunda lnea de este diseo. Para resolver este problema introducimos una nueva instruccin que llamaremos condicional que tiene la siguiente forma: if <condicin>: <hacer algo si se da la condicin> donde if es una palabra reservada. Qu es la condicin que aparece luego de la palabra reservada if? Antes de seguir adelante con la construccin debemos introducir un nuevo tipo de expresin que nos indicar si se da una cierta situacin o no. Hasta ahora las expresiones con las que trabajamos fueron de tipo numrica y de tipo texto. Pero ahora la respuesta que buscamos es de tipo s o no.

4.1.

Expresiones booleanas

Adems de los nmeros y los textos que vimos hasta ahora, Python introduce las constantes True y False para representar los valores de verdad verdadero y falso respectivamente. Vimos que una expresin es un trozo de cdigo Python que produce o calcula un valor (resultado). Una expresin booleana o expresin lgica es una expresin que vale o bien True o bien False. 39

40

Unidad 4. Decisiones

4.1.1.

Expresiones de comparacin

En el ejemplo que queremos resolver, la condicin que queremos ver si se cumple o no es x > 0. Python provee las llamadas expresiones de comparacin que sirven para comparar valores entre s, y que por lo tanto permiten codicar ese tipo de pregunta. En particular la pregunta x > 0 se codica en Python como x >0. Vemos que 5 > 3 es una expresin booleana cuyo valor es True, y 5 < 3 tambin es una expresin booleana, pero su valor es False. >>> 5 > 3 True >>> 3 > 5 False >>> Los expresiones booleanas de comparacin que provee Python son las siguientes: Expresin Signicado a == b a es igual a b a != b a es distinto de b a <b a es menor que b a <= b a es menor o igual que b a >b a es mayor que b a >= b a es mayor o igual que b >>> 6==6 True >>> 6!=6 False >>> 6>6 False >>> 6>=6 True >>> 6>4 True >>> 6<4 False >>> 6<=4 False >>> 4<6 True >>>

4.1.2.

Operadores lgicos

De la misma manera que se puede operar entre nmeros mediante las operaciones de suma, resta, etc., tambin existen tres operadores lgicos para combinar expresiones booleanas: and (y), or (o) y not (no). El signicado de estos operadores es igual al del castellano, pero vale la pena recordarlo:

4.2. Comparaciones simples Expresin a and b a or b not a Signicado El resultado es True solamente si a es True y b es True de lo contrario el resultado es False El resultado es True si a es True o b es True de lo contrario el resultado es False El resultado es True si a es False de lo contrario el resultado es False

41

a >b and a >c es verdadero si a es simultneamente mayor que b y que c. >>> 5>2 and 5>3 True >>> 5>2 and 5>6 False >>> a >b or a >c es verdadero si a es mayor que b o a es mayor que c. >>> 5>2 or 5>3 True >>> 5>2 or 5>6 True >>> 5>8 or 5>6 False >>> not (a >b) es verdadero si a >b es falso (o sea si a <= b es verdadero). >>> 5>8 False >>> not (5>8) True >>> 5>2 True >>> not (5>2) False >>>

4.2.

Comparaciones simples

Volvemos al problema que nos plantearon: Debemos leer un nmero y, si el nmero es positivo, debemos escribir en pantalla el cartel Numero positivo. Retomamos entonces la instruccin if que introdujimos en el primer prrafo, y que sirve para tomar decisiones simples. Dijimos que su formato ms sencillo es: if <condicin>: <hacer algo si se da la condicin>

42

Unidad 4. Decisiones

cuyo signicado es el siguiente: se evala <condicin> y si el resultado es True (verdadero) se ejecutan las acciones indicadas como <hacer algo si se da la condicin>. Como ahora ya sabemos tambin cmo construir condiciones de comparacin, estamos en condiciones de implementar nuestra solucin. Escribimos la funcin es_positivo() que hace lo pedido: >>> def es_positivo(): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... >>> y la probamos: >>> es_positivo() Ingrese un numero: 4 Numero positivo >>> es_positivo() Ingrese un numero: -25 >>> es_positivo() Ingrese un numero: 0 >>> En la etapa de mantenimiento nos dicen que, en realidad, tambin se necesitara un cartel Numero no positivo cuando no se cumple la condicin. Modicamos la especicacin consistentemente y modicamos el diseo: 1. Desplegar un mensaje y leer x. 2. Si x > 0 a) Imprimir Numero positivo 3. Si no se cumple x > 0 a) Imprimir Numero no positivo La negacin de x > 0 es (x > 0) que se traduce en Python como not (x >0), por lo que implementamos nuestra solucin en Python como: >>> def positivo_o_no(): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... if not (x > 0): ... print "Numero no positivo" ... >>> Probamos la nueva solucin y obtenemos el resultado buscado:

4.2. Comparaciones simples >>> positivo_o_no() Ingrese un numero: 4 Numero positivo >>> positivo_o_no() Ingrese un numero: -25 Numero no positivo >>> positivo_o_no() Ingrese un numero: 0 Numero no positivo >>>

43

Sin embargo hay algo que nos preocupa: si ya averiguamos una vez, en la segunda lnea del cuerpo, si x >0, tenemos que volverlo a preguntar en la cuarta?. Existe una construccin de alternativa que soporta la estructura de decisin: Si se da la condicin C, hacer S, de lo contrario, hacer T. Esta estructura tiene la forma: if <condicin>: <hacer algo si se da la condicin> else: <hacer otra cosa si no se da la condicin> donde if y else son palabras reservadas. Su signicado es el siguiente: se evala <condicin>, si el resultado es True (verdadero) se ejecutan las acciones indicadas como <hacer algo si se da la condicin>, y si el resultado es False (falso) se ejecutan las acciones indicadas como <hacer otra cosa si no se da la condicin>. Volvemos a nuestro diseo: 1. Desplegar un mensaje y leer x. 2. Si x > 0 a) Imprimir Numero positivo 3. de lo contrario a) Imprimir Numero no positivo Este diseo se implementa como: >>> def positivo_o_no_nue(): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... else: ... print "Numero no positivo" ... >>> y lo probamos: >>> positivo_o_no_nue() Ingrese un numero: 4

44

Unidad 4. Decisiones

Numero positivo >>> positivo_o_no_nue() Ingrese un numero: -25 Numero no positivo >>> positivo_o_no_nue() Ingrese un numero: 0 Numero no positivo >>> Es importante destacar que, en general, negar la condicin del if y poner else no son intercambiables, no necesariamente producen el mismo efecto en el programa. Fjense qu sucede en los dos programas que se transcriben a continuacin (discutan el por qu de los resultados):
>>> def pn(): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... x = -x ... if x < 0: ... print "Numero no positivo" ... >>> pn() Ingrese un numero: 25 Numero positivo Numero no positivo >>> >>> def pn1(): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... x = -x ... else: ... print "Numero no positivo" ... >>> pn1() Ingrese un numero: 25 Numero positivo >>>

4.3.

Mltiples decisiones consecutivas

La decisin de incluir una alternativa en un programa parte de una lectura cuidadosa de la especicacin. En nuestro caso la especicacin nos deca Si el nmero es positivo escribir un cartel Numero positivo, de lo contrario escribir un cartel Numero no positivo. Veamos qu se puede hacer cuando se presentan tres o ms alternativas: Si el nmero es positivo escribir un cartel Numero positivo, si el nmero es igual a 0 un cartel Igual a 0, y si el nmero es negativo escribir un cartel Numero negativo. Una posibilidad es considerar que se trata de una estructura con dos casos como antes, slo que el segundo caso es complejo (es nuevamente una alternativa): 1. Desplegar un mensaje y leer x. 2. Si x > 0 a) Imprimir Numero positivo 3. de lo contrario

4.3. Mltiples decisiones consecutivas a) Si x = 0 1) Imprimir Igual a 0 b) de lo contrario 1) Imprimir Numero negativo Este diseo se implementa como: >>> def pcn1(): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... else: ... if x == 0: ... print "Igual a 0" ... else: ... print "Numero negativo" ... >>>

45

Esta estructura se conoce como de alternativas anidadas ya que dentro de una de las ramas de la alternativa (en este caso la rama del else) se anida otra alternativa. Pero sta no es la nica forma de implementarlo. Existe otra construccin, equivalente a la anterior pero que no exige sangras cada vez mayores en el texto. Se trata de la estructura de alternativas encadenadas, que tiene la forma if <condicin1 >: <hacer algo1 si se da la condicin1 > elif <condicin2 >: <hacer algo2 si se da la condicin2 > ... elif <condicinn >: <hacer algon si se da la condicinn > else: <hacer otra cosa si no se da ninguna de las condicin anteriores> donde if, elif y else son palabras reservadas. En nuestro ejemplo: >>> def pcn2(): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... elif x == 0: ... print "Igual a 0" ... else: ... print "Numero negativo" ... >>> Se evala la primera alternativa, si es verdadera se ejecuta su cuerpo. de lo contrario se evala la segunda alternativa, si es verdadera se ejecuta su cuerpo, etc. Finalmente, si todas las alternativas anteriores fallaron, se ejecuta el cuerpo del else.

46

Unidad 4. Decisiones

4.4.

Ejercicios

Ejercicio 4.1. El usuario del tarifador nos pide ahora una modicacin, ya que no es lo mismo la tarifa por segundo de las llamadas cortas que la tarifa por segundo de las llamadas largas. Al inicio del programa se informar la duracin mxima de una llamada corta, la tarifa de las llamadas cortas y la de las largas. Se deber facturar con alguno de los dos valores de acuerdo a la duracin de la comunicacin. Ejercicio 4.2. Mantenimiento del tarifador: [(a)] 1. Otra vez hay que corregir el programa para que: Los centavos se escriban con dos decimales. Informe adems cul fue el total facturado en la corrida. 2. Nos piden que modiquemos el programa para que slo informe cantidad de llamadas cortas, valor total de llamadas cortas facturadas, cantidad de llamadas largas, valor total de llamadas largas facturadas, y total facturado. Ya va siendo hora de encapsular algunos clculos en funciones! Ejercicio 4.3. Dados tres puntos en el plano expresados como coordenadas (x, y) informar cul es el que se encuentra ms lejos del centro de coordenadas.

Unidad 5

Ms sobre ciclos
Volvamos al problema del captulo anterior que deca: Leer un nmero. Si el nmero es positivo escribir un cartel Numero positivo, si el nmero es igual a 0 un cartel Igual a 0, y si el nmero es negativo escribir un cartel Numero negativo. Ahora nos piden que el usuario pueda ingresar muchos nmeros y cada vez que se ingresa uno informemos si es positivo, cero o negativo. Como recordamos los ciclos denidos que vimos hace algunas clases, le ofrecemos al usuario preguntarle cada vez, al inicio del programa, cuntos nmeros va a ingresar para consultar. La solucin propuesta resulta: >>> def muchos_pcn(): ... i = input("Cuantos numeros quiere procesar: ") ... for j in range(0,i): ... x = input("Ingrese un numero: ") ... if x > 0: ... print "Numero positivo" ... elif x == 0: ... print "Igual a 0" ... else: ... print "Numero negativo" ... >>> Su ejecucin es exitosa: >>> muchos_pcn() Cuantos numeros quiere procesar: 3 Ingrese un numero: 25 Numero positivo Ingrese un numero: 0 Igual a 0 Ingrese un numero: -5 Numero negativo >>> 47

48

Unidad 5. Ms sobre ciclos

Sin embargo al usuario considera que este programa no es muy intuitivo, porque lo obliga a contar de antemano cuntos nmeros va a querer procesar, sin equivocarse, en lugar de ingresar uno a uno los nmeros hasta procesarlos a todos.

5.1.

Ciclos indenidos

Para poder resolver este problema sin averiguar primero la cantidad de nmeros a procesar, debemos introducir una instruccin que nos permita construir ciclos que no requieran que se informe de antemano la cantidad de veces que se repetir el clculo del cuerpo. Se trata de ciclos indenidos en los cuales se repite el clculo del cuerpo mientras una cierta condicin es verdadera. Un ciclo indenido es de la forma while <condicin>: <hacer algo> donde while es una palabra reservada. Ac la condicin es una expresin booleana, igual que en las instrucciones if. Y el cuerpo es, como siempre, una o ms instrucciones de Python. El sentido de esta instruccin es el siguiente: 1. Evaluar la condicin. 2. Si la condicin es falsa, salir del ciclo. 3. Si la condicin es verdadera, ejecutar el cuerpo. 4. Volver a 1.

5.2.

Ciclo interactivo

Cul es la condicin y cul es el cuerpo del ciclo en nuestro problema? Claramente, el cuerpo del ciclo es el clculo. En cuanto a la condicin, es que haya ms datos para seguir calculando. Denimos una variable hayMasDatos, que valdr Si mientras haya datos. Se le debe preguntar al usuario, despus de cada clculo, si hay o no ms datos. Cuando el usuario deje de responder Si, dejaremos de ejecutar el cuerpo del ciclo. >>> def pcn_loop(): while hayMasDatos == "Si": x = input("Ingrese un numero: ") if x > 0: print "Numero positivo" elif x == 0: print "Igual a 0" else: print "Numero negativo" hayMasDatos = input("Quiere seguir? <Si-No>: ")

5.2. Ciclo interactivo >>> Veamos qu pasa cuando ejecutamos la funcin: >>> pcn_loop()

49

Traceback (most recent call last): File "<pyshell#25>", line 1, in <module> pcn_loop() File "<pyshell#24>", line 2, in pcn_loop while hayMasDatos == "Si": UnboundLocalError: local variable hayMasDatos referenced before assignment >>> El problema es que hayMasDatos no tiene un valor asignado en el momento de evaluar la condicin del ciclo por primera vez. Es importante prestar atencin a cules son las variables que hay que inicializar antes de ejecutar un ciclo: al menos tiene que tener algn valor la expresin booleana que lo controla. Una posibilidad es preguntarle al usario, antes de evaluar la condicin, si tiene datos; otra posibilidad es suponer que si llam a este programa es porque tena algn dato para calcular, y darle el valor inicial Si a hayMasDatos. Ac encararemos la segunda posibilidad: >>> def pcn_loop(): hayMasDatos = "Si" while hayMasDatos == "Si": x = input("Ingrese un numero: ") if x > 0: print "Numero positivo" elif x == 0: print "Igual a 0" else: print "Numero negativo" hayMasDatos = input("Quiere seguir? <Si-No>: ")

>>> El esquema del ciclo interactivo es el siguiente: hayMasDatos hace referencia a Si. Mientras hayMasDatos haga referencia a "Si: Pedir datos.

50

Unidad 5. Ms sobre ciclos Realizar clculos. Preguntar al usuario si hay ms datos (Si cuando los hay). hayMasDatos hace referencia al valor ingresado. sta es una ejecucin:

>>> pcn_loop() Ingrese un numero: 25 Numero positivo Quiere seguir? <Si-No>: "Si" Ingrese un numero: 0 Igual a 0 Quiere seguir? <Si-No>: "Si" Ingrese un numero: -5 Numero negativo Quiere seguir? <Si-No>: "No" >>>

5.3.

Ciclo con centinela

Un problema que tiene nuestra primera solucin es que resulta poco amigable preguntarle al usuario despus de cada clculo si desea continuar. Se puede usar el mtodo del centinela: un valor distinguido que, si se lee, le indica al programa que el usuario desea salir del ciclo. En este caso, podemos suponer que si ingresa el caracter * es una indicacin de que desea terminar. El esquema del ciclo con centinela es el siguiente: Pedir datos. Mientras el dato pedido no coincida con el centinela: Realizar clculos. Pedir datos. En nuestro caso, pedir datos corresponde a lo siguiente: Pedir nmero. El programa resultante es el siguiente: >>> def pcn_loop2(): x=input("Ingrese un numero (* para terminar): ") while x <>"*": if x > 0: print elif x == print else: print

"Numero positivo" 0: "Igual a 0" "Numero negativo"

5.4. Cmo romper un ciclo x=input("Ingrese un numero (* para terminar): ")

51

Y ahora lo ejecutamos: >>> pcn_loop2() Ingrese un numero Numero positivo Ingrese un numero Igual a 0 Ingrese un numero Numero negativo Ingrese un numero >>> (* para terminar): 25 (* para terminar): 0 (* para terminar): -5 (* para terminar): *

5.4.

Cmo romper un ciclo

El ciclo con centinela es muy claro pero tiene un problema: hay dos lugares (la primera lnea del cuerpo y la ltima lnea del ciclo) donde se ingresa el mismo dato. Si en la etapa de mantenimiento tuviramos que realizar un cambio en el ingreso del dato (cambio de mensaje, por ejemplo) deberamos estar atentos y hacer dos correcciones iguales. Nos preguntamos si podemos leer el dato x en un nico punto del programa, y tratamos de disear una solucin con esa restriccin. Es claro que en ese caso la lectura tiene que estar dentro del ciclo para poder leer ms de un nmero, pero entonces la condicin del ciclo no puede depender del valor ledo, ni tampoco de valores calculados adentro del ciclo. Pero un ciclo que no puede depender de valores ledos o calculados dentro de l ser de la forma: 1. Repetir indenidamente: a) Hacer algo. Y esto se traduce a Python como: while True: <hacer algo> Un ciclo cuya condicin es True parece ser un ciclo innito (o sea que nunca va a terminar). Pero eso es gravsimo! Nuestros programas tienen que terminar! Afortunadamente hay una instruccin de Python, break, que nos permite salir de adentro de un ciclo (tanto sea for como while) en medio de su ejecucin. En esta construccin while <condicin>: <hacer algo1 > if <condif>: break <hacer algo2 > el sentido del break es el siguiente:

52

Unidad 5. Ms sobre ciclos 1. Se evala <condicin> y si es falso se sale del ciclo. 2. Se ejecuta <hacer algo1 >. 3. Se evala <condif> y si es verdadero se sale del ciclo (con break). 4. Se ejecuta <hacer algo2 >. 5. Se vuelve al paso 1. Diseamos entonces: Repetir indenidamente: Pedir dato. Si el dato ingresado es el centinela, salir del ciclo. Operar con el dato. Codicamos en Python la solucin al problema de los nmeros usando ese esquema:

>>> def pcn_loop3(): while True: x = input("Ingrese un numero (* para terminar): ") if x == *: break elif x > 0: print "Numero positivo" elif x == 0: print "Igual a 0" else: print "Numero negativo" >>> Y la probamos: >>> pcn_loop3() Ingrese un numero Numero positivo Ingrese un numero Igual a 0 Ingrese un numero Numero negativo Ingrese un numero >>>

(* para terminar): 25 (* para terminar): 0 (* para terminar): -5 (* para terminar): *

5.5. Ejercicios

53

5.5.

Ejercicios

Ejercicio 5.1. Se desea facturar el uso de un telefono. Para ello se informa la tarifa por segundo y la duracion de cada comunicacion expresada en horas, minutos y segundos. Como resultado se informa la duracion en segundos de cada comunicacion y su costo. Resolver este problema usando 1. Ciclo denido. 2. Ciclo interactivo. 3. Ciclo con centinela. 4. Ciclo innito que se rompe. Ejercicio 5.2. Mantenimiento del tarifador: al nal del da se debe informar cuntas llamadas hubo y el total facturado. Hacerlo con todos los esquemas anteriores. Ejercicio 5.3. Nos piden que escribamos una funcin que le pida al usuario que ingrese un nmero positivo. Si el usuario ingresa cualquier cosa que no sea lo pedido se le debe informar de su error mediante un mensaje y volverle a pedir el nmero. Resolver este problema usando 1. Ciclo interactivo. 2. Ciclo con centinela. 3. Ciclo innito que se rompe. Tendra sentido hacerlo con ciclo denido? Justiquen.

54

Unidad 5. Ms sobre ciclos

Unidad 6

Cadenas de caracteres
Una cadena es una secuencia de caracteres. Ya las hemos usado para desplegar mensajes, pero sus usos son mucho ms amplios que slo se: los textos que manipulamos mediante los editores de texto, los textos de Internet que analizan los buscadores, son todos ejemplos de cadenas de caracteres. Pero para poder programar este tipo de aplicaciones debemos aprender a manipularlas. Comenzaremos a ver ahora cmo hacer clculos con cadenas.

6.1.

Operaciones con cadenas

Ya vimos en 2.4 que se podan Sumar cadenas entre s (y el resultado es la concatenacin de todas las cadenas dadas): >>> "Un divertido "+"programa "+"de "+ "radio" Un divertido programa de radio >>> Multiplicar una cadena s por un nmero k (y el resultado es la concatenacin de s consigo misma, k veces): >>> 3*"programas " programas programas programas >>> "programas "*3 programas programas programas >>> Tambin se puede averiguar la longitud de una cadena utilizando una funcin provista por Python: len{}. >>> len("programas ") 10 >>> Hay una cadena especial, que llamaremos cadena vaca, que es la cadena que no contiene ningn carcter (se la indica slo con un apstrofe o comilla que abre, y un apstrofe o comilla que cierra), y que por lo tanto tiene longitud cero: 55

56

Unidad 6. Cadenas de caracteres

>>> s="" >>> s >>> len(s) 0 >>>

6.2.

Una operacin para recorrer todos los caracteres de una cadena

Python nos permite recorrer todos los caracteres de una cadena de manera muy sencilla, usando directamente un ciclo denido: >>> for x in "programas ": ... print x ... p r o g r a m a s >>>

6.3.

Acceder a una posicin de la cadena

Queremos averiguar cul es el carcter que est en la posicin i-sima de una cadena. Para ello Python nos provee de una notacin con corchetes: escribiremos a[i] para hablar de la posicin i-sima de la cadena a. Trataremos de averiguar con qu letra empieza una cadena. >>> a="Veronica" >>> a[1] e >>> Algo fall: a[1] nos muestra la segunda letra, no la primera! Lo que sucede es que en Python las posiciones se cuentan desde 0. >>> a[0] V >>> Ahora s hemos conseguido averiguar en Python cul es el primer carcter de a.

6.4. Segmentos de cadenas

57

Las distintas posiciones de una cadena a se llaman ndices. Los ndices son nmeros enteros que pueden tomar valores entre -len(a) y len(a) - 1. Los ndices entre 0 y len(a) - 1 son lo que ya vimos: los caracteres de la cadena del primero al timo. Los ndices negativos proveen una notacin que hace ms fcil indicar cul es el ltimo carcter de la cadena: a[-1] es el ltimo carcter de a, a[-2] es el penltimo carcter de a, a[-len(a)] es el primer carcter de a. >>> a="Veronica" >>> len(a) 8 >>> a[0] V >>> a[7] a >>> a[8] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> a[-9] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> a[-9] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> Ejercicio 6.1. Escribir un ciclo que permite mostrar los caracteres de una cadena del nal al principio.

6.4.

Segmentos de cadenas

Python ofrece tambin una notacin para identicar segmentos de una cadena. La notacin es similar a la de los rangos que vimos en los ciclos denidos: a[0:2] se reere a la subcadena formada por los caracteres cuyos ndices estn en el rango [0,2): >>> a[0:2] Ve >>> a[-4:-2] ni >>> a[0:8] Veronica >>> >>> Si j es un entero no negativo, se puede usar la notacin a[:j] para representar al segmento a[0:j]; tambin se puede usar la notacin a[j:] para representar al segmento a[j:len(a)].

58

Unidad 6. Cadenas de caracteres

>>> a[:3] Ver >>> a[3:] onica >>> Pero hay que tener cuidado con salirse del rango (en particular hay que tener cuidado con la cadena vaca): >>> a[10] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> >>> s="" >>> s >>> len(s) 0 >>> s[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range Sin embargo s[0:0] no da error. Por qu? >>> s[0:0] >>> Ejercicio 6.2. Investigar qu signica la notacin a[:]. Ejercicio 6.3. Investigar qu signican las notaciones a[:j] y a[j:] si j es un nmero negativo.

6.5.

Las cadenas son inmutables

Nos dicen que la persona sobre la que estamos hablando en realidad se llama "Veronika"(s, con "k"). Como conocemos la notacin de corchetes, tratamos de corregir slo el carcter correspondiente de la variable a: >>> a[6]="k" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: str object does not support item assignment >>> El error que se despliega nos dice que la cadena no soporta la modicacin de un carcter. Decimos que las cadenas son inmutables. Si queremos corregir la ortografa de una cadena, debemos hacer que la cadena a se reera a otro valor:

6.6. Procesamiento sencillo de cadenas >>> a="Veronika" >>> a Veronika >>>

59

6.6.

Procesamiento sencillo de cadenas

Problema 6.1. Nuestro primer problema es muy simple: Queremos contar cuntas letras A hay en una cadena x. 1. Especicacin: Dada una cadena x, la funcin retorna un valor contador que representa cuntas letras A tiene x. 2. Diseo: Se parece a algo que ya conocemos? Ante todo es claro que se trata de un ciclo denido, porque lo que hay que tratar es cada uno de los caracteres de la cadena x, o sea que estamos frente a un esquema para cada letra de x averiguar si la letra es A y tratarla en consecuencia

Nos dice la especicacin que se necesita una variable contador que cuenta la cantidad de letras A que contiene x. Y por lo tanto sabemos que el tratamiento es: si la letra es A se incrementa el contador en 1, y si la letra no es A no se lo incrementa, o sea que nos quedamos con un esquema de la forma: para cada letra de x averiguar si la letra es A y si lo es, incrementar en 1 el contador

Estar todo completo? Alicia Hacker nos hace notar que en el diseo no planteamos el retorno del valor del contador. Lo completamos entonces: para cada letra de x averiguar si la letra es A y si lo es, incrementar en 1 el contador retornar el valor del contador

Y ahora estar todo completo? E. Lapurado, nuestro alumno impaciente nos induce a poner manos a la obra y a programar esta solucin, y el resto del curso est de acuerdo.

60

Unidad 6. Cadenas de caracteres 3. Implementacin Ya vimos que Python nos provee de un mecanismo muy poderoso para recorrer una cadena: una instruccin for que nos brinda un carcter por vez, del primero al ltimo. Proponemos la siguiente solucin: >>> def contarA(x): ... for letra in x: ... if letra == "A": ... contador = contador + 1 ... return(contador) ... >>> contarA("Ana") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in contarA UnboundLocalError: local variable contador referenced before assignment >>> Qu es lo que fall? Fall el diseo! Evidentemente la variable contador debe tomar un valor inicial antes de empezar a contar las apariciones del caracter A. Volvamos al diseo entonces. Es muy tentador quedarse arreglando la implementacin, sin volver al diseo, pero eso es de muy mala prctica, porque el diseo queda mal documentado, y adems podemos estar dejando de tener en cuenta otras situaciones errneas. 4. Diseo (revisado) Habamos llegado a un esquema de la forma para cada letra de x averiguar si la letra es A y si lo es, incrementar en 1 el contador retornar el valor del contador

Cul es el valor inicial que debe tomar contador? Como nos dice la especicacin contador cuenta la cantidad de letras A que tiene la cadena x. Pero si nos detenemos en medio de la computacin, cuando an no se recorri toda la cadena sino slo los primeros 10 caracteres, por ejemplo, el valor de contador reeja la cantidad de A que hay en los primeros 10 caracteres de x. Si llamamos parte izquierda de x al segmento de x que ya se recorri, diremos que cuando lemos los primeros 10 caracteres de x, su parte izquierda es el segmento x[0:10]. El valor inicial que debemos darle a contador debe reejar la cantidad de A que contiene la parte izquierda de x cuando an no iniciamos el recorrido, es decir cuando esta parte izquierda es x[0:0] (o sea la cadena vaca). Pero la cantidad de caracteres iguales a A de la cadena vaca es 0. Por lo tanto el diseo ser:

6.6. Procesamiento sencillo de cadenas inicializar el contador en 0 para cada letra de x averiguar si la letra es A y si lo es, incrementar en 1 el contador retornar el valor del contador

61

(lo identicaremos como el esquema Inicializacin - Ciclo de tratamiento - Retorno de valor). Pasamos ahora a implementar este diseo: 5. Implementacin (del diseo revisado)

>>> def contarA (x): ... # la funcion contarA(x) cuenta cuantas ... # letras "A" aparecen en la cadena x ... contador = 0 ... for letra in x: ... if letra == "A": ... contador = contador + 1 ... return(contador) ... 6. Prueba >>> 0 >>> 1 >>> 1 >>> 2 >>> 2 >>> contarA ("banana") contarA ("Ana") contarA ("lAn") contarA ("lAAn") contarA ("lAnA")

7. Mantenimiento: Esta funcin resulta un poco limitada. Cuando nos pidan que contemos cuntas letras E hay en una cadena tendremos que hacer otra funcin. Tiene sentido hacer una funcin ms general que nos permita contar cuntas veces aparece un carcter dado en una cadena. Ejercicio 6.4. Escribir una funcin contar(l, x) que cuente cuntas veces aparece un carcter l dado en una cadena x. Ejercicio 6.5. Hay ms letras A o ms letras E en una cadena? Escribir un programa que lo decida. Ejercicio 6.6. Escribir un programa que cuente cantas veces aparecen cada una de las vocales en una cadena. No importa si la vocal aparece en mayscula o en minscula.

62

Unidad 6. Cadenas de caracteres

6.7.

Nuestro primer juego

Con todo esto ya estamos en condiciones de escribir un programa para jugar con la computadora: el Mastermind. El Mastermind es un juego que consiste en deducir un cdigo numrico de (por ejemplo) cuatro cifras. 1. Anlisis (explicacin del juego): Cada vez que se empieza un partido, el programa debe eligir un nmero de cuatro cifras (sin cifras repetidas), que ser el cdigo que el jugador debe adivinar en la menor cantidad de intentos posibles. Cada intento consiste en una propuesta de un cdigo posible que tipea el jugador, y una respuesta del programa. Las respuestas le darn pistas al jugador para que pueda deducir el cdigo. Estas pistas indican cun cerca estuvo el nmero propuesto de la solucin a travs de dos valores: la cantidad de aciertos es la cantidad de dgitos que propuso el jugador que tambin estn en el cdigo en la misma posicin. La cantidad de coincidencias es la cantidad de digitos que propuso el jugador que tambin estn en el cdigo pero en una posicin distinta. Por ejemplo, si el cdigo que eligi el programa es el 2607, y el jugador propone el 1406, el programa le debe responder un acierto (el 0, que est en el cdigo original en el mismo lugar, el tercero), y una coincidencia (el 6, que tambin est en el cdigo original, pero en la segunda posicin, no en el cuarto como fue propuesto). Si el jugador hubiera propuesto el 3591, habra obtenido como respuesta ningn acierto y ninguna coincidencia, ya que no hay nmeros en comn con el cdigo original, y si se obtienen cuatro aciertos es porque el jugador adivin el cdigo y gan el juego. 2. Especicacin: El programa, entonces, debe generar un nmero que el jugador no pueda predecir. A continuacin, debe pedirle al usuario que introduzca un nmero de cuatro cifras distintas, y cuando ste lo ingresa, procesar la propuesta y evaluar el nmero de aciertos y de coincidencias que tiene de acuerdo al cdigo elegido. Si es el cdigo original, se termina el programa con un mensaje de felicitacin. En caso contrario, se informa al jugador la cantidad de aciertos y la de coincidencias, y se le pide una nueva propuesta. Este proceso se repite hasta que el jugador adivine el cdigo. 3. Diseo: Lo primero que tenemos que hacer es indicarle al programa que tiene que elegir un nmero de cuatro cifras al azar. Esto lo hacemos a travs del mdulo random. Este mdulo provee funciones para hacer elecciones aleatorias1 . La funcin del mdulo que vamos a usar se llama choice. Esta funcin devuelve un elemento al azar de una n-upla, y toma como parmetro la n-upla de la que tiene que elegir. Vamos a usarla entonces para elegir cifras. Para eso tenemos que construir una n-upla que tenga todas las cifras, lo hacemos de la misma manera que en la parte 3.4: digitos = (0,1,2,3,4,5,6,7,8,9)
En realidad, la computadora nunca puede hacer elecciones completamente aleatorias. Por eso los nmeros al azar que puede elegir se llaman pseudoaleatorios.
1

6.7. Nuestro primer juego

63

Como estn entre comillas, los dgitos son tratados como cadenas de caracteres de longitud uno. Sin las comillas, habran sido considerados nmeros enteros. En este caso elegimos verlos como cadenas de caracteres porque lo que nos interesa hacer con ellos no son cuentas sino comparaciones, concatenaciones, contar cuntas veces aparece o donde est en una cadena de mayor longitud, es decir, las operaciones que se aplican a cadenas de texto. Entonces que sean variables de tipo cadena de caracteres es lo que mejor se adapta a nuestro problema. Ahora tenemos que generar el nmero al azar, asegurndonos de que no haya cifras repetidas. Esto lo podemos modelar as: a) Tomar una cadena vaca b) Repetir cuatro veces: 1) Elegir un elemento al azar de la lista de dgitos 2) Si el elemento no est en la cadena, agregarlo 3) En caso contrario, volver al punto 3b1 Una vez elegido el nmero, hay que interactuar con el usuario y pedirle su primera propuesta. Si el nmero no coincide con el cdigo, hay que buscar la cantidad de aciertos y de coincidencias y repetir el pedido de propuestas, hasta que el jugador adivine el cdigo. Para vericar la cantidad de aciertos se pueden recorrer las cuatro posiciones de la propuesta: si alguna coincide con los dgitos en el cdigo en esa posicin, se incrementa en uno la cantidad de aciertos. En caso contrario, se verica si el dgito est en alguna otra posicin del cdigo, y en ese caso se incrementa la cantidad de coincidencias. En cualquier caso, hay que incrementar en uno tambin la cantidad de intentos que lleva el jugador. Finalmente, cuando el jugador acierta el cdigo elegido, hay que dejar de pedir propuestas, informar al usuario que ha ganado y terminar el programa. 4. Implementacin: Entonces, de acuerdo a lo diseado en 3, el programa quedara ms o menos as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# ARCHIVO: mastermind.py # modulo que va a permitir elegir numeros aleatoriamente import random # el conjunto de simbolos validos en el codigo digitos = (0,1,2,3,4,5,6,7,8,9) # "elegimos" el codigo codigo = for i in range(4): candidato = random.choice(digitos) # vamos eligiendo digitos no repetidos while candidato in codigo: candidato = random.choice(digitos) codigo = codigo + candidato # iniciamos interaccion con el usuario

64
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

Unidad 6. Cadenas de caracteres print "Bienvenido/a al Mastermind!" print "Tenes que adivinar un numero de", 4, "cifras distintas" propuesta = raw_input("Que codigo propones?: ") # procesamos las propuestas e indicamos aciertos y coincidencias intentos = 1 while propuesta != codigo: intentos = intentos + 1 aciertos = 0 coincidencias = 0 # recorremos la propuesta y verificamos en el codigo for i in range(4): if propuesta[i] == codigo[i]: aciertos = aciertos + 1 elif propuesta[i] in codigo: coincidencias = coincidencias + 1 print "Tu propuesta (", propuesta, ") tiene", aciertos, \ "aciertos y ", coincidencias, "coincidencias." # pedimos siguiente propuesta propuesta = raw_input("Propone otro codigo: ") print "Felicitaciones! Adivinaste el codigo en", intentos, "intentos." Cuando lo que queremos escribir es demasiado largo como para una sola lnea que entre cmodamente en el editor o en el campo visual, le indicamos al intrprete que queremos seguir en la siguiente lnea por medio de la barra invertida (como al nal de la lnea 36). 5. Pruebas: La forma ms directa de probar el programa es jugndolo, y vericando manualmente que las respuestas que da son correctas, por ejemplo: jugador@casino:~$ python mastermind.py Bienvenido/a al Mastermind! Tenes que adivinar un numero de 4 cifras distintas Que codigo propones?: 1234 Tu propuesta ( 1234 ) tiene 0 aciertos y 1 coincidencias. Propone otro codigo: 5678 Tu propuesta ( 5678 ) tiene 0 aciertos y 1 coincidencias. Propone otro codigo: 1590 Tu propuesta ( 1590 ) tiene 1 aciertos y 1 coincidencias. Propone otro codigo: 2960 Tu propuesta ( 2960 ) tiene 2 aciertos y 1 coincidencias. Propone otro codigo: 0963 Tu propuesta ( 0963 ) tiene 1 aciertos y 2 coincidencias. Propone otro codigo: 9460 Tu propuesta ( 9460 ) tiene 1 aciertos y 3 coincidencias. Propone otro codigo: 6940 Felicitaciones! Adivinaste el codigo en 7 intentos.

6.7. Nuestro primer juego

65

Podemos ver que para este caso el programa parece haberse comportado bien. Pero cmo podemos saber que el cdigo nal era realmente el que eligi originalmente el programa? O qu habra pasado si no encontrbamos la solucin? Para probar estas cosas recurrimos a la depuracin del programa. Una forma de hacerlo es simplemente agregar algunas lneas en el cdigo que nos informen lo que est sucediendo que no podemos ver. Por ejemplo, los nmeros que va eligiendo al azar y el cdigo que queda al nal. As podremos vericar si las respuestas son correctas a medida que las hacemos y podremos elegir mejor las propuestas enlas pruebas.
9 10 11 12 13 14 15 16 17 18

# "elegimos" el codigo codigo = for i in range(4): candidato = random.choice(digitos) # vamos eligiendo digitos no repetidos while candidato in codigo: print DEBUG: candidato =, candidato candidato = random.choice(digitos) codigo = codigo + candidato print DEBUG: el codigo va siendo =, codigo De esta manera podemos monitorear cmo se va formando el cdigo que hay que adivinar, y los candidatos que van apareciendo pero se rechazan por estar repetidos: jugador@casino:~$ python master_debug.py DEBUG: el codigo va siendo = 8 DEBUG: candidato = 8 DEBUG: el codigo va siendo = 81 DEBUG: candidato = 1 DEBUG: el codigo va siendo = 814 DEBUG: el codigo va siendo = 8145 Bienvenido/a al Mastermind! Tenes que adivinar un numero de 4 cifras distintas Que codigo propones?:

6. Mantenimiento: Supongamos que queremos jugar el mismo juego, pero en lugar de hacerlo con un nmero de cuatro cifras, adivinar uno de cinco. Qu tendramos que hacer para cambiarlo? Para empezar, habra que reemplazar el 4 en la lnea 11 del programa por un 5, indicando que hay que elegir 5 dgitos al azar. Pero adems, el ciclo en la lnea 31 tambin necesita cambiar la cantidad de veces que se va a ejecutar, 5 en lugar de 4. Y hay un lugar ms, adentro del mensaje al usuario que indica las instrucciones del juego en la lnea 20. El problema de ir cambiando estos nmeros de a uno es que si quisiramos volver al programa de los 4 dgitos o quisiramos cambiarlo por uno que juegue con 3, tenemos que volver a hacer los reemplazos en todos lados cada vez que lo queremos cambiar, y corremos el riesgo de olvidarnos de alguno e introducir errores en el cdigo. Una forma de evitar esto es jar la cantidad de cifras en una variable y cambiarla slo ah:

66
9 10 11 12 13 14

Unidad 6. Cadenas de caracteres # "elegimos" el codigo cant_digitos = 5 codigo = for i in range(cant_digitos): candidato = random.choice(digitos) # vamos eligiendo digitos no repetidos El mensaje al usuario queda entonces:
19 20 21 22

# iniciamos interaccion con el usuario print "Bienvenido/a al Mastermind!" print "Tenes que adivinar un numero de", cant_digitos, \ "cifras distintas" Y el chequeo de aciertos y coincidencias:

32 33 34

# recorremos la propuesta y verificamos en el codigo for i in range(cant_digitos): if propuesta[i] == codigo[i]: Con 5 dgitos, el juego se pone ms difcil. Nos damos cuenta que si el jugador no logra adivinar el cdigo, el programa no termina: se queda preguntando cdigos y respondiendo aciertos y coincidencias para siempre. Entonces queremos darle al usuario la posibilidad de rendirse y saber cul era la respuesta y terminar el programa. Para esto agregamos en el ciclo while principal una condicin extra: para seguir preguntando, la propuesta tiene que ser distinta al cdigo pero adems tiene que ser distinta del texto "Me doy".

25 26 27 28 29

# procesamos las propuestas e indicamos aciertos y coincidencias intentos = 1 while propuesta != codigo and propuesta != "Me doy": intentos = intentos + 1 aciertos = 0 Entonces, ahora no slamente sale del while si acierta el cdigo, sino adems si se rinde y quiere saber cul era el cdigo. Entonces afuera del while tenemos que separar las dos posibilidades, y dar distintos mensajes:

42 43 44 45 46 47

if propuesta == "Me doy": print "El codigo era", codigo print "Suerte la proxima vez!" else: print "Felicitaciones! Adivinaste el codigo en", \ intentos, "intentos." El cdigo de todo el programa queda entonces as:

1 2 3 4 5

# ARCHIVO: mastermind5.py # modulo que va a permitir elegir numeros aleatoriamente import random

6.8. Ejercicios
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

67

# el conjunto de simbolos validos en el codigo digitos = (0,1,2,3,4,5,6,7,8,9) # "elegimos" el codigo cant_digitos = 5 codigo = for i in range(cant_digitos): candidato = random.choice(digitos) # vamos eligiendo digitos no repetidos while candidato in codigo: candidato = random.choice(digitos) codigo = codigo + candidato # iniciamos interaccion con el usuario print "Bienvenido/a al Mastermind!" print "Tenes que adivinar un numero de", cant_digitos, \ "cifras distintas" propuesta = raw_input("Que codigo propones?: ") # procesamos las propuestas e indicamos aciertos y coincidencias intentos = 1 while propuesta != codigo and propuesta != "Me doy": intentos = intentos + 1 aciertos = 0 coincidencias = 0 # recorremos la propuesta y verificamos en el codigo for i in range(cant_digitos): if propuesta[i] == codigo[i]: aciertos = aciertos + 1 elif propuesta[i] in codigo: coincidencias = coincidencias + 1 print "Tu propuesta (", propuesta, ") tiene", aciertos, \ "aciertos y ", coincidencias, "coincidencias." # pedimos siguiente propuesta propuesta = raw_input("Propone otro codigo: ") if propuesta == "Me doy": print "El codigo era", codigo print "Suerte la proxima vez!" else: print "Felicitaciones! Adivinaste el codigo en", \ intentos, "intentos."

6.8.

Ejercicios

Ejercicio 6.7. En el punto 6 (Mantenimiento) usamos una variable que guardara el valor de la cantidad de dgitos para no tener que cambiarlo todas las veces. Cmo haran para evitar esta

68

Unidad 6. Cadenas de caracteres

variable usando la funcin len(cadena)? Ejercicio 6.8. Modicar el programa para permitir repeticiones de dgitos. Cuidado con el cmputo de aciertos y coincidencias!

Unidad 7

Tuplas y listas
Veremos en esta unidad otros tipos de datos utilizados para representar informacin en Python.

7.1.

Tuplas

En 3.4 ya usamos n-uplas (o tuplas) como una construccin que nos permita que una funcin devolviera mltiples valores. En programacin, en general, al querer modelar objetos de la vida real, es muy comn que querramos describir un objeto como un agrupamiento de datos de distintos tipos. Veamos algunos ejemplos: Una fecha la podemos querer representar como la terna da (un nmero entero), mes (una cadena de caracteres), y ao (un nmero entero), y tendremos por ejemplo (25, Mayo, 1810). Como datos de los alumnos queremos guardar nmero de padrn, nombre y apellido, como por ejemplo (89766, Alicia, Hacker). Se pueden anidar tuplas: Como datos de los alumnos queremos guardar nmero de padrn, nombre, apellido y fecha de nacimiento, como por ejemplo (89766, Alicia, Hacker, (9, Julio, 1988)).

7.1.1.

Elementos y segmentos de tuplas

Las tuplas son secuencias, igual que las cadenas, y se puede utilizar la misma notacin de ndices que en las cadenas para obtener cada una de sus componentes. Pero atencin sobre quin es el n-simo elemento de una tupla!: El primer elemento de (25, Mayo, 1810) es 25. El segundo elemento de (25, Mayo, 1810) es Mayo. El tercer elemento de (25, Mayo, 1810) es 1810. >>> t=(25, "Mayo", 1810) >>> t[0] 69

70

Unidad 7. Tuplas y listas 25 >>> t[1] Mayo >>> t[2] 1810 >>> t[3] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: tuple index out of range >>>

Tambin se puede utilizar la notacin de rangos de la cadena para obtener una tupla con menos componentes. Si en el ejemplo de la fecha queremos quedarnos con un par que slo contenga da y mes podremos tomar el rango [:2] de la misma:

>>> t[:2] (25, Mayo) >>> Ejercicio 7.1. Cul es el cuarto elemento de (89766, Alicia, Hacker, (9, Julio, 1988))?

7.1.2.

Las tuplas son inmutables

Igual que las cadenas, las tuplas tampoco soportan la posibilidad de modicar una de sus componentes: >>> t[2]=2008 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: tuple object does not support item assignment >>>

7.1.3.

Longitud de tuplas, tuplas vacas, tuplas unitarias

A las tuplas tambin se les puede aplicar la funcin len() para calcular su longitud. El valor de esta funcin aplicada a una tupla nos indica cuntas componentes tiene esa tupla. >>> len(t) 3 >>> Ejercicio 7.2. Cul es la longitud de (89766, Alicia, Hacker, (9, Julio, 1988))? Una tupla vaca es una tupla con 0 componentes, y se la indica como (). Una tupla unitaria es una tupla con una componente. Para distinguir la tupla unitaria de la componente que contiene, Python exige que a la componente no slo se la encierre entre parntesis sino que se le ponga una coma a continuacin del valor de la componente (as (1810) es un nmero, pero (1810,) es la tupla unitaria cuya nica componente vale 1810).

7.1. Tuplas >>> z=() >>> len(z) 0 >>> u=(1810) >>> len(u) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: object of type int has no len() >>> u=(1810,) >>> len(u) 1 >>> u[0] 1810 >>> z[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: tuple index out of range >>>

71

7.1.4.

Empaquetado y desempaquetado de tuplas

Si a una variable se le asigna una secuencia de valores separados por comas, el valor de esa variable ser la tupla formada por todos los valores asignados. A esta operacin se la denomina empaquetado de tuplas. >>> a=125 >>> b="$" >>> c="Ana" >>> d=a,b,c >>> len(d) 3 >>> d (125, $, Ana) >>> Si se tiene una tupla de longitud k, se puede asignar la tupla a k variables distintas y en cada variable quedar una de las componentes de la tupla. A esta operacin se la denomina desempaquetado de tuplas. Si las variables no son distintas, se pierden valores. Y si las variables no son exactamente k se produce un error. >>> x,y,z = d >>> x 125 >>> y $ >>> z Ana >>> p,p,p = d

72

Unidad 7. Tuplas y listas

>>> p Ana >>> m,n = d Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: too many values to unpack >>> m,n,o,p=d Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: need more than 3 values to unpack >>> Ejercicio 7.3. (a) Proponer una representacin con tuplas para una baraja inglesa. (b) Escribir una funcin poker que reciba cinco cartas de la baraja inglesa e informe (devuelva el valor lgico correspondiente) si esas cartas forman o no un poker (es decir que hay 4 cartas con el mismo nmero). Ejercicio 7.4. (a) Proponer una representacin con tuplas para representar el tiempo. (b) Escribir una funcin sumaTiempo que reciba dos tiempos dados y devuelva su suma. Ejercicio 7.5. Escribir una funcin diaSiguienteE que dada una fecha expresada como la terna (Da, Mes, Ao) (donde Da, Mes y Ao son nmeros enteros) calcule el da siguiente al dado, en el mismo formato. Ejercicio 7.6. Escribir una funcin diaSiguienteT que dada una fecha expresada como la terna (Da, Mes, Ao) (donde Da y Ao son nmeros enteros, y Mes es el texto Ene, Feb, . . ., Dic, segn corresponda) calcule el da siguiente al dado, en el mismo formato.

7.2.

Listas

Presentaremos ahora una nueva estructura de datos: la lista. Usaremos listas para poder modelar datos compuestos pero cuya cantidad y valor varan a lo largo del tiempo. Son secuencias mutables y vienen dotadas de operaciones muy potentes. La notacin para lista es una secuencia de valores encerrados entre corchetes y separados por comas. Por ejemplo, si representamos a los alumnos mediante su nmero de padrn, se puede tener una lista de inscriptos en la materia como la siguiente: [78455, 89211, 66540, 45750]. Al abrirse la inscripcin, antes de que hubiera inscriptos, la lista de inscriptos se representar por una lista vaca: [].

7.2.1.

Longitud de la lista. Elementos y segmentos de listas


Como a todas las secuencias, a las listas tambin se les puede aplicar la funcin len() para conocer su longitud. Para acceder a los distintos elementos de la lista se utilizar la misma notacin de ndices de siempre, con valores que van de 0 a la longitud de la lista 1. >>> xs=[78455, 89211, 66540, 45750] >>> xs[0]

7.2. Listas 78455 >>> len(xs) 4 >>> xs[4] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range >>> xs[3] 45750 >>>

73

Para obtener una sublista a partir de la lista original, se utiliza la notacin de rangos, como en las otras secuencias. Para obtener la lista que contiene slo a quin se inscribi en segundo lugar podemos escribir: >>> xs[1:2] [89211] >>> Para obtener la lista que contiene al segundo y tercer inscriptos podemos escribir: >>> xs[1:3] [89211, 66540] >>> Para obtener la lista que contiene al primero y segundo inscriptos podemos escribir: >>> xs[:2] [78455, 89211] >>>

7.2.2.

Cmo mutar listas

Dijimos antes que las listas son secuencias mutables. Para lograr la mutabilidad Python provee operaciones que nos permiten cambiarle valores, agregarle valores y quitarle valores. Para cambiar una componente de una lista, se selecciona la componente mediante su ndice y se le asigna el nuevo valor: >>> xs[1]=79211 >>> xs [78455, 79211, 66540, 45750] >>> Para agregar un nuevo valor al nal de la lista se utiliza la operacin append(). Escribimos xs.append(47890) para agregar el padrn 47890 al nal de xs.

74

Unidad 7. Tuplas y listas >>> xs.append(47890) >>> xs [78455, 79211, 66540, 45750, 47890] >>> Para insertar un nuevo valor en la posicin cuyo ndice es k (y desplazar un lugar el resto de la lista) se utiliza la operacin insert(). Escribimos xs.insert(2, 54988) para insertar el padrn 54988 en la tercera posicin de xs. >>> xs.insert(2, 54988) >>> xs [78455, 79211, 54988, 66540, 45750, 47890] >>> Observacin 7.2.1. Las listas no controlan que se inserten elementos repetidos, si necesitamos exigir unicidad, debemos hacerlo por programa >>> xs.insert(1,78455) >>> xs [78455, 78455, 79211, 54988, 66540, 45750, 47890] >>> Para eliminar el valor v de una lista se utiliza la operacin remove(). Escribimos xs.remove(45750) para borrar el padrn 45750 de la lista de inscriptos: >>> xs.remove(45750) >>> xs [78455, 78455, 79211, 54988, 66540, 47890] >>> Si el valor a borrar est repetido, se borra slo su primera aparicin: >>> xs.remove(78455) >>> xs [78455, 79211, 54988, 66540, 47890] >>> Si el valor a borrar no existe, se produce un error: >>> xs.remove(78) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list >>>

7.2. Listas

75

7.2.3.

Cmo buscar dentro de las listas

Queremos poder formular dos preguntas ms respecto de la lista de inscriptos: Est la persona cuyo padrn es v inscripta en esta materia? y En qu orden se inscribi la persona cuyo padrn es v?. Veamos qu operaciones sobre listas se pueden usar para lograr esos dos objetivos: Para preguntar si un valor determinado es un elemento de una lista usaremos la operacin in: >>> xs [78455, 79211, 54988, 66540, 47890] >>> 78 in xs False >>> 66540 in xs True >>> Para averiguar la posicin de un valor dentro de una lista usaremos la operacin index(). Atencin, que si el valor no est en la lista, se producir un error: >>> xs.index(78455) 0 >>> xs.index(47890) 4 >>> xs.index(78) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.index(x): x not in list >>> Si el valor est repetido, el ndice que devuelve es el de la primera aparicin: >>> ys=[10,20,10] >>> ys.index(10) 0 >>> Para iterar sobre todos los elementos de una lista usaremos una construccin for: >>> zs = [5, 3, 8, 10, 2] >>> for x in zs: ... print x ... 5 3

76

Unidad 7. Tuplas y listas 8 10 2 >>>

Problema 7.1. Queremos escribir un programa que nos permita armar la lista de los inscriptos de una materia. 1. Anlisis: El usuario ingresa datos de padrones que se van guardando en una lista. 2. Especicacin: El programa solicitar al usuario que ingrese uno a uno los padrones de los inscriptos. Con esos nmeros construir una lista, que al nal se mostrar. 3. Diseo: Qu estructura tiene este programa? Se parece a algo conocido? Es claramente un ciclo en el cual se le pide al usuario que ingrese uno a uno los padrones de los inscriptos, y estos nmeros se agregan a una lista. Y en algn momento, cuando se terminaron los inscriptos, el usuario deja de cargar. El ciclo es denido o indenido? Para que fuera un ciclo denido deberamos contar de antemano cuntos inscriptos tenemos, y luego cargar exactamente esa cantidad, pero eso no parece muy til. Estamos frente a una situacin parecida al problema de la lectura de los nmeros, en el sentido de que no sabemos cuntos elementos queremos cargar de antemano. Para ese problema, en 5.3, vimos una solucin muy sencilla y cmoda: se le piden datos al usuario y, cuando se cargaron todos los datos se ingresa un valor distinguido (que se usa slo para indicar que no hay ms informacin). A ese diseo lo hemos llamado ciclo con centinela y tiene el siguiente esquema: Pedir datos. Mientras el dato pedido no coincida con el centinela: Realizar clculos. Pedir datos. Como sabemos que los nmeros de padrn son siempre enteros positivos, podemos considerar que el centinela puede ser cualquier nmero menor o igual a cero. Tambin sabemos que en nuestro caso tenemos que ir armando una lista que inicialmente no tiene ningn inscripto. Modicamos el esquema anterior para ajustarnos a nuestra situacin: La lista de inscriptos es vaca. Pedir padrn. Mientras el padrn sea positivo: Agregar el padrn a la lista. Pedir padrn. 4. Implementacin: De acuerdo a lo diseado en el prrafo anterior, el programa quedara as:

7.2. Listas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

77

# ARCHIVO: ins0.py # modulo para inscribir gente al curso version 0 # iniciamos la interaccin con el usuario print "Inscripcion en el curso piloto de 75.40" # leemos el primer padron padron=input("Ingresa un padron (<=0 para terminar): ") # procesamos los padrones # inicialmente no hay inscriptos ins = [] while padron > 0: # agregamos el padron leido # a la lista de inscriptos ins.append(padron) # leemos otro padron mas padron=input("Ingresa un padron (<=0 para terminar): ") # mostramos el resultado print "Esta es la lista de inscriptos: ", ins

5. Prueba: Para probarlo lo ejecutamos con algunos lotes de prueba (inscripcin de tres alumnos, inscripcin de cero alumnos, inscripcin de alumnos repetidos): >>> Inscripcion en el curso piloto de 75.40 Ingresa un padron (<=0 para terminar): 30 Ingresa un padron (<=0 para terminar): 40 Ingresa un padron (<=0 para terminar): 50 Ingresa un padron (<=0 para terminar): 0 Esta es la lista de inscriptos: [30, 40, 50] >>> Inscripcion en el curso piloto de 75.40 Ingresa un padron (<=0 para terminar): 0 Esta es la lista de inscriptos: [] >>> Inscripcion en el curso piloto de 75.40 Ingresa un padron (<=0 para terminar): 30 Ingresa un padron (<=0 para terminar): 40 Ingresa un padron (<=0 para terminar): 40 Ingresa un padron (<=0 para terminar): 30 Ingresa un padron (<=0 para terminar): 50 Ingresa un padron (<=0 para terminar): 0 Esta es la lista de inscriptos: [30, 40, 40, 30, 50] >>>

78

Unidad 7. Tuplas y listas Evidentemente el programa funciona de acuerdo a lo especicado, pero hay algo que no tuvimos en cuenta: permite inscribir a una misma persona ms de una vez. 6. Mantenimiento: No permitir que haya padrones repetidos. 7. Diseo revisado: Para no permitir que haya padrones repetidos debemos revisar que no exista el padrn antes de agregarlo en la lista: La lista de inscriptos es vaca. Pedir padrn. Mientras el padrn sea positivo: Si el padrn no est en la lista: Agregar el padrn a la lista pero si est en la lista: Avisar que el padrn ya est en la lista Pedir padrn. 8. Nueva implementacin: De acuerdo a lo diseado en el prrafo anterior, el programa ahora quedara as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# modulo para inscribir gente al curso version 1 # iniciamos la interaccin con el usuario print "Inscripcion en el curso piloto de 75.40" # leemos el primer padron padron=input("Ingresa un padron (<=0 para terminar): ") # procesamos los padrones # inicialmente no hay inscriptos ins = [] while padron > 0: # agregamos el padron leido a la lista de inscriptos todavia no esta if padron not in ins: ins.append(padron) # de lo contrario avisamos que ya figura else: print "Ya figura en la lista" # leemos otro padron mas padron=input("Ingresa un padron (<=0 para terminar): ") # mostramos el resultado print "Esta es la lista de inscriptos: ", ins

9. Nueva prueba: Para probarlo lo ejecutamos con los mismos lotes de prueba anteriores (inscripcin de tres alumnos, inscripcin de cero alumnos, inscripcin de alumnos repetidos):

7.3. Ordenar listas >>> Inscripcion en el curso piloto de 75.40 Ingresa un padron (<=0 para terminar): 30 Ingresa un padron (<=0 para terminar): 40 Ingresa un padron (<=0 para terminar): 50 Ingresa un padron (<=0 para terminar): 0 Esta es la lista de inscriptos: [30, 40, 50] >>> Inscripcion en el curso piloto de 75.40 Ingresa un padron (<=0 para terminar): 0 Esta es la lista de inscriptos: [] >>> Inscripcion en el curso piloto de 75.40 Ingresa un padron (<=0 para terminar): 30 Ingresa un padron (<=0 para terminar): 40 Ingresa un padron (<=0 para terminar): 40 Ya figura en la lista Ingresa un padron (<=0 para terminar): 30 Ya figura en la lista Ingresa un padron (<=0 para terminar): 50 Ingresa un padron (<=0 para terminar): 0 Esta es la lista de inscriptos: [30, 40, 50] >>> Pero ahora el resultado es satisfactorio: no tenemos inscriptos repetidos. Ejercicio 7.7. Permitir que los alumnos se puedan inscribir o borrar.

79

Ejercicio 7.8. Inscribir y borrar alumnos como antes, pero registrar tambin el nombre y apellido de la persona inscripta, de modo de tener como lista de inscriptos [(20, Ana, Garca), (10, Juan, Dos)].

7.3.

Ordenar listas

Nos puede interesar que los elementos de una lista estn ordenados: una vez que naliz la inscripcin en un curso, tener a los padrones de los alumnos por orden de inscripcin puede ser muy incmodo, siempre ser preferible tenerlos ordenados por nmero para realizar cualquier comprobacin. Python provee dos operaciones para obtener una lista ordenada a partir de una lista desordenada. Para dejar la lista original intacta pero obtener una nueva lista ordenada a partir de ella, se usa la funcin sorted. >>> bs=[5,2,4,2] >>> cs=sorted(bs) >>> bs

80

Unidad 7. Tuplas y listas [5, 2, 4, 2] >>> cs [2, 2, 4, 5] >>> Para modicar directamente la lista original usaremos sort(). >>> >>> >>> [3, >>> ds=[5,3,4,5] ds.sort() ds 4, 5, 5]

7.4.

Listas y cadenas

A partir de una cadena de caracteres, podemos obtener una lista con sus componentes usando la funcin split. Si queremos obtener las palabras (separadas entre s por espacios) que componen la cadena xs escribiremos simplemente xs.split(): >>> c = " Una cadena con espacios >>> c.split() [Una, cadena, con, espacios] >>> "

En este caso split elimina todos los blancos de ms, y devuelve slo las palabras que conforman la cadena. Si en cambio el separador es otro carcter (por ejemplo la arroba, @), se lo debemos pasar como parmetro a la funcin split. En ese caso se considera una componente todo lo que se encuentra entre dos arrobas consecutivas. En el caso particular de que el texto contenga dos arrobas una a continuacin de la otra, se devolver una componente vaca: >>> d="@@Una@@@cadena@@@con@@arrobas@" >>> d.split("@") [, , Una, , , cadena, , , con, , arrobas, ] >>> La casiinversa de split es una funcin join que tiene la siguiente sintaxis: <separador>.join(lista de componentes) y que devuelve la cadena que resulta de unir todas las componentes separadas entre s por medio del separador: >>> xs = [aaa, bbb, cccc] >>> " ".join(xs) aaa bbb cccc >>> ", ".join(xs) aaa, bbb, cccc >>> "@@".join(xs) aaa@@bbb@@cccc >>>

7.4. Listas y cadenas

81

Ejercicio 7.9. Escribir una funcin que reciba como parmetro una cadena de palabras separadas por espacios y devuelva, como resultado, cuntas palabras de ms de cinco letras tiene la cadena dada. Ejercicio 7.10. Procesamiento de telegramas. Nuestro ocial de correos neurtico decide optimizar el trabajo de su ocina cortando todas las palabras de ms de cinco letras a slo cinco letras (e indicando que una palabra fue cortada con el agregado de una arroba). Adems elimina todos los espacios en blanco de ms. Por ejemplo, al texto " Llego maana alrededor del medioda " lo transcribe como "Llego maan@ alred@ del medio@". Por otro lado cobra un valor para las palabras cortas y otro valor para las palabras largas (que deben ser cortadas). (a) Escribir una funcin que reciba un texto, la longitud mxima de las palabras, el costo de cada palabra corta, el costo de cada palabra larga, y devuelva como resultado el texto del telegrama y el costo del mismo. (b) Los puntos se reemplazan por la palabra especial "STOP", y el punto nal (que puede faltar en el texto original) se indica como "STOPSTOP". Al texto " Llego maana alrededor del medioda. Voy a almorzar " lo transcribe como "Llego maan@ alred@ del medio@ STOP Voy a almor@ STOPSTOP". Extender la funcin anterior para agregar el tratamiento de los puntos.

82

Unidad 7. Tuplas y listas

Unidad 8

Algoritmos de bsqueda
8.1. El problema de la bsqueda

Presentamos ahora uno de los problemas ms clsicos de la computacin, el problema de la bsqueda, que se puede enunciar de la siguiente manera: Problema: Dada una lista xs y un valor x devolver el ndice de x en xs si x est en xs, y 1 si x no est en xs. Alicia Hacker arma que este problema tiene una solucin muy sencilla en Python: se puede usar directamente la poderosa funcin index() de lista. Probamos esa solucin para ver qu pasa: >>> [1,3,5,7].index(5) 2 >>> [1,3,5,7].index(20) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.index(x): x not in list >>> Vemos que usar la funcin index() resuelve nuestro problema si el valor buscado est en la lista, pero si el valor no est no slo no devuelve un 1, sino que se produce un error. El problema es que para poder aplicar la funcin index() debemos estar seguros de que el valor est en la lista, y para averiguar eso Python nos provee del operador in: >>> 5 in [1,3,5,7] True >>> 20 in [1, 3, 5, 7] False >>> O sea que si llamamos a la funcin index() slo cuando el resultado de in es verdadero, y devolvemos 1 cuando el resultado de in es falso, estaremos resolviendo el problema planteado usando slo funciones provistas por Python. La solucin nos queda as:
1 2 3

#ARCHIVO: bus.py # la funcion de busqueda de un elemento x 83

84
4 5 6 7 8 9 10 11 12 13 14

Unidad 8. Algoritmos de bsqueda

# en una lista xs # si x esta en xs devuelve xs.index(x) # de lo contrario devuelve -1 def busConIndex(xs, x): if x in xs: return(xs.index(x)) else: return(-1) Probamos la funcin busConIndex(): >>> 0 >>> 5 >>> 3 >>> -1 >>> -1 >>> bus([1, 4, 54, 3, 0, -1], 1) bus([1, 4, 54, 3, 0, -1], -1) bus([1, 4, 54, 3, 0, -1], 3) bus([1, 4, 54, 3, 0, -1], 44) bus([], 0)

8.1.1.

Cuntas comparaciones hace este programa?

La pregunta del ttulo se reere a cunto esfuerzo computacional requiere este programa?, cuntas veces compara el valor que buscamos con los datos de la lista? No lo sabemos porque no sabemos cmo estn implementadas las funciones in e index(). La pregunta queda planteada por ahora pero daremos un mtodo para averiguarlo en la prxima unidad.

8.2.

Cmo programar la bsqueda lineal a mano

No interesa ver qu sucede si programamos la bsqueda usando operaciones ms elementales, y no las grandes primitivas in e index(). Esto nos permitir estudiar una solucin que puede portarse a otros lenguajes que no tienen instrucciones tan poderosas. Supongamos entonces que nuestra versin de Python no existen ni in ni index(). Podemos en cambio acceder a cada uno de los elementos de la lista a travs de una construccin for, y tambin, por supuesto, podemos acceder a un elemento de la lista mediante un ndice.

8.3.

Bsqueda lineal

Diseamos una solucin: Podemos comparar uno a uno los elementos de la lista con el valor de x, y retornar el valor de la posicin donde lo encontramos en caso de encontrarlo. Si llegamos al nal de la lista sin haber salido antes de la funcin es porque el valor de x no est en la lista, y en ese caso retornamos 1.

8.3. Bsqueda lineal

85

En esta solucin necesitamos una variable i que cuente en cada momento en qu posicin de la lista estamos parados. Esta variable se inicializa en 0 antes de entrar en el ciclo y se incrementa en 1 en cada paso. El programa nos queda entonces:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

#ARCHIVO: busl.py # si x esta en xs devuelve xs.index(x) # de lo contrario devuelve -1 # es decir: # la funcion de busqueda de un elemento x # en una lista xs # pero no podemos (no nos dejan) usar index(x) # estrategia: se recorren uno a uno los elementos de la # lista y se los compara con el valor x buscado. def busl(xs, x): # # i cuenta en que posicion de la lista estamos parados # se inicializa en 0 i=0 # # el ciclo for recorre todos los elementos de xs: for z in xs: # # # # estamos en la posicion i en z esta el valor de xs[i] si z es igual a x, devuelve i if z==x: return(i) # # #

si z es distinto de x, incrementa en 1 el valor de i y pasa a considerar el proximo elemento de xs i=i+1

# # salio del ciclo sin haber encontrado coincidencias # tiene que devolver -1 return(-1)

86

Unidad 8. Algoritmos de bsqueda Y ahora lo probamos:

>>> -1 >>> 3 >>> 4 >>> -1 >>>

busl([1, 4, 54, 3, 0, -1], 44) busl([1, 4, 54, 3, 0, -1], 3) busl([1, 4, 54, 3, 0, -1], 0) busl([], 0)

8.3.1.

Cuntas comparaciones hace este programa?

Volvemos a preguntarnos lo mismo que en la seccin anterior, pero con el nuevo programa: cunto esfuerzo computacional requiere este programa?, cuntas veces compara el valor que buscamos con los datos de la lista? Ahora podemos analizar el texto de busl: La lnea 24 del texto es un ciclo que recorre uno a uno los elementos de la lista, y en el cuerpo de ese ciclo, en la lnea 31 se compara cada elemento con el valor buscado. En el caso de encontrarlo (lnea 32) se devuelve la posicin. Si el valor no est en la lista se recorrer la lista entera, haciendo una comparacin por elemento. O sea que si el valor est en la posicin p de la lista se hacen p comparaciones, y si el valor no est se hacen tantas comparaciones como elementos tiene la lista. Nuestra hiptesis es: Si la lista crece, la cantidad de comparaciones para encontrar un valor arbitrario crecer en forma proporcional al tamao de la lista. Diremos que este algoritmo tiene un comportamiento proporcional a la longitud de la lista involucrada, o que es un algoritmo lineal. En la prxima unidad haremos experimentos para probar esta hiptesis.

8.4.

Buscar sobre una lista ordenada

Por supuesto que si la lista est ordenada podemos hacer lo mismo que antes, con algunas modiciaciones que den cuenta de la condicin de ordenada de la lista. Ejercicio 8.1. Modicar la bsqueda lineal para el caso de listas ordenadas. Cul es nuestra nueva hiptesis sobre comportamiento del algoritmo?

8.5.

Bsqueda binaria

Podemos hacer algo mejor? Trataremos de aprovechar el hecho de que la lista est ordenada y vamos a hacer algo novedoso. Nuestro espacio de bsqueda se ir achicando a segmentos cada vez menores de la lista original. La idea es descartar pedazos de la lista donde el valor seguro que no puede estar: 1. Consideramos como segmento nicial de bsqueda a la lista completa.

8.5. Bsqueda binaria 2. Si el segmento mide 0 sabemos que el valor buscado no est en la lista: devolver -1.

87

3. Si el segmento mide 1 sabemos que el valor buscado est en la lista slo si es igual al nico elemento del segmento: devolver el ndice del elemento o -1 segn corresponda. 4. Sabemos que el segmento mide al menos 2 (en el caso en que mide 2, el medio est en la primera posicin): Tomamos el elemento del medio del segmento y lo comparamos con x, el valor que estamos buscando. 5. Si x es mayor que el valor central sabemos que hay que descartar todo el subsegmento de la izquierda (el medio inclusive). Consideramos como nuevo segmento lo no descartado y vamos al paso 2. 6. De lo contrario sabemos que hay que descartar todo el subsegmento de la derecha (excluido el medio). Consideramos como nuevo segmento lo no descartado y vamos al paso 2. Veamos qu pasa cuando se busca el valor 18 en la lista [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23]:

88

Unidad 8. Algoritmos de bsqueda No encontr al valor buscado y devuelve 1. Ac tenemos una implementacin de este algoritmo:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

def busbin(xs, x): # precondicion: xs lista ordenada; x valor a buscar # postcondicion: devuelve -1 si x no esta en xs; # devuelve alguna posicion p tal que xs[p] == x, si x esta en x

# busca en toda la lista usando el buscador por segmentos # y cosiderando a la lista completa como el segmento que # empieza en 0 y termina en len(xs)-1. # izq va a guardar el indice inicio del segmento # der va a guardar el indice fin del segmento izq = 0 der = len(xs) - 1 # un segmento es vacio cuando izq > der: while izq <= der: print "DEBUG:", "izquierdo: ", izq, "derecho: ", der # un segmento tiene un unico elemento cuando izq == der: if izq == der: # si el unico elemento es igual al valor buscado, lo encontre if (xs[izq] == x): return izq

# si el unico elemento es distinto al valor buscado, el valor no esta else: return -1

# el segmento tiene longitud 2 o mas: else:

# el punto medio del segmento: medio = (izq+der)/2 print "DEBUG:", "izquierdo: ", izq, "derecho: ", der, "medio: ", medi

# si el valor del punto medio no es menor que x, seguimos buscando en # [izq, medio] # (y descartamos la derecha): if xs[medio]>=x: der = medio

# si el valor del punto medio es menor que x, seguimos buscando en el # [medio+1, der]

8.5. Bsqueda binaria


47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

89

# (y descartamos la izquierda): else: izq = medio+1 # y volvemos a iterar con el nuevo segmento

# salimos del ciclo de manera no exitosa: era una lista vacia, asi que no enc return -1 def main(): xs = input ("Dame una lista [[]] para terminar>: ") while xs != [[]]: x = input("Valor busca?: ") print busbin(xs, x) xs = input ("Dame una lista [[]] para terminar>: ") main() Y una ejecucin: Dame una lista [[]] para terminar>: Valor busca?: 0 DEBUG: izquierdo: 0 derecho: 2 DEBUG: izq: 0 der: 2 medio: 1 DEBUG: izquierdo: 0 derecho: 1 DEBUG: izq: 0 der: 1 medio: 0 DEBUG: izquierdo: 0 derecho: 0 Resultado BB: -1 Dame una lista [[]] para terminar>: Valor busca?: 1 DEBUG: izquierdo: 0 derecho: 2 DEBUG: izq: 0 der: 2 medio: 1 DEBUG: izquierdo: 0 derecho: 1 DEBUG: izq: 0 der: 1 medio: 0 DEBUG: izquierdo: 0 derecho: 0 Resultado BB: 0 Dame una lista [[]] para terminar>: Valor busca?: 2 DEBUG: izquierdo: 0 derecho: 2 DEBUG: izq: 0 der: 2 medio: 1 DEBUG: izquierdo: 0 derecho: 1 DEBUG: izq: 0 der: 1 medio: 0 DEBUG: izquierdo: 1 derecho: 1 Resultado BB: -1 Dame una lista [[]] para terminar>: Valor busca?: 3 DEBUG: izquierdo: 0 derecho: 2 DEBUG: izq: 0 der: 2 medio: 1 [1, 3, 5]

[1, 3, 5]

[1, 3, 5]

[1, 3, 5]

90

Unidad 8. Algoritmos de bsqueda

DEBUG: izquierdo: 0 derecho: 1 DEBUG: izq: 0 der: 1 medio: 0 DEBUG: izquierdo: 1 derecho: 1 Resultado BB: 1 Dame una lista [[]] para terminar>: Valor busca?: 5 DEBUG: izquierdo: 0 derecho: 2 DEBUG: izq: 0 der: 2 medio: 1 DEBUG: izquierdo: 2 derecho: 2 Resultado BB: 2 Dame una lista [[]] para terminar>: Valor busca?: 6 DEBUG: izquierdo: 0 derecho: 2 DEBUG: izq: 0 der: 2 medio: 1 DEBUG: izquierdo: 2 derecho: 2 Resultado BB: -1 Dame una lista [[]] para terminar>: Valor busca?: 0 Resultado BB: -1 Dame una lista [[]] para terminar>: Valor busca?: 1 DEBUG: izquierdo: 0 derecho: 0 Resultado BB: 0 Dame una lista [[]] para terminar>: Valor busca?: 3 DEBUG: izquierdo: 0 derecho: 0 Resultado BB: -1 Dame una lista [[]] para terminar>: >>>

[1, 3, 5]

[1, 3, 5]

[]

[1]

[1]

[[]]

8.5.1.

Cuntas comparaciones hace este programa?

En cada paso el segmento se divide por la mitad y se desecha una de esas mitades, y en cada paso se hace una compracin con el valor buscado. Por lo tanto, la cantidad de comparaciones que hacen con el valor buscado es aproximadamente igual a la cantidad de pasos necesarios para llegar a un segmento de tamao 1. Veamos el caso ms sencillo para razonar, y supongamos que la longitud de la lista es una potencia de 2, es decir len(xs)= 2k : En el primer paso el segmento a tratar es de tamao 2k . En el segundo paso el segmento a tratar es de tamao 2k1 . En el tercer paso el segmento a tratar es de tamao 2k2 . ... En el k-simo paso el segmento a tratar es de tamao 1. Por lo tanto este programa hace aproximadamente k comparaciones con el valor buscado cuando len(xs)= 2k . Pero si despejamos k de la ecuacin anterior, podemos ver que este programa realiza aproximadamente log2 (len(xs)) comparaciones.

8.5. Bsqueda binaria

91

Cuando len(xs) no es una potencia de 2 el razonamiento es menos prolijo, pero tambin vale que este programa realiza aproximadamente log2 (len(xs)) comparaciones. Vemos entonces que si xs es una lista ordenada, la bsqueda binaria es muchsimo ms eciente que la bsqueda lineal (por ejemplo, dado que 220 es aproximadamente 1.000.000, si xs+ tiene 1.000.000 de elementos, la bsqueda lineal sobre xs ser proporcional a 1.000.000, y en promedio har unas 500.000 comparaciones, mientras que la bsqueda binaria har aproximadamente 20 comparaciones siempre).

92

Unidad 8. Algoritmos de bsqueda

Unidad 9

Diccionarios
En esta unidad analizaremos otro tipo de dato importante: los diccionarios. Su importancia, radica no solo en las grandes posibilidades que presentan como estructuras para almacenar informacin, sino tambin en que, en Python, son utilizados por el propio lenguaje para realizar diversas operaciones y para almacenar informacion de otras estructuras.

9.1.

Qu es un diccionario

Segn Wikipedia, [u]n diccionario es una obra de consulta de palabras y/o trminos que se encuentran generalmente ordenados alfabticamente. De dicha compilacin de palabras o trminos se proporciona su signicado, etimologa, ortografa y, en el caso de ciertas lenguas ja su pronunciacin y separacin silbica. Al igual que los diccionarios a los que se reere Wikipedia, y que usamos habitualmente en la vida diaria, los diccionarios de Python son una lista de consulta de trminos de los cuales se proporcionan valores asociados. A diferencia de los diccionarios a los que se reere Wikipedia, los diccionarios de Python no estn ordenados. En Python, un diccionario es una coleccin no-ordenada de valores que son accedidos a traves de una clave. Es decir, en lugar de acceder a la informacin mediante el ndice numrico, como es el caso de las listas y tuplas, es posible acceder a los valores a travs de sus claves, que pueden ser de diversos tipo. Las claves son nicas dentro de un diccionario, es decir que no puede haber un diccionario que tenga dos veces la misma clave, si se asigna un valor a una clave ya existente, se reemplaza el valor anterior. No hay una forma directa de acceder a una clave a travs de su valor, y nada impide que un mismo valor se encuentre asignado a distintas claves La informacion almacenada en los diccionarios, no tiene un orden particular. Ni por clave ni por valor valor, ni tampoco por el orden en que han sido agregados al diccionario. Pueden ser claves de diccionarios cualquier variable de tipo inmutable: cadenas, enteros, tuplas (con valores inmutables en sus miembros), etc. No hay restricciones para los valores, cualquier tipo puede ser el valor: listas, cadenas, tuplas, otros diccionarios, objetos, etc. Sabas que . . .
En otros lenguajes, a los diccionarios se los llama arreglos asociativos, o tambin hash.

93

94

Unidad 9. Diccionarios

9.2.

Utilizando diccionarios en Python

De la misma forma que con listas, es posible denir un diccionario directamente con los miembros que va a contener, o bien inicializar el diccionario vaco y luego ingresar los valores. Para denirlo junto con los miembros que va a contener, se encierra el listado de valores entre llaves, las parejas de clave y valor se separan con comas, y la clave y el valor se separan con :. punto = {x: 2, y: 1, z: 4} Para declararlo vaco y luego ingresar los valores, se lo declara como un par de llaves sin nada en medio, y luego se asignan valores directamente a los ndices. materias = {} materias["lunes"] = [6103, 7540] materias["martes"] = [6201] materias["mircoles"] = [6103, 7540] materias["jueves"] = [] materias["viernes"] = [6201] Para acceder al valor asociado a una determinada clave, se lo hace de la misma forma que con las listas, pero utilizando la clave elegida en lugar del ndice. print materias["lunes"] Sin embargo, esto falla si se provee una clave que no est en el diccionario. Es posible, por otro lado, utilizar la funcin get, que devuelve el valor None si la clave no est en el diccionario. >>> print materias["domingo"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: domingo >>> print materias.get("domingo") None Existen diversas formas de recorrer un diccionario. Es posible recorrer sus claves y usar esas claves para acceder a los valores. for dia in materias: print dia, ":", materias[dia] Es posible, tambin, obtener los valores como tuplas donde el primer elemento es la clave y el segundo el valor. for dia, codigos in materias.items(): print dia, ":", codigos Para vericar si una clave se encuentra en el diccionario, es posible utilizar la funcin has_key o la palabra reservada in. d = {x: 12, y: 7} if d.has_key(x): print d[x] # Imprime 12 if d.has_key(z): print d[z] # No imprime nada

9.2. Utilizando diccionarios en Python if y in d: print d[y] # Imprime 7

95

Ms all de la creacin y el acceso, hay muchas otras operaciones que se pueden realizar sobre los diccionarios, para poder manipular la informacin segn sean nuestras necesidades, algunos de estos mtodos pueden verse en la referencia al nal del captulo. Referencia del lenguaje Python
diccionario.has_key(clave) Permite vericar si un diccionario tiene o no una determinada clave. Devuelve un valor booleano, que es verdadero si la clave est en el diccionario. Tambin es posible obtener el mismo resultado, utilizando: if clave in diccionario. diccionario.get(clave) Devuelve el valor asociado a la clave. A diferencia del acceso directo utilizando [clave], en el caso en que el valor no se encuentre, no da un error, sino que devuelve None. for clave in diccionario Esta estructura permite recorrer una a una todas las claves almacenadas en el diccionario. diccionario.keys() Devuelve una lista desordenada, con todas las claves que se hayan ingresado al diccionario diccionario.values() Devuelve una lista desordenada, con todos los valores que se hayan ingresado al diccionario. diccionario.items() Devuelve una lista desordenada con tuplas de dos elementos, en las que el primer elemento es la clave y el segundo el valor. diccionario.pop(clave) Devuelve el valor asociado a la clave, y elimina la clave y el valor asociado del diccionario. diccionario.popitem() Devuelve un elemento al azar del diccionario, representndolo como una tupla (clave, valor) y elimina esta pareja del diccionario. diccionario.clear() Elimina todos los elementos del diccionario diccionario.update(otro_diccionario) Actualiza los valores del diccionario con los recibidos por parmetro. Si una clave est en ambos, se modica el valor asociado en diccionario, para que tenga el mismo valor que en otro_diccionario. Si una clave no est en diccionario, se agrega con el valor que tenga en otro_diccionario.

96

Unidad 9. Diccionarios

Unidad 10

Contratos
En este captulo se le dar cierta formalizacin a algunos temas que se haban visto informalmente, como por ejemplo, la documentacin de las funciones. Se formalizarn las condiciones que debe cumplir un algoritmo, al comenzar, en su transcurso, y al terminar, y algunas tcnicas para tener en cuenta estas condiciones. Tambin se ver una forma de modelizar el espacio donde viven las variables.

10.1.

Pre y Postcondiciones

Cuando hablamos de contratos o programacin por contratos, nos referimos a la necesidad de estipular tanto lo que necesita y como lo que devuelve nuestro cdigo. Las condiciones que deben estar dadas para que el cdigo funcione las llamamos precondiciones y la especicacin de lo que el cdigo devuelve las llamamos postcondiciones. En denitiva, este concepto es similar al ya mencionado con respecto a la documentacin de funciones, es decir que se debe documentar cmo deben ser los parmetros recibidos y cmo va a ser lo que se devuelve. Esta estipulacin es mayormente para que la utilicen otros programadores, por lo que es particularmente til cuando se encuentra dentro de la documentacin. En ciertos casos, adems, se puede querer que el programa revise si las condiciones realmente se cumplen y de no ser as, acte en consecuencia. Existen herramientas en algunos lenguajes de programacin que facilitan estas acciones, en el caso de Python, es posible utilizar la instruccin assert.

10.1.1.

Precondiciones

Las precondiciones son las condiciones que debe cumplir los parmetros que el cdigo recibe. Por ejemplo, en una funcin divisin las precondiciones son que los parmetros son nmeros, y que el divisor sea distinto de 0. Tener una precondicin permite asumir desde el cdigo que no es necesario lidiar con los casos en que las precondiciones no se cumplen.

10.1.2.

Postcondiciones

Las postcodiciones denen qu va a devolver nuestro cdigo. En el ejemplo anterior, la funcin divisin con las precondiciones asignadas, puede asegurar que devolver un nmero correspondiente al cociente solicitado. 97

98

Unidad 10. Contratos

10.1.3.

Aseveraciones

Tanto las precondiciones como las postcondiciones son aseveraciones (en ingls assert). Es decir, armaciones realizadas en un momento particular de la ejecucin sobre el estado computacional. Si llegaran a ser falsas signicara que hay algn error en el diseo o utilizacin del algoritmo. Para comprobar estas armaciones desde el cdigo en algunos casos podemos utilizar la instruccin assert, est instruccin recibe una condicin a vericar y, opcionalmente, un mensaje de error que devolver en caso que la condicin no se cumpla. >>> n=0 >>> assert n!=0, "El divisor no puede ser 0" Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: El divisor no puede ser 0

Atencin
Es importante tener en cuanta que assert est pensado para ser usado en la etapa de desarrollo. Un programa terminado nunca debera dejar de funcionar por este tipo de errores.

10.1.4.

Ejemplos

Usando los ejemplos anteriores nuestra funcin division nos quedara de la siguiente forma: def division(dividendo, divisor): """ Calculo de la divisin Pre: Recibe dos nmeros, divisor debe ser distinto de 0. Post: Devuelve un nmero real, con el cociente de ambos. """ assert divisor != 0, "El divisor no puede ser 0" return dividendo / ( divisor * 1.0 ) Otro ejemplo, tal vez ms interesante, puede ser una funcin que implemente una sumatoria ( f inal i=inicial f (i)). En este caso hay que analizar cules van a ser los parmetros que recibir la funcin, y las precondiciones que estos parmetros debern cumplir. La funcin sumatoria a escribir, necesita de un valor inicial, un valor nal, y una funcin a la cual llamar en cada paso. Es decir que recibe tres parmetros. def sumatoria(inicial, final, f): Tanto inicial como final deben ser nmeros enteros, y dependiendo de la implementacin a realizar o de la especicacin previa, puede ser necesario que final deba ser mayor o igual a inicial. Con respecto a f, se trata de una funcin que ser llamada con un parmetro en cada paso y se requiere poder sumar el resultado, por lo que debe ser una funcin que reciba un nmero y devuelva un nmero. La declaracin de la funcin queda, entonces, de la siguiente manera.

10.2. Invariantes de ciclo def sumatoria(inicial, final, f): """Calcula la sumatoria desde i=inicial hasta final de f(i) Pre: inicial y final son nmeros enteros, f es una funcin que recibe un entero y devuelve un nmero. Post: Se devuelve el valor de la sumatoria de aplicar f a cada nmero comprendido entre inicial y final. """ Ejercicio 10.1.1. Realizar la implementacin correspondiente a la funcin sumatoria.

99

En denitiva, la documentacin de pre y postcondiciones dentro de la documentacin de las funciones es una forma de especicar claramente el comportamiento del cdigo de forma que quienes lo vayan a utilizar no requieran conocer cmo est implementado para poder aprovecharlo. Esto es til incluso en los casos en los que el programador de las funciones es el mismo que el que las va a utilizar, ya que permite separar responsabilidades. Las pre y postcondiciones son, en efecto, un contrato entre el cdigo invocante y el invocado.

10.2.

Invariantes de ciclo

Los invariantes se reeren a estados o situaciones que no cambian dentro de un contexto o porcin de cdigo. El invariante de ciclo permite conocer cmo llegar desde las precondiciones hasta las postcondiciones. El invariante de ciclo es, entonces, una aseveracin que debe ser verdadera al comienzo de cada iteracin. Por ejemplo, si el problema es ir desde el punto A al punto B, las precondiciones dicen que estamos parados en A y las postcondiciones que estamos parados en B, un invariante podra ser estamos en algn punto entre A y B, estoy en el punto ms cercano a B que estuve hasta ahora.. Ms especcamente, si analizamos el ciclo para buscar el mximo en una lista desordenada, la precondicin es que la lista contiene elementos que son comparables y la postcondicin es que se devuelve el elemento mximo de la lista.
1 2 3 4 5 6 7 8 9

def maximo(lista): "Devuelve el elemento mximo de la lista o None si est vaca." if not len(lista): return None max_elem = lista[0] for elemento in lista: if elemento > max_elem: max_elem = elemento return max_elem

En este caso, el invariante del ciclo es que max_elem contiene el valor mximo de la porcin de lista analizada. Los invariantes son de gran importancia al momento de demostrar que un algoritmo funciona, pero an cuando no hagamos una demostracin formal es muy til tener los invariantes

100

Unidad 10. Contratos

a la vista, ya que de esta forma es ms fcil entender cmo funciona un algoritmo y encontrar posibles errores. Los invariantes, adems, son tiles a la hora de determinar las condiciones iniciales de un algoritmo, ya que tambin deben cumplirse para ese caso. Por ejemplo, consideremos el algoritmo para obtener la potencia n de un nmero.
1 2 3 4 5 6

def potencia(b, n): "Devuelve la potencia n del nmero b, con n entero mayor que 0." p = 1 for i in range(n): p *= b return p

En este caso, el invariante del ciclo es que la variable p contiene el valor de la potencia correspondiente a esa iteracin. Teniendo en cuenta esta condicin, es fcil ver que p debe comenzar el ciclo con un valor de 1, ya que ese es el valor correspondiente a p0 . De la misma manera, si la operacin que se quiere realizar es sumar todos los elementos de una lista, el invariante ser que una variable suma contenga la suma de todos los elementos ya recorridos, por lo que es claro que este invariante debe ser 0 cuando an no se haya recorrido ningn elemento.
1 2 3 4 5 6

def suma(lista): "Devuelve la suma de todos los elementos de la lista." suma = 0 for elemento in lista: suma += elemento return suma

10.2.1.

Comprobacin de invariantes desde el cdigo

Cuando la comprobacin necesaria para saber si seguimos en camino es simple, se la puede tener directamente dentro del cdigo. Evitando seguir avanzando con el algoritmo si se produjo un error crtico. Por ejemplo, en una bsqueda binaria, el elemento a buscar debe ser mayor que el elemento inicial y menor que el elemento nal, de no ser as, no tiene sentido continuar con la bsqueda. Es posible, entonces, agregar una instruccin que compruebe esta condicin y de no ser cierta realice alguna accin para indicar el error, por ejemplo, utilizando la instruccin assert, vio anteriormente.

10.3.

Mutabilidad e Inmutabilidad

Hasta ahora cada vez que estudiamos un tipo de variables indicamos si son mutables o inmutables. Cuando una variable es de un tipo inmutable, como por ejemplo, una cadena, es posible asignar un nuevo valor a esa variable, pero no es posible modicar su contenido. >>> a="ejemplo" >>> a="otro" >>> a[2]="c" Traceback (most recent call last):

10.3. Mutabilidad e Inmutabilidad File "<stdin>", line 1, in <module> TypeError: str object does not support item assignment

101

Esto se debe a que cuando se realiza una nueva asignacin, no se modica la cadena en s, sino que la variable a pasa a apuntar a otra cadena. En cambio, no es posible asignar un nuevo caracter en una posicin, ya que esto implicara modicar la cadena inmutable. En el caso de los parmetros mutables, la asignacin tiene el mismo comportamiento, es decir que las variables pasan a apuntar a un nuevo valor. >>> lista1 = [10, 20, 30] >>> lista2 = lista1 >>> lista1 = [3, 5, 7] >>> lista1 [3, 5, 7] >>> lista2 [10, 20, 30] Algo importante a tener en cuenta en el caso de las variables de tipo inmutable es que si hay dos o ms variables que apuntan a un mismo dato, y este dato se modica, el cambio se ve reejado en ambas variables. >>> >>> >>> >>> [1, lista1=[1, 2, 3] lista2 = lista1 lista2[1] = 5 lista1 5, 3]

10.3.1.

Parmetros mutables e inmutables

Las funciones reciben parmetros que pueden ser mutables o inmutables. Si dentro del cuerpo de la funcin se modica uno de estos parmetros para que apunte a otro valor, este cambio no se ver reejado fuera de la funcin. Si, en cambio, se modica el contenido de alguno de los parmetros mutables, este cambio s se ver reejado fuera de la funcin. A continuacin un ejemplo en el cual se asigna la variable recibida, a un nuevo valor. Esa asignacin slo tiene efecto dentro de la funcin. >>> def no_cambia_lista(lista): ... lista = range(len(lista)) ... print lista ... >>> lista = [10, 20, 30, 40] >>> no_cambia_lista(lista) [0, 1, 2, 3] >>> lista [10, 20, 30, 40] A continuacin un ejemplo en el cual se modica la variable recibida. En este caso, los cambios realizados tienen efecto tanto dentro como fuera de la funcin. >>> def cambia_lista(lista): ... for i in range(len(lista)):

102 ... ... >>> >>> >>> [1,

Unidad 10. Contratos lista[i] = lista[i]**3 lista = [1, 2, 3, 4] cambia_lista(lista) lista 8, 27, 64]

Atencin
En general, se espera que una funcin que recibe parmetros mutables, no los modique, ya que si se los modica se podra perder informacin valiosa. En el caso en que por una decisin de diseo o especicacin se modiquen los parmetros recibidos, esto debe estar claramente documentado.

10.4.

Resumen

Las precondiciones son las condiciones que deben cumplir los parmetros recibidos por una funcin. Las postcondiciones son los resultados que la funcin devuelve, si las precondiciones fueron vlidas. Los invariantes de ciclo son las condiciones que deben cumplirse al comienzo de cada iteracin de un ciclo. En el caso en que estas aseveraciones no sean verdaderas, se deber a un error en el diseo o utilizacin del cdigo. En general una funcin no debe modicar el contenido de sus parmetros, an cuando esto sea posible, a menos que sea la funcionalidad explcita de esa funcin. Referencia del lenguaje Python
assert condicion[,mensaje] Verica si la condicin es verdadera. En caso contrario, levanta una excepcin con el mensaje recibido por parmetro.

10.5. Apndice

103

10.5.

Apndice

El acertijo MU1 es un buen ejemplo de un problema lgico donde es til determinar el invariante. El acertijo consiste en buscar si es posible convertir MI a MU, utilizando las siguientes operaciones. 1. Si una cadena termina con una I, se le puede agregar una U (xI ->xIU) 2. Cualquier cadena luego de una M puede ser totalmente duplicada (Mx ->Mxx) 3. Donde haya tres Is consecutivas (III) se las puede reemplazar por una U (xIIIy ->xUy) 4. Dos Us consecutivas, pueden ser eliminadas (xUUy ->xy) Para resolver este problema, es posible pasar horas aplicando estas reglas a distintas cadenas. Sin embargo, puede se ms fcil encontrar una armacin que sea invariante para todas las reglas y que haga imposible llegar a obtener MU. Al analizar las reglas, la forma de deshacerse de las Is es conseguir tener tres Is consecutivas en la cadena. La nica forma de deshacerse de todas las Is es que haya un cantidad de Is consecutivas mltiplo de tres. Es por esto que es interesante considerar la siguiente armacin como invariante: el nmero de Is en la cadena no es mltiplo de tres. Para que esta armacin sea invariante al acertijo, para cada una de las reglas se debe cumplir que: si el invariante era verdadero antes de aplicar la regla, seguir siendo verdadero luego de aplicarla. Para ver si esto es cierto o no, es necesario considerar la aplicacin del invariante para cada una de las reglas. 1. Se agrega una U, la cantidad de Is no vara, por lo cual se mantiene el invariante. 2. Se duplica toda la cadena luego de la M, siendo n la cantidad de Is antes de la duplicacin, si n no es mltiplo de 3, 2n tampoco lo ser. 3. Se reemplazan tres Is por una U. Al igual que antes, siendo n la cantidad de Is antes del reemplazo, si n no es mltiplo de 3, n 3 tampoco lo ser. 4. Se eliminan Us, la cantidad de Is no vara, por lo cual se mantiene el invariante. Todo esto indica claramente que el invariante se mantiene para cada una de las posibles transformaciones. Esto signica que sea cual fuere la regla que se elija, si la cantidad de Is no es un mltiplo de tres antes de aplicarla, no lo ser luego de hacerlo. Teniendo en cuenta que hay una nica I en la cadena inicial MI, y que uno no es mltiplo de tres, es imposible llegar a MU con estas reglas, ya que MU tiene cero Is, que s es mltiplo de tres.

http://en.wikipedia.org/wiki/Invariant_(computer_science)

104

Unidad 10. Contratos

Unidad 11

Manejo de archivos
Veremos en esta unidad cmo manejar archivos desde nuestros programas. Existen dos formas bsicas de acceder a un archivo, una es utilizarlo como un archivo de texto, que procesaremos lnea por lnea; la otra es tratarlo como un archivo binario, que procesaremos byte por byte. En Python, para abrir un archivo usaremos la funcin open, que recibe el nombre del archivo a abrir. archivo = open("archivo.txt") Esta funcin intentar abrir el archivo con el nombre indicado. Si tiene xito, devolver una variable que nos permitir manipular el archivo de diversas maneras. La operacin ms sencilla a realizar sobre un archivo es leer su contenido. Para procesarlo, lnea por lnea, es posible hacerlo de la siguiente forma: linea=archivo.readline() while linea != : # procesar linea linea=archivo.readline() Esto funciona ya que cada archivo que se encuentre abierto tiene una posicin asociada, que indica el ltimo punto que fue ledo. Cada vez que se lee una lnea, avanza esa posicin. Es por ello que readline() devuelve cada vez una lnea distinta y no siempre lo mismo. La siguiente estructura es una forma equivalente a la vista en el ejemplo anterior. for linea in archivo: # procesar linea De esta manera, la variable linea ir almacenando distintas cadenas correspondientes a cada una de las lneas del archivo. Es posible, adems, obtener todas las lneas del archivo utilizando una sola llamada a funcin: lineas = archivo.readlines() En este caso, la variable lineas tendr una lista de cadenas con todas las lneas del archivo.

11.1.

Cerrar un archivo

Al terminar de trabajar con un archivo, es recomendable cerrarlo, por diversos motivos: en algunos sistemas los archivos solo pueden ser abiertos de a un programa por la vez; en otros, lo 105

106

Unidad 11. Manejo de archivos Atencin

Es importante tener en cuenta que cuando se utilizan funciones como archivo.readlines(), se est cargando en memoria el archivo completo. Siempre que una instruccin cargue un archivo completo en memoria debe tenerse cuidado de utilizarla slo con archivos pequeos, ya que de otro modo podra agotarse la memoria de la computadora.

que se haya escrito no se guardar realmente hasta no cerrar el archivo; o el lmite de cantidad de archivos que puede manejar un programa puede ser bajo, etc. Para cerrar un archivo simplemente se debe llamar a: archivo.close()

11.2.

Ejemplo de procesamiento de archivos

Por ejemplo, para mostrar todas las lneas de un archivo, precedidas por el nmero de lnea, podemos hacerlo de la siguiente manera: Cdigo 1 numera_lineas.py: Imprime las lneas de un archivo con su nmero
1 2 3 4 5 6 7

archivo = open("archivo.txt") i = 1 for linea in archivo: linea = linea.rstrip("\n") print " %4d: %s" % (i, linea) i+=1 archivo.close()

La llamada a rstrip es necesaria ya que cada lnea que se lee del archivo contiene un n de lnea y con la llamada a rstrip("\n") se remueve. Sabas que . . .
Los archivos de texto son sencillos de manejar, pero existen por lo menos 3 formas distintas de marcar un n de lnea. En Unix tradicionalmente se usa el caracter \n (valor de ASCII 10, denido como nueva lnea) para el n de lnea, mientras que en Macintosh el n de lnea se sola representar como un \r (valor ASCII 13, denido como retorno de carro) y en Windows se usan ambos caracteres \r\n. Si bien esto es algo que hay que tener en cuenta en una diversidad de casos, en particular en Python por omisin se maneja cualquier tipo de n de lnea como si fuese un \n, salvo que se le pida lo contrario. Para manejar los caracteres de n de lnea a mano se puede poner una U en el parmetro modo que le pasamos a open.

11.3.

Modo de apertura de los archivos

La funcin open recibe un parmetro opcional para indicar el modo en que se abrir el archivo. Los tres modos de apertura que se pueden especicar son:

11.4. Escribir en un archivo

107

Modo de slo lectura (r). En este caso no es posible realizar modicaciones sobre el archivo, solamente leer su contenido. Modo de slo escritura (w). En este caso el archivo es truncado si existe, y se lo crea si no existe. Modo slo escritura posicionndose al nal del archivo (a). En este caso se crea el archivo, si no existe, pero no se lo trunca en caso de que exista. Por otro lado, en cualquiera de estos modos se puede agregar un + para pasar a un modo lectura-escritura. El comportamiento de r+ y de w+ no es el mismo, ya que en el primer caso se tiene el archivo completo, y en el segundo caso se trunca el archivo, perdiendo as los datos. Si un archivo no existe y se lo intenta abrir en modo lectura, se generar un error; en cambio si se lo abre para escritura, Python se encargar de crear el archivo al momento de abrirlo, ya sea con w, a, w+ o con a+). En caso de que no se especique el modo, los archivos sern abiertos en modo slo lectura (r). Atencin
Si un archivo existente se abre en modo escritura (w o w+), todos los datos anteriores son borrados y reemplazados por lo que se escriba en l.

11.4.

Escribir en un archivo

De la misma forma que para la lectura, existen dos formas distintas de escribir a un archivo. Mediante cadenas: archivo.write(cadena) O mediante listas de cadenas: archivo.writelines(lista_de_cadenas) As como la funcin read devuelve las lneas con los caracteres de n de lnea (\n), ser necesario agregar los caracteres de n de lnea a las cadenas que se vayan a escribir en el archivo. Cdigo 2 genera_saludo.py: Genera el archivo saludo.py
1 2 3 4 5

saludo = open("saludo.py", "w") saludo.write(""" print "Hola Mundo" """) saludo.close()

En este ejemplo tenemos un programa Python que a su vez genera el cdigo de otro programa Python.

108

Unidad 11. Manejo de archivos Atencin

Si un archivo existente se abre en modo lectura-escritura, al escribir en l se sobreescribirn los datos anteriores, a menos que se haya llegado al nal del archivo. Este proceso de sobreescritura se realiza caracter por caracter, sin consideraciones adicionales para los caracteres de n de lnea ni otros caracteres especiales.

11.5.

Agregar informacin a un archivo

Abrir un archivo en modo agregar al nal puede parece raro, pero es bastante til. Uno de sus usos es para escribir un archivo de bitcora (o archivo de log), que nos permita ver los distintos eventos que se fueron sucediendo, y as encontrar la secuencia de pasos (no siempre evidente) que hace nuestro programa. Esta es una forma muy habitual de buscar problemas o hacer un seguimiento de los sucesos. Para los administradores de sistemas es una herramienta esencial de trabajo.

11.6.

Manipular un archivo en forma binaria

No todos los archivos son archivos de texto, y por lo tanto no todos los archivos pueden ser procesados por lneas. Existen archivos en los que cada byte tiene un signicado particular, y es necesario manipularlos conociendo el formato en que estn los datos para poder procesar esa informacin. Para abrir un archivo y manejarlo de forma binaria es necesario agregarle una b al parametro de modo. Sabas que . . .
La b en el modo de apertura viene de binario, por el sistema de numeracin binaria, ya que en el procesador de la computadora la informacin es manejada nicamente mediante ceros o unos (bits) que conforman nmeros binarios. Si bien no es necesaria en todos los sistemas, es una buena costumbre usarla, por ms que sirva principalmente como documentacin.

Para procesar el archivo de a bytes en lugar de lneas, se utiliza la funcin contenido = archivo.read(n) para leer n bytes y archivo.write(contenido), para escribir contenido en la posicin actual del archivo. Al manejar un archivo binario, es necesario poder conocer la posicin actual en el archivo y poder modicarla. Para obtener la posicin actual se utiliza archivo.tell(), que indica la cantidad de bytes desde el comienzo del archivo. Para modicar la posicin actual se utiliza archivo.seek(corrimiento, desde), que permite desplazarse una cantidad de bytes en el archivo, contando desde el comienzo del archivo, desde la posicin actual o desde el nal.

11.7.

Persistencia de datos

Se llama persistencia a la capacidad de guardar la informacin de un programa para poder volver a utilizarla en otro momento. Es lo que los usuarios conocen como Guardar el archivo y

11.8. Directorios

109

despus Abrir el archivo. Pero para un programador puede signicar ms cosas y suele involucrar un proceso de serializacin de los datos a un archivo o a una base de datos o a algn otro medio similar, y el proceso inverso de recuperar los datos a partir de la informacin serializada. Guardar el estado de un programa se puede hacer tanto en un archivo de texto, como en un archivo binario. En muchas situaciones es preferible guardar la informacin en un archivo de texto, ya que de esta manera es posible modicarlo fcilmente desde cualquier editor de textos. En general, los archivos de texto van a desperdiciar un poco ms de espacio, pero son ms faciles de entender y fciles de usar desde cualquier programa. Por otro lado, en un archivo binario bien denido se puede evitar el desperdicio de espacio, o tambin hacer que sea ms rpido acceder a los datos. Adems, para ciertas aplicaciones como archivos de sonido o video, tendra poco sentido almacenarlos en archivos de texto. En denitiva, la decisin de qu formato usar queda a discrecin del programador. Es importante recordar que el sentido comn es el valor ms preciado en un programador.

11.7.1.

Persistencia en archivos CSV

Un formato que suele usarse para transferir datos entre programas es csv (del ingls comma separated values: valores separados por comas) es un formato bastante sencillo, tanto para leerlo como para procesarlo desde el cdigo. Nombre,Apellido,Telefono,Cumpleaos "John","Smith","555-0101","1973-11-24" "Jane","Smith","555-0101","1975-06-12" En el ejemplo se puede ver una pequea base de datos. En la primera lnea del archivo tenemos los nombres de los campos, un dato opcional desde el punto de vista del procesamiento de la informacin, pero que facilita entender el archivo. En las siguientes lineas se ingresan los datos de la base de datos, cada campo separado por comas. Los campos que son cadenas se suelen escribir entre comillas dobles, si alguna cadena contiene alguna comilla doble se la reemplaza por \" y una contrabarra se escribe como \\. En Python es bastante sencillo procesar de este tipo de archivos, tanto para la lectura como para la escritura, mediante el mdulo csv. En el apndice de este captulo puede verse una aplicacin completa que almacena los datos del programa en archivos csv.

11.7.2.

Persistencia en archivos binarios

En el caso de que decidiramos grabar los datos en un archivo binario, Python incluye una herramienta llamada pickle que permite hacerlo de forma sencilla. Hay que tener en cuenta, sin embargo, que no es nada simple acceder a un archivo en este formato desde un programa que no est escrito en Python. En el apndice de este captulo pueden verse las funciones necesarias para utilizar pickle para almacenar datos en archivos.

11.8.

Directorios

Hasta aqu se ha mostrado el acceso a los archivos utilizando slo el nombre del archivo, esto nos permite acceder a los archivos en el directorio actual donde corre el programa.

110

Unidad 11. Manejo de archivos

Un problema relacionado con la utilizacin de directorios es que los separadores de directorios en distintos sistemas son distintos, / en Unix y Macintosh, \ en Windows. La manera de acceder a directorios independientemente del sistema en el que estamos desde Python es usando el modulo os. os.path.join("data","archivo.csv")

11.9.

Resumen

Para utilizar un archivo desde un programa, es necesario abrirlo, y cuando ya no se lo necesite, se lo debe cerrar. Las intrucciones ms bsicas para manejar un archivo son leer y escribir. Cada archivo abierto tiene relacionada una posicin que se puede consultar o cambiar. Los archivos de texto se procesan generalmente lnea por lnea y sirven para intercambiar informacin entre diversos programas o entre programas y humanos. En el caso de los archivos binarios, cada formato tiene sus propias reglas a seguir. Leer todo el contenido de un archivo, puede consumir memoria innecesariamente. Referencia del lenguaje Python
archivo=open(nombre[,modo[,tamao_buffer]]) Abre un archivo, nombre es el nombre completo del archivo, modo especica si se va usar para lectura (r), escritura truncando el archivo (w), o escritura agregando al nal del archivo (a), agregndole un + al modo el archivo se abre en lectura-escritura, agregndole una b el archivo se maneja como archivo binario, agregndole U los n de lnea se manejan a mano. tamao_buffer es un entero que especica el tamao del buffer deseado, si es negativo (por omisin es -1) el sistema operativo decide el tamao del buffer, si es 0 no se usa buffer, si es 1 se usa buffer por lneas. archivo.close() Cierra el archivo. linea=archivo.readline() Lee una lnea de texto del archivo for linea in archivo: for linea in archivo: # procesar linea Itera sobre las lineas del archivo. lineas = archivo.readlines() Devuelve una lista con todas las lneas del archivo. bytes = archivo.read([n])

11.9. Resumen
Devuelve la cadena de n bytes situada en la posicin actual de archivo. Si la cadena devuelta no contiene ningn caracter, es que se ha llegado al nal del archivo. De omitirse el parmetro n, devuelve una cadena que contiene todo el contenido del archivo. archivo.write(contenido) Escribe contenido en la posicin actual de archivo. posicion = archivo.tell() Devuelve un nmero que indica la posicin actual en archivo, es equivalente a la cantidad de bytes desde el comienzo del archivo. archivo.seek(corrimiento, [desde]) Modica la posicin actual en archivo, trasladndose corrimiento bytes. El parmetro opcional desde especica desde dnde se mide el valor que le pasemos a corrimiento. desde=0, contar desde el comienzo del archivo. Valor predeterminado. desde=1, contar desde la posicin actual. desde=2, contar desde el nal del archivo. Ejemplos: archivo.seek(0) # va al principio del archivo archivo.seek(0,2) # va al final del archivo archivo.seek(-16,1) # retrocede 16 bytes de la posicin actual os.path.exists(ruta) Indica si la ruta existe o no. No nos dice si es un directorio, un archivo u otro tipo de archivo especial del sistema. os.path.isfile(ruta) Indica si la ruta existe y es un archivo. Ejemplo de uso:
1 2 3 4 5 6 7 8 9 10

111

import os nombre="mi_archivo.txt" if not os.path.exists(nombre): archivo = open(nombre,"w+") elif os.path.isfile(nombre): archivo = open(nombre,"r+") else: print "Error, %s no es un archivo" % nombre

os.path.isdir(ruta) Indica si la ruta existe y es un directorio. os.path.join(ruta, ruta1[, ... rutaN]]) Une las rutas con el caracter de separacin de directorios que le corresponda al sistema en uso.

112

Unidad 11. Manejo de archivos

11.10.

Apndice

A continuacin, el cdigo para un programa de agenda que utiliza archivos csv. Luego, los cambios necesarios para que la agenda que utilice archivos en formato pickle, en lugar de csv. agenda-csv.py Agenda con los datos en csv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

#!/usr/bin/env python # encoding: latin1 import csv ARCHIVO="agenda.csv" CAMPOS=["Nombre","Apellido","Telefono","Cumpleaos"] def leer_csv(datos_csv): """ Devuelve la siguiente lnea o None si se termin el archivo. """ try: return datos_csv.next() except: return None def leer_datos(archivo): """ Carga todos los datos del archivo en una lista y la devuelve. """ abierto = open(archivo) datos_csv = csv.reader(abierto) campos = leer_csv(datos_csv) datos = [] elemento = leer_csv(datos_csv) while elemento: datos.append(elemento) elemento = leer_csv(datos_csv) abierto.close() return datos def guardar_datos(datos, archivo): """ Guarda los datos recibidos en el archivo. """ abierto = open(archivo,"w") datos_csv = csv.writer(abierto) datos_csv.writerow(CAMPOS) datos_csv.writerows(datos) abierto.close() def leer_busqueda(): """ Solicita al usuario nombre y apellido y los devuelve. """ nombre = raw_input("Nombre: ") apellido = raw_input("Apellido: ") return (nombre,apellido) def buscar(nombre, apellido, datos): """ Busca el primer elemento que coincida con nombre y con apellido. """ for elemento in datos: if nombre in elemento[0] and apellido in elemento[1]: return elemento return None

11.10. Apndice
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

113

def menu_alta(nombre, apellido, datos): """ Pregunta si se desea ingresar un nombre y apellido y de ser as, pide los datos al usuario. """ print "No se encuentra %s %s en la agenda." % (nombre, apellido) confirmacion = raw_input("Desea ingresarlo? (s/n) ") if confirmacion.lower() != "s": return telefono = raw_input("Telefono: ") cumple = raw_input("Cumpleaos: ") datos.append([nombre,apellido,telefono,cumple]) def mostrar_elemento(elemento): """ Muestra por pantalla un elemento en particular. """ print print " %s %s" % (elemento[0],elemento[1]) print "Telefono: %s" % elemento[2] print "Cumpleaos: %s" % elemento[3] print def menu_elemento(): """ Muestra por pantalla las opciones disponibles para un elemento existente. """ o = raw_input("b: borrar, m: modificar, ENTER para continuar (b/m): ") return o.lower() def modificar(viejo, nuevo, datos): """ Reemplaza el elemento viejo con el nuevo, en la lista datos. """ indice = datos.index(viejo) datos[indice] = nuevo def menu_modificacion(elemento, datos): """ Solicita al usuario los datos para modificar una entrada. """ nombre = raw_input("Nuevo nombre: ") apellido = raw_input("Nuevo apellido: ") telefono = raw_input("Nuevo telfono: ") cumple = raw_input("Nuevo cumpleaos: ") modificar(elemento, [nombre, apellido, telefono, cumple], datos) def baja(elemento, datos): """ Elimina un elemento de la lista. """ datos.remove(elemento) def confirmar_salida(): """ Solicita confirmacin para salir """ confirmacion = raw_input("Desea salir? (s/n) ") return confirmacion.lower() == "s" def agenda(): """ Funcin principal de la agenda. Carga los datos del archivo, permite hacer bsquedas, modificar borrar, y al salir guarda. """ datos = leer_datos(ARCHIVO) fin = False

114
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

Unidad 11. Manejo de archivos


while not fin: (nombre, apellido) = leer_busqueda() if nombre == "" and apellido == "": fin = confirmar_salida() continue elemento = buscar(nombre, apellido, datos) if not elemento: menu_alta(nombre, apellido, datos) continue mostrar_elemento(elemento) opcion = menu_elemento() if opcion == "m": menu_modificacion(elemento, datos) elif opcion == "b": baja(elemento, datos) guardar_datos(datos, ARCHIVO)

agenda()

agenda-pickle.py Diferencia de agenda con datos en pickle


4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

import pickle ARCHIVO="agenda.dat" def leer_datos(archivo): """ Carga todos los datos del archivo en una lista y la devuelve. """ abierto = open(archivo) datos = pickle.load(archivo) abierto.close() return datos def guardar_datos(datos, archivo): """ Guarda los datos recibidos en el archivo. """ abierto = open(archivo,"w") pickle.dump(archivo, datos) abierto.close()

Unidad 12

Manejo de errores y excepciones


12.1. Errores

En un programa podemos encontrarnos con distintos tipos de errores pero a grandes rasgos podemos decir que todos los errores pertenecen a una de las siguientes categoras. Errores de sintaxis: estos errores son seguramente los ms simples de resolver, pues son detectados por el intrprete (o por el compilador, segn el tipo de lenguaje que estemos utilizando) al procesar el cdigo fuente y generalmente son consecuencia de equivocaciones al escribir el programa. En el caso de Python estos errores son indicados con un mensaje SyntaxError. Por ejemplo, si trabajando con Python intentamos denir una funcin y en lugar de def escribimos dev. Errores semnticos: se dan cuando un programa, a pesar de no generar mensajes de error, no produce el resultado esperado. Esto puede deberse, por ejemplo, a un algoritmo incorrecto o a la omisin de una sentencia. Errores de ejecucin: estos errores aparecen durante la ejecucin del programa y su origen puede ser diverso. En ocasiones pueden producirse por un uso incorrecto del programa por parte del usuario, por ejemplo si el usuario ingresa una cadena cuando se espera un nmero. En otras ocasiones pueden deberse a errores de programacin, por ejemplo si una funcin intenta acceder a la quinta posicin de una lista de 3 elementos o realizar una divisin por cero. Una causa comn de errores de ejecucin que generalmente excede al programador y al usuario, son los recursos externos al programa, por ejemplo si el programa intenta leer un archivo y el mismo se encuentra daado. Tanto a los errores de sintaxis como a los semnticos se los puede detectar y corregir durante la construccin del programa ayudados por el intrprete y la ejecucin de pruebas. Pero no ocurre esto con los errores de ejecucin ya que no siempre es posible saber cuando ocurrirn y puede resultar muy complejo (o incluso casi imposible) reproducirlos. Es por ello que el resto del captulo nos centraremos en cmo preparar nuestros programas para lidiar con este tipo de errores.

12.2.

Excepciones

Los errores de ejecucin son llamados comnmente excepciones y por eso de ahora en ms utilizaremos ese nombre. Durante la ejecucin de un programa, si dentro de una funcin surge 115

116

Unidad 12. Manejo de errores y excepciones

una excepcin y la funcin no la maneja, la excepcin se propaga hacia la funcin que la invoc, si esta otra tampoco la maneja, la excepcin continua propagndose hasta llegar a la funcin inicial del programa y si esta tampoco la maneja se interrumpe la ejecucin del programa. Veamos entonces como manejar excepciones.

12.2.1.

Manejo de excepciones

Para el manejo de excepciones los lenguajes proveen ciertas palabras reservadas, que nos permiten manejar las excepciones que puedan surgir y tomar acciones de recuperacin para evitar la interrupcin del programa o, al menos, para realizar algunas acciones adicionales antes de interrumpir el programa. En el caso de Python, el manejo de excepciones se hace mediante los bloques que utilizan las sentencias try, except y finally. Dentro del bloque try se ubica todo el cdigo que pueda llegar a levantar una excepcin, se utiliza el trmino levantar para referirse a la accin de producir una excepcin. A continuacin se ubica el bloque except, que se encarga de capturar la excepcin y nos da la oportunidad de procesarla mostrando por ejemplo un mensaje adecuado al usuario. Dado que dentro de un mismo bloque try pueden producirse excepciones de distinto tipo, es posible utilizar varios bloques except, cada uno para capturar un tipo distinto de excepcin. Esto se hace especicando a continuacin de la sentencia except el nombre de la excepcin que se pretende capturar. Un mismo bloque except puede atrapar varios tipos de excepciones, lo cual se hace especicando los nombres de la excepciones separados por comas a continuacin de la palabra except. Es importante destacar que si bien luego de un bloque try puede haber varios bloques except, se ejecutar, a lo sumo, uno de ellos. try: # aqu ponemos el cdigo que puede lanzar excepciones except IOError: # entrar aqu en caso que se haya producido # una excepcin IOError except ZeroDivisionError: # entrar aqu en caso que se haya producido # una excepcin ZeroDivisionError except: # entrar aqu en caso que se haya producido # una excepcin que no corresponda a ninguno # de los tipos especificados en los except previos Como se muestra en el ejemplo precedente tambin es posible utilizar una sentencia except sin especicar el tipo de excepcin a capturar, en cuyo caso se captura cualquier excepcin, sin importar su tipo. Cabe destacar, tambin, que en caso de utilizar una sentencia except sin especicar el tipo, la misma debe ser siempre la ltima de las sentencias except, es decir que el siguiente fragmento de cdigo es incorrecto. try: # aqu ponemos el cdigo que puede lanzar excepciones except: # ERROR de sintaxis, esta sentencia no puede estar aqu, # sino que debera estar luego del except IOError.

12.2. Excepciones except IOError: # Manejo de la excepcin de entrada/salida

117

Finalmente, puede ubicarse un bloque finally donde se escriben las sentencias de nalizacin, que son tpicamente acciones de limpieza. La particularidad del bloque finally es que se ejecuta siempre, haya surgido una excepcin o no. Si hay un bloque except, no es necesario que est presente el finally, y es posible tener un bloque try slo con finally, sin except. Veamos ahora como es que acta Python al encontrarse con estos bloques. Python comienza a ejecutar las instrucciones que se encuentran dentro de un bloque try normalmente. Si durante la ejecucin del mismo se levanta una excepcin, Python interrumpe la ejecucin en el punto exacto en que surgi la excepcin y pasa a la ejecucin del bloque except correspondiente. Para ello, Python verica uno a uno los bloques except y si encuentra alguno cuyo tipo haga referencia al tipo de excepcin levantada, comienza a ejecutarlo. Sino encuentra ningn bloque del tipo correspondiente pero hay un bloque except sin tipo, lo ejecuta. Al terminar de ejecutar el bloque correspondiente, se pasa a la ejecucin del bloque finally, si se encuentra denido. Si, por otra parte, no hay problemas durante la ejecucin del bloque try, se completa la ejecucin del bloque, y luego se pasa directamente a la ejecucin del bloque finally (si es que est denido). Bajemos todo esto a un ejemplo concreto, supongamos que nuestro programa tiene que procesar cierta informacin ingresada por el usuario y guardarla en un archivo. Dado que el acceso a archivos es posible que genere excepciones, siempre deberamos colocar el cdigo de manipulacin de archivos cdigo dentro de un bloque try. Luego deberamos colocar un bloque except que atrape una excepcin del tipo IOError, que es el tipo de excepciones que lanzan la funciones de manipulacin de archivos. Adicionalmente podramos agregar un bloque except sin tipo por si surge alguna otra excepcin. Finalmente deberamos agregar un bloque finally para cerrar el archivo, haya surgido o no una excepcin. try: archivo = open("miarchivo.txt") # procesar el archivo except IOError: print "Error de entrada/salida." # realizar procesamiento adicional except: # procesar la excepcin finally: # si el archivo no est cerrado hay que cerrarlo if not(archivo.closed): archivo.close()

12.2.2.

Procesamiento y propagacin de excepciones

Hemos visto cmo atrapar excepciones, es necesario ahora que veamos qu se supone que hagamos al atrapar una excepcin. En primer lugar podramos ejecutar alguna lgica particular del caso como: cerrar un archivo, realizar una procesamiento alternativo al del bloque try, etc. Pero ms all de esto tenemos algunas opciones genricas que consisten en: dejar constancia de la ocurrencia de la excepcin, propagar la excepcin o, incluso, hacer ambas cosas.

118

Unidad 12. Manejo de errores y excepciones

Para dejar constancia de la ocurrencia de la excepcin, se puede escribir en un archivo de log o simplemente mostrar un mensaje en pantalla. Generalmente cuando se deja constancia de la ocurrencia de una excepcin se suele brindar alguna informacin del contexto en que ocurri la excepcin, por ejemplo: tipo de excepcin ocurrida, momento en que ocurri la excepcin y cules fueron las llamadas previas a la excepcin. El objetivo de esta informacin es facilitar el diagnstico en caso de que alguien deba corregir el programa para evitar que la excepcin siga apareciendo. Es posible, por otra parte, que luego de realizar algn procesamiento particular del caso se quiera que la excepcin se propague hacia la funcin que haba invocado a la funcin actual. Para hacer esto Python nos brinda la instruccin raise. Si se invoca esta instruccin dentro de un bloque except, sin pasarle parmetros, Python levantar la excepcin atrapada por ese bloque. Tambin podra ocurrir que en lugar de propagar la excepcin tal cual fue atrapada, quisiramos lanzar una excepcin distinta, ms signicativa para quien invoc a la funcin actual y que posiblemente contenga cierta informacin de contexto. Para levantar una excepcin de cualquier tipo, utilizamos tambin la sentencia raise, pero indicndole el tipo de excepcin que deseamos lanzar y pasando a la excepcin los parmetros con informacin adicional que queramos brindar. El siguiente fragmento de cdigo muestra este uso de raise. def dividir(dividendo, divisor): try: resultado = dividendo / divisor return resultado except ZeroDivisionError: raise ZeroDivisionError("El divisor no puede ser cero")

12.2.3.

Acceso a informacin de contexto

Para acceder a la informacin de contexto estando dentro de un bloque except existen dos alternativas. Se puede utilizar la funcin exc_info del mdulo sys. Esta funcin devuelve una tupla con informacin sobre la ltima excepcin atrapada en un bloque except. Dicha tupla contiene tres elementos: el tipo de excepcin, el valor de la excepcin y las llamadas realizadas. Otra forma de obtener informacin sobre la excepcin es utilizando la misma sentencia except, pasndole un identicador para que almacene una referencia a la excepcin atrapada. try: # cdigo que puede lanzar una excepcin except Exception, ex: # procesamiento de la excepcin cuya informacin # es accesible a travs del identificador ex

Sabas que . . .
En otros lenguajes, como el lenguaje Java, si una funcin puede lanzar una excepcin en alguna situacin, la o las excepciones que lance deben formar parte de la declaracin de la funcin y quien invoque dicha funcin est obligado a hacerlo dentro de un bloque try que la atrape.

12.3. Validaciones

119

12.3.

Validaciones

Las validaciones son tcnicas que permiten asegurar que los valores con los que se vaya a operar estn dentro de determinado dominio. Estas tcnicas son particularmente importantes al momento de utilizar entradas del usuario o de un archivo (o entradas externas en general) en nuestro cdigo, y tambin se las utiliza para comprobar precondiciones. Al uso intensivo de estas tcnicas se lo suele llamar programacin defensiva. Si bien quien invoca una funcin debe preocuparse de cumplir con las precondiciones de sta, si las validaciones estn hechas correctamente pueden devolver informacin valiosa para que el invocante pueda actuar en consecuencia. Hay distintas formas de comprobar el dominio de un dato. Se puede comprobar el contenido; que una variable sea de un tipo en particular; o que el dato tenga determinada caracterstica, como que ser comparable, o iterable. Tambin se debe tener en cuenta qu har nuestro cdigo cuando una validacin falle, ya que queremos darle informacin al invocante que le sirva para procesar el error. El error producido tiene que ser fcilmente reconocible. En algunos casos, como por ejemplo cuando se quiere devolver una posicin, devolver -1 nos puede asegurar que el invocante lo vaya a reconocer. En otros casos, levantar una excepcin es una solucin ms elegante. En cualquier caso, lo importante es que el resultado generado por nuestro cdigo cuando funciona correctamente y el resultado generado cuando falla debe ser claramente distinto. Por ejemplo, si el cdigo debe devolver un elemento de una secuencia, no es una buena idea que devuelva None en el caso de que la secuencia est vaca, ya que None es un elemento vlido dentro de una secuencia.

12.3.1.

Comprobaciones por contenido

Cuando queremos validar que los datos provistos a una porcin de cdigo contengan la informacin apropiada, ya sea porque esa informacin la ingres un usuario, fue leda de un archivo, o porque por cualquier motivo es posible que sea incorrecta, es deseable comprobar que el contenido de las variables a utilizar estn dentro de los valores con los que se puede operar. Estas comprobaciones no siempre son posibles, ya que en ciertas situaciones puede ser muy costoso corroborar las precondiciones de una funcin. Es por ello que este tipo de comprobaciones se realizan slo cuando sea posible. Por ejemplo, la funcin factorial est denida para los nmeros naturales incluyendo el 0. Es posible utilizar assert (que es otra forma de levantar una excepcin) para comprobar las precondiciones de factorial.
1 2 3 4 5 6 7 8 9 10

def factorial(n): """ Calcula el factorial de n. Pre: n debe ser un entero, mayor igual a 0 Post: se devuelve el valor del factorial pedido """ assert n >= 0, "n debe ser mayor igual a 0" fact=1 for i in xrange(2,n+1): fact*=i return fact

120

Unidad 12. Manejo de errores y excepciones

12.3.2.

Entrada del usuario

En el caso particular de una porcin de cdigo que trate con entrada del usuario, no se debe asumir que el usuario vaya a ingresar los datos correctamente, ya que los seres humanos tienden a cometer errores al ingresar informacin. Por ejemplo, si se desea que un usuario ingrese un nmero, no se debe asumir que vaya a ingresarlo correctamente. Se lo debe guardar en una cadena y luego convertir a un nmero, es por eso que es recomendable el uso de la funcin raw_input ya que devuelve una cadena que puede ser procesada posteriormente.
1 2 3 4 5

def lee_entero(): """ Solicita un valor entero y lo devuelve. Si el valor ingresado no es entero, lanza una excepcin. """ valor = raw_input("Ingrese un nmero entero: ") return int(valor) Esta funcin devuelve un valor entero, o lanza una excepcin si la conversin no fue posible. Sin embargo, esto no es suciente. En el caso en el que el usuario no haya ingresado la informacin correctamente, es necesario volver a solicitarla.

1 2 3 4 5 6 7 8 9 10

def lee_entero(): """ Solicita un valor entero y lo devuelve. Si el valor ingresado no es entero, vuelve a solicitar un valor. """ while True: valor = raw_input("Ingrese un nmero entero: ") try: valor = int(valor) return valor except ValueError: print "ATENCIN: Debe ingresar un nmero entero." Podra ser deseable, adems, poner un lmite a la cantidad mxima de intentos que el usuario tiene para ingresar la informacin correctamente y, superada esa cantidad mxima de intentos, levantar una excepcin para que sea manejada por el cdigo invocante.

1 2 3 4 5 6 7 8 9 10 11 12 13

def lee_entero(): """ Solicita un valor entero y lo devuelve. Si el valor ingresado no es entero, da 5 intentos para ingresarlo correctamente, y de no ser as, lanza una excepcin. """ intentos = 0 while intentos < 5: valor = raw_input("Ingrese un nmero entero: ") try: valor = int(valor) return valor except ValueError: intentos += 1 raise ValueError, "Valor incorrecto ingresado en 5 intentos" Por otro lado, cuando la entrada ingresada sea una cadena, no es esperable que el usuario la vaya a ingresar en maysculas o minsculas, ambos casos deben ser considerados.

def lee_opcion():

12.3. Validaciones
2 3 4 5 6 7

121

""" Solicita una opcin de men y la devuelve. """ while True: print "Ingrese A (Altas) - B (Bajas) - M (Modificaciones): ", opcion = raw_input().upper() if opcion in ["A", "B", "M"]: return opcion

12.3.3.

Comprobaciones por tipo

En esta clase de comprobaciones nos interesa el tipo del dato que vamos a tratar de validar, Python nos indica el tipo de una variable usando la funcin type(variable). Por ejemplo, para comprobar que una variable contenga un tipo entero podemos hacer: if type(i) != int: raise TypeError, "i debe ser del tipo int" Esto permite comprobar los tipos de las variables. Sin embargo, estas comprobaciones suelen resultar demasiado restrictivas, ya que es muy posible que una porcin de cdigo que opere con un tipo en particular funcione correctamente con otros tipos de variables que se comporten de forma similar. Ya hemos visto que tanto las listas como las tuplas y las cadenas son secuencias, y muchas de las funciones utilizadas puede utilizar cualquiera de estas secuencias. De la misma manera, una funcin puede utilizar un valor numrico, y que opere correctamente ya sea entero, otante, o complejo. Es posible comprobar el tipo de nuestra variable contra una secuencia de tipos posibles. if type(i) not in (int, float, long, complex): raise TypeError, "i debe ser numrico" Si bien esto es bastante ms exible que el ejemplo anterior, tambin puede ser restrictivo ya que -como se ver ms adelante- cada programador puede denir sus propios tipos utilizando como base los que ya estn denidos. Con este cdigo se estn descartando todos los tipos que se basen en int, float, long o complex. Para poder incluir estos tipos en la comprobacin a realizar, Python nos provee de la funcin isinstance(variable, tipos). if not isinstance(i, (int, float, long, complex) ): raise TypeError, "i debe ser numrico" Con esto comprobamos si una variable es de determinado tipo o subtipo de ste. Esta opcin es bastante exible, pero existen an ms opciones. Para la mayora de los tipos bsicos de Python existe una funcin que se llama de la misma manera que el tipo que devuelve un elemento de ese tipo, por ejemplo, int() devuelve 0, dict() devuelve {} y as. Adems, estas funciones suelen poder recibir un elemento de otro tipo para tratar de convertirlo, por ejemplo, int(3.0) devuelve 3, list("Hola") devuelve [H, o, l, a]. Usando est conversin conseguimos dos cosas: podemos convertir un tipo recibido al que realmente necesitamos, a la vez que tenemos una copia de este, dejando el original intacto, que es importante cuando estamos tratando con tipos mutables. Por ejemplo, si se quiere contar con una funcin de divisin entera que pueda recibir diversos parmetros, podra hacerse de la siguiente manera.

122

Unidad 12. Manejo de errores y excepciones

def division_entera(x,y): """ Calcula la divisin entera despus de convertir los parmetros a enteros. """ try: dividendo = int(x) divisor = int(y) return dividendo/divisor except ValueError: raise ValueError, "x e y deben poder convertirse a enteros" except ZeroDivisionError: raise ZeroDivisionError, "y no puede ser cero" De esta manera, la funcin division_entera puede ser llamada incluso con cadenas que contengan expresiones enteras. Que este comportamiento sea deseable o no depende siempre de cada caso.

12.3.4.

Comprobaciones por caractersticas

Otra posible comprobacin, dejando de lado los tipos, consiste en vericar si una variable tiene determinada caracterstica o no. Python promueve este tipo de programacin, ya que el mismo intrprete utiliza este tipo de comprobaciones. Por ejemplo, para imprimir una variable, Python convierte esa variable a una cadena, no hay en el interprete una vericacin para cada tipo, sino que busca la funcin __str__ de la variable a imprimir, y si existe, la utiliza para convertir la variable a una cadena. Sabas que . . .
Python utiliza la idea de duck typing, que viene del concepto de que si algo parece un pato, camina como un pato y grazna como un pato, entonces, se lo puede considerar un pato. Esto se reere a no diferenciar las variables por los tipos a los que pertenecen, sino por las funciones que tienen.

Para comprobar si una variable tiene o no una funcin Python provee la funcin hasattr(objeto, metodo). Por ejemplo, existe la funcin __add__ para realizar operaciones de suma entre elementos. Si se quiere corroborar si un elemento es sumable, se lo hara de la siguiente forma. if not hasattr(i,"__add__"): raise TypeError, "El elemento no es sumable" Sin embargo, que el atributo exista no quiere decir que vaya a funcionar correctamente en todos los casos. Por ejemplo, tanto las cadenas como los nmeros denen su propia suma, pero no es posible sumar cadenas y nmeros, de modo que en este caso sera necesario tener en cuenta una posible excepcin. Por otro lado, en la mayora de los casos se puede aplicar la frase: es ms fcil pedir perdn que permiso, atribuda a la programadora Grace Hopper. Es decir, en este caso es ms sencillo hacer la suma dentro de un bloque try y manejar la excepcin en caso de error, que saber cuales son los detalles de la implementacin de __add__ de cada tipo interactuante.

12.4. Resumen

123

12.4.

Resumen

Los errores que se pueden presentar en un programa son de sintaxis (detectados por el intrprete), de semntica (el programa no funciona correctamente), o de ejecucin (excepciones). Cuando el cdigo a ejecutar pueda producir una excepcin es deseable encerrarlo en los bloques correspondientes para actuar en consecuencia. Si una funcin no contempla la excepcin, sta es levantada a la funcin invocante, si sta no la contempla, la excepcin se pasa a la invocante, hasta que se llega a una porcin de cdigo que contemple la excepcin, o bien se interrumpe la ejecucin del programa. Cuando una porcin de cdigo puede levantar diversos tipos de excepciones, es deseable tratarlas por separado, si bien es posible tratarlas todas juntas. Cuando se genera una excepcin es importante actuar en consecuencia, ya sea mostrando un mensaje de error, guardndolo en un archivo, o modicando el resultado nal de la funcin. Antes de actuar sobre un dato en una porcin de cdigo, es deseable corroborar que se lo pueda utilizar, se puede validar su contenido, su tipo o sus atributos. Cuando no es posible utilizar un dato dentro de una porcin de cdigo, es importante informar el problema al cdigo invocante, ya sea mediante una excepcin o mediante un valor de retorno especial.

Referencia del lenguaje Python


try: ... except: try: # cdigo except [tipo_de_excepcin [, variable]]: # manejo de excepcin Puede tener tantos except como sea necesario, el ltimo puede no tener un tipo de excepcin asociado. Si el cdigo dentro del bloque try levanta una excepcin, se ejecuta el cdigo dentro del bloque except correspondiente. try: ... finally: try: # cdigo finally: # cdigo de limpieza El cdigo que se encuentra en el bloque finally se ejecuta al nalizar el cdigo que se encuentra en el bloque try, sin importar si se levant o no una excepcin. try: ... except: ... finally:

124
try:

Unidad 12. Manejo de errores y excepciones

# cdigo except [tipo_de_excepcin [, variable]]: # manejo de excepcin finally: # cdigo de limpieza Es una combinacin de los otros dos casos. Si el cdigo del bloque try levanta una excepcin, se ejecutar el manejador correspondiente y, sin importar lo que haya sucedido, se ejecutar el bloque finally al concluir los otros bloques. raise [excepcin[, mensaje]] Levanta una excepcin, para interrumpir el cdigo de la funcin invocante. Puede usarse sin parmetros, para levantar la ltima excepcin atrapada. El primer parmetro corresponde al tipo de excepcin a levantar. El mensaje es opcional, se utiliza para dar ms informacin sobre el error acontecido.

12.5.

Apndice

A continuacin se muestran dos ejemplos de implementacin de un programa que calcula la serie de Fibonacci para nmeros menores a 20. En primer lugar se muestra una implementacin sin uso de excepciones y luego otra que s las utiliza. def calcularFibonacciSinExcepciones(n): if (n>=20) or (n<=0): print Ha ingresado un valor incorrecto. El valor debe ser nmero entero mayor a cero y menor a 20 return salida=[] a,b = 0,1 for x in range(n): salida.append(b) a, b = b, a+b return salida def mainSinExcepciones(): input = raw_input(Ingrese n para calcular Fibonacci(n):) n = int(input) print calcularFibonacciSinExcepciones(n) def calcularFibonacciConExcepciones(n): try: assert(n>0) assert(n<20) except AssertionError: raise ValueError() a=0 b=1 salida = [] for x in range(n):

12.5. Apndice salida.append(b) a, b = b, a+b return salida

125

def mainConExcepciones(): try: input = raw_input(Ingrese n para calcular Fibonacci(n):) n = int(input) print calcularFibonacci2(n) except ValueError: print Ha ingresado un valor incorrecto. El valor debe ser un nmero entero mayor a cero y menor a 20

126

Unidad 12. Manejo de errores y excepciones

Unidad 13

Procesamiento de archivos
En la unidad anterior se explic como abrir, leer y escribir datos en los archivos. En general se quiere poder procesar la informacin que contienen estos archivos, para hacer algo til con ella. Dentro de las operaciones a realizar ms sencillas se encuentran los denominados ltros, programas que procesan la entrada lnea por lnea, pudiendo seleccionar qu lneas formarn parte de la salida y pudiendo aplicar una operacin determinada a cada una de estas lneas antes de pasarla a la salida. En esta unidad se indican algunas formas ms complejas de procesar la informacin leda en particular, dos algoritmos bastante comunes, llamados corte de control y apareo de archivos.

13.1.

Corte de control

La idea bsica de este algoritmo es poder analizar informacin, generalmente provista mediante registros, agrupndolos segn diversos criterios. Como precondicin se incluye que la informacin debe estar ordenada segn los mismos criterios por los que se la quiera agrupar. De modo que si varios registros tienen el mismo valor en uno de sus campos, se encuentren juntos, formando un grupo. Se lo utiliza principalmente para realizar reportes que requieren subtotales, cantidades o promedios parciales u otros valores similares. El algoritmo consiste en ir recorriendo la informacin, de modo que cada vez que se produzca un cambio en alguno de los campos correspondiente a uno de los criterios, se ejecutan los pasos correspondientes a la nalizacin de un criterio y el comienzo del siguiente.

13.1.1.

Ejemplo

Supongamos que en un archivo csv tenemos los datos de las ventas de una empresa a sus clientes y se necesita obtener las ventas por cliente, mes por mes, con un total por ao, otro por cliente y uno de las ventas totales. El formato est especicado de la siguiente forma: cliente,ao,mes,da,venta Para poder hacer el reporte como se solicita, el archivo debe estar ordenado en primer lugar por cliente, luego por ao, y luego por mes. Teniendo el archivo ordenado de esta manera, es posible recorrerlo e ir realizando los subtotales correspondientes, a medida que se los va obteniendo. 127

128

Unidad 13. Procesamiento de archivos ventas.py Recorre un archivo de ventas e imprime totales y subtotales

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

# encoding: latin1 import csv def leer_datos(datos): """ Devuelve el siguiente registro o None si no hay ms """ try: return datos.next() except: return None def ventas_clientes_mes(archivo_ventas): """ Recorre un archivo csv, con la informacin almacenada en el formato: cliente,ao,mes,da,venta """ # Inicializacin ventas = open(archivo_ventas) ventas_csv = csv.reader(ventas) item = leer_datos(ventas_csv) total = 0 while item: # Inicializacin para el bucle de cliente cliente = item[0] total_cliente = 0 print "Cliente %s" % cliente while item and item[0] == cliente: # Inicializacin para el bucle de ao anyo = item[1] total_anyo = 0 print "\tAo: %s" % anyo while item and item[0] == cliente and item[1] == anyo: mes, monto = item[2], float(item[3]) print "\t\tVentas del mes %s: %.2f" % (mes, monto) total_anyo += monto # Siguiente registro item = leer_datos(ventas_csv) # Final del bucle de ao print "\tTotal para el ao %s: %.2f" % (anyo, total_anyo) total_cliente += total_anyo # Final del bucle de cliente print "Total para el cliente %s: %.2f\n" % (cliente, total_cliente) total += total_cliente # Final del bucle principal print "Total general: %.2f" % total # Cierre del archivo ventas.close()

13.2. Apareo
54 55

129

ventas_clientes_mes("ventas.csv")

Se puede ver que para resolver el problema es necesario contar con tres bucles anidados, que van incrementando la cantidad de condiciones a vericar. Las soluciones de corte de control son siempre de estar forma. Una serie de bucles anidados, que incluyen las condiciones del bucle padre y agregan su propia condicin, y el movimiento hacia el siguiente registro se realiza en el bucle con mayor nivel de anidacin.

13.2.

Apareo

As como el corte de control nos sirve para generar un reporte, el apareo nos sirve para asociar/relacionar datos que se encuentran en distintos archivos. La idea bsica es a partir de dos archivos (uno principal y otro relacionado) que tienen alguna informacin que los enlace, generar un tercero, como una mezcla de los dos. Para hacer esto es conveniente que ambos archivos esten ordenados por el valor que los relaciona. Veamos un ejemplo sencillo: notas.py Recorre un archivo de alumnos y otro de notas e imprime las notas que corresponden a cada alumno
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

#!/usr/bin/env python # encoding: latin1 import csv def leer_datos(datos): """ Obtiene el siguiente registro, o devuelve None si lleg al fin del archivo. """ try: return datos.next() except: return None def imprimir_notas_alumnos(alumnos, notas): """ Abre los archivos de alumnos y notas, y por cada alumno imprime todas las notas que le corresponden. """ notas_a = open(notas) alumnos_a = open(alumnos) notas_csv = csv.reader(notas_a) alumnos_csv = csv.reader(alumnos_a) # Saltea los encabezados leer_datos(notas_csv) leer_datos(alumnos_csv) # Empieza a leer alumno = leer_datos(alumnos_csv) nota = leer_datos(notas_csv) while (alumno): print alumno[2]+", "+alumno[1]+" - "+alumno[0] if (not nota or nota[0] != alumno[0]): print "\tNo se registran notas" while (nota and nota[0] == alumno[0]):

130
33 34 35 36 37 38 39 40 41

Unidad 13. Procesamiento de archivos


print "\t"+nota[1]+": "+nota[2] nota = leer_datos(notas_csv) alumno = leer_datos(alumnos_csv) # Cierro los archivos notas_a.close() alumnos_a.close()

imprimir_notas_alumnos("alumnos.csv", "notas.csv")

En el ejemplo anterior usamos apareo de datos para agregar informacin, de forma similar se puede utilizar para agregar informacin nueva, borrar informacin o modicar datos de la tabla principal. Gran parte de las bases de datos relacionales basan su funcionamiento en estas funcionalidades.

13.3.

Resumen

Existen diversas formas de procesar archivos de informacin. Se puede simplemente ltrar la entrada para obtener una salida, o se pueden realizar operaciones ms complejas como el corte de control o el apareo El corte de control es una tcnica de procesamiento de datos ordenados por diversos criterios, que permite agruparlos para obtener subtotales. El apareo es una tcnica de procesamiento que involucra dos archivos con datos ordenados, y permite generar una salida combinada a partir de estos dos archivos.

Unidad 14

Objetos
Los objetos son una manera de organizar datos y de relacionar esos datos con el cdigo apropiado para manejarlo. Nosotros ya vimos (sin mencionar explcitamente esa palabra) algunos objetos de Python: todos los datos que hemos usado hasta el momento (todos los nmeros enteros, nmeros reales, lgicos, cadenas de caracteres, funciones, listas, tuplas, diccionarios y archivos que aparecieron hasta ahora en nuestros programas son ni ms ni menos que objetos). Hemos escrito por ejemplo cosas del estilo miarchivo.readline() o cadenita.split(:) y sa es simplemente la notacin que se usa para decir le aplicamos al objeto miarchivo (del tipo file) el mtodo readline o le aplicamos al objeto cadenita (del tipo str) el mtodo split con parmetro ":".

14.1.

Tipos

Un tipo dene un conjunto de datos y las operaciones que son aplicables a los objetos de ese tipo. Justamente hasta ahora hemos estado usando algunos tipos provistos por Python para poder programar (los enteros o int, los enteros largos o long, los nmeros con coma decimal o float, las cadenas o str, los valores lgicos o bool, las funciones o function, las listas o list, los diccionarios o dict, las tuplas o tuple y los archivos o file). Y hemos usado las operaciones especialmente denidas para esos tipos. >>> x=25 >>> type(x) <type int> >>> x=1.25 >>> type(x) <type float> >>> x="diccionario: dictionary" >>> type(x) <type str> >>> x.split(":") [diccionario, dictionary] >>> x=True >>> type(x) <type bool> 131

132

Unidad 14. Objetos

>>> def suma_uno(x): ... return x+1 ... >>> type(suma_uno) <type function> >>> x=[1,3,5] >>> type(x) <type list> >>> x={1:24, 34:87} >>> x[37] 87 >>> type(x) <type dict> >>> x=(45,12,22) >>> type(x) <type tuple> >>> x=open("texto.txt") >>> type(x) <type file> >>> print x.readline() A234567890123 >>> Con ellos podemos hacer todo lo que necesitamos. Podramos despreocuparnos y no estudiar nuevos tipos de ahora en adelante, y tendramos suciente material como para programar cualquier cosa que nos pidan. Sin embargo hay algo que les falta a estos tipos que vimos hasta ahora: son demasiado generales y les falta la posibilidad de asignarle una lgica interna, un comportamiento. Si nos piden que representemos un punto en el plano sabemos que podemos utilizar tuplas, de modo de representar un punto mediante un par de la forma (x, y), donde los dos atributos (coordenadas x e y) son nmeros. Pero nada nos impide que en medio de un programa escribamos una expresin de la forma p + q de modo que si p vale (7.22, -49.55) y q vale (8.5, 12.4), el resultado de esa operacin ser la cuaterna (7.22, -49.55, 8.5, 12.4)! Observacin 14.1.1. Esa concatenacin no tiene sentido como operacin entre puntos pero es totalmente vlida como operacin entre tuplas, y por eso Python la permite. Vamos ahora a mostrar cmo puede un programador denir nuevos tipos llamados clases (para Python clases son simplemente eso: tipos denidos por el usuario). Es decir nosotros, como programadores, construiremos clases (sencillas por ahora) que sirvan para modelar los requerimientos de un problema dado. Vamos a empezar justamente por los puntos del plano. Antes de empezar a construir la clase de los puntos introducimos algo ms de notacin: llamaremos mtodos a las funciones que pertenecen a los objetos, y que escribiremos dentro de las clases.

14.2.

Cmo denimos nuevos tipos: clases

Queremos denir una clase Point que nos servir para fabricar puntos en el plano.

14.2. Cmo denimos nuevos tipos: clases

133

Podemos imaginar a una clase como una fbrica de objetos. Cada vez que creemos un objeto usando a esa clase (ya veremos cmo) dicho objeto contendr los datos que se necesitan para representar a un punto y tambin se comportar como punto, es decir que entender slo de operaciones que tienen que ver con los puntos. Los objetos fabricados por una clase se denominan instancias de esa clase. Debemos decidir mediante qu datos (que llamaremos atributos) caracterizaremos a un punto en el plano. Surgen algunas alternativas: Representar los puntos por sus coordenadas cartesianas. Representar los puntos por sus coordenadas polares. Elegimos la representacin de coordenadas cartesianas por simplicidad, o sea que nos quedamos con dos atributos x e y que deben ser nmeros.

14.2.1.

Denicin de una clase

Inicialmente, construimos esa clase como sigue (los comentarios fueron usados para describir los elementos que constituyen el cuerpo de la clase): # este es el encabezado de la clase: class Point(object): # y esta es su descripcion """clase Point con atributos x, y que representan las coordenadas cartesianas""" # # # # # # # para asignarle valores iniciales a los atributos de un objeto que son instancias de una clase se define un metodo __init__ que tiene un parametro mas que la cantidad de atributos. Este parametro, el primero de todos, al que llamaremos self por cuestiones historicas, representa al propio objeto fabricado (en este caso el punto): def __init__(self, x=0, y=0): """se asignan valores iniciales a los atributos: Si no se le pasa un valor a x se considera 0. Si no se le pasa un valor a y se considera 0.""" # esta asignacion indica que a la coordenada x del punto que se crea # se le da el valor del parametro x de __init__ self.x = x # esta asignacion indica que a la coordenada y del punto que se crea # se le da el valor del parametro y de __init__ self.y = y

134

Unidad 14. Objetos Para construir un punto, simplemente se hace lo siguiente:

>>> p = Point(5,7) >>> p <__main__.Point object at 0x00D04E50> >>> Lo que acabamos de crear es un objeto de acuerdo al siguiente diagrama:

Para averiguar cunto valen las coordenadas de p (sus atributos) usamos la notacin <objeto> . <atributo>. Como en este caso el objeto se llama p y los atributos son x e y, escribiremos p.x y p.y: >>> p.x 5 >>> p.y 7 >>> Parecera que ya sabemos fabricar construcciones que se comporten como puntos. Lo volvemos a probar: >>> p=Point("hola", "Juan") >>> p.x hola >>> p.y Juan >>> Por lo visto todava a nuestra solucin le falta agregarle lgica interna, ya que __init__ acepta cualquier cosa como atributo, pese a que solamente debera aceptar como coordenadas slo a nmeros. Hacemos entonces una modicacin a __init__ de modo tal que: 1. Se testea si cada argumento es un nmero, y si alguno no lo es, se levanta una excepcin, (que en este caso ser TypeError, que es una excepcin que provee Python para errores de tipo) y no se construye el punto.

14.2. Cmo denimos nuevos tipos: clases 2. Si todo anda bien, se fabrica el objeto punto.

135

3. Se construye una funcin auxiliar isNumber para ser llamada desde __init__, que testea si el tipo de un valor est en la lista [int, oat, long]. Observacin 14.2.1. La manera de levantar una excepcin es mediante la instruccin raise excepcin El cdigo de __init__ queda as: def __init__(self, x=0, y=0): """se asignan valores iniciales Si no se le pasa un valor a Si no se le pasa un valor a Si x o y no son numeros, se TypeError"""

a los atributos: x se considera 0. y se considera 0. levanta una excepcion

# el constructor de la clase Point # verifica que los argumentos que se pasan # sean numeros if isNumber(x) and isNumber(y): # esta asignacion indica que # a la coordenada x del punto que se crea # se le da el valor del parametro x de __init__ self.x=x # esta asignacion indica que # a la coordenada y del punto que se crea # se le da el valor del parametro y de __init__ self.y=y else: # si alguno de los argumentos no es un mumero # levanta una excepcion TypeError raise TypeError def isNumber(value): """ funcion auxiliar para ver si un valor es numerico devuelve True si el tipo del valor es int, float o long""" return type(value) in [int, float, long]

14.2.2.

Una prueba poco robusta

Lo probamos con una funcin que prueba fabricar puntos de la ms variada calaa. Toda la fabricacin va a adentro de un bloque try, de modo que si al intentar fabricar un punto se levanta una excepcin, lo atrapa la instruccin except. def pruebita():

136

Unidad 14. Objetos try: a, b=1, 1 p=Point(a,b) print p.x, p.y a, b =2.0, 1.9 p=Point(a, b) print p.x, p.y a, b = "Hola", 3 p=Point(a,b) print p.x, p.y p=Point(3,"Juan") print p.x, p.y

except TypeError: print a, b, "argumento invalido"

Ejecutamos pruebita() y obtenemos la siguiente respuesta: >>> pruebita() 1 1 2.0 1.9 Hola 3 argumento invalido >>> Podemos hacer dos observaciones: Evidentemente ahora que usamos excepciones hay que tener ms cuidado con las pruebas, porque tal como la hemos estructurado, cuando salta el primer error no contina probando. Hasta donde lleg (hasta el tercer caso incluido) funcion de acuerdo a lo esperado. Deberamos poder mostrar un punto con un mtodo propio y no a travs de sus atributos. Nos quedan entonces dos tareas pendientes: (a) escribir un mtodo dentro de la clase Point para mostrar un punto y (b) disear una mejor prueba.

14.2.3.

Un mtodo para mostrar objetos

Para mostrar objetos, Python indica que hay que agregarle a la clase un mtodo __str__ que devuelva una cadena de caracteres con lo que queremos mostrar. Ese mtodo luego se puede invocar directamente, pero adems se invoca cada vez que se llama a la funcin str. El mtodo __str__ tiene un solo parmetro, self. En nuestro caso decidimos mostrar el punto como un par, por lo que escribimos el siguiente mtodo dentro de la clase Point:

def __str__(self): """ el metodo para mostrar los objetos de la clase,

14.2. Cmo denimos nuevos tipos: clases los muestra como si fueran pares ordenados:""" return str((self.x, self.y)) Veamos cmo se lo usa: >>> p=Point(5,7) >>> p.__str__() (5, 7) >>> str(p) (5, 7) >>>

137

Observacin 14.2.2. En Python, los mtodos se invocan con la notacin punto: p.__str__(), cadenita.split(":"). Analicemos la segunda expresin. El signicado de sta es: self, que en este caso es cadenita, llama al mtodo split (del cual es duea por tratarse de una instancia del tipo str) con el argumento ":". Esta notacin provoc un cambio de paradigma en la programacin, y es uno de los ejes de la Programacin Orientada a Objetos. Las funciones, como str o isNumber, que no pertenecen a ninguna clase, se invocan con la notacin estndar <funcin>(<argumentos>).

14.2.4.

Una prueba robusta

Nos abocamos ahora a la construccin de la funcin que prueba la construccin de puntos. Respecto de la especicacin, debe cumplir con los siguientes requisitos: Leer cada una de las coordenadas y ser robusta ante los errores que pueden cometer los usuarios (en particular es muy comn, por ejemplo, que no se tipeen nmeros sino cadenas que Python confunde con nombres de variables). Permitir que los usuarios ingresen tantos puntos como lo deseen. Muestrar los puntos construidos (o informar la excepcin levantada, en caso contrario). Respecto del diseo, tomamos las siguientes decisiones: Disearemos por separado una funcin para leer cualquier dato, que sea robusta ante los errores de tipeo de los usuarios. Dado que nadie sabe cuntos datos se querrn cargar, la estructura ser la de un ciclo con centinela. El centinela ser una cadena * en la variable x, que se reere al valor de la abscisa del punto. El tratamiento de los datos estar dentro de un bloque try para manejar los errores de la construccin del punto: ac se construir cada punto y se lo mostrar. El bloque except mostrar la excepcin levantada por el contructor del punto. Mostramos ahora el programa de prueba completo, incluyendo una funcin leerDato robusta.

138

Unidad 14. Objetos

def leerDato(s): """El parmetro s es la cadena que se muestra para solicitarle el dato al usuario. Se devuelve el valor tipeado por el usuario.""" # Los problemas del ingreso de datos se basan en que # el usuario puede poner mal los dedos. Hay que atrapar # esos errores hasta que el dato ingrese bien. while True: # apenas el usuario ingresa un dato sin error, lo devuelve try: return input(s) except: # Aca se atrapa cualquier excepcion de dato mal ingresado. # Por ejemplo es muy comun que se levante SyntaxError: # SyntaxError se levanta cada vez que # se tipea * en lugar de *: # File "<string>", line 1 # * # ^ # SyntaxError: unexpected EOF while parsing pass def main(): """Prueba la construccion de puntos. Se usa el esquema de ciclo con centinela. Los puntos validos leidos se muestran por pantalla. Se informan tambien los casos de datos invalidos.""" x=leerDato("Coordenada x de un punto, * para terminar: ") while x != *: try: y=leerDato("Coordenada y de un punto: ") p=Point(x,y) print str(p) except TypeError: # a TypeError la levanta el constructor de Point # cuando no recibe un numero ## print x,y, "coordenadas invalidas" x=leerDato("Coordenada x de un punto, * para terminar: ") Ac hay un ejemplo de ejecucin de la prueba: >>> main() Coordenada x de un punto, * para terminar: a Coordenada x de un punto, * para terminar: *

14.3. Denir relaciones de orden Coordenada x de un punto, * para terminar: Coordenada y de un punto: "hola" 1 hola coordenadas invalidas Coordenada x de un punto, * para terminar: Coordenada y de un punto: 1 (1, 1) Coordenada x de un punto, * para terminar: Coordenada y de un punto: 2.0 (1.5, 2.0) Coordenada x de un punto, * para terminar: Coordenada y de un punto: 1 hola 1 coordenadas invalidas Coordenada x de un punto, * para terminar: Coordenada y de un punto: 3 (1, 2, 3, 4) 3 coordenadas invalidas Coordenada x de un punto, * para terminar: Coordenada y de un punto: 5 (333333333333333344444444444L, 5) Coordenada x de un punto, * para terminar: Coordenada x de un punto, * para terminar: >>> 1

139

1.5

"hola"

(1,2,3,4)

333333333333333344444444444

* "*"

Ejercicio 14.1. Dado que a la funcin isNumber la usaremos a menudo, construir un mdulo validaciones donde incluiremos esa funcin y todas las validaciones de tipo que vayamos necesitando. Ejercicio 14.2. Agregar a la clase Point un mtodo dist que permita calcular la distancia eucldea entre dos puntos dada por la frmula dist((x1 , y1 ), (x2 , y2 )) = (x1 x2 )2 + (y1 y2 )2 . El mtodo recibir como parmetro al punto hasta el que se quiere medir la distancia.

14.3.

Denir relaciones de orden

Python posee una funcin cmp que se usa internamente para comparar objetos entre s. Esta funcin recibe dos argumentos y devuelve -1 si el primer argumento es menor que el segundo,0 si el primer argumento es igual al segundo, y 1 si el primer argumento es mayor que el segundo. >>> -1 >>> 1 >>> -1 >>> 0 >>> 1 >>> cmp (5, 7) # 5 < 7 cmp (7, 5) # 7 > 5 cmp(5, A) # 5 < A cmp (A, A) # A == A cmp (A, 5) # A> 5

140

Unidad 14. Objetos

Esta funcin es la que se usa internamente para comparar entre s elementos de una lista y ordenarlos mediante sort: >>> >>> >>> [5, >>> xs= [A, 5, 7, A] xs.sort() xs 7, A, A]

Python permite que adems denamos un orden arbitrario para ordenar nuestros objetos. Para ello debemos denir una funcin que reciba dos argumentos y que devuelva -1 si el primer argumento es menor que el segundo segn la nueva regla, 0 si el primer argumento es igual al segundo segn la nueva regla, y 1 si el primer argumento es mayor que el segundo segn la nueva regla. Al invocar la funcin sort debemos indicarle que cmp se reemplaza por la nueva funcin, de acuerdo a una sintaxis que indicaremos a continuacin. Supongamos que queremos ordenar una lista segn la siguiente regla: Los nmeros son mayores que los dems objetos, pero adems se ordenan en orden decreciente. Los dems objetos son menores que los nmeros pero van ordenados en orden creciente. Antes que nada debemos denir la funcin de comparacin de acuerdo a esa regla: def orden_raro(x,y): Devuelve -1 si x < y, 0 si x == y, 1 si x > y segun la siguiente regla: Los nmeros son mayores que los dems objetos, pero adems se ordenan en orden decreciente. Los dems objetos son menores que los nmeros pero van ordenados en orden creciente. if type(x) in [int, long, float] \ and type (y) in [int, long, float]: # x e y numeros return -cmp(x,y) # los numeros en orden inverso elif type (x) in [int, long, float]: # x es numero, y no lo es return 1 # los numeros son mayores que cualquier cosa elif type (y) in [int, long, float]: # y es numero, x no lo es return -1 # los numeros son mayores que cualquier cosa else: return cmp(x,y) Para indicarle a sort que la funcin de comparacin es orden_raro escribiremos sort(cmp = orden_raro)} cuando invocamos el mtodo: >>> ys=[A, B, 5, 7, A] >>> ys.sort(cmp=orden_raro) >>> ys [A, A, B, 7, 5] >>>

14.4. Denir relaciones de orden dentro de los elementos de una clase

141

14.4.

Denir relaciones de orden dentro de los elementos de una clase

Nos contratan para disear una clase que permita evaluar la relacin calidadprecio de hoteles. Nos dicen que los atributos que se cargarn de los hoteles son nombre, ubicacin, puntaje obtenido por votacin, y precio, y que adems de fabricar hoteles y mostrarlos, debemos poder compararlos en trminos de sus valores de relacin calidadprecio, de modo tal que x <y signique que el hotel x es peor en cuanto a la relacin calidadprecio que el hotel y, y que dos hoteles son iguales si tienen la misma relacin calidadprecio. La relacin calidadprecio de un hotel la denen nuestros clientes como (puntaje2 ) 10./precio. Adems, y como resultado de todo esto, tendremos que ser capaces de ordenar de menor a mayor una lista de hoteles, usando el orden que nos acaban de denir. Averiguamos un poco ms respecto de los atributos de los hoteles: El nombre y la ubicacin deben ser cadenas no vacas. El puntaje debe ser un nmero (sin restricciones sobre su valor) y el precio debe ser un nmero distinto de cero. Empezamos disear a la clase: El mtodo __init__: Fabricar objetos Hotel con los atributos que se indicaron en el prrafo anterior. Los valores por omisin para la construccin son: puntaje en 0, precio en float("inf"), nombre y ubicacin en * (el precio muy alto sirve para que no se elija un hotel cuyo precio no se informa). Necesitamos validar que puntaje y precio sean nmeros (ya construimos la funcin isNumber para usarla en el caso de los puntos). Cuando un precio viene en cero se reemplaza su valor por float("inf") (de modo de asegurar que el precio nunca quede en cero). Necesitamos validar que nombre y ubicacin sean cadenas no vacas (para lo cual tenemos que construir una funcin isNEString). Cuando los datos no satisfagan los requisitos se levantar una excepcin TypeError. Contar con un mtodo __str__ para mostrar a los hoteles mediante una cadena del estilo "Hotel City de Mercedes - Puntaje: 3.25 - Precio: 78 pesos". Respecto a la relacin de orden entre hoteles, la clase deber poder contar con los mtodos necesarios para realizar esas comparaciones y para ordenar una lista de hoteles. Casi todas las tareas, excepto el ltimo tem de la lista anterior, son sencillas de realizar (ya vimos casi todos los ingredientes necesarios cuando fabricamos la clase Point). Ejercicio 14.3. Escribir la funcin isNEString(value) que decide si un valor cualquiera es una cadena no vaca o no, e incluirla en el mdulo validaciones. El fragmento inicial de la clase programada en Python queda as:

142

Unidad 14. Objetos

#validaciones de condiciones generales de los datos from validaciones import isNumber, isNEString class Hotel(object): """Hotel: sus atributos son nombre, ubicacion, puntaje y precio. Nombre y ubicacion son cadenas puntaje y precio son numeros. Si el precio es 0 se reemplaza con lo cual el atributo precio Los valores por defecto son: * para nombre y ubicacion, 0 float("inf") para precio.""" def __init__(self, nombre = *, \ ubicacion = *, \ puntaje = 0, \ precio = 1000): if isNEString (nombre) and isNEString (ubicacion) \ and isNumber(puntaje) and isNumber(precio): self.nombre = nombre self.ubicacion = ubicacion self.puntaje = puntaje if precio!=0: self.precio = precio else: self.precio = 1000 else: raise TypeError def __str__(self): """para mostrar una instancia""" return self.nombre + " de "+self.ubicacion+\ " - Puntaje: "+ str(self.puntaje) + " - Precio: "+\ str(self.precio)+ " pesos." Si denimos el mtodo auxiliar ratio(self) que calcula la relacin calidadprecio de una instancia de Hotel como: def ratio(self): """calcula la relacion calidad--precio de un hotel de acuerdo a la formula

no vacias, por 1000, nunca es 0. para puntaje,

14.4. Denir relaciones de orden dentro de los elementos de una clase que nos dio el cliente""" return ((self.puntaje**2)*10.)/self.precio

143

Las reglas de comparacin se denen en una funcin de dos parmetros __cmp__ que devuelve -1 si self es menor que el segundo parmetro, 0 si self es igual al segundo parmetro, y 1 si self es mayor que el segundo parmetro, segn la regla establecida: def __cmp__(self, otro_hotel): """Compara el ratio de self con el ratio de otro_hotel. -1 si self tiene peor ratio 0 si self tiene igual ratio 1 si self tiene mejor ratio

Y devuelve: que otro_hotel que otro_hotel que otro_hotel"""

return cmp(self.ratio(), otro_hotel.ratio()) Una vez denida la compracin segn el criterio deseado, se podr ordenar una lista de hoteles, y tambin comparar hoteles entre s mediante las relaciones de orden habituales: >>> h1=Hotel("Hotel >>> h2=Hotel("Hotel >>> h3=Hotel("Hotel >>> h4=Hotel("Hotel >>> h1<h2 False >>> h1==h2 True >>> h1>h2 False >>> lp=[h1, h2, h3, >>> lp.sort() >>> for h in lp: str(h) 1* normal", "MDQ", 1, 10) 2* normal", "MDQ", 2, 40) 3* carisimo", "MDQ", 3, 130) vale la pena" ,"MDQ", 4, 130)

h4]

Hotel Hotel Hotel Hotel >>>

3* carisimo de MDQ - Puntaje: 3 - Precio: 130 pesos. 1* normal de MDQ - Puntaje: 1 - Precio: 10 pesos. 2* normal de MDQ - Puntaje: 2 - Precio: 40 pesos. vale la pena de MDQ - Puntaje: 4 - Precio: 130 pesos.

Ejercicio 14.4. Ordenar de menor a mayor las listas de hoteles segn el criterio: (1) ubicacin en orden alfabtico y (2) dentro de cada ubicacin por relacin calidadprecio. Ejercicio 14.5. Queremos representar cunto dinero hay en una caja, desglosado por tipo de billete (por ejemplo, en el quiosco de la esquina hay 5 billetes de 10 pesos, 7 monedas de 25 centavos y 4 monedas de 10 centavos).

144

Unidad 14. Objetos

Tenemos que poder comparar cajas por la cantidad de dinero que hay en cada una, y adems ordenar una lista de cajas de menor a mayor segn la cantidad de dinero disponible.

Unidad 15

Ms sobre objetos: polimorsmo y herencia


En esta clase veremos dos temas que son centrales a la programacin orientada a objetos: polimorsmo y herencia.

15.1.

Polimorsmo

Una funcin o un mtodo es polimrco si puede recibir parmetros de distintos tipos. Este concepto signica que esperamos que objetos de distintos tipos puedan realizar la misma accin. Veamos algunos ejemplos de funciones polimrcas. Para comenzar, escribiremos una funcin que recibe dos parmetros de cualquier tipo a y b y que devuelve la terna (a, b, a): def palindromo(a,b): """ devuelve una terna (a, b, a) a partir de dos parametros cualesquiera a, b""" return(a,b,a) La podemos invocar con argumentos de cualquier tipo: >>> palindromo(1,5) (1, 5, 1) >>> palindromo([3, 2], [["hola"]]) ([3, 2], [[hola]], [3, 2]) >>> palindromo({"a": 1, "c": 3}, "Centro") ({a: 1, c: 3}, Centro, {a: 1, c: 3}) >>> A veces puede suceder que una funcin polimrca exija que el tipo del parmetro tenga alguna restriccin. En el ejemplo que sigue, se calcula la frecuencia de apariciones de valores que conforman una secuencia. El parmetro debe ser de cualquier tipo secuencial (lista, cadena, tupla o rango): 145

146

Unidad 15. Ms sobre objetos: polimorsmo y herencia

def frecuencias(seq): """ calcula las frecuencias de aparicion de valores en una secuencia. seq tiene que ser una secuencia (lista, cadena de caracteres, tupla o rango). Devuelve un diccionario de la forma valor: frecuencia de aparicion en seq""" # crea diccionario vacio frec = dict() # recorre la secuencia for v in seq: if v in frec: frec[v]=frec[v]+1 else: frec[v] = 1 return(frec) Vemos que el argumento puede ser de varios tipos, pero siempre de la familia de las secuencias. En cambio, si la llamamos con un entero se levanta una excepcin. >>> frecuencias(["peras", "manzanas", "peras", "manzanas", "uvas"]) {uvas: 1, peras: 2, manzanas: 2} >>> frecuencias((1,3,4,2,3,1)) {1: 2, 2: 1, 3: 2, 4: 1} >>> frecuencias("Una frase") {a: 2, : 1, e: 1, f: 1, n: 1, s: 1, r: 1, U: 1} >>> ran = xrange(3, 10, 2) >>> frecuencias(ran) {9: 1, 3: 1, 5: 1, 7: 1} >>> frecuencias(4) Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> frecuencias(4) File "C:\src\frecuencias.py", line 12, in frecuencias for v in seq: TypeError: int object is not iterable >>>

15.1.1.

Sobrecarga

Cuando analizamos las funciones de respiracin, reproduccin, etc., de los seres vivos vemos que siempre se repite el mismo patrn: si bien la accin en todos los casos es la misma, puede suceder que haya diferencias en la implementacin en cada tipo, ya que no es lo mismo la respiracin de una mojarrita que la de un malvn, no es lo mismo la reproduccin de una ameba que la de un elefante.

15.2. Herencia

147

En la unidad anterior vimos ese mismo patrn al analizar el comportamiento de la funcin str(), que muestra lo que contiene un objeto. Si no se dene explcitamente un mtodo __str__ dentro de la clase correspondiente (en nuestro caso Point), lo que muestra str() al ser invocada con un objeto de esa clase es una expresin del estilo de <__main__.Point object at 0x00D09E10>. Sin embargo, pudimos denir explcitamente el mtodo __str__ dentro de Point, de modo tal de mostrar los puntos como un par ordenado: def __str__(self): """ el metodo para mostrar los objetos de la clase, los muestra como si fueran pares ordenados:""" return str((self.x, self.y)) Esto es entonces lo que sucede cuando se lo invoca: >>> str(Point(5,4)) (5, 4) >>> Este mecanismo se conoce con el nombre de sobrecarga: con el mismo nombre se denen mtodos especcos para clases especcas. Diremos entonces que la funcin que invoca a estos mtodos (en este caso str()) est sobrecargada. Veamos cmo est sobrecargado un viejo conocido, el operador +: >>> 1+4 5 >>> [1]+[4] [1, 4] >>> Y en la clase pasada vimos otras sobrecargas: originalmente, la clase Hotel no tena manera de comparar objetos (hoteles) entre s. Sin embargo, denimos una relacin de orden entre hoteles, y la implementamos mediante el agregado de mtodos __eq__, __lt__, __gt__, etc. Pudimos entonces escribir expresiones tales como Hotel("H.City", "MDQ", 3, 100) < Hotel("H.Castelar", "MDQ", 4, 100), etc., sobrecargando de ese modo los operadores de comparacin.

15.2.

Herencia

La herencia es un mecanismo de la programacin orientada a objetos que sirve para fabricar clases nuevas a partir de clases preexistentes. Se toman (heredan) atributos y comportamientos de las clases viejas y se los modica para modelar una nueva situacin. La clase vieja se llama clase base y la que se construye a partir de ella es una clase derivada. Por ejemplo, a partir de una clase Automovil (que contenga como atributos marca, modelo, ao de fabricacin y nmero de motor) podemos construir la clase RegistroAutomotor que extiende a Automovil y agrega como atributos patente y nombre del propietario. Para indicar el nombre de la clase base, se la pone entre parntesis a continuacin del nombre de la clase (en lugar de la expresin object que ponamos anteriormente en realidad object es el nombre de la clase base genrica). Denimos Automovil:

148

Unidad 15. Ms sobre objetos: polimorsmo y herencia

class Automovil(object): def __init__(self, marca=*, modelo=*, anho=0, motor=0): self.marca=marca self.modelo=modelo self.anho=anho self.motor=motor def __str__(self): return str((self.marca, self.modelo, self.anho, self.motor)) A continuacin denimos RegistroAutomotor como derivada de Automovil, modicando la inicializacin para registrar la patente y el dueo (llamaremos a la inicializacin de los atributos compartidos con la clase base, para aprovechar la funcionalidad de la misma):

class RegistroAutomotor(Automovil): def __init__(self, marca=*, modelo=*, anho=0, motor=0, \ patente =*, duenho=*): # aprovechamos la inicializacion de Automovil Automovil.__init__(self, marca, modelo, anho, motor)

# y luego agregamos los nuevos atributos self.patente=patente self.duenho = duenho

Probamos la nueva clase: >>> a=RegistroAutomotor("Ford", "Fiesta", 1998, 0, "XYZ 145", "Juan Lopez") >>> str(a) "(Ford, Fiesta, 1998, 0)" Vemos que se hered el mtodo __str__ de la clase base. Si queremos, podemos sobreescribirlo: # el nuevo metodo para mostrar objetos de RegistroAutomotor def __str__(self): return str((self.marca, self.modelo, self.anho, self.motor, \ self.patente, self.duenho))

Volvemos a probar: >>> a=RegistroAutomotor("Ford", "Fiesta", 1998, 0, "XYZ 123", "Juan Perez") >>> str(a) "(Ford, Fiesta, 1998, 0, XYZ 123, Juan Perez)" >>>

15.2. Herencia

149

Por supuesto que de una clase base se pueden construir muchas clases derivadas. En el caso de Python, tambin se puede construir una clase derivada a partir de varias clases base (por ejemplo, vehculos anbios como derivados de los vehculos de tierra y los vehculos de agua), pero no lo veremos por ahora. Ejercicio 15.1. A RegistroAutomotor le falta un mtodo para cambiar la titularidad del automvil. Agregarlo. Ejercicio 15.2. Derivar una clase DisponibilidadHotel a partir de Hotel que tenga un calendario de disponibilidades de un hotel y donde se puedan hacer reservas para una fecha dada. Ejercicio 15.3. Se necesita una clase que permita ver la disponibilidad hotelera de una ciudad, y que cuente con un mtodo aconsejar que, para una fecha dada, aconseje cules son los hoteles disponibles ordenados de mayor a menor por la relacin calidadprecio. Debe permitir reservar una habitacin en el hotel elegido.

15.2.1.

La clase de las guras

Un ejemplo clsico de herencia es el de las guras cerradas en el plano, con un mtodo para calcular el rea. En este caso, la clase base no tiene comportamiento denido ni atributos, dado que cada gura tiene atributos muy distintos (radio en el caso del crculo, base y altura en el caso del tringulo, etc.), y en cuanto al clculo del rea, cada gura tiene una frmula diferente: La clase base: class Figura(object): def area(self): pass Los crculos: from math import pi class Circulo(Figura): def __init__(self, radio=0): self.radio = radio def area (self): return pi * self.radio * self.radio Y los tringulos: class Triangulo(Figura): def __init__(self, base=0, altura=0): self.base = base self.altura = altura def area (self): return self.base * self.altura / 2.0

150

Unidad 15. Ms sobre objetos: polimorsmo y herencia Y ahora las pruebas:

>>> c=Circulo(4) >>> c.area() 50.26548245743669 >>> >>> t = Triangulo(3, 5) >>> t.area() 7.5 >>>

Unidad 16

Referencias - Listas enlazadas


Hasta ahora vimos ejemplos en los que los atributos de un objeto eran de tipos primitivos de Python (enteros, cadenas de caracteres, etc.). Sin embargo, tambin es posible que se desee construir un objeto que haga referencia a otro objeto.

16.1.

Referencias

Debemos construir ahora una clase Rectangulo, donde cada objeto se describe por los siguientes atributos: Longitud de su base: un nmero. Longitud de su altura: un nmero. El punto del plano de su esquina SO: un punto del plano. con mtodos para inicializar y mostrar. Construimos entonces la clase: import clasePoint class Rectangulo(object): """la clase de los rectangulos""" def __init__(self, base, altura, esquinaSO): """ base (numero) es la longitud de su base, altura (numero) es la longitud de su base, esquinaSO (Point) es el punto del plano de su esquina SO""" self.base = base self.altura = altura self.esquinaSO = esquinaSO def __str__(self): """ muestra un rectangulo""" return "Base: "+str(self.base)+ " Altura: "+str(self.altura)+ \ " Esquina SO: " +str(self.esquinaSO) 151

152

Unidad 16. Referencias - Listas enlazadas def modBase(self, nbase): """ modifica el valor de la base, lo cambia a nbase""" self.base = nbase def modAltura(self, naltura): """ modifica el valor de la altura, lo cambia a naltura""" self.altura = naltura def modSO(self, nSO): """ modifica el valor de la esquina SO, lo cambia a nSO""" self.esquinaSO = nSO

Para construir un rectngulo hacemos lo siguiente: >>> r=Rectangulo(2,4,clasePoint.Point(6,-1)) >>> str(r) Base: 2 Altura: 4 Esquina SO: (6, -1) >>> Lo que acabamos de crear es un objeto de acuerdo al siguiente diagrama:

El punto que describe la posicin de la esquina SO del rectngulo es un objeto Point. El atributo esquinaSO contiene una referencia a dicho objeto. De hecho, podemos luego hacer lo siguiente: >>> q=clasePoint.Point(7,2) >>> r.modSO(q) >>> str(r) Base: 2 Altura: 4 Esquina SO: (7, 2) >>>

16.2. Listas enlazadas Con lo cual nuestro diagrama se transforma en:

153

Observacin 16.1.1. El Point(6, -1) no se usa ms, por lo que queda a disposicin de un mecanismo de recoleccin de basura que estudiarn ms adelante, y que se encarga de juntar todos los pedazos de memoria que se descartan en medio de la ejecucin de un programa.

16.2.

Listas enlazadas

Ahora que sabemos como enlazar objetos entre s, estamos en condiciones de construir nuestras propias listas (enlazando objetos). Y ya veremos por qu pueden ser tiles esas listas, pese a que Python ya viene con su propia lista.

16.2.1.

Una clase sencilla de vagones

Deniremos una clase muy simple, Nodo, que se comportar como un vagn: tendr slo dos atributos (dato, que servir para almacenar cualquier informacin, y prox, que servir para poner una referencia al siguiente vagn (tambin de la clase Nodo), y como siempre, para comenzar, los dos mtodos ms elementales, __init__ y __str__. class Nodo(object): def __init__(self, dato=None, prox = None): self.dato = dato self.prox = prox def __str__(self): return str(self.dato) Ejecutamos este cdigo: >>> v3=Nodo("Bananas") >>> v2=Nodo("Peras", v3) >>> v1=Nodo("Manzanas", v2) >>> str(v1) Manzanas >>> str(v2)

154

Unidad 16. Referencias - Listas enlazadas

Peras >>> str(v3) Bananas >>> Con esto hemos generado esta estructura:

El atributo prox de v3 tiene una referencia nula, lo que indica que v3 es el ltimo vagn de nuestra estructura. Hemos creado una lista en forma manual. Si nos interesa recorrerla, podemos hacer lo siguiente: >>> def verLista(nodo): """ recorre todos los nodos a traves de sus enlaces, mostrando sus contenidos """ # cicla mientras nodo no es None while nodo: # muestra el dato print str(nodo) # ahora nodo apunta a nodo.prox nodo = nodo.prox

>>> verLista(v1) Manzanas Peras Bananas >>> Noten que la estructura del recorrido de la lista es el siguiente: Se le pasa a la funcin slo la referencia al primer nodo. El resto del recorrido se consigue siguiendo las cadena de referencias. Para desenganchar un vagn del medio, alcanza con cambiar el enganche: >>> v1.prox=v3 >>> verLista(v1) Manzanas Bananas >>> v1.prox = None

16.2. Listas enlazadas >>> verLista(v1) Manzanas >>>

155

De esta manera tambin se pueden generar estructuras impensables. Qu sucede si escribimos v1.prox = v1? La representacin es nita y sin embargo en este caso verLista(v1) no termina ms. Hemos creado una lista innita. Ejercicio 16.1. Cul es la mejor manera de tener siempre manzanas y peras a disposicin de uno? Si seguimos las echas dadas por las referencias, obtenemos un camino en la lista. Los caminos cerrados se denominan ciclos. Son ciclos, por ejemplo, la autorreferencia de v1 a v1, como as tambin una echa de v1 a v2 seguida de una echa de v2 a v1. Observacin 16.2.1. Las listas innitas no tienen nada de malo en s mismas, mientras su representacin sea nita. El problema, en cambio, es que debemos tener mucho cuidado al escribir programas para recorrerlas, ya que el recorrido debe ser acotado (por ejemplo no habra problema en ejecutar un programa que liste los 20 primeros nodos de una lista innita). Cuando una funcin recibe una lista y el recorrido no est acotado por programa, se debe aclarar en su precondicin que la ejecucin de la misma terminar slo si la lista no contiene ciclos. se es el caso de la funcin verLista(v1). Una cuestin no contemplada hasta el momento es la de mantener una referencia a la lista completa. Por ahora para nosotros la lista es la coleccin de nodos que se enlazan a partir de v1. Sin embargo puede suceder que querramos borrar a v1 y continuar con el resto de la lista como la coleccin de nodos a tratar (en las listas de Python, del xs[0] no nos hace perder la referencia a xs). Para ello lo que haremos es asociar una referencia al principio de la lista, que llamaremos xs, y que mantendremos independientemente de cul sea el nodo que est al principio de la lista: >>> v3=Nodo("Bananas") >>> v2=Nodo("Peras", v3) >>> v1=Nodo("Manzanas", v2) >>> xs=v1 >>> verLista(xs) Manzanas Peras Bananas >>> Ahora s estamos en condiciones de borrar el primer elemento de la lista sin perder la identidad de la misma: >>> xs=v1.prox >>> verLista(xs) Peras Bananas >>>

156

Unidad 16. Referencias - Listas enlazadas

Una cosa muy importante que sucede con esta implementacin de lista es que ahora borrar el primer elemento se puede hacer en tiempo constante (a diferencia de la lista de Python, donde esta operacin se hace en tiempo proporcional a la longitud de la lista). Volveremos a esta representacin cada vez que necesitemos una estructura con esa propiedad. Sin embargo no todo son rosas: el acceso a la posicin p se realiza en tiempo lineal.

16.2.2.

La clase ListaEnlazada

Deniremos ahora la clase ListaEnlazada, de modo tal que no tengamos que hacer toda la manipulacin a travs de las referencias de los nodos, sino a travs de operaciones de lista. Es claro que necesitamos tener como atributo la referencia al primer elemento de la lista. En cuanto a sus mtodos, incluiremos: __str__, para mostrar la lista. __len__, para calcular la longitud de la lista. append(x), para agregar un elemento al nal de la lista. insert(i, x), para agregar el elemento x en la posicin i (levanta una excepcin si la posicin i es invlida). remove(x), para eliminar la primera aparicin de x en la lista (levanta una excepcin si x no est). pop([i]), para borrar el elemento que est en la posicin i y devolver su valor. Si no se especica el valor de i, pop() elimina y devuelve el elemento que est en el ltimo lugar de la lista (levanta una excepcin si se hace referencia a una posicin no vlida de la lista). index(x), devuelve la posicin de la primera aparicin de x en la lista (levanta una excepcin si x no est). reverse(), da vuelta la lista. Valen ahora algunas consideraciones ms antes de empezar a implementar la clase: Si necesitamos incluir un mtodo __len__ no tiene sentido recorrer la lista cada vez para contar cuntos elementos tiene, alcanza con agregar un atributo ms (la longitud de la lista), que se inicializa en 0 cuando se crea la lista vaca, se incrementa en 1 cada vez que se agrega un elemento y se decrementa en 1 cada vez que se borra un elemento. Finalmente, si ya tenemos las operaciones de listas, no tiene sentido que la clase Nodo est disponible para que los programadores puedan destrozar las listas a voluntad usando operaciones de nodos. Para eso incluiremos la clase Nodo de manera privada (es decir oculta) ya veremos cmo, de modo que la podamos usar nosotros como dueos (fabricantes) de la clase, pero no cualquier programador que pasa por ah. Python tiene una convencin para hacer que atributos, mtodos o clases dentro de una clase dada no puedan ser usado por los usuarios, y slo tenga acceso a ellos quienes programan la clase: su nombre tiene que empezar con un guin bajo y terminar sin guin bajo. As que para

16.2. Listas enlazadas

157

hacer que los nodos sean privados, nombraremos a esa clase como _Nodo, y la dejaremos tal como hasta ahora. Denimos la inicializacin de la clase. Tambin hay que denir __str__, pero eso queda como ejercicio: class ListaEnlazada(object): def __init__(self): """crea una lista enlazada vacia. prim: apuntara al primer nodo (por ahora None) len: longitud de la lista (por ahora 0) """ self.prim = None self.len = 0 def __str__(self): """ queda como ejercicio """ Nuestra estructura ahora ser la siguiente:

pop y un mtodo para ubicar nodos por posicin Analizaremos a continuacin pop([i]), que borra el elemento que est en la posicin i y devuelve su valor. Si no se especica el valor de i, pop() elimina y devuelve el elemento que est en el ltimo lugar de la lista (levanta una excepcin si se hace referencia a una posicin no vlida de la lista). Si no se indica posicin se considera i como la ultima posicin de la lista. Esto se resuelve con este fragmento de cdigo: if i==None: # se considera i como la ultima posicion de la lista i=self.len-1 Si la posicin es invlida (i menor que 0 o mayor o igual a la longitud de la lista, se considera error (y se levanta la excepcin ValueError). Esto se resuelve con este fragmento de cdigo: if (i < 0) or (i>=self.len): # la posicion es invalida raise IndexError

158

Unidad 16. Referencias - Listas enlazadas Cuando la posicin es 0 se trata de un caso particular, ya que ac, adems de borrar el nodo, hay que rehacer la referencia de self.prim al principio de la lista (y pasar de self.prim ->nodo0 ->nodo1 a self.prim ->nodo1). Esto se resuelve con este fragmento de cdigo: if i == 0: # caso particular, borrar el primer nodo # hay que saltearlo desde la cabecera de la lista valorRetorno = self.prim.dato self.prim = self.prim.prox Vemos ahora el caso general: Mediante un ciclo, se deben ubicar los nodos npi-1 y npi que estn en las posiciones i-1 e i de la lista, respectivamente, de modo de poder ubicar no slo el nodo que se borrar, sino tambin estar en condiciones de saltear el nodo borrado en los enlaces de la lista (la lista debe pasar de contener el camino npi-1 ->npi ->npi.prox a contener el camino npi-1 ->npi.prox). Nos basaremos un esquema muy simple (y til) que se denomina mquina de parejas: Si nuestra secuencia tiene la forma ABCDE, se itera sobre ella de modo de tener las parejas AB, BC, CD, DE. En la pareja XY, llamaremos a X el elemento anterior y a Y el elemento actual. En general estos ciclos terminan o bien cuando no hay ms parejas que formar, o bien cuando el elemento actual cumple con una determinada condicin. En nuestro problema, tenemos la siguiente situacin: Las parejas son parejas de nodos. Para avanzar en la secuencia se usa la referencia al prximo nodo de la lista. Las condicin de terminacin es siempre que la posicin del nodo en la lista sea igual al valor buscado (en nuestro caso no debemos preocuparnos por la terminacin de la lista porque la validez del ndice buscado ya fue testeado ms arriba). Esto da lugar a una funcin auxiliar _ubicarPorIndice(self, i) que utiliza el esquema de mquina de parejas y devuelve dos referencias: al nodo anterior al buscado y al nodo buscado, y que se especica as: def _ubicarPorIndice(self, i): """ ubicar el nodo que tiene el indice i de la lista. Precondicion: 0<i<self.len Postcondicion: rant hace referencia al nodo anterior ract hace referencia al nodo buscado"""

Con esta funcin a nuestra disposicin, se programa el caso general de pop: else: # se deben ubicar los nodos npi-1 y npi # que estn en las posiciones i-1 e i de la lista

16.2. Listas enlazadas npi_1, npi = self._ubicarPorIndice(i) valorRetorno = npi.dato # se saltea el nodo borrado npi_1.prox = npi.prox

159

Finalmente, en todos los casos de xito, se debe devolver el dato que contena el nodo eliminado y decrementar la longitud en 1:

# hay que restar 1 de len self.len = self.len-1 # y retornar el valor borrado return valorRetorno

Este es el cdigo de pop y de la funcin auxiliar:

def pop(self, i = None): """ Elimina el nodo de la posicion i y retorna el dato que contiene. Si i esta fuera de rango, se levanta la excepcion IndexError. pop() se interpreta como pop(self.len -1 )"""

if i==None: # se considera i como la ultima posicion de la lista i=self.len-1 if (i < 0) or (i>=self.len): # la posicion es invalida raise IndexError if i == 0: # caso particular, borrar el primer nodo # hay que saltearlo desde la cabecera de la lista valorRetorno = self.prim.dato self.prim = self.prim.prox else: # se deben ubicar los nodos npi-1 y npi # que estn en las posiciones i-1 e i de la lista

160

Unidad 16. Referencias - Listas enlazadas npi_1, npi = self._ubicarPorIndice(i) valorRetorno = npi.dato # se saltea el nodo borrado npi_1.prox = npi.prox # hay que restar 1 de len self.len = self.len-1 # y retornar el valor borrado return valorRetorno

def _ubicarPorIndice(self, i): """ ubicar el nodo que tiene el indice i de la lista. Precondicion: 0<i<self.len Postcondicion: rant hace referencia al nodo anterior ract hace referencia al nodo buscado""" # iact es el indice del actual nodo iact = 1 # rant hace referencia al nodo anterior # ract hace referencia al nodo actual # inicialmente ract es el segundo nodo de la lista rant = self.prim ract = self.prim.prox # ciclo hasta que iact alcance el valor de i while iact < i: iact=iact+1 rant = ract ract = ract.prox return rant, ract

remove y un mtodo para ubicar nodos por valor Anlogamente se resuelve remove(self,x), que debe eliminar la primera aparicin de x en la lista (y levantar una excepcin si x no est). Los casos particulares son la lista vaca (es un error y hay que levantar una excepcin) y el caso en el que x esta en el primer nodo (hay que saltear el primer nodo desde la cabecera de la lista). El fragmento de cdigo que resuelve estos casos es: if self.len == 0:

16.2. Listas enlazadas # lista vacia es error raise ValueError elif self.prim.dato == x: # caso particular, x esta en el primer nodo # hay que saltearlo desde la cabecera de la lista self.prim = self.prim.prox

161

El caso general tambin implica un recorrido con mquina de pareja, slo que esta vez la condicin de terminacin es: o bien la lista se termin o bien encontramos un nodo con el valor (x) buscado. Para ello construimos una funcin auxiliar _ubicarPorValor(self, x) que se especica de la siguiente manera: def _ubicarPorValor(self, x): """ ubicar el primer nodo cuyo dato vale x, devuelve rant y ract. Si encuentra el nodo: rant hace referencia al nodo anterior ract hace referencia al nodo buscado, si no encuentra el nodo, ract es None. Precondicion: self.len >=2 y x no esta en la primera posicion."""

El cdigo del caso general es el siguiente: else: # # # # # # busca a x en la lista obtiene el nodo anterior al que contiene a x (rant) y el nodo que contiene a x (rx). Cuando encuentre x (si lo encuentra) debera pasar de rant -> rx -> rx.prox a rant -> rx.prox

rant, rx = self._ubicarPorValor(x) if rx == None: # no se encontro a x en la lista: raise ValueError else: # rx.dato == x # hay que cortocircuitar rant->rx->rx.prox # y pasar a rant -> rx.prox rant.prox = rx.prox

162

Unidad 16. Referencias - Listas enlazadas Finalmente, en todos los casos de xito debemos decrementar en 1 el valor de self.len. El cdigo completo de remove y de _ubicarPorValor son los siguientes: def remove(self, x): """ Borra la primera aparicion del valor x en la lista. Si x no esta levanta ValueError """ if self.len == 0: # lista vacia es error raise ValueError elif self.prim.dato == x: # caso particular, x esta en el primer nodo # hay que saltearlo desde la cabecera de la lista self.prim = self.prim.prox else: # # # # # # busca a x en la lista obtiene el nodo anterior al que contiene a x (rant) y el nodo que contiene a x (rx). Cuando encuentre x (si lo encuentra) debera pasar de rant -> rx -> rx.prox a rant -> rx.prox

rant, rx = self._ubicarPorValor(x) if rx == None: # no se encontro a x en la lista: raise ValueError else: # rx.dato == x # hay que cortocircuitar rant->rx->rx.prox # y pasar a rant -> rx.prox rant.prox = rx.prox

# hay que restar 1 de len self.len = self.len - 1 def _ubicarPorValor(self, x): """ ubicar el primer nodo cuyo dato vale x, devuelve rant y ract. Si encuentra el nodo: rant hace referencia al nodo anterior ract hace referencia al nodo buscado, si no encuentra el nodo, ract es None.

16.2. Listas enlazadas Precondicion: self.len >=2 y x no esta en la primera posicion.""" # rant hace referencia al nodo anterior # ract hace referencia al nodo actual # inicialmente ract es el segundo nodo de la lista rant = self.prim ract = self.prim.prox # cicla hasta que encuentra a x # o hasta que se acaba la lista while (ract != None) and (ract.dato != x): rant = ract ract = ract.prox

163

return rant, ract

insert Debemos programar ahora insert(i, x), que debe agregar el elemento x en la posicin i (y levantar una excepcin si la posicin i es invlida). Veamos qu debemos tener en cuenta para programar esta funcin. Los casos particulares son: Insertar en una posicin menor que cero o mayor que la longitud de la lista (error, debe levantarse una excepcin). Insertar en la posicin self.len (tiene que llamar a append). Insertar en la posicin 0 (hay que cambiar la referencia de self.prim). Para estos casos particulares el cdigo resultante es: if (i > self.len) or (i < 0): # error raise IndexError # indice valido if i == self.len: # insertar al final, es append: self.append(x) else: # cualquier otro caso # crea nuevo nodo, con x como dato:

164

Unidad 16. Referencias - Listas enlazadas nuevo = _Nodo(x) if i == 0: # insertar al principio # (caso particular) # el siguiente del nuevo es # el actual primero de la lista nuevo.prox = self.prim # el primero de la lista # es el nuevo self.prim = nuevo Construir un nodo nuevo cuyo dato sea x. Obtener referencias al nodo npi_1, que se encuentra en la posicin i-1, y al nodo npi, que se encuentra en la posicin i, y que constituyen el camino npi_1 ->npi Insertar el nodo nuevo entre los dos. De esa manera obtendremos el camino npi_1 >nuevo ->npi (y por lo tanto nuevo se ubicar en la posicin isima de la lista). Por el requisito de encontar un par de nodos consecutivos, volvemos ac a utilizar la ya construida _ubicarPorIndice. El cdigo del caso general es (nuevo ya fue construido ms arriba): else: # insertar en cualquier lugar > 0 # # # # # se debe ubicar el par de nodos npi-1 -> npi que esta en las posiciones i-1, i de modo de poder intercalar nuevo y obtener npi-1 -> nuevo -> npi

npi_1, npi = self._ubicarPorIndice(i) nuevo.prox = npi_1.prox npi_1.prox = nuevo En todos los casos de xito se debe incrementar en 1 la longitud de la lista. # incrementar en 1 la longitud self.len = self.len + 1 El cdigo resultante de insert es: def insert(self, i, x): """ inserta el elemento x

16.2. Listas enlazadas en la posicion i. Levanta una excepcion IndexError si la posicion no tiene sentido"""

165

if (i > self.len) or (i < 0): # error raise IndexError # indice valido if i == self.len: # insertar al final, es append: self.append(x) else: # cualquier otro caso # crea nuevo nodo, con x como dato: nuevo = _Nodo(x) if i == 0: # insertar al principio # (caso particular) # el siguiente del nuevo es # el actual primero de la lista nuevo.prox = self.prim # el primero de la lista # es el nuevo self.prim = nuevo else: # insertar en cualquier lugar > 0 # # # # # se debe ubicar el par de nodos npi-1 -> npi que esta en las posiciones i-1, i de modo de poder intercalar nuevo y obtener npi-1 -> nuevo -> npi

npi_1, npi = self._ubicarPorIndice(i) nuevo.prox = npi_1.prox npi_1.prox = nuevo # incrementar en 1 la longitud self.len = self.len + 1

166

Unidad 16. Referencias - Listas enlazadas

Ejercicio 16.2. Completar la clase ListaEnlazada con los mtodos que faltan: __str__, append, index y reverse. Ejercicio 16.3. Mantenimiento: Con esta representacin conseguimos que la insercin en la posicin 0 se realice en tiempo constante, sin embargo ahora append es lineal en la longitud de la lista. Como nuestro cliente no est satisfecho con esto debemos agregar un atributo ms a los objetos de la clase, la referencia al ltimo nodo, y modicar append para que se pueda ejecutar en tiempo constante. Por supuesto que adems hay que modicar todos los mtodos de la clase para que se mantenga la propiedad de que ese atributo siempre es una referencia al timo nodo.

Unidad 17

Tipos abstractos de datos: Pilas y colas


Los tipos nuevos que denimos hasta ahora fueron tipos de datos concretos: un punto se dena como un par ordenado de nmeros, un hotel se dena por dos cadenas de caracteres (nombre y unicacin) y dos nmeros (calidad y precio), etc. Vamos a ver ahora una nueva manera de denir datos: por las operaciones que tienen y por lo que tienen que hacer esas operaciones (cul es el resultado esperado de esas operaciones). Esa manera de denir datos se conoce como tipos abstractos de datos o TADs. Lo novedoso de este enfoque respecto del anterior es que en general se puede encontrar ms de una representacin mediante tipos concretos para representar el mismo TAD, y que se puede elegir la representacin ms conveniente en cada caso, segn el contexto de uso. Los programas que los usan hacen referencia a las operaciones que tienen, no a la representacin, y por lo tanto ese programa sigue funcionando si se cambia la representacin. Vamos a ver dos ejemplos de tipos abstractos de datos, los ms clsicos de la literatura: pilas y colas.

17.1.

Pilas

Una pila es un TAD que tiene las siguientes operaciones (se describe tambin la accin que lleva adelante cada operacin): __init__: Inicializa una pila nueva, vaca. apilar: Agrega un nuevo elemento a la pila. desapilar: Elimina el tope de la pila y lo devuelve. El elemento que se devuelve es siempre el ltimo que se agreg. esPilaVacia: Devuelve True o False segn si la pila est vaca o no. Noten que el comportamiento de una pila se puede describir mediante la frase Lo ltimo que se apil es lo primero que se usa, que es exactamente lo que uno hace con una pila (de platos por ejemplo): en una pila de platos uno slo puede ver la apariencia completa del plato de arriba, y slo puede tomar el plato de arriba (si se intenta tomar un plato del medio de la pila lo ms probable es que alguno de sus vecinos, o l mismo, se arruine). Dentro del ciclo de vida de un TAD hay dos fases: la programacin del TAD y la construccin de los programas que lo usan. 167

168

Unidad 17. Tipos abstractos de datos: Pilas y colas

Para programar un TAD hay que elegir una representacin, y luego programar cada uno de los mtodos sobre ella. En el caso de la pila, si bien puede haber ms de una representacin, por ahora veremos la ms clsica: representaremos una pila mediante una lista. Sin embargo, para los que construyen programas que usan un TAD vale el siguiente llamado de atencin: Observacin 17.1.1. Al usar esa pila dentro de un programa, deberemos ignorar que se est trabajando sobre una lista: solamente podremos usar los mtodos de pila. Si alguien viola este principio, y usa la representacin dentro del programa usuario, termina por recibir el peor castigo imaginable para un/a programador/a: sus programas pueden dejar de funcionar el cualquier momento, tan pronto como quien produce del TAD decida cambiar, aunque sea sutilmente, dicha representacin.

17.1.1.

Pilas representadas por listas

Deniremos una clase Pila con un atributo, items, de tipo lista, que contendr los elementos de la pila. El tope de la pila se encontrar en la ltima posicin de la lista, y cada vez que se apile un nuevo elemento, se lo agregar al nal. El mtodo __init__ no recibir parmetros adicionales, ya que deber crear una pila vaca (que representaremos por una lista vaca): class Pila: """ crea una pila vacia representada por una lista vacia""" def __init__(self): self.items=[] El mtodo apilar se implementar agregando el nuevo elemento al nal de la lista: def apilar(self, x): """ apila x en self (lo agrega al final de la lista)""" self.items.append(x)

Para implementar desapilar, se usar el mtodo pop de lista que hace exactamente lo requerido: elimina el ltimo elemento de la lista y devuelve el valor del elemento eliminado. Si la pila est vaca levanta una excepcin. def desapilar(self): """ elimina el ultimo elemento de la lista y retorna su valor. Si la pila esta vacia se devolvera None.""" try: return self.items.pop() except: return None

17.1. Pilas def esPilaVacia(self): """ devuelve True si la pila esta vacia, False en caso contrario.""" return (self.items == []) Construimos algunas pilas y operamos con ellas: >>> p=Pila() >>> p.esPilaVacia() True >>> p.apilar(1) >>> p.esPilaVacia() False >>> p.apilar(5) >>> p.apilar("+") >>> p.apilar(22) >>> p.desapilar() 22 >>> p <__main__.Pila instance at 0x00D11418> >>> q=Pila() >>> q.desapilar() >>>

169

17.1.2.

Uso de pila: calculadora cientca

La famosa calculadora porttil HP-35 (de 1972) populariz la notacin polaca inversa (o notacin prejo) para hacer clculos sin necesidad de usar parntesis. Esa notacin, inventada por el lgico polaco Jan Lukasiewicz en 1920, se basa en el principio de que un operador siempre se escribe a continuacin de sus operandos. La operacin (5 3) + 8 se escribir como 5 3 - 8 +, que se interpretar como: restar 3 de 5, y al resultado sumarle 8. Es posible implementar esta notacin de manera sencilla usando una pila de la siguiente manera, a partir de una cadena de entrada de valores separados por blancos: Mientras se lean nmeros, se apilan. En el momento en el que se detecta una operacin binaria +, -, *, / o % se desapilan los dos ltimos nmeros apilados, se ejecuta la operacin indicada, y el resultado de esa operacin se apila. Si la expresin est bien formada, tiene que quedar al nal un nico nmero en la pila (el resultado). Los posibles errores son: (a) queda ms de un nmero al nal (por ejemplo si la cadena de entrada fue "5 3"), (b) ingresa algn caracter que no se puede interpretar ni como nmero ni como una de las cinco operaciones vlidas (por ejemplo si la cadena de entrada fue "5 3 &") y (c) no hay sucientes operandos para realizar la operacin (por ejemplo si la cadena de entrada fue "5 3 - +").

170

Unidad 17. Tipos abstractos de datos: Pilas y colas

La siguiente es la estrategia de resolucin: Dada una cadena con la expresin a evaluar, podemos separar sus componentes utilizando el mtodo split(). Recorreremos luego la lista de componentes realizando las acciones indicadas en el prrafo anterior, utilizando una pila auxiliar para operar. Si la expresin est bien formada devolveremos el resultado, de lo contrario levantaremos una excepcin (devolveremos None). Y ac est la implementacin:

import pila def polaca(lexp): """Dada una lista de textos que representan las componentes de una expresion en notacion polaca inversa, evalua dicha expresion. Si la expresion esta mal formada, devuelve None""" p = pila.Pila() for tok in lexp: print "DEBUG: ",tok try: # eval levanta una excepcion # si la cadena no representa a un numero evtok = eval(tok) p.apilar(evtok) print "DEBUG: apila ", evtok except: # llego aca porque la cadena no representa a un numero try: # solo admitimos alguna de las 5 operaciones: if tok not in "+-*/%" or len(tok)!=1: # Si hay algun texto incomprensible # se senhala el error y se devuelve None print "DEBUG: error operador" return None # es alguna de las operaciones validas: # si no hay suficientes (2) operandos en # la pila, se levanta una excepcion: a1=p.desapilar() print "DEBUG: desapila ",a1 a2=p.desapilar() print "DEBUG: desapila ",a2 if tok=="+":

17.1. Pilas p.apilar(a2 + print "DEBUG: elif tok=="-": p.apilar(a2 print "DEBUG: elif tok == "*": p.apilar(a2 * print "DEBUG: elif tok == "/": p.apilar(a2 / print "DEBUG: a1) apila ", a2+a1 a1) apila ", a2-a1 a1) apila ", a2*a1 a1) apila ", a2/a1

171

else: # tok == "%": p.apilar(a2 % a1) print "DEBUG: apila ", a2%a1

except: # llega aca cuando se levanta la excepcion al # intentar desapilar una pila vacia: print "DEBUG: error pila faltan operandos" return None finally: pass # no hay nada mas en la lista res = p.desapilar() if p.esPilaVacia(): return res else: print "DEBUG: error pila sobran operandos" return None

def main(): expre = input("Ingrese la expresion a evaluar: ") lexp = expre.split() print polaca(lexp)

Veamos algunos casos de prueba: El caso de una expresin que es slo un nmero (es correcta):

>>> main()

172

Unidad 17. Tipos abstractos de datos: Pilas y colas Ingrese la expresion a evaluar: "5" DEBUG: 5 DEBUG: apila 5 5 >>> El caso en el que sobran operandos:

>>> main() Ingrese la expresion a evaluar: "4 5" DEBUG: 4 DEBUG: apila 4 DEBUG: 5 DEBUG: apila 5 DEBUG: error pila sobran operandos None >>> El caso en el que faltan operandos:

>>> main() Ingrese la expresion a evaluar: "4 %" DEBUG: 4 DEBUG: apila 4 DEBUG: % DEBUG: desapila 4 DEBUG: desapila None DEBUG: error pila faltan operandos None >>> El caso de un operador invlido: >>> main() Ingrese la expresion a evaluar: "4 5 &" DEBUG: 4 DEBUG: apila 4 DEBUG: 5 DEBUG: apila 5 DEBUG: & DEBUG: error operador None >>> 4 % 5

17.1. Pilas >>> main() Ingrese la expresion a evaluar: "4 5 %" DEBUG: 4 DEBUG: apila 4 DEBUG: 5 DEBUG: apila 5 DEBUG: % DEBUG: desapila 5 DEBUG: desapila 4 DEBUG: apila 4 4 >>> (4 + 5) * 6: >>> main() Ingrese la expresion a evaluar: "4 5 + 6 *" DEBUG: 4 DEBUG: apila 4 DEBUG: 5 DEBUG: apila 5 DEBUG: + DEBUG: desapila 5 DEBUG: desapila 4 DEBUG: apila 9 DEBUG: 6 DEBUG: apila 6 DEBUG: * DEBUG: desapila 6 DEBUG: desapila 9 DEBUG: apila 54 54 >>> 4 * (5 + 6): >>> main() Ingrese la expresion a evaluar: "4 5 6 + * " DEBUG: 4 DEBUG: apila 4 DEBUG: 5 DEBUG: apila 5 DEBUG: 6 DEBUG: apila 6 DEBUG: + DEBUG: desapila 6 DEBUG: desapila 5 DEBUG: apila 11

173

174

Unidad 17. Tipos abstractos de datos: Pilas y colas DEBUG: * DEBUG: desapila 11 DEBUG: desapila 4 DEBUG: apila 44 44 >>> (4 + 5) * (3 + 8):

>>> main() Ingrese la expresion a evaluar: "4 5 + 3 8 + * " DEBUG: 4 DEBUG: apila 4 DEBUG: 5 DEBUG: apila 5 DEBUG: + DEBUG: desapila 5 DEBUG: desapila 4 DEBUG: apila 9 DEBUG: 3 DEBUG: apila 3 DEBUG: 8 DEBUG: apila 8 DEBUG: + DEBUG: desapila 8 DEBUG: desapila 3 DEBUG: apila 11 DEBUG: * DEBUG: desapila 11 DEBUG: desapila 9 DEBUG: apila 99 99 >>> Ejercicio 17.1. Si se oprime la tecla <BACKSPACE>(o <>) del teclado, se borra el ltimo caracter ingresado. Construir una funcin visualizar para modelar el tipeo de una cadena de caracteres desde un teclado: La funcin recibe una cadena de caracteres con todo lo que el usuario ingres por teclado (incluyendo <BACKSPACE>que se reconoce como \b), y devuelve el texto tal como debe presentarse (por ejemplo, vusualizar("Holas\b chau") debe devolver Hola chau). Atencin, que muchas veces la gente aprieta de ms la tecla de <BACKSPACE>, y no por eso hay que cancelar la ejecucin de toda la funcin.

17.1.3.

Cunto cuestan los mtodos?

Al elegir de una representacin debemos tener en cuenta cunto nos costarn los mtodos implementados. En nuestro caso, el tope de la pila se encuentra en la ltima posicin de la lista, y cada vez que se apila un nuevo elemento, se lo agregar al nal.

17.2. Colas

175

Por lo tanto se puede implementar el mtodo apilar mediante un append de la lista, que se ejecuta en tiempo constante. Tambin el mtodo desapilar, que se implementa mediante pop de lista, se ejecuta en tiempo constante. Vemos que la alternativa que elegimos fue barata: Otra alternativa posible hubiera sido agregar el nuevo elemento en la posicin 0 de la lista (es decir implementar el mtodo apilar mediante self.items.insert(0,x) y el mtodo desapilar mediante del self.items[0]. Sin embargo, sta no es una solucin inteligente, ya que tanto insertar al comienzo de la lista como borrar al comienzo de la lista consumen tiempo proporcional a la longitud de la lista. Ejercicio 17.2. Disear un pequeo experimento para vericar que la implementacin elegida es mucho mejor que la implementacin con listas en la cual el elemento nuevo se inserta al principio de la lista. Ejercicio 17.3. Implementar pilas mediante listas enlazadas.

17.2.

Colas

Todos sabemos lo que es una cola. Ms an, estamos hartos de hacer colas! El TAD cola modela precisamente ese comportamiento: el primero que llega es el primero en ser atendido. Sus operaciones son: __init__: Inicializa una cola nueva, vaca. encolar: Agrega un nuevo elemento al nal de la cola. desencolar: Elimina el primero de la cola y lo devuelve. esColaVacia: Devuelve True o False segn si la cola est vaca o no. Cmo representamos a las colas?

17.2.1.

Colas implementadas sobre listas

Veamos si podemos implementar colas usando listas. Deniremos una clase Cola con un atributo, items, de tipo lista, que contendr los elementos de la cola. El primero de la cola se encontrar en la primera posicin de la lista, y cada vez que encole un nuevo elemento, se lo agregar al nal. El mtodo __init__ no recibir parmetros adicionales, ya que deber crear una cola vaca (que representaremos por una lista vaca): class Cola: """ crea una cola vacia representada por una lista vacia""" def __init__(self): self.items=[] El mtodo encolar se implementar agregando el nuevo elemento al nal de la lista:

176

Unidad 17. Tipos abstractos de datos: Pilas y colas def encolar(self, x): """ encola x en self (lo agrega al final de la lista)""" self.items.append(x)

Para implementar desencolar, se eliminar el primer elemento de la lista y se devolver el valor del elemento eliminado. Si la cola est vaca se levantar una excepcin. def desencolar(self): """ elimina el primer elemento de la lista y retorna su valor. Si la cola esta vacia se devolvera None.""" try: val = self.items[0] del self.items[0] return val except: return None def esColaVacia(self): """ devuelve True si la cola esta vacia, False en caso contrario.""" return (self.items == []) Veamos una ejecucin de este cdigo:

>>> q=Cola() >>> q.encolar(1) >>> q.encolar(2) >>> q.encolar(5) >>> q.esColaVacia() False >>> q.desencolar() 1 >>> q.desencolar() 2 >>> q.encolar(8) >>> q.desencolar() 5 >>> q.desencolar() 8 >>> q.desencolar() >>> q.esColaVacia() True >>>

17.2. Colas

177

Cunto cuesta esta implementacin? Dijimos en la seccin anterior que usar listas comunes para borrar elementos al principio da muy malos resultados. Como en este caso necesitamos agregar elementos por un extremo y quitar por el otro extremo, esta implementacin ser una buena alternativa slo si nuestras listas son pequeas. Pero si queremos hacer que tanto el encolar como el desencolar se ejecuten en tiempo constante, debemos apelar a otra implementacin.

17.2.2.

Colas implementadas sobre listas enlazadas

En la unidad anterior vimos la clase ListaEnlazada. Inicialmente esa clase ejecutaba la insercin en la primera posicin en tiempo constante, pero el append se haba convertido en lineal. Sin embargo, como ejercicio, se les solicit que mejoraran el append. Vamos a suponer que esas mejoras ya fueron hechas. En ese caso tenemos una estructura que ejecuta la insercin al nal y la remocin del primer elemento en tiempo constante, y que puede contener cualquier tipo de objeto. Es todo lo que necesitamos para implementar la clase Cola. Los items encolados van a ser ahora una lista enlazada. Inicialmente los items son una lista enlazada vaca: import claseListaEnlazadaConUlt class Cola: """ cola comun implementada sobre lista enlazada""" def __init__(self): """ crea una cola vacia representada por una lista enlazada vacia""" self.items = claseListaEnlazadaConUlt.ListaEnlazada() Para encolar, simplemente se utiza append: def encolar(self, x): """ encola x en self (lo agrega al final de la lista)""" self.items.append(x)

El resultado esperado de desencolar es el mismo de pop(0). Se atrapa la excepcin que lanza pop(0) en el caso de la lista vaca y se enmascara por un return None, porque los usuarios no tienen por qu recibir excepciones incomprensibles basadas en una representacin que no tienen por qu conocer. def desencolar(self): """ elimina el primer elemento de la lista y retorna su valor. Si la cola esta vacia se devolvera None.""" try:

178

Unidad 17. Tipos abstractos de datos: Pilas y colas return self.items.pop(0) except: # se cambia la excepcion de la ListaEnlazada # por una excepcin de cola: return None La cola est vaca siempre que la lista enlazada subyacente est vaca: def esColaVacia(self): """ devuelve True si la cola esta vacia, False en caso contrario.""" return (self.items.__len__() == 0) Y nalmente probamos el TAD:

>>> q=Cola() >>> q.esColaVacia() True >>> q.encolar(1) >>> q.encolar(5) >>> q.esColaVacia() False >>> q.encolar("Manzanas") >>> q.desencolar() 1 >>> q.desencolar() 5 >>> q.desencolar() Manzanas >>> q.desencolar() >>> q.esColaVacia() True >>> Ejercicio 17.4. Este ejercicio surgi (y lo hicieron ya muchas generaciones de alumnos), haciendo cola: Hace un montn de aos haba una viejsma sucursal del correo en la vereda impar de Av. de Mayo al 800 que tena un cartel que deca No se recibirn ms de 5 cartas por persona. O sea que la gente entregaba sus cartas (hasta la cantidad permitida) y luego tena que volver a hacer la cola si tena ms cartas para despachar. Modelar una cola de correo generalizada, donde en la inicializacin se indica la cantidad (no necesariamente 5) de cartas que se reciben por persona.

Unidad 18

Funciones y su modelo de ejecucin


18.1. La pila de ejecucin de las funciones

Si miramos este segmento de cdigo y su ejecucin vemos que, pese a tener el mismo nombre, la variable de x de la funcin f y la variable de x de la funcin g no tienen nada que ver: una y otra se reeren a valores distintos, y modicar una no modica a la otra: >>> def f(): x = 50 a = 20 print "En f, x vale ", x

>>> def g(): x = 10 b = 45 print "En g, antes de llamar a f, x vale ", x f() print "En g, despus de llamar a f, x vale ", x b = b + 1

>>> g() En g, antes de llamar a f, x vale 10 En f, x vale 50 En g, despus de llamar a f, x vale 10 >>> Vamos a ver en esta seccin cmo se ejecutan las llamadas a funciones, para comprender cul es la razn de este comportamiento. Cada funcin tiene asociado por un lado un cdigo (el texto del programa) que se ejecutar, y por el otro un conjunto de variables que le son propias (en este caso x y a se asocian con f y x y b se asocian con g) y que no se confunden entre s pese a tener el mismo nombre (no debera llamarnos la atencin ya que despus de todo conocemos a muchas personas que tienen el mismo nombre, en este caso la funcin a la que pertenecen funciona como una especie de apellido). 179

180

Unidad 18. Funciones y su modelo de ejecucin

Estos nombres asociados a una funcin los va descubriendo el intrprete de Python a medida que va ejecutando el programa (hay otros lenguajes en los que los nombres se descubren todos juntos antes de iniciar la ejecucin). La ejecucin del programa se puede modelar por el siguiente diagrama, en el cual los nombres asociados a cada funcin se encerrarn en una caja o marco: 1. g() g

2. x = 10

x 10

3. b = 45

x 10 b 45

4. print vale 10.

x 10 e imprime En g, antes de llamar a f, x b 45

5. f()

f g

x 10 b 45

6. x = 50

f g

x 50 x 10 b 45

f 7. a = 20 g

x 50 a 20 x 10 b 45

f 8. print g

x 50 a 20 e imprime En f, x vale 50. x 10 b 45

9. print x vale 10.

x 10 e imprime En g, despues de llamar a f, b 45

10.

pila vaca

18.2. Pasaje de parmetros Se puede observar que:

181

Cuando se invoca a g, se arma un marco vaco para contener las referencias a las variables asociadas con g. Ese marco se apila sobre una pila vaca. Cuando se ejecuta dentro de g la invocacin f() (en 5) se apila un marco vaco que va a alojar las variables asociadas con f (y se transere el control del programa a la primera instruccin de f. El marco de g queda debajo del tope de la pila, y por lo tanto el intrprete no lo ve. Mientras se ejecuta f, todo el tiempo el intrprete busca los valores que necesita usando el marco que est en el tope de la pila. Despus de ejecutar 8, se encuentra el nal de la ejecucin de f. Se desapila el marco de f y reaparece el marco de g en el tope de la pila. Sigue ejecutando g a partir de donde se suspendi por la invocacin a f. g slo ve su marco en el tope de la pila. Despus de ejecutar 9, se encuentra el nal de la ejecucin de g. Se desapila el marco de g y queda la pila vaca. El mbito de denicin de una variable est constituido por todas las partes del programa desde donde esa variable se ve.

18.2.

Pasaje de parmetros

Un parmetro es un nombre ms dentro del marco de una funcin. Slo hay que tener en cuenta que si se en la invocacin se le pasa un valor a ese parmetro, en el marco inicial esa variable ya aparecer ligada a un valor. Vemos un ejemplo: >>> def fun(a): print a+1

>>> def gun(b): fun (b+5) print "Volvio a gun"

>>> gun(43) 49 Volvio a gun >>> La ejecucin se puede representar de la siguiente manera: 1. gun(43) gun b 43 a 48 b 43

2. fun(b+5)

fun gun

182

Unidad 18. Funciones y su modelo de ejecucin fun gun a 48 e imprime 49 (es decir 48+1). b 43

3. print

4. print

gun

b 43 e imprime Volvio a gun.

5.

pila vaca

Cuando se pasan objetos como parmetros, las dos variables hacen referencia al mismo objeto. Eso signica que si el objeto pasado es mutable, cualquier modicacin que la funcin invocada realice sobre su parmetro se reejar en el argumento de la funcin llamadora, como se puede ver en el siguiente ejemplo: >>> def modif(xs): xs[0]=5

>>> def llama(): ls = [1,2,3,4] print ls modif(ls) print ls

>>> llama() [1, 2, 3, 4] [5, 2, 3, 4] >>> Cuando se invoca a modif(ls) desde llama, el esquema de la pila es el siguiente: en modif: xs [1,2,3,4] en llama: ls Cuando se modica la lista desde modif, el esquema de la pila es el siguiente: en modif: xs [5,2,3,4] en llama: ls Cuando se retorna a llama, ls seguir apuntando a la lista [5, 2, 3, 4]. En cambio, cuando el parmetro cambia la referencia que se le pas por una referencia a otro objeto, el llamador no se entera: >>> def cambia_objeto(xs): xs=[5,1,2,3,4]

18.3. Devolucin de resultados >>> def llama2(): ls=[1,2,3,4] print ls cambia_objeto(ls) print ls

183

>>> llama2() [1, 2, 3, 4] [1, 2, 3, 4]

Cuando se invoca a cambia_objeto(ls) desde llama2, el esquema de la pila es el siguiente: en cambia_objeto: en llama2: xs ls [1,2,3,4]

Cuando se cambia referencia a la lista desde cambia_objeto, el esquema de la pila es el siguiente: en cambia_objeto: en llama2: [5,1,2,3,4] [1,2,3,4]

xs

ls

Cuando se retorna a llama2, ls seguir apuntando a la lista [1, 2, 3, 4].

18.3.

Devolucin de resultados

Finalmente, y para completar el cuadro, debemos tener en cuenta que los resultados que devuelve la funcin llamada, se reciben en la expresin correspondiente de la funcin llamadora.

184

Unidad 18. Funciones y su modelo de ejecucin

18.4.

La recursin y cmo puede ser que funcione

Figura 18.1: Una imagen recursiva: la publicidad de Cacao Droste, bajada de http://en.wikipedia.org/wiki/Image:Droste.jpg Estamos acostumbrados a escribir funciones que llaman a otras funciones. Pero lo cierto es que nada impide que en Python (y en muchos otros lenguajes) una funcin se llame a s misma. Y lo ms interesante es que esta propiedad, que se llama recursin, permite en muchos casos encontrar soluciones muy elegantes para determinados problemas. Ustedes seguramente ya vieron en materias de matemtica los razonamientos por induccin para probar propiedades de nmeros enteros, la recursin no es ms que una generalizacin de la induccin a ms estructuras: las listas, las cadenas de caracteres, las funciones, etc.

18.5.

Algunos ejemplos con nmeros

Es muy comn tener deniciones inductivas de operaciones, como por ejemplo: x! = x (x 1)! si x > 0, 0! = 1 Este tipo de denicin se traduce naturalmente en una funcin en Python: >>> def factorial(n): """ Precondicion: n entero >=0 devuelve n! """ if n==0: res = 1 return res else:

18.5. Algunos ejemplos con nmeros f1 = factorial(n-1) res = n * f1 return res

185

>>> factorial(3) 6 >>> factorial(0) 1 >>> El sentido de la instruccin del par de instrucciones f1 = factorial (n-1) res = n * f1 es exactamente el mismo que el de la denicin inductiva: para calcular el factorial de n se debe multiplicar n por el factorial de n 1. Dos piezas fundamentales para garantizar el funcionamiento de este programa son Que se dena un caso base (en este caso la indicacin, no recursiva, de cmo calcular factorial(0)), que corta las llamadas recursivas. Que el argumento de la funcin respete la precondicin de que n debe ser un entero mayor o igual que 0. Dado que ya vimos la pila de evaluacin, y cmo funciona, no debera llamarnos la atencin que esto pueda funcionar adecuadamente en un lenguaje de programacin que utilice pila para evaluar. Analicemos el factorial(3) mediante la pila de evaluacin: 1. factorial(3) factorial n 3

2. if n == 0:

factorial

n 3

3. f1 = factorial (n-1) ma a factorial(2).

factorial

n 3 se suspende el clculo y se lla-

4. factorial(2)

factorial factorial

n 2 n 3

5. if n == 0:

factorial factorial

n 2 n 3

6. f1 = factorial (n-1) ma a factorial(1).

factorial factorial

n 2 se suspende el clculo y se llan 3

186

Unidad 18. Funciones y su modelo de ejecucin factorial factorial factorial n 1 n 2 n 3 n 1 n 2 n 3 n 1 n 2 se suspende el clculo y se llan 3

7. factorial(1)

8. if n == 0:

factorial factorial factorial

9. f1 = factorial (n-1) ma a factorial(0).

factorial factorial factorial

10. factorial(0)

factorial factorial factorial factorial

n 0 n 1 n 2 n 3 n 0 n 1 n 2 n 3 n 0 res 1 n 1 n 2 n 3

11. if n == 0:

factorial factorial factorial factorial

factorial 12. res = 1 factorial factorial factorial

13. en factorial(0): return res y en factorial(1): f1 = factorial (n-1) factorial factorial factorial n 1 f1 1 n 2 n 3 n 1 f1 1 res 1 n 2 n 3

factorial 14. res = n * f1 factorial factorial

18.5. Algunos ejemplos con nmeros 15. en factorial(1): return res y en factorial(2): f1 = factorial (n-1) factorial factorial n 2 f1 1 n 3 n 2 f1 1 res 2 n 3

187

factorial 16. res = n * f1 factorial

17. en factorial(2): return res y en factorial(3): f1 = factorial (n-1) factorial n 3 f1 2 n 3 f1 2 res 6

factorial 18. res = n * f1

19. return res

pila vaca y retorna el valor 6

Figura 18.2: Otra imagen recursiva: captura de pantalla de RedHat, bajada de http://www.jfedor.org/

188

Unidad 18. Funciones y su modelo de ejecucin

Unidad 19

Ordenar listas
Ya vimos que las listas poseen un mtodo sort que las ordena de menor a mayor de acuerdo a una clave (e incluso de acuerdo a una relacin de orden dada a travs del parmetro cmp). Las preguntas que nos planteamos ahora son: (a) cmo se hace para ordenar cuando no tenemos un mtodo sort, y (b) cunto cuesta ordenar. Ante todo una advertencia: hay varias maneras de ordenar, y no todas cuestan lo mismo. Vamos a empezar viendo las ms sencillas de escribir (que en general suelen ser las ms caras).

19.1.

Ordenamiento por seleccin

ste mtodo de ordenamiento se basa en la siguiente idea: Paso 1.1: Buscar el mayor de todos los elementos de la lista. 3 2 -1 5 0 2 Encuentra el valor 5 en la posicin 3. Paso 1.2: Poner el mayor al nal (intercambiar el que est en la ltima posicin de la lista con el mayor encontrado). 3 2 -1 2 0 5 Intercambia el de la posicin 3 con el valor de la posicin 5. En la ltima posicin de la lista est el mayor de todos. Paso 2.1: Buscar el mayor de todos los elementos del segmento de la lista entre la primera y la anteltima posicin. 3 2 -1 2 0 5 Encuentra el valor 3 en la posicin 0. 189

190

Unidad 19. Ordenar listas

Paso 2.2: Poner el mayor al nal del segmento (intercambiar el que est en la ltima posicin del segmento o sea anteltima posicin de la lista con el mayor encontrado). 0 2 -1 2 3 5 Intercambia el valor de la posicin 0 con el valor de la posicin 4. En la anteltima y ltima posicin de la lista estn los dos mayores en su posicin denitiva. ... Paso n: Se termina cuando slo queda un elemento sin tratar, el que est en la primera posicin de la lista, y que es el menor de todos porque todos los mayores fueron reubicados. -1 0 2 2 3 5

Lo programamos en Python de la siguiente manera (el cdigo de posmax queda como ejercicio para ustedes): def selectord(xs): """ordena una lista de elementos comparables segun el metodo de seleccion""" # n = posicion donde termina el segmento a tratar # inicialmente n = len(xs)-1 n = len(xs)-1 # cicla mientras haya elementos para ordenar # o sea mientras el segmento a tratar tenga longitud >=2 while n>0: # p es la posicion del mayor valor del segmento p = posmax(xs, 0, n) # intercambia el valor que esta en p # con el valor que esta en la ultima # posicion del segmento xs[p], xs[n] = xs[n], xs[p] print "DEBUG: ", p, n, xs # achicar el segmento en 1 n = n - 1

19.2. Ordenamiento por insercin

191

def posmax(xs, ini, fin): """ devuelve la posicion del maximo elemento en un segmento de lista de elementos comparables. xs es la lista sobre la que se trabaja. ini es la posicion inicial del segmento. fin es la posicion final del segmento. ini y fin deben ser posiciones validas de xs. La lista debe ser no vacia"""

Esta es una ejecucin de ejemplo de ese cdigo: >>> xs=[3, 2,-1,5, 0, 2] >>> selectord(xs) DEBUG: 3 5 [3, 2, -1, 2, DEBUG: 0 4 [0, 2, -1, 2, DEBUG: 1 3 [0, 2, -1, 2, DEBUG: 1 2 [0, -1, 2, 2, DEBUG: 0 1 [-1, 0, 2, 2, >>> xs [-1, 0, 2, 2, 3, 5] >>> l=[] >>> selectord(l) >>> l [] >>>

0, 3, 3, 3, 3,

5] 5] 5] 5] 5]

19.1.1.

Cunto cuesta ordenar por seleccin?

Para buscar el mximo elemento en un segmento de lista se debe recorrer todo ese segmento, por lo que en nuestro caso debemos recorrer en el primer paso N elementos, en el segundo paso N 1 elementos, en el tercer paso N 2 elementos, etc. Cada visita a un elemento implica una cantidad constante y pequea de comparaciones (que no depende de N ). Por lo tanto tenemos que T (N ) c (2 + 3 + . . . + N ) c N (N + 1)/2 N 2 O sea que ordenar por seleccin una lista de tamao N insume tiempo del orden de N 2 . En cuanto al espacio insumido, slo la lista que se desea ordenar y algunas variables de tamao 1.

19.2.

Ordenamiento por insercin

ste mtodo de ordenamiento se basa en la siguiente idea:

-1

192

Unidad 19. Ordenar listas

Paso 1: Considerar el segundo elemento de la lista, y ordenarlo respecto del primero, deplazndolo hasta la posicin correcta, si corresponde. 2 3 -1 5 0 2 Se desplaza el valor 2 antes de 3. Paso 2: Considerar el tercer elemento de la lista, y ordenarlo respecto del primero y el segundo, deplazndolo hasta la posicin correcta, si corresponde. Se desplaza el valor 1 antes de 2 y de 3. -1 2 3 5 0 2

Paso 3: Considerar el cuarto elemento de la lista, y ordenarlo respecto del primero, el segundo y el tercero, deplazndolo hasta la posicin correcta, si corresponde. -1 2 3 5 0 2 El 5 est correctamente ubicado respecto de 1,2 y 3 (como el segmento hasta la tercera posicin est ordenado, basta con comparar con el tercer elemento del segmento para vericarlo).

... -1 0 2 3 5 2

Paso N: Considerar el N simo elemento de la lista, y ordenarlo respecto del primero, el segundo, . . ., el N 1simo, deplazndolo hasta la posicin correcta, si corresponde. Se desplaza el valor 2 antes de 3 y de 5. -1 0 2 2 3 5

Lo programamos en Python de la siguiente manera

def insertord(xs): """ordena in-situ la lista xs usando el procedimiento de ordenamiento por insercion. xs debe contener valores comparables"""

19.2. Ordenamiento por insercin # i recorre desde la primera hasta # la penultima posicion de la lista for i in range(len(xs)-1): # si el elemento que esta la posicion # i+1 esta desordenado respecto al que # esta en la posicion i, # hay que reubicar al elemento que esta # en la posicion i+1 en el segmento [0:i] if xs[i+1]< xs[i]: insertar(xs, i+1) print "DEBUG: ", xs

193

def insertar(xs, p): """reubica al elemento que esta en la posicion p de la lista xs dentro del segmento [0:p-1]. p tiene que ser una posicion valida de xs"""

# v se refiere al valor que queremos poner en la # posicion correcta v = xs[p] # recorre el segmento [0:p-1] de derecha a izquierda # para encontrar el mayor indice j para el cual # xs[j]<=v y v<xs[j+1] j = p-1 while (j>=0) and (v<xs[j]): j = j-1 # borra la posicion xs[p] porque va a reinsertar # el valor v en el lugar adecuado de xs del xs[p] if j<0: # hay que poner a v en el lugar 0, # es menor que todos xs.insert(0,v) else: # v es mayor que xs[j] pero menor # que xs[j+1]: # hay que ponerlo en la posicion j+1 xs.insert(j+1, v)

194

Unidad 19. Ordenar listas 2] 0, 0, 0, 5, 3, 2] 2] 2] 2] 5]

>>> xs=[3, 2,-1,5, 0, >>> insertord(xs) DEBUG: [2, 3, -1, 5, DEBUG: [-1, 2, 3, 5, DEBUG: [-1, 2, 3, 5, DEBUG: [-1, 0, 2, 3, DEBUG: [-1, 0, 2, 2, >>> xs [-1, 0, 2, 2, 3, 5] >>> l=[] >>> insertord(l) >>> l [] >>>

19.2.1.

Cunto cuesta ordenar por insercin?

Lo peor que le puede pasar a un elemento que est en la posicin j es que deba ser ubicado al principio de la lista (piensen en la lista [10, 8, 6, 2, -2, -5]). En el primer paso, el segundo elemento se debe intercambiar con el primero; en el segundo paso, el tercer elemento se compara con el segundo y el primer elemento, y se ubica adelante de todo; en el tercer paso, el cuarto elemento se compara con el tercero, el segundo y el primer elemento, y se ubica adelante de todo; etc... T (N ) c (2 + 3 + . . . + N ) c N (N + 1)/2 N 2 O sea que ordenar por insercin una lista de tamao N puede insumir (en el peor caso) tiempo del orden de N 2 . En cuanto al espacio insumido, slo la lista que se desea ordenar y algunas variables de tamao 1.

Unidad 20

Algunos ordenamientos recursivos


En esta unidad vamos a ver otros mtodos de ordenamiento, basados stos en un planteo recursivo del problema.

20.1.

Ordenamiento por mezcla, o Mergesort

Este mtodo se basa en la siguiente idea: 1. Si la lista es pequea (vaca o de tamao 1) ya est ordenada y no hay nada que hacer. De lo contrario hacer lo siguiente: 2. Dividir la lista en dos sublistas de (aproximadamente) el mismo tamao. 3. Ordenar cada una de esas dos sublistas (usando este mismo mtodo). 4. Intercalar de manera ordenada las dos sublistas ordenadas. Por ejemplo, si la lista original es [6, 7, -1, 0, 5, 2, 3, 8] deberemos ordenar recursivamente [6, 7, -1, 0] y [5, 2, 3, 8] con lo cual obtendremos [-1, 0, 6, 7] y [2, 3, 5, 8]. Si intercalamos ordenadamente las dos listas ordenadas obtenemos la solucin buscada: [-1, 0, 2, 3, 5, 6, 7, 8]. Diseamos la solucin: funcin merge_sort(lista): 1. Si lista es pequea (vaca o de tamao 1) ya est ordenada y no hay nada que hacer. Se devuelve lista tal cual. 2. De lo contrario: a) medio = len(lista)/2 b) izq = merge_sort(lista[:m]) c) der = merge_sort(lista[m:]) d) Se devuelve merge(izq, der). Falta slo disear la funcin merge que realiza la intercalacin ordenada de dos listas ordenadas (dadas dos listas ordenadas se debe obtener una nueva lista que resulte de intercalar a ambas de manera ordenada): Diseamos la solucin: funcin merge(xs, ys): 195

196

Unidad 20. Algunos ordenamientos recursivos

1. ix = 0 # ix ser el ndice para recorrer xs 2. iy = 0 # iy ser el ndice para recorrer ys 3. result = [] # result es la lista resultado 4. Mientras ix <len(xs) e iy <len(ys): # Hay elementos para comparar en las dos listas a) Si xs[ix] <ys[iy]: 1) Agregar xs[ix] al nal de la lista resultado 2) ix += 1 # Avanzar en xs b) de lo contrario: 1) Agregar ys[iy] al nal de la lista resultado 2) iy += 1 # Avanzar en ys # Se termin alguna de las dos listas, hay que agregar al nal del resultado lo que resta de la otra: 5. Agregar al nal de resultado xs[ix:] 6. Agregar al nal de resultado ys[iy:] Codicamos la solucin de merge_sort: def merge_sort(lista): if len(lista) < 2: return lista else: medio = len(lista) / 2 izq = merge_sort(lista[:medio]) der = merge_sort(lista[medio:]) return merge(izq, der) En cuanto a la funcin que intercala ordenadamente, resulta: def merge(xs, ys): ix ,iy = 0, 0 result = [] while(ix < len(xs) and iy < len(ys)): if (xs[ix] < ys[iy]): result.append(xs[ix]) ix = ix + 1 else: result.append(ys[iy]) iy = iy + 1 result += xs[ix:] result += ys[iy:] return result

20.2. Cunto cuesta el Mergesort? Sabas que . . .

197

El mtodo que hemos usado para resolver este problema se llama Divisin y Conquista, y se aplica en las situaciones en las que vale el siguiente principio: Para obtener una solucin es posible partir el problema en varios subproblemas de tamao menor, resolver cada uno de esos subproblemas por separado aplicando la misma tcnica (en nuestro caso ordenar por mezcla cada una de las dos sublistas), y nalmente juntar estas soluciones parciales en una solucin completa del problema mayor (en nuestro caso la intercalacin ordenada de las dos sublistas ordenadas). Como siempre sucede con las soluciones recursivas, debemos encontrar un caso base en el cual no se aplica la llamada recursiva (en nuestro caso la base sera el paso 1: Si la lista es pequea (vaca o de tamao 1) ya est ordenada y no hay nada que hacer). Adems debemos asegurar que siempre se alcanza el caso base, y en nuestro caso aseguramos eso porque las lista original se divide siempre en mitades cuando su longitud es mayor que 1.

20.2.

Cunto cuesta el Mergesort?

Sea N la longitud de la lista. Observamos lo siguiente: El tiempo que se tarda en intercalar dos listas de longitud N/2 es proporcional a N . Llamemos a N a ese tiempo. Si llamamos T (N ) al tiempo que tarda el algoritmo en ordenar una lista de longitud N , vemos que T (N ) = 2 T (N/2) + a N . T (1) = T (0) = b. Para simplicar la cuenta vamos a suponer que N = 2k . T (N ) = T (2k ) = 2 T (2k1 ) + a 2k = 2 (2 T (2k2 ) + a 2k1 ) + a 2k = 22 T (2k2 ) + a 2k + a 2k . . . = 2i T (2ki ) + i a 2i . . . = 2k T (1) + k a 2k = b 2k + k a 2k

Pero si N = 2k entonces k = log2 N , y por lo tanto hemos demostrado que T (N ) = b N + a N log2 N . Como lo que nos interesa es aproximar el valor, diremos (despreciando el trmino de menor orden) que T (N ) N log2 N . Hemos mostrado entonces un algoritmo que se porta mucho mejor que los que vimos en la unidad pasada. Si analizamos el espacio que consume, vemos que necesita copiar la lista (o sea que duplica el espacio consumido).

198

Unidad 20. Algunos ordenamientos recursivos

20.3.

Ordenamiento rpido o Quicksort

Veremos ahora el ms famoso de los algoritmos recursivos de ordenamiento. Su fama radica en que en la prctica, con casos reales, es uno de los algoritmos ms ecientes para ordenar. Este mtodo se basa en la siguiente idea: 1. Si la lista es pequea (vaca o de tamao 1) ya est ordenada y no hay nada que hacer. De lo contrario hacer lo siguiente: 2. Tomar un elemento de la lista (por ejemplo el primero)al que llamaremos pivote y armar a partir de esa lista tres sublistas: la de todos los elementos de la lista menores al pivote, la formada slo por el pivote, y la de los elementos mayores o iguales al pivote, pero sin contarlo al pivote. 3. Ordenar cada una de esas tres sublistas (usando este mismo mtodo). 4. Concatenar las tres sublistas ya ordenadas. Por ejemplo, si la lista original es [6, 7, -1, 0, 5, 2, 3, 8] consideramos que el pivote es el primer elemento (el 6) y armamos las sublistas [-1, 0, 5, 2, 3], [6] y [7, 8]. Se ordenan recursivamente [-1, 0, 5, 2, 3] (obtenemos [-1, 0, 2, 3, 5]) y [7, 8] (obtenemos la misma) y concatenamos en el orden adecuado, y as obtenemos [-1, 0, 2, 3, 5, 6, 7, 8]. Para disear, vemos que lo ms importante es conseguir armar las tres listas en las que se parte la lista original. Para eso denimos una funcin partition que recibe una lista no vaca y devuelve las tres sublistas menores, igual y mayoriguales en las que se parte la lista original usando como pivote al primer elemento. Si contamos con partition, el diseo del Quicksort es muy simple: funcin quick_sort(lista): 1. Si lista es pequea (vaca o de tamao 1) ya est ordenada y no hay nada que hacer. Se devuelve lista tal cual. 2. De lo contrario: a) menores, igual, mayoriguales = partition(lista) b) Se devuelve quick_sort(menores) + igual + quick_sort(mayoriguales) Y en cuanto a la funcin de particin: funcin partition(lista): 1. Tiene como precondicin que la lista es no vaca. 2. pivote = lista [0] 3. menores = [] 4. mayoriguales = [] 5. Para x en lista [1:]: a) Si x<pivote entonces menores.append(x) b) De lo contrario mayoriguales.append(x)

20.4. Cunto cuesta el Quicksort? 6. Devolver menores, [pivote], mayoriguales Los cdigos resultantes son los siguientes:

199

def quick_sort(lista): if len(lista)<2: return lista else: menores, igual, mayoriguales = partition(lista) return quick_sort(menores) + igual + quick_sort(mayoriguales) def partition(lista): Pre: lista no vacia. Post: devuelve la lista partida por el pivote lista[0] pivote = lista[0] menores = [] mayoriguales = [] for x in lista[1:]: if x < pivote: menores.append(x) else: mayoriguales.append(x) return menores, [pivote], mayoriguales

20.4.

Cunto cuesta el Quicksort?

A primera vista, la ecuacin del tiempo consumido parece ser la misma que en el Mergesort: Una particin que se hace en tiempo lineal ms dos llamadas recursivas a mitades de la lista original. Pero el problema ac es que la particin tomando como pivote lista[0] no siempre parte la lista en mitades: puede suceder (y ese es el peor caso) que parta a la lista en ([], [lista[0]], lista[1:]) (piensen en lo que pasa cuando la lista est ordenada de entrada), y en ese caso se comporta como seleccin. En cambio, cuando la lista tiene nmeros ubicados de manera arbitraria dentro de ella, podemos imaginar un comportamiento parecido al del Mergesort, y por lo tanto ah s T (N ) N log2 N . Si analizamos el espacio que consume, vemos que se puede llegar a hacer la particin con cuidado, si las restricciones de espacio son grandes, sin copiar la lista (o sea que el espacio consumido puede llegar a ser slo el de la lista original ms alguna cantidad constante de variables).

También podría gustarte