jueves, 13 de octubre de 2011

Aprender A Programar Con Python Lección I

La Roseta De Números:

Este es el primer post dedicado a fundamentar los conceptos de programación, pero antes de comenzar cabe aclarar dos puntos; el lenguaje y el ejercicio en cuestión:

  • Por qué Python?: La respuesta es sencilla, "Python es simple y elegante"; esto significa python permite al aprendiz de programación concentrarse en los conceptos y escribir código de forma muy parecida a su lenguaje natural, sin preocuparse por sintaxis complicadas y estructuras  confusas. Además de que ofrece una buena motivación para implementar buenas practicas de programación.
  • La roseta de números?: Seguramente pocos habrán escuchado sobre este curioso ejercicio que consiste en generar un rombo usando secuencias de números, naturalmente no tiene ninguna aplicación mas allá de la lúdica y la pedagogía. Sin embargo es mi caso predilecto, cuando de iniciar un curso de programación se trata ya que permite al estudiante conocer de forma sencilla los conceptos mas fundamentales de programación: ciclos, decisiones, variables y funciones, todo en unas pocas lineas de código especialmente fáciles de entender en un lenguaje como python.

Suficientes explicaciones por ahora, procedamos con lo interesante: el código y su interpretación:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# roseta.py
# 2011 - 10 -12
# http://dakuregashi.blogspot.com/

n = input("Limite?: ")                              # Limite de la roseta
sIzq = ""                                           # Acumulador izquierdo
sDer = ""                                           # Acumulador derecho
sSup = ""                                           # Acumulador Superior
sInf = ""                                           # Acumulador Inferior

i = 1                                               # Contador
while(i <= n):                                      # Ciclo principal

    espa = (" "*(n-i))                              # Espacios adicionales                                             
    line = espa + sIzq + str(i) + sDer + espa +"\n" # Linea a generar
    sSup = sSup + line                              # Acumula linea en la parte superior
    
    if(i < n):                                      # Evita que se repita la ultima linea
        sInf = line + sInf                          # Acumula linea en la parte inferior
    
    sIzq = sIzq + str(i)                            # Acumula numero por izquierda para la próxima linea
    sDer = str(i) + sDer                            # Acumula numero por derecha para la próxima linea
    i += 1                                          # Incrementa el contador

print sSup + sInf                                   # Imprime el resultado final

Lo primero que llama la atención son las lineas que inician con # bastará con decir que en python los comentarios (es decir las lineas que no se interpretan como parte del programa) se indican con este símbolo. Sin embargo las lineas 1 y 2 a pesar de ser comentarios tienen un propósito especial:
#!/usr/bin/env python
En sistemas Unix / Linux, esta linea tiene el proposito de indicar donde se enecuentra el interprete de python, es decir el programa que sabe leer y ejecutar nuestro código fuente.
# -*- coding: utf-8 -*-
Esta linea le indica al interprete de python que codificación (Juego de caracteres) se usa en el archivo, para idioma español lo ideal es usar el estándar Unicode UTF-8, que permite escribir signos no ASCII como la Ñ o las vocales tildadas.

Las próximas lineas son comentarios comunes que sirven solo para identificar el archivo, pero no tienen uso real en el programa. Ya en la linea 8 aparece algo interesante:
n = input("Limite?: ")                              # Limite de la roseta
En este caso se esta definiendo una variable, que en python puede contener cualquier cosa como números, texto o incluso objetos complejos; para este caso concreto estamos almacenando en n un valor numérico ingresado por el usuario, esto se logra mediante la función input() a la que podemos dar opcionalmente un mensaje que se mostrara en pantalla antes de solicitar el dato. input() permite capturar únicamente valores numéricos, si se ingresara algo como "Hola Mundo" generaría un error de ejecución; para el caso en que se necesitara capturar por ejemplo un nombre, lo apropiado seria usar la función raw_input() que lee la entrada del usuario como una cadena de texto.

Las lineas 9, 10, 11 y 12 definen variables de tipo cadena de texto:
sIzq = ""                                           # Acumulador izquierdo
sDer = ""                                           # Acumulador derecho
sSup = ""                                           # Acumulador Superior
sInf = ""                                           # Acumulador Inferio
Estas variables actuarán de una forma particular, que en programación se conoce como "ACUMULADOR" que no es mas que una variable cuyo valor se incrementara periódicamente en saltos irregulares. El mejor ejemplo de un acumulador es el precio que muestra una caja registradora, digamos de supermercado; el precio inicia en 0 y luego a medida que se registra un articulo el valor de precio cambia, digamos $1000 un jabón entonces precio = 1000, luego $3500 un caja de galletas entonces precio = 4500 ya que ha acumulado $3500 de las galletas sobre el valor del jabón que ya teníamos.... y así sucesivamente crece el acumulador "precio" hasta dejarnos sin dinero!.

Para nuestro ejercicio los acumuladores no contendrán valores numéricos, sino letras o mas bien dígitos 1, 2, 3.... que se irán acumulando hasta formar la roseta de números; si nos fijamos un poco en la estructura de la roseta, veremos que tanto la parte izquierda como la derecha de la misma son idénticas, salvo la linea de en medio, ocurre lo mismo con la parte inferior y superior; la única diferencia real es el orden en que se acumulan los dígitos, el la parte derecha se acumulan por izquierda 2 -> 1 y la izquierda lo contrario 1 -> 2. Entender esto es la base para solucionar el ejercicio y el motivo de que existan cuatro acumuladores.

La linea 14 merece un trato especial:
i = 1                                               # Contador
En este caso la variable i que se define como un valor numérico entero cumple un papel muy común en programación, actúa como "CONTADOR"... si un contador que se dedica a contar, por simple que parezca en programación es de vital importancia contar (que redundancia XD) lo que hacemos, cuantas veces se suma un numero, cuantos datos quedan en la cola de espera, cuantas veces a ocurrido un error... los contadores están presentes en cualquier programa medio decente.

Para nuestro caso de estudio este contador nos dirá que tanto hemos avanzado en la construcción de nuestra roseta, de tal forma que cuando i sea igual a n (el valor indicado por el usuario) se habrá terminado la ejecución del programa; esta capacidad de "CONTROL" hace que i actué tanto como contador y como "BANDERA" (en inglés FLAG), una bandera es una variable que al alcanzar un cierto valor hace que el comportamiento del programa cambie; el ejemplo mas común de banderas son los semáforos, cuando un semáforo esta en verde los autos cruzan y cuando esta en rojo estos se detienen. Dominar el uso de banderas es una habilidad que dota de elegancia y sencillez al código de un programador y es un requisito fundamental para progresar en algoritmia.

La linea 15 define el ciclo principal del programa, veamos su estructura:
while(i <= n):                                      # Ciclo principal
Primero diré que existen dos tipos de ciclos, los mientras (while) y los para (for); ambos cumplen la misma función, obligan al programa a ejecutar repetidas veces algunas instrucciones, sin embargo cada tipo de ciclo tiene implementaciones diferentes según el problema... dado que en python el ciclo for se destina principalmente al manejo de listas lo dejare para tratarlo en detalle cuando escriba una entrada sobre ese tema; por ahora me centrare en el ciclo while.

El ciclo while, es en extremo sencillo define una repetición de ordenes mientras se cumpla una condición, su estructura es:  while (condición): condición es cualquier cosa que pueda ser evaluada de forma lógica, es decir que se pueda afirmar que es falsa o verdadera, por ejemplo 2 > 5, "Casa" = "cAsas",  (5*6)/4.0 < 1000, en nuestro caso la condición es que i (nuestro contador) sea menor o igual a n (el limite dado por el usuario), el ciclo se ejecuta (repite las ordenes) si y solo si la condición es verdadera, en caso contrario se ejecuta la linea siguiente al final del ciclo.

Es importante tener en cuneta que la instrucción while termina con :, esto le indica a python que de ese punto en adelante las ordenes que encuentre deben ser consideradas como propias del ciclo, es decir que deben repetirse mientras la condición sea verdadera. Después de un : todas las lineas deben ir indentadas, "Corridas" una cantidad de espacios fijos desde el margen izquierdo, por convención se usan 4 espacios o lo que es igual un tabulador (la tecla que esta sobre la de Bloq Mayús, perdonad pero muchos de mis alumnos no saben que es un tabulador), si se han declarado 2 signos : se debe indentar 2 veces (8 espacios), para 3 signos : 3 tabuladores (12 espacios) y así sucesivamente. Esto le indica a python que instrucciones le pertenecen a que estructura (ciclos, condicionales, clases... etc) y le dice donde termina cada estructura al encontrar una linea NO INDENTADA, en nuestro código el ciclo termina en la linea 26 ya que la siguiente instrucción no esta indentada.

Veamos ahora las lineas 17 y 18 que generan cada una de las lineas que componen nuestra roseta:
espa = (" "*(n-i))                              # Espacios adicionales
line = espa + sIzq + str(i) + sDer + espa +"\n" # Linea a generar
La linea 17 define la cantidad de espacios que deben colocarse a derecha e izquierda de los dígitos para mantenerlos centrados, el máximo ancho de la fila es (n*2)-1 ya que cada dígito tiene su par al lado contrario salvo el del medio, por ejemplo 1234321, así que para una roseta de n = 5 su máximo ancho sera de (5*2)-1 = 9, por otro lado cada linea tiene ((i-1)*2)+1 dígitos, así para la primer y ultima linea i = 1 la cantidad de dígitos sera ((1-1)*2)+1 = 1, para la segunda y penúltima linea i = 2 sera ((2-1)*2)+1 = 3 y así sucesivamente. Por tanto  en cada linea se necesita n-i espacios a cada lado, por ejemplo en la primer linea la cantidad de dígitos es 1 y el ancho 9, así que se necesitan 4 (n - i = 5 - 1) espacios a cada lado para cubrir los 8 restantes........ Ufff, que lío.... eso fue confuso, afortunadamente es solo la explicación de porque esta la linea y se aplica solo a este ejercicio aunque ilustra muy bien la forma de pensamiento estructurado que debe tener un buen programador... lograr ese modo de pensamiento es lo realmente importante y lo mas difícil de programar.

Ahora bien algo curioso es ese " "*(n - i) resulta muy confuso, pero en realidad es muy fácil, si el operado + sirve para concatenar (unir, sumar, pegar...) dos fragmentos de texto, entonces el operador *  se usa para multiplicar el texto, por ejemplo si tenemos la cadena "hola" y la multiplicamos por 3 ("hola"*3) el resultado sera "holaholahola", la verdad resulta algo muy útil pues en muchos casos una cadena sigue un patrón que se repite.

La linea 18 crea una variable "Temporal" es decir que solo existe por un rato y luego desaparece, en este caso desaparece al repetirse cada ronda del ciclo, esa variable temporal es la linea que generamos y es solo la suma (unión) de los espacios faltantes, los dígitos ya acumulados y el dígito actual, por ejemplo para i = 3 faltan 2 espacios a cada lado, ya se han acumulado los dígitos "12" a la izquierda y "21" a la derecha; así que la linea queda "  "+"12"+3+"21"+"  ". Al final se ha agregado un "\n" que es una secuencia de escape la cual indica que la linea se ha terminado en ese punto y que se debe hacer un salto de fila... vulgarmente hablando es el equivalente a dar un "ENTER" en cualquier editor de textos.

Tratemos ahora las lineas 19, 21 y 22, que son las encargadas de formar la roseta verticalmente:
sSup = sSup + line                              # Acumula linea en la parte superior

if(i < n):                                      # Evita que se repita la ultima linea
    sInf = line + sInf                          # Acumula linea en la parte inferior
La linea 19 es la encargada de crear la parte superior de la roseta, es de tener en cuenta que la concatenación de la linea se hace a la derecha sSup + line esto garantiza que se coloque una linea debajo de otra como corresponde a las lineas superiores, análogamente la linea 22 crea la parte inferior de la roseta, si se observa un poco se vera que es idéntica a la 19 salvo que la concatenación se hace a la izquierda line + sInf garantizando que se coloque una linea sobre otra, lo que es necesario para construir la parte baja de la roseta.

La linea 21 merece una explicación más detallada, esto es lo que se conoce como una "CONDICIONAL", funciona de forma idéntica a la condición del ciclo, salvo que en lugar de verificar si se repite una instrucción verifica simplemente si se ejecuta o no. Si la expresión que acompaña al if se evalúa como verdadera, las lineas asociadas a esta (indentadas, nótese que la linea 22 esta indentada respecto a la 21) se ejecutan, en caso contrario se omiten. 

La razón de que exista un if en este punto es que todas las lineas superiores tienen un doble en la parte inferior salvo la enésima (n) linea; si n = 5, entonces la quinta linea no tendrá par, para garantizar esto se usa la condicional if(i < n): antes de acumular las lineas en la variable de la parte inferior. Las condicionales pueden ser múltiples, es decir hacer varias preguntas encadenada con la orden elif(condición): o tener una orden por defecto else: que se ejecuta sin ninguna condición asociada es verdadera (nótese que todas estas son estructuras, por tanto terminan en : y requieren que se indente las lineas posteriores), esto lo explicare en detalle en una futura entrada.

Las lineas 24, 25 y 26 se encargan de acumular los dígitos en cada paso del ciclo y son el corazón del ejercicio:
sIzq = sIzq + str(i)                            # Acumula numero por izquierda para la próxima linea
sDer = str(i) + sDer                            # Acumula numero por derecha para la próxima linea
i += 1                                          # Incrementa el contador
Lo primero que salta a la vista es el str() que contiene a i en las lineas 24 y 25, diré solamente que python puede concatenar únicamente cadenas de texto y dado que i es un numero entero se requiere ordenarle al interprete que lo transforme en un carácter para poder unirlo con el acumulador; análogamente existen funciones int(), float(), long()....etc, que convierten de un tipo de dato a otro, por el momento esto no viene al caso.

Por demás es de notar que las lineas 24 y 25 realizan la misma labor que la 19 y la 22 solo que sobre dígitos 1, 2, 3.... en lugar de lineas completas así que estos se acumulas por derecha o izquierda en lugar se sobre o debajo.

La linea 26 es la encargada de incrementar el contador i, cosa de vital importancia por tres razones. Primero si i no se incrementa jamas se alcanzaría la condición de salida del ciclo (i <= n, sea falso) dejando atascado eternamente el programa; segundo , todos los dígitos serian siempre 1 y tercero no se tendría conciencia de que parte de la roseta se esta creando.

Por ultimo esta la tímida linea 28, cuya labor es tan simple como vital:
print sSup + sInf                                   # Imprime el resultado final
Esta modesta linea realiza dos labores, la primera de ella; por intuición nos imaginamos que es imprimir (pintar, mostrar) el resultado del programa, concatenando la parte superior e inferior de la roseta.... lo que es muy cierto. La segunda menos obvia es cerrar el ciclo, al no tener indentación esta linea marca el final del while... el resultado seria algo diferente si se tabulara... dejo ese cambio a curiosidad del lector!.

Bueno eso ha sido todo por hoy, un ejercicio bastante lúdico e instructivo... ya se que seguramente se habrán aburrido en extremo, en especial aquellos que tienen nociones de programación y dirán que soy un exagerado con los ejemplos tan tontos y las repeticiones, pero así están las cosas y tratare en lo posible de ser cada vez mas detallado cuando se trate de esta clase de entradas dedicadas a fundamentar conceptos de programación....

Abran notado que la roseta solo sirve para números entre 1 y 9 inclusive, se ha omitido el 0 y no se ha validado que n sea mayor a 9, un buen ejercicio personal seria agregar estas mejoras... y algo más avanzado para quienes deseen explorar, seria por ejemplo implementar letras os símbolos para dígitos mayores a 9, como A, B, C, D.... del mismo modo que se hace en sistema hexadecimal... en el futuro colocare el código de este caso... espero que lo hagan antes que yo!.
La próxima hablare mas detalladamente de listas y cadenas, el ciclo for y algo de control de flujo!... hasta entonces.....Paz y Suerte...........黒オオカミ

No hay comentarios:

Publicar un comentario