Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados

Fin de semana Pythonico

Es la noche del domingo del fin de semana en el que se llevó a cabo la 2ª Jornada Python en Santa Fe. La verdad es que tenía muchas cosas que quería contar aquí al respecto, pero también es mucho el sueño que tengo y las cosas que tengo que hacer mañana así que solamente voy a dejarles un fuerte GRACIAS, a los asistentes, disertantes, spónsores, colaboradore y al resto de los organizadores.

En la semana voy a ir dejando en formato-comentario algunas cosas/ideas/recursos sobre el evento.



2da Jornada Python en Santa Fe

El 9 de junio se realizará en el Auditorio de la Facultad Regional Santa Fe de la Universidad Tecnológica Nacional, Argentina, la 2da Jornada Python en Santa Fe. Con el objetivo de promocionar el lenguaje Python, este año se han programado un mayor número de temas dirigidos a la comunidad de programadores, profesionales de sistemas, estudiantes y público en general. Las charlas se desarrollarán entre las 8.30 y las 18.30 y estarán a cargo de varios disertantes invitados.

El LUGLi, Grupo de Usuarios de Software Libre del Litoral, y la Universidad Tecnológica Nacional, FRSF, tienen el agrado de invitarlo a participar. La entrada es libre y gratuita.

Puede obtenerse más información y registrarse para participar del evento en http://www.pythonsantafe.com.ar


Clausuras en Python

Hace unos días leí este post en el blog de Javier Smaldone.

Closures: Essentially a closure is a block of code that can be passed as an argument to a function call.

Como parte de la didáctica Javier iba mostrando ejemplos que resolvía con algún lenguaje sin Clausuras (como Java o PHP) y con Ruby (el post trata sobre las Clausuras como una fortaleza de Ruby). Mientras leía fuí resolviendo los mismos ejemplos en Python y los dejé en forma de comentos. Como la identación no salió muy bien, los reproduzco aca:

Juanjo:

Las que siguen son soluciones en Python de los ejemplos que planteaste, algunas usan clausuras, otros puede que no :)

El ejemplo simple:

Supongamos que deseamos escribir una función que tome como parámetro un arreglo de valores enteros y retorne otro arreglo, producto de multiplicar por 2 cada uno de los elementos del parámetro.
def f(a):

    return [2*x for x in a]

El ejemplo de people:

Supongamos ahora que tenemos una clase "Person", cuyo método "age" calcula la edad de la persona. Si tenemos una colección de personas en el arreglo "people" y deseamos obtener sólo aquellas que son mayores de 18 años.
[p.name for p in people if p.age > 18]

Problema 1:

Convertir las cadenas de un arreglo a mayúsculas.
a = [x.upper() for x in a]

Problema 2:

Dado un arreglo de la forma [["nombre", "apellido"]], obtener un arreglo de la forma ["apellido, nombre"]. Por ejemplo, dado:
[["Javier","Smaldone"],["Santiago","Lobos"],["Bon","Scott"]]
obtener:
["Smaldone, Javier", "Lobos, Santiago", "Scott, Bon"]
[”, “.join([y,x]) for [x,y] in a]

Problema 3:

Listar los nombres de las personas mayores de edad y mostrar la edad promedio de las mismas (según el ejemplo planteado anteriormente).

Este no quedó tan bien como los otros, pero te dejo mi interacción en REPL de Python:

>>> p1 = Persona()

>>> p1.age = 19

>>> p1.name = “Juan Hewr”

>>> p2 = Persona()

>>> p2.age = 2

>>> p2.name = “Baby Jones”

>>> p3 = Persona()

>>> p3.age = 30

>>> p3.name = “Sussan Ahoria”

>>> people = [p1, p2, p3]



>>> s = 0.0

>>> adults = [p for p in people if p.age > 18]

>>> for p in adults:

print p.name

s += p.age

Juan Hewr

Sussan Ahoria

>>> print s/len(adults)

24.5

Problema 4:

Dado un arreglo de cadenas, obtener la de mayor longitud.
max(cadenas)

Javier:

Muchas gracias Juanjo por las soluciones.

Con respecto al problema 4, un detalle:

En Ruby también puedes obtener el máximo elemento de un arreglo ‘a’ usando

a.max

Sin embargo, la idea era mostrar como aplicar una función al estilo del ‘fold’ de los lenguajes funcionales. ¿Podrías reescribir la solución en esos términos?

Juanjo:

Primero una aclaración de algo que me pasé por alto: max(a) en Python cuando a es una lista de strings no da el string más largo sino el que está más al final en un ordenamiento creciente alfabético estando las minúsculas después que las mayúsculas. Es así también en ruby?


>>> max(”hola”, “juanjo”)

‘juanjo’

>>> max(”hola”, “Juanjo”)

‘hola’

>>> max(”a”, “A”)

‘a’

>>> max(”a”, “AAAAAAAAAAAAAAAAAAAAAa”)

‘a’

>>> max(”a”, “AAAAAAAAAAAAAAAAAAAAA”, “a”)

‘a’

>>> max(”AAAAAAAAAAAAAAAAAAAAA”, “a”)

‘a’

>>> max(”b”, “a”)

‘b’

>>> max(”B”, “a”)

‘a’

Javier:

Así es, Juanjo. Yo también me equivoqué. max, aplicado a strings devuelve el máximo según el orden lexicográfico, y no según la longitud.

Me sigue quedando la duda. ¿Hay algo similar a ‘inject’ o ‘fold’ en Python?

Juanjo:

Ahora si vamos a intentar resolver el ejemplo 4 en Python:

Mmm inject está bueno para resolver ese problema.. pero creo que no tenemos algo así en Python. Intentemos de todas formas:

Tenemos una lista de strings:

a = [”hola”, “juAnjO”, “Argentina”, “8″]

Una función ‘criterio’:

def longer(a, b):

    if len(a) > len(b):

        return a

    else:

        return b

And this magic functions called ‘mas’:

def mas(l, c, v):

    if l:

        return mas(l[1:], c, c(l[0], v))

    else:

        return v

Que nos va a permitir hacer algo como esto:

>>> mas(a, longer, “”)

‘Argentina’

Mirando desde más cerca.. esta función ‘mas’, es muy parecida a ‘inject’, salvo por el nombre :)

>>> inject = mas

>>> numeros = [1,2,3,4,5,6]

>>> def suma(a, b):

    return a + b

>>> inject(numeros, suma, 0)

21

Mmm parece que funciona, comprobemoslo:

>>> sum(numeros)

21

Si!

Nos leemos!


Shuffle un diccionario en Python

El módulo random de Python tiene varias funciones muy útiles.

>>> import random

Cómo choice, que permite elegir un elemento al asar de una secuencia:

>>> lista = [1, 2, 3, "hola", 'q', '$', 0]

>>> random.choice(lista)

1

>>> random.choice(lista)

0

>>> random.choice(lista)

3

>>> random.choice(lista)

'q'

>>> random.choice("Esta es una oración muy interesante")

'E'

>>> random.choice((1,2,3,4))

3

O shuffle, que desordena una lista (in place, es decir que no retorna una lista desordenada sino que la misma es desordenada):

>>> random.shuffle(lista)

>>> lista

[3, 1, 0, 'hola', '$', 'q', 2]

>>> texto = "Esto es una pruba con SHUFFLE"

>>> random.shuffle(texto)

Traceback (most recent call last):

File "<stdin>", line 1, in ?

File "/usr/lib/python2.4/random.py", line 262, in shuffle

x[i], x[j] = x[j], x[i]

TypeError: object does not support item assignment

Obviamente ni a tuplas ni a strings se puede aplicar esta función ya que esos dos tipos de datos son inmutables.

Me encontraba yo programando y se me ocurrió que podría desordenar (shuffle) un diccionario para lograr cierto efecto. ¿Cual sería la semántica de esto? Seguro se entenderá mejor con un ejemplo, dado el diccionario:

{0 : "cero", 1 : "uno", 2 : "dos"}

que tiene números como claves y strings como valores, luego de que se le aplique la función shuffle

random.shuffle(dicc)

Se obtendría por ejemplo:

{0 : "uno", 1 : "dos", 2 : "cero"}

Bien, probémoslo en el REPL de Python:

>>> from random import shuffle

>>> lista = [1,2,3,4,5,6,7,8]

>>> shuffle(lista)

>>> lista

[4, 1, 7, 8, 6, 2, 5, 3]

>>> dicc = {}

>>> for a in range(8):

dicc[a] = a

>>> dicc

{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7}

>>> shuffle(dicc)

>>> dicc

{0: 1, 1: 4, 2: 7, 3: 6, 4: 2, 5: 5, 6: 3, 7: 0}

Mmm, parece que funciona. ¿Qué dicen uds? El del último ejemplo no es en realidad el diccionario que uso en mi programa, sino que es más bien algo así:

>>> dicc = {(1,1): "primero", (1,2): "segundo", (2,2): "otro"}

Intentamos mezclarlo y..

>>> shuffle(dicc)

Traceback (most recent call last):

File "<stdin>", line 1, in ?

File "/usr/lib/python2.4/random.py", line 262, in shuffle

x[i], x[j] = x[j], x[i]

KeyError: 2

¿Qué pasó? La respuesta a este raro comportamiento está en el código mismo de Python, en el módulo random. En mi instalación de Python2.4 en Debian GNU/Linux lo encontramos en: /usr/lib/python2.4/random.py

def shuffle(self, x, random=None, int=int):

"""x, random=random.random -> shuffle list x in place; return None.



Optional arg random is a 0-argument function returning a random

float in [0.0, 1.0); by default, the standard random.random.

"""



    if random is None:

        random = self.random

    for i in reversed(xrange(1, len(x))):

    # pick an element in x[:i+1] with which to exchange x[i]

        j = int(random() * (i+1))

        x[i], x[j] = x[j], x[i]

Sólo de ver la implementación se nota que solo funcionará con listas. La siguiente es mi alternativa, una función shuffle_dict que sirve para mezclar los elementos de un diccionario:

from random import choice



def shuffle_dict(d):

"""Shuffle the d dict."""

    for i in d.keys():

        k = d.keys()

        k.remove(i)

        j = choice(k)

        d[i], d[j] = d[j], d[i]


Problemas con la versión de wxPython al intentar iniciar SPE

SPE (Stani's Python Editor) es un Entorno de Desarrollo Integrado (IDE leyendo las siglas de atrás para adelante :-) ) libre (licenciado bajo GPL) para Python. Estaba con ganas de probarlo así que hoy lo instalé. En Debian:

apt-get install spe

Cuando intenté ejecutarlo, hubo un problema:


juanjo@sarge:~$ spe

You need to install at least wxPython v2.5.4.1 to run SPE.

Get it from http://www.wxpython.org

Pero mi versión instalada de wxPython era la 2.6.x.. en el resto del artículo: 2 formas de solucionar el problema.

Además de saber que el paquete que tenía instalado era wxpython2.6 y no wxpython2.4 (que también era un candidato), comprobé la versión que tenía instalada desde el intérprete interactivo de Python:


>>> import wxPython

>>> dir(wxPython)

['__builtins__', '__doc__', '__file__', '__name__', '__path__', '__version__', '_controls', '_core', '_gdi', '_misc', '_windows', '_wx', 'wx']

>>> wxPython.__version__

'2.6.3.2'

Entré al canal #python de irc.freenode.org donde alguien me dijo que en las versiones viejas de SPE había un bug con el chequeo de versiones y me me convenía instalar manualmente la última versión.


svn checkout svn://svn.berlios.de/python/spe/trunk/_spe

python _spe/SPE.py

me daba el mismo error.

Cundo revisé el código de _spe/SPE.py, estas eran las primeas líneas:


import sys

try:

    WX_VERSION  = '2.5.4.1'

    import wxversion

    wxversion.ensureMinimal(WX_VERSION)

except ImportError:

    print 'You need to install at least wxPython v%s to run SPE.\nGet it from http://www.wxpython.org'%WX_VERSION

    sys.exit()

Solución 1

Probé importar wxversion desde un interprete de Python, no tenía el módulo y eso producía una excepción, lo que producía que se muestre ese error y se termine el programa. Comentando las líneas:


    #import wxversion

    #wxversion.ensureMinimal(WX_VERSION)

pude correr el programa sin problemas.

Solución 2

En realidad el problema es que el módulo wxversion no está instalado. En un momento había pasado por la página de wxPython y entre sus nuevas features había leido:

..wxPython now supports having more than one wxPython runtime installed at the same time, and provides a mechanism for choosing a non-default version at runtime if the app needs to.

El módulo wxversion sirve para hacer la selección de versión de wxPython. Por lo que la otra solución es instalar este módulo:


apt-get install python-wxversion


Lista circular en Python

Estoy haciendo un juego inspirado en el Juego de la Vida. A modo de bonus-track también se podrán ver algunos patrones del tradicional juego de cero jugadores. Algunos patrones se cargarán de archivos en la computadora y se pondrán a disposición de usuario para que los examine. Para una mayor comodidad va a haber un botón "atrás" (>).

Si lo anterior fuera un enunciado en un examen de Algoritmos y Estructuras de Datos, ¿cual sería la estructura de datos más adecuada para mantener esa información? Sin dudas una lista circular. No sería muy agradable llegar al último patrón, apreatar "siguiente" y que no pase nada. Quiere que cuando esté parado en el último elemento y apriete "siguiente", me muestre el primero.

Una lista circular es una lista lineal en la que el último nodo a punta al primero.

Las listas circulares evitan excepciones en la operaciones que se realicen sobre ellas. No existen casos especiales, cada nodo siempre tiene uno anterior y uno siguiente.

El artículo va a ser un poco largo, va apasar por algunos lenguajes de programación y va a evaluar distintos enfoques. Pero adelanto que va a terminar en una implementación de una lista circular en Python basada en el manejo de índice.

Enfoque C

Conocí este tipo de estructura en la materia que mensioné más arriba. Usabamos C como lenguaje de programación, así que fué el primer lenguaje dónde implementé una. A esta estructura de datos se la llama dinámica por que puede crecer indefinidamente alocando porciones de memoria de la computadora, sin necesidad de que estas estén físicamente contiguas.

Supongamos que la lista circular va a ser de enteros (por simplicidad). Se necesitarán 1) una estructura Nodo más o menos así:


typedef struct nodo {

   int dato;

   struct nodo *next;

} nodo;

Node with next pointer

o así:


typedef struct nodo {

   int dato;

   struct nodo *next;

   struct nodo * prev;

} nodo;

Node with next and prev pointers

Las flechas de los dibujos son punteros (direcciones en memoria) a otros nodos, mediante los cuales estos se van a ir uniendo, bien para formar una lista lineal o para formar una lista como las que nos interesan, cuando el último nodo con su puntero next apunte al primero.

2) una forma de solicitar memoria para un nuevo nodo:


nodo * pnodo = (nodo *)malloc(sizeof(nodo));

y 3) manejar punteros; un puntero a la lista, uno a la posición actual, uno a la anterior a la actual.

Si bien usar estos elementos de la forma correcta is not rocket science, requiere cierto conocimiento de un nivel más bajo (por más cercano al hardware) de lo que los modernos lenguajes dinámicos nos tiene acostumbrado (de hecho en los lenguajes de más alto nivel, este problema ya viene solucionado).

Para solucionar mi problema voy a preferir una solución con un poquito más de nivel de abstracción, pero creo que este enfoque a modo de introducción sirve para ir poniendo las piezas en el tablero.

Enfoque Haskell

En la pasada CafeConf fuí a una charla de mi amigo Pupeno sobre lenguajes de programación, allí vi algo que no conocía. En Haskell, nos mostro este concepto: Listas infinitas. Lenguajes como este tiene una propiedad llamada Lazyness

En programación, lazy evaluation, también llamada delayed evaluation, es la técnica que consiste en retrasar un cálculo computacional hasta el momento en que su resultado se necesite.

Por ejemplo:


x = calculo_intensivo(1000)

no necesita ser evaluado hasta que por ejemplo se haga:


print x + 1

Esta propiedad permite entre otras cosas crear listas infinitas (de nuevo un un pseudo código muy parecido a Python):


miListaInfinita = ListaInfinita(arg)

Claro que no vamos a poder hacer:


print miListaInfinita

O podríamos pero la operación no terminaría nunca, pero si prodríamos hacer:


print miListaInfinita[100004]

Y dependiendo de la clase de lista infinita que sea se necesitarán calcular los anteriores términos (en el caso de por ejemplo la lista de Fibonacci) o no (en una lista de todos 0).

Bien. A dónde estamos yendo con todo esto? Al fin de cuentas yo estaba buscando una lista circular y me encontré con listas infinitas. La conclusión es que una lista circular puede verse como una lista infinita ya que:

a) Siempre puedo pedirle un elemento siguiente sin que se agoten.

b) Puedo pedir el elemento 100004º y también obtenerlo (luego de algunas vueltas, por su puesto).

Así sería una lista crcular (infinita) en Haskell:


cycle [1,2,3]

o


a = [1,2,3|a]  

Bien, puedo hacer esto en Python? Con poco esfuerzo, con mis conocimientos y en una tade de lluvia, yo no. Salvo en algunas excepciones, la evaluación en Python no es Lazy, sino Eager o estricta. De todas formas algunos links por si alguien quiere seguir investigado en esa línea:

<li>Generator expressions</li>

<ul>

<li><a href="http://gnosis.cx/publish/programming/charming_python_b13.html">http://gnosis.cx/publish/programming/charming_python_b13.html</a></li>

<li><a href="http://www.python.org/dev/peps/pep-0289/">http://www.python.org/dev/peps/pep-0289/</a></li>

<li><a href="http://docs.python.org/ref/genexpr.html">http://docs.python.org/ref/genexpr.html</a></li>

</ul>
Juanjo-ar-stafe> is python lazy? Habbie> python allows programmers to be lazy Habbie> and that's what matters itchi> Juanjo-ar-stafe: Are you not lazy?? :-)

Ya que agoté esta rama, backtracking y adelante!

De todas formas esto se trataba de Python, no?

Yo estaba cometiendo el error en pensar en el término Lista circular doblemente enlazada, lo cual tiene sentido en C, pero no en Python. Yo no se como están implementadas las estrcturas de datos que provee el lenguaje y sin embargo puedo usarlas sin problemas. La diferencia en que la lista en cuestión sea doble o simplemente enlazada es solo una cuestión de optimización y no de funcionalidad. Cito una parte de uno de los mails que intercambiamos con Pupeno al respecto y que me ayudó mucho a encarar mejor el problema:

El hacer una lista doblemente enlazada nos permite agregar funciones sobre la lista que la simplemente no nos permite y/o hacer que ciertas funciones sean mas rapido. En una lista podes hacer l.next() y tambien podes hacer l.prev() la diferencia es que prev() en una lista doblemente enlazada es constante en el tiempo O(1) y en una simple seria O(n) donde n es la cantidad de items en la lista (probablemente tenga un promedio de O(n/2)). Ahora, me parece que lo que vos necesitas realmente es que sea circular, no es cierto?

Ahora que ya se lo que necesito, puedo poner manos a la obra. ¿Qué necesito? Un objeto que sea una secuencia mutable de elementos al que siempre que quiera pueda decirle next() o prev() y me de el elemento siguiente o anterior al actual.

Lo primero en lo que podría pensar es en crear objetos Nodo y hacer una implementación C-like, usando referencias a objetos en lugar de punteros a estructuras pero.. creo que esto sería:

a) relativamente complicado, casi tanto como sería hacerlo en C. Más si mis objetivos de eficiencia, robustez y usabilidad son altos.

y b) un desapovechamiento de lo que ya está hecho (si llegó hasta aca, por favor siga leyendo).

Una de las estructuras de datos más poderosas que vienen con Python es list, algunos ejemplos rápidos:


>>> li = [1, "hola", 5, 8.5]

>>> li[3]

8.5

>>> li[:3]

[1, 'hola', 5]

>>> li[:]

[1, 'hola', 5, 8.5]

>>> li[-2]

5

>>> li[-2:]

[5, 8.5]

>>> li + ["muy bien"]

[1, 'hola', 5, 8.5, 'muy bien']

Robusta, eficiente y muy usable.

Como pueden ver el manejo de índices le da mucha flexibilidad y es uno de los elementos más usados que constituyen el lenguaje. ¿Cuanto tiempo me tomaría hacer una implementación desde cero así solo por encapricharme con usar referencia circular? Paso.. voy a extender list y a agregar la funcionalidad que requiero mediante el manejo de índices.

Primer implementación

Esta fue la priemr implementación que hice: circular.py

Enseguida mandé un mail a mi lista preferida de Python para pedir opiniones, de seguro tenía muchas cosas por mejorar.

Review 1

Enseguida Manuel Quiñones me mandó algunas optimizaciones (antes hacía llamadas recursivas de los métodos) + pruebas hechas con DocTest: circularDocTest.py

Mezclé sus optimizaciones con mi código original (seguí sin usar una forma automatizada de testeo) para seguir discutiendo en base a este: circular-r1.py

Review 2

Lucio Torre me apuntó este comportamiento no previsto:


>> Circular([]).prev() == Circular([None]).prev()

True

Estaba retornando None cuando se solicitaba un elemento de una lista vacía. Decidí que sería mejor lanzar una excepción en ese caso: circular-r2.py

Review 3

John Lenton se tomó el trabajo de reescribir la clase en una foma mucho más compacta (gacias!). Yo estaba considerando como especiales casos que en realidad no lo eran. Además me mostró como usar test de unidad en Python, cosa que nunca había hecho: circular-r3Tested.py

Review 4

Luego de unas sugerencias de estilo decidí hacer un mejor uso de los nombres de las variables (y métodos). También agregué una sugerencia muy interesante de Gabriel Genellina, en lugar de hacer:


if self == []:

para saber si la lista está vacía, usar:


if not self:

que es más rápido.

También permito que se contruya lal ista circular sin parámetros (lo que da una list circular vacía): circular-r4Tested.py

Review 5

Me deshago de la burocracia que tiene la clase y confío más en el programador (chau verificación de rango, chau set/get inncecesarios): circular-r5Tested.py

Este es el hilo en la lista de PyAr: http://mx.grulic.org.ar/lurker/message/20070221.033607.fd125b8e.es.html

Algunas referencias


Python multiplataforma: os.path.join()

Por lo general cuando se habla de multiplataforma se hace sólo referencia al hecho de que cierto código pueda ser ejecutado en diferentes plataformas (léase en la mayoría de los casos hardware + SO) sin tener que ser portado, es decir que se realicen cambios importantes en este.

En el caso de los programas escritos en un lenguaje compilado es necesario construir un archivo ejecutable (un archivo que contiene código binario) específico para cada plataforma en la que se lo quiera correr. Esto no sucede con los lenguajes interpretados, dónde la cuestión es más simple. Un programa escrito en un lenguaje interpretado será multiplataforma si el intérprete del lenguaje en el que está escrito lo es (siempre y cuando no se haga uso de alguna característica específica de alguna plataforma). Al fin de cuentas, el archivo ejecutable de un programa interpretado no será más que un archivo de texto plano.

Pero puede haber algo más...

Hace poco, usando Python, descubrí algo que me hizo pensar un poco más sobre las características que debe brindar un lenguaje de programación para que lo consideremos multiplataforma. Piensen lo siguiente: archivos. Un programa suele hacer un uso intenso de archivos (de configuración, imágenes, para guardar datos y luego recuperarlos, etc..). Lo que nos lleva a pesar: ¿cómo encuentra un programa esos archivos? Probablemente el lenguaje de programación que estemos utilizando nos proveerá de una función para abrir archivos[1] que recibiera como parámetros el nombre del archivo y la forma (modo) en que queremos abrirlo (para lectura, para escritura, crearlo si no existe, en modo binario, etc..). Dejemos de lado el modo y pensemos en lo que significa el nombre del archivo, ¿sería config.php un nombre válido? ¿Lo sería files/config.php? ¿Y que tal /var/www/prog/files/config.php? Me animo a darles una alternativa más en la que pensar: C:\wamp\www\prog\files\config.php.

Con esto quiero expresar que el parámetro nombre puede ser tanto un nombre absoluto para el archivo como uno relativo. config.php y files/config.php son ejemplos de nombre relativo mientras que /var/www/prog/files/config.php y C:\wamp\www\prog\files\config.php lo son de nombres absolutos. En mi opinión lo más cómodo es utilizar nombres relativos al directorio dónde se encuentra el archivo ejecutable, pero tenerlos todos juntos en el primer nivel de este sería engorroso y poco elegante.

Uno puede pensar en tener una organización de directorios que le permita trabajar cómodo a la vez que les permite a otros encontrar fácilmente lo que esta buscando. Por ejemplo:

data/ : para guardar los datos usados por el programa.

data/imgs/ : para las imágenes

data/sounds/ : para los sonidos

config/ : para los archivos de configuración

etc..

Y aquí es justamente dónde surge el problema cuya solución quería comentar.

Habrán notado en los ejemplos de más arriba hay una sutil diferencia. En los primeros se separa los nombres de los directorios con la barra "/" y en otros con "\". El primero es el separador de directorios utilizado en GNU/Linux (y en UNIX en general), mientras que el segundo es el utilizado en MS Windows. Y este es solo un ejemplo, pero pueden existir muchas diferencias (en este punto) de plataforma en plataforma, algunas que incluso no conozcamos u otras que ni siquiera han surgido aún!!

Usando la función os.path.join, un pathname que funcione correctamente en la plataforma dónde el programa sea ejecutado será creado[2].

La forma de usarla que he siembre visto es importando el módulo os:

import os

os.path.join("data", "sounds", "wrong.wav")

Yo conocí esta función leyendo un tutorial de PyGame. ¿Conocen algo parecido en otro lenguaje?

[1] Cuando hablo de abrir un archivo lo hago para ejemplificar la necesidad de encontrar un archivo, lo siguiente es igualmente válido para una función que por ejemplo reciba como parámetro el nombre de archivo de una imagen y la cargue en memoria para que luego podamos disponer de esta.

[2] Las versiones modernas de MS Windows se las arreglan para manejar pathnames que utilizan la barra "/" en lugar de la barra "\", pero esto es solo una parte de las plataformas dónde potencialmente ejecutaremos nuestro programa.


Alfabeto Calculadora

Todo empezó con este mail en la lista del Gleducar.

La entrada: una lista de palabras representables en una calculadora normal simplemente girando el display.

Esto parece algo sin mucha utilidad, pero esta técnica es utilizada en muchos dispositivos con capacidades gráficas reducidas para mostrar mensajes de error u otra informaición adicional a la salida numérica.

En este archivo están las palabras a procesar enviadas a la lista de correos (en la selección original de palabras no se incluyeron aquellas con la letra 'z', pero también podrían haber sido elejidas para representar en Alfabeto Calculadora): palabras.txt

El programa

Almuerzo de por medio escribí este código y lo mandé a la lista del Gleducar como respuesta al reto: alfabeto_calc.py (update: tiene la corrección que me sugirió Alecu)

La forma de representación que elejí es esta: tomar todas las letras y conviértalas a mayúsculas excepto a la 'h' ('h' puede representarse con un '4' de calculadora invertido, pero 'H' no se puede representar con ningún número). Podemos representar la 'l' en minúsculas o en mayúscula ('L'), al usar la mayúscula y representarla con el '7', nos guardamos el '1' para la 'I'. Una vez que se tiene la palabra en formato upper_except_h cambiar cada letra por el número correspondiente e invertir el orden de los números.

Links para leer un poco más

Resultado

Esta es la salida que se obtiene al correr el programa en una terminal con el archivo palabras.txt en el mismo directorio:

be = BE = 38

bebe = BEBE = 3838

bebible = BEBIBLE = 3781838

beisbol = BEISBOL = 7085138

belio = BELIO = 01738

bello = BELLO = 07738

beso = BESO = 0538

bielgo = BIELGO = 097318

bilioso = BILIOSO = 0501718

bilis = BILIS = 51718

biologo = BIOLOGO = 0907018

bis = BIS = 518

bisbiseo = BISBISEO = 03518518

bisel = BISEL = 73518

biso = BISO = 0518

bobilis = BOBILIS = 5171808

bobillo = BOBILLO = 0771808

bobo = BOBO = 0808

bohio = BOhIO = 01408

boi = BOI = 108

bolillo = BOLILLO = 0771708

bollo = BOLLO = 07708

bolo = BOLO = 0708

bolsillo = BOLSILLO = 07715708

bolso = BOLSO = 05708

e = E = 3

eh = Eh = 43

el = EL = 73

ele = ELE = 373

elegible = ELEGIBLE = 37819373

eliseo = ELISEO = 035173

elisio = ELISIO = 015173

elle = ELLE = 3773

ello = ELLO = 0773

elogio = ELOGIO = 019073

elogioso = ELOGIOSO = 05019073

eolio = EOLIO = 01703

es = ES = 53

ese = ESE = 353

eso = ESO = 053

gel = GEL = 739

geologo = GEOLOGO = 0907039

giboso = GIBOSO = 050819

gil = GIL = 719

gili = GILI = 1719

gis = GIS = 519

globo = GLOBO = 08079

globoso = GLOBOSO = 0508079

gobio = GOBIO = 01809

gol = GOL = 709

goloso = GOLOSO = 050709

he = hE = 34

helio = hELIO = 01734

heliosis = hELIOSIS = 51501734

hibleo = hIBLEO = 037814

hiel = hIEL = 7314

hielo = hIELO = 07314

higo = hIGO = 0914

hilo = hILO = 0714

hiogloso = hIOGLOSO = 05079014

hobo = hOBO = 0804

ibis = IBIS = 5181

ilegible = ILEGIBLE = 37819371

ileo = ILEO = 0371

ileso = ILESO = 05371

le = LE = 37

legible = LEGIBLE = 3781937

lego = LEGO = 0937

leible = LEIBLE = 378137

lelili = LELILI = 171737

lelo = LELO = 0737

leo = LEO = 037

les = LES = 537

lesbio = LESBIO = 018537

leso = LESO = 0537

libelo = LIBELO = 073817

libio = LIBIO = 01817

liego = LIEGO = 09317

ligio = LIGIO = 01917

lilili = LILILI = 171717

lio = LIO = 017

lioso = LIOSO = 05017

lis = LIS = 517

lisis = LISIS = 51517

liso = LISO = 0517

lo = LO = 07

lobo = LOBO = 0807

loboso = LOBOSO = 050807

logis = LOGIS = 51907

o = O = 0

obelo = OBELO = 07380

obeso = OBESO = 05380

oboe = OBOE = 3080

obolo = OBOLO = 07080

obseso = OBSESO = 053580

oh = Oh = 40

oible = OIBLE = 37810

oislo = OISLO = 07510

ole = OLE = 370

oleo = OLEO = 0370

oleoso = OLEOSO = 050370

os = OS = 50

oseo = OSEO = 0350

oso = OSO = 050

se = SE = 35

sebe = SEBE = 3835

sebillo = SEBILLO = 0771835

sebo = SEBO = 0835

seboso = SEBOSO = 050835

seis = SEIS = 5135

seise = SEISE = 35135

sello = SELLO = 07735

seo = SEO = 035

ses = SES = 535

seseo = SESEO = 03535

sesgo = SESGO = 09535

seso = SESO = 0535

si = SI = 15

sibil = SIBIL = 71815

sieso = SIESO = 05315

sigilo = SIGILO = 071915

sigiloso = SIGILOSO = 05071915

siglo = SIGLO = 07915

silbo = SILBO = 08715

silboso = SILBOSO = 0508715

silesio = SILESIO = 0153715

silo = SILO = 0715

siseo = SISEO = 03515

so = SO = 05

sobeo = SOBEO = 03805

sobo = SOBO = 0805

sois = SOIS = 5105

sol = SOL = 705

soleo = SOLEO = 03705

solio = SOLIO = 01705

solo = SOLO = 0705

sos = SOS = 505

sosiego = SOSIEGO = 0931505

soso = SOSO = 0505


Programación Temporal de Eventos con Python

Este artículo estuvo en mi lista de borradores (draft posts) por mucho tiempo ya. Evidente mente no cumplí con la consigna release early del desarrollo tipo bazar. Enough!

Este artículo va a hablar un poco de simulación de sistemas discretos, va a presentar un módulo que escribí como prueba de que se podía hacer y me va a servir para contarles algunas cosas uq e aprendí en el camino.

Sigo Aprendiendo Python y esta es una clase más.

Programación temporal de eventos

En el primer cuatrimestre de este año cursé una materia llamada Simulación. Estudiamos principalmente como simular sistemas discretos (como un grupo de pasajeros subiendo a un avión. Suben de a uno, en un momento hay 7 pasajeros a bordo y en otro momento hay 8, nunca hay 7.5) en contraposición a los sistemas continuos (como el tanque de nafta del avión dónde su nivel baja instante a instante cuando el avión está volando).

Básicamente existen dos tipos de técnicas que se pueden utilizar para simular sistemas discretos, las de orientación al intervalo y las de orientación al evento. En las del primer tipo, cada cierto tiempo t se verifica de alguna forma si ha ocurrido un evento y se realiza el procesamiento asociado a este. Esto lleva a impresiciones y en ocasiones a un gasto innecesesario de cpu. En las técnicas de orientación al evento estos problemas no existen.

Una de las técnicas más primitivas de orientación al intervalo (no se si se usa en la actualidad) es la de Programación Temporal de Eventos. Básicamente se tiene un reloj que cuenta el tiempo de simulación y una lista de eventos ordenada en el tiempo. Uno debe escribir una función o procedimiento por cada uno de los eventos principales del sistema (por ejemplo la llegada de una pieza a una máquina) y una función principal que contenga el main loop de la simulación. Esta irá tomando eventos de la lista, avanzará el tiempo de simulación según corresponda y los ejecutará. Siempre se toma el primero y se lo ejecuta, si no hay más elementos en la lista termina la siumulación.

Los eventos son agregados a la lista con una primitiva llamada por ejemplo PPE (programar próximo evento), dónde además de indicar cual será el próximo evento, se debe indicar dentro de cuanto tiempo a partir del actual se debe ejecutar.

Mientras cursaba, estudiaba y rendía pensaba que sería muy fácil implementar en Python un módulo que provea las primitivas necesarias para realizar una simulción por programación temporal de eventos. Poner y sacar funciones de una lista seguro sería sencillo en un lenguaje muy dinámico y donde todo (incluidas las funciones) son objetos. La semana siguiente a rendir estuve escribiendo un poco de código Python. El resultado es un módulo llamado pte que cubre las funcionalidades mínimas que yo pretendía.

Resultado

No pretendía realizar un imponente desarrollo ni pienso que alguien use esto alguna vez en serío. Es más bien una prueba de concepto (Proof of concept). Lo había avandonado y ni siquiera funcionaba sin tirar errores. Objetivo: que funcione.

Este es el módulo: pte.py (versión orginal)

Una versión con el consejo de Daniel: pte-d.py (gracias Daniel!)

Y este un ejemplo en el que lo uso: pte_ej2.py

Ejemplo de ejecución:


juanjo@sarge:~/python$ ./pte_ej2.py



500 piezas fueron aceptadas en 1247.55 unidades de tiempo.



Máximo tamaño de la cola para procesamiento 5.

Qué aprendí

Módulos

Más de lo que pueda decir sobre módulos: http://docs.python.org/tut/node8.html

reload(modulo)
Reload a previously imported module. The argument must be a module object, so it must have been successfully imported before. This is useful if you have edited the module source file using an external editor and want to try out the new version without leaving the Python interpreter. The return value is the module object (the same as the module argument).
Entonces lo que hay que hacer cuando se está trabajando en la consola de Python[1] es, primer importar el módulo que se quiere probar:

>>> import modulo

Hacer algunas pruebas:

>>> modulo.var1

1

>>> modulo.cuad(3)

9

Si luego editamos el texto de modulo y queremos probar la función que agregarmos:

>>> modulo = reload(modulo)

Funciones como objetos

En Python todo es un objeto, incluso las funciones. Sabiendo esto y teniendo en mente lo que quería hacer (en la definición de una función, explicitar que esta se llame en el futuro, es decir almacenarla en una lista de eventos) hice una pequeña prueba en el intérprete:

>>> l = []

>>> def f():

    l.append(f)





>>> l

[]

>>> f()

>>> l

[<function f at 0x418bce9c>]

>>> l[0]

<function f at 0x418bce9c>

>>> l

[<function f at 0x418bce9c>]

>>> l[0]()

>>> l

[<function f at 0x418bce9c>, <function f at 0x418bce9c>]

Esto es posible justamente gracias a que en Python las funciones son objetos. En ciencias de la computación esto se conoce como Funciones de Primera Clase, es decir que las funciones sean Objetos de Primera Clase, es decir que puedan ser creadas durante la ejecución de un programa, almacenadas en estructuras de datos, recuperadas más tarde para usarlas, pasadas como argumentos y retornadas en otras funciones. Más al respecto en wikipedia.

Ordenar según un criterio propio

Una lista puede ser ordenada según un criterio propio pasándole al método sort de una lista ese criterio en forma de función:

def _comparar_tiempos(a,b):

    """ Función auxiliar usada para ordenar los pares eventos,tiempos

    en la lista de eventos.

    """

    t1 = a[1]

    t2 = b[1]

    if (t1 > t2): return 1

    elif (t1 < t2): return -1

    else: return 0

y luego..

eventos.sort(_comparar_tiempos)

Espero les haya resultado interesante! [1] También conocida como REPL, algo muy piola cuando uno esta desarrollando.