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

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!


Smarter N-Puzzle

N-Puzzle sigue sin tener una real inteligencia, pero hice una modificación que lo hace verse un poco más inteligente:

Si un jugador o el desordenador aleatorio hace movimientos que se cancelan entre sí

    <li>Izquierda - Derecha</li>
    
    <li>Izquierda - Izquierda - Derecha - Derecha</li>
    
    <li>Izquierda - Izquierda - Arriba - Arriba - Derecha - Izquierda - Abajo - Abajo - Derecha - Derecha</li>
    

    el siguiente algoritmo es capás de eliminarlos de la lista de acciones y así lograr una resolución en menos movimientos y que parezca menos tonta.

    El método que encapsula al algoritmo en cuestión es llamado cada vez que se entra al modo Auto Solve:

    
        def _reduce_actions(self):
    
            '''This method try to reduce the actions' log
    
            removing repeted moves.'''
    
    
    
            log = self.actions_log
    
            stack = []
    
    
    
            while log:
    
    
    
                a = log.pop()
    
                ia = self._inverse_action(a)
    
    
    
                if stack:
    
                    s = stack.pop()
    
                    if ia != s:
    
                        stack += [s, a]
    
                else:
    
                    stack += [a]
    
    
    
                print "Stack: ", len(stack)
    
                print "Log: ", len(log)
    
    
    
            stack.reverse()
    
            self.actions_log = stack

    Download last version: n-puzzle-0.1-2


    N-Puzzle

    Hace unos días mandé a la lista de correos de PyAr una implementación inicial del juego N-Puzzle. n-puzzle-0.1.tgz.

    Es muy sencilla. Toma una imagen, la parte en cuadraditos, quita uno de los cuadraditos y los mezcla. Luego uno puedo deslizar los cuadraditos adyasentes al espacio en blanco con las flechas de teclado. El objetivo del juego es recomponer la imagen original.

    N Puzzle 0.01
    Hoy estuve mejorando el código y agregándole una funcionalidad que permite que la computadora resuelva el problema (o nos ayude a hacerlo).La idea original para este programa es luego utilizarlo para probar los métodos de búsqueda que estamos estudiando en Inteligencia Artificial.

    Ojo! Si bien en esta nueva versión apretando la tecla 'S' entramos y salimos del modo Auto Solve, no estoy usando IA para implementarlo. Siempre pueden mirar el código y ver como lo estoy haciendo :)

    Actualmente el juego está planteado como una prueba de concepto más que como un programa totalmente usable. Esto implica que pueden ocurrir errores durante su ejecución y que algunas opciones que podrían ser configurables están clavadas en el código:

    • N es el número de fichas que participan del juego: 8 (en un tablero de 3 x 3), 15 (en uno de 4 x 4), 24 (en uno de 5 x 5). Por defecto es 8 pero puede cambiarse en el archivo run.py.
    • La imagen que se usará para generar las fichas se llama ejemplo.jpg y debe estar en el mismo directorio que los scripts (yo proveo una). Pueden pisarla con otra imagen o pueden cambiar la ruta a la misma en el archivo run.py. La imagen debe ser de 600 x 600 pixeles.
    • El desordenamiento original del tablero se efectua mediante un número X de movimientos. En el código fuente el valor usado es 20 pero puede cambiarse en el método __init__ de la clase Puzzle en el archivo puzzle.py (van a encontrarlo en la llamada al método _shuffle).

    El puzzle resuelto se ve así:

    N Puzzle solved
    La última versión puede encontrarse en: files/python/n-puzzle-0.1-1.tgz.Y este es el CHANGELOG de mi primer intento a este segundo mejorado:
    0.1-1: mix(x,y) se realiza ahroa como x,y = y,x en lugar de usar una variable temporal. Cada Card tiene un cid (Card ID), un par i,j correspondiente a su posición original en el tablero. El desordenado del tablero se realiza mediante el movimiento aleatorio de las piezas (usando los métodos up, down, right y left) para evitar que el juego comience con una configuración que no tiene solución (Para un ejemplo, Google: 14-15 Puzzle). Apretando la tecla 'S' se entra y sale del modo 'Auto Solve' en el cual la computadora nos ayuda a resolver el juego. Atención: NO IA was used in this feature.

    Requiere Python y PyGame. Si tienen problemas para instalarlos mandenme un mail y los ayudo.


    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]
    
    


    El juego del verano

    Life Fighter es un juego de tablero, de estrategia, desarrollado en Python haciendo uso de PyGame, que estuve pensando y pogramando este verano. Del 10 al 20 de diciembre, del5 al 15 de enero y ahora estoy volviendo a trabajar en él.

    Me cansé de trabajar solo, así que escupí todo lo que tenía a Internet. Voy a tener que ir organizándolo un poco estos días. Pero ya está, está ahí. No está ya más solo en mi frágil computadora.

    "Only wimps use tape backup: real men just upload their important stuff on ftp, and let the rest of the world mirror it."

    -- Linus Torvalds

    Todavía no hay ningún paquete listo para instalar, pero pueden bajar el código fuente desde el servidor svn:

    svn checkout https://life-fighter.googlecode.com/svn/trunk/ life-fighter --username usuario

    dónde usuario es el de su google account. También van a necesitar una password

    o anónimamente:

    svn checkout http://life-fighter.googlecode.com/svn/trunk/ life-fighter

    (sólo se necesita tener instalado Python --estoy usando la version 2.4-- y PyGame pero si tienen algún problema mandenme mail o dejen el comentario aca y los ayudo)

    La página del proyecto en Google code: http://code.google.com/p/life-fighter/

    Licencia: GPL 2.0

    Espero que se lo bajen y lo prueben (como para ir entusiasmando la cosa)

    Saludos

    Juanjo


    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