<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1085745093237715146</id><updated>2010-07-23T14:42:42.426-02:00</updated><title type='text'>Displicencias</title><subtitle type='html'>Blog de Gonzalo Sainz-Trápaga (GomoX)</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/-/Python'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/search/label/Python'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>12</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-245823181657360605</id><published>2009-09-17T06:19:00.008-02:00</published><updated>2009-09-23T02:07:36.512-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Redes'/><category scheme='http://www.blogger.com/atom/ns#' term='Desarrollo'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>Como armar un gateway SMS con tu celular</title><content type='html'>Estuve averiguando y al parecer la única manera de hacer una aplicación que reciba mensajes de texto y pueda responder a los mismos, es apersonarse muy bien trajeado en una empresa que brinde servicios de alertas SMS y cuestiones afines, y firmar un jugoso contrato para obtener el muy preciado servicio. Ojo, enviar mensajes SMS desde una computadora es mucho más sencillo: la mayoría de los &lt;i&gt;carriers&lt;/i&gt; permiten hacerlo enviando un correo electrónico a una dirección conveniente. El problema está en recibir los benditos textos: ¿qué pasa si quiero hacer el Twitter argentino, y quiero que mis usuarios posteen actualizaciones desde su celular? ¿qué pasa si quiero preguntarle a mi máquina como va la descarga de mi &lt;i&gt;torrent&lt;/i&gt;?&lt;br /&gt;&lt;br /&gt;

El servicio necesario para enviar o recibir SMS desde una computadora conectada a Internet se denomina &lt;i&gt;gateway&lt;/i&gt; SMS, y si bien el servicio de envío lo proveen las mismas compañías de telefonía celular, la recepción es más delicada, ya que nadie provee un servicio de acceso sencillo para &lt;i&gt;forwardearme&lt;/i&gt; los mensajes que lleguen a un determinado número a mi aplicación. A continuación voy a explicar como se puede fabricar un gateway SMS &lt;i&gt;fatto in casa&lt;/i&gt; utilizando un celular común y silvestre.&lt;br /&gt;&lt;br /&gt;

&lt;b&gt;Ingredientes&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;Una computadora con un adaptador &lt;a href="http://es.wikipedia.org/wiki/Bluetooth"&gt;Bluetooth&lt;/a&gt; (o no, ver nota a continuación)&lt;/li&gt;
&lt;li&gt;Un celular con Bluetooth&lt;/li&gt;
&lt;li&gt;Python + &lt;a href="http://code.google.com/p/pybluez/"&gt;python-bluez&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

En mi caso utilicé mi maltrecho &lt;a href="http://www.nokia.com.ar/productos/todos-los-modelos/nokia-6103"&gt;Nokia 6103&lt;/a&gt;, y la solución que propongo debería funcionar en cualquier celular con dos dedos de frente (o sea, la gran mayoría de los Nokia y Sony Ericcsson, y tal vez algún que otro Motorola).&lt;br /&gt;&lt;br /&gt;

Técnicamente no es necesario usar bluetooth puesto que lo que vamos a hacer es enviar &lt;a href="http://www.developershome.com/sms/atCommandsIntro.asp"&gt;comandos AT estándar&lt;/a&gt; sobre un enlace serial. Esto se puede hacer tanto por Bluetooth como con el cable adaptador USB o serial correspondiente, artículo del que no dispongo. Debería ser trivial portar las ideas de mi programa para funcionar con un enlace cableado.&lt;br /&gt;&lt;br /&gt;

Como sistema operativo utilicé Ubuntu 9.04, pero debería funcionar en casi cualquier sistema actual puesto que las librerías necesarias funcionan también en Windows.&lt;br /&gt;&lt;br /&gt;

&lt;b&gt;Pasos a seguir&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

El funcionamiento del gateway que propongo es sencillo; se conecta al teléfono mediante la conexión inalámbrica y puede realizar una de tres acciones:
&lt;ul&gt;
&lt;li&gt;Enviar un mensaje de texto a un número determinado&lt;/li&gt;
&lt;li&gt;Obtener todos los mensajes de texto almacenados en el teléfono&lt;/li&gt;
&lt;li&gt;Borrar todos los mensajes de texto almacenados en el teléfono&lt;/li&gt;&lt;/ul&gt;

El único paso manual consiste en identificar la dirección de &lt;i&gt;hardware&lt;/i&gt; del teléfono para indicarla al conectarse. En Linux esto puede hacerse habilitando Bluetooth tanto en el celular como en la computadora, activando en el teléfono el modo "visible", y finalmente ejecutando el comando &lt;b&gt;hcitool scan&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;

Tras ingresar dicho valor en el script que pongo a continuación, se realizará la conexión con el celular. Es probable que el celular requiera una aprobación manual para cada vez que se realice una conexión (un mensaje del tipo "¿Desea conectarse con el dispositivo XXX?"). Como esto no es muy cómodo en un servidor, en los teléfonos Nokia esta opción puede desactivarse una vez que se ha establecido la conexión (en el apartado "Conexiones activas" del menú Bluetooth, se puede indicar que un cierto dispositivo no requiere confirmación). &lt;br /&gt;&lt;br /&gt;

El código que adjunto a continuación hace el resto del trabajo, gran parte del cual consiste en parsear el extraño formato &lt;a href="http://dreamfabric.com/sms/"&gt;PDU&lt;/a&gt; en que el teléfono entrega los mensajes de texto cuando se le pide. El código que hace esto está fuertemente basado en &lt;a href="http://pypi.python.org/pypi/smspdu/0.2"&gt;smspdu&lt;/a&gt;, que modifiqué para hacer un poco más amigable.

&lt;p align="right"&gt;&lt;a href="http://blog.gomox.com.ar/2009/09/como-armar-un-gateway-sms-con-tu.html"&gt;Seguir leyendo &gt;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;span class="fullpost"&gt;

&lt;b&gt;gateway.py&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

&lt;pre name="code" class="Python"&gt;
#!/usr/bin/python

#Copyright (c) 2009 Gonzalo Sainz-Trapaga
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.

import bluetooth
import select
import pdu

DEBUG = False

class Nokia6103:
    def __init__(self, hwaddr, port):
        self.sockfd = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
        self.sockfd.connect((hwaddr, port))
        self._send('ATZ')

    def _log(self, s):
        if DEBUG:
            print s

    def _read(self):
        i = [self.sockfd]
        w = []
        e = [self.sockfd]
        out = ''
        while True:
            ir, wr, er = select.select(i, w, e, 3)
            if len(ir) == 0:
                self._log("Select: espera finalizada - saliendo.")
                break
            if len(er) &gt; 0:
                self._log("Select: condicion de excepcion - saliendo.")
                break
    
            out += i[0].recv(1000)
            if out.find('OK\r\n') != -1:
                self._log("Select: OK alcanzado - saliendo.")
                break
        return out
    
    
    def _send(self, s):
        self.sockfd.send('%s\r' % s)
        out = self._read()
        self._log(out)
        return out

    def sendSMS(self, num, txt):
        self._send('AT+CMGF=1')
        self._send('AT+CMGS="%s"' % num)
        self.sockfd.send(txt + "\n")
        self.sockfd.send(chr(26))

    def getAllSMS(self):
        s = self._send('AT+CMGL=4')
        lines = s.split('\r\n')
        lines.pop(0)
        msgs = []
        for i, msg in enumerate(lines):
            if i % 2 == 0:
                if not msg.startswith('+CMGL'):
                    break
            else:
                msgs.append(pdu.decodePdu(msg))
        return msgs

    def deleteAllSMS(self):
        self._send('AT+CMGD=1,4')

    def close(self):
        self.sockfd.close()

if __name__ == '__main__':
    port = 1
    hwaddr = '00:19:B7:XX:XX:XX'
    n = Nokia6103(hwaddr, port)
    print n.sendSMS('+541150501234', 'Mensaje de prueba!')
    print n.getAllSMS()

&lt;/pre&gt;

&lt;b&gt;pdu.py&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

&lt;pre name="code" class="Python"&gt;
#Copyright (c) 2009 Eric Gradman, Gonzalo Sainz-Trapaga
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
from cStringIO import StringIO
from math import ceil
from binascii import unhexlify, hexlify
from itertools import cycle
from datetime import datetime


def decodePdu(s):
    s = unhexlify(s)
    d = StringIO(s)

    # parse SMSC information
    p = {}
    p['smsc_len'] = d.read(1)
    p['type_of_address'] = d.read(1)
    p['sc_num'] = unsemi(d.read(ord(p['smsc_len'])-1))

    p['msg_type'] = d.read(1)
    p['address_len'] = d.read(1)
    p['type_of_address'] = d.read(1)

    p['sender_num'] = unsemi(d.read(int(ceil(ord(p['address_len'])/2.0))))
    p['pid'] = d.read(1)
    p['dcs'] = d.read(1)
    ts = d.read(7)
    p['ts'], p['tz'] = parseTimeStamp(ts)

    p['udl'] = d.read(1)
    p['user_data'] = d.read(ord(p['udl']))
    p['user_data'] = decodeUserData(p['user_data'])

    
    for f in ['sc_num', 'sender_num']:
        if p[f].endswith('f'):
            p[f] = p[f][:-1]
    
    return p

def unnibleSwapChar(c):
    c = ord(c)
    d1 = c &amp; 0x0F
    d2 = c &gt;&gt; 4
    return int(str(d1) + str(d2))

def parseTimeZone(c):
    c = ord(c)
    d1 = c &amp; 0x0F
    d2 = c &gt;&gt; 4

    neg = d1 &gt;&gt; 3
    d1 = d1 &amp; 0x7
    
    units = int(str(d1) + str(d2))
    if neg:
        zona = '-'
    else:
        zona = ''
    zona += str(units // 4)
    zona += ':'
    zona += "%.02d" % ((units % 4) * 15)

    return zona


def parseTimeStamp(s):
    ts = s[:6]
    tz = s[-1:]

    f = [unnibleSwapChar(c) for c in ts]
    f[0] = f[0] + 2000

    zona = parseTimeZone(tz)
    return datetime(*f), zona

def decodeUserData(s):
    bytes = map(ord, s)
    strips = cycle(range(1,9))
    out = ""
    c = 0    # carry
    clen = 0 # carry length in bits
    while len(bytes):
      strip = strips.next()
      if strip == 8:
        byte = 0
        ms = 0
        ls = 0
      else:
        byte = bytes.pop(0)
        # take strip bytes off the top
        ms = byte &gt;&gt; (8-strip)
        ls = byte &amp; (0xff &gt;&gt; strip)
      #print "%d byte %x ms %x ls %x" % (strip, byte, ms, ls)

      # append the previous
      byte = ((ls &lt;&lt; clen) | c) &amp; 0xff
      out += chr(byte)

      c = ms
      clen = strip % 8

    if strip == 7:  out += chr(ls) # changed 6/11/09 to incorporate Carl's suggestion in comments
    return out

def unsemi(s):
    """turn PDU semi-octets into a string"""
    l = list(hexlify(s))
    out = ""
    while len(l):
      out += l.pop(1)
      out += l.pop(0)
    return out
&lt;/pre&gt;
&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-245823181657360605?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/245823181657360605/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2009/09/como-armar-un-gateway-sms-con-tu.html#comment-form' title='5 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/245823181657360605'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/245823181657360605'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2009/09/como-armar-un-gateway-sms-con-tu.html' title='Como armar un gateway SMS con tu celular'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-6290477397107141964</id><published>2009-08-14T20:29:00.006-02:00</published><updated>2009-08-14T20:52:02.102-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>PyCon AR 2009 en Buenos Aires</title><content type='html'>&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://ar.pycon.org/"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 468px; height: 60px;" src="http://ar.pycon.org/common/2009/website/img/banners/PyConAR-2009-banner-chico.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;
Hace un tiempo vi en la facultad los primeros afiches llamando a enviar charlas y presentaciones para realizarse en la PyCon 2009, la primera conferencia sobre Python en español! Se va a realizar los días 4 y 5 de septiembre en la &lt;a href="http://maps.google.com/maps/ms?ie=UTF8&amp;hl=en&amp;msa=0&amp;msid=106382046091568425856.00046d7094da580d5236f&amp;source=embed&amp;ll=-34.566408,-58.44512&amp;spn=0.024914,0.055704&amp;z=15"&gt;sede de la Universidad de Belgrano&lt;/a&gt;, las charlas plenarias son de:
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Jacob Kaplan-Moss&lt;/b&gt;, uno de los &lt;i&gt;lead developers&lt;/i&gt; de &lt;a href="http://www.djangoproject.com"&gt;Django&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Collin Winter&lt;/b&gt;, uno de los &lt;i&gt;core developers&lt;/i&gt; de Python que está trabajando en Google en el proyecto &lt;a href="http://code.google.com/p/unladen-swallow/"&gt;Unladen Swallow&lt;/a&gt; (un intérprete optimizado que busca multiplicar por 5 la velocidad actual de Python).
&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;

Además de éstas que son plenarias, está plagado de &lt;a href="http://ar.pycon.org/2009/conference/talks/"&gt;otras charlas&lt;/a&gt; de menor envergadura, además de las llamadas &lt;i&gt;charlas relámpago&lt;/i&gt; que duran solo 5 minutos. Los temas son de los más diversos, y van desde una introducción al lenguaje a cómo usar Python para manipular su propio &lt;i&gt;bytecode&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;

En resumen, novatos, hackers añejos e incluso personas que solo haya oído hablar de Python van a tener cosas para mirar y aprender. La inscripción es gratuita, solo hay que &lt;a href="http://ar.pycon.org/2009/profile/new/"&gt;registrarse en el sitio web de la conferencia&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-6290477397107141964?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/6290477397107141964/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2009/08/pycon-ar-2009-en-buenos-aires.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/6290477397107141964'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/6290477397107141964'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2009/08/pycon-ar-2009-en-buenos-aires.html' title='PyCon AR 2009 en Buenos Aires'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-4442181484416174589</id><published>2009-04-28T03:23:00.008-02:00</published><updated>2009-04-28T05:12:23.018-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>Python 3: iteradores en map y filter</title><content type='html'>Las primitivas funcionales &lt;span style="font-weight:bold;"&gt;map&lt;/span&gt; y &lt;span style="font-weight:bold;"&gt;filter&lt;/span&gt; se agregaron &lt;a href="http://python-history.blogspot.com/2009/04/origins-of-pythons-functional-features.html"&gt;a Python en Enero de 1994&lt;/a&gt; por un programador cuya identidad hoy se desconoce. De esta manera, lo que antes se escribía repetidas veces con un bucle puede reducirse a una única y expresiva línea de código.

&lt;pre name="code" class="python"&gt;
lista = [1,2,3,4]

# utilizando un bucle
lista_mapeada = []
for e in lista:
    lista_mapeada.append(f(e))

lista_filtrada = []
for e in lista:
    if f(e):
        lista_filtrada.append(e)

# utilizando map y filter
lista_mapeada = map(f, lista)
lista_filtrada = filter(f, lista)
&lt;/pre&gt;

En Python 2.0 se agregaron &lt;a href="http://docs.python.org/whatsnew/2.0.html#list-comprehensions"&gt;listas por comprensión&lt;/a&gt;, que ofrecen una sintaxis más intuitiva para las primitivas &lt;span style="font-weight:bold;"&gt;map&lt;/span&gt; y &lt;span style="font-weight:bold;"&gt;filter&lt;/span&gt;.

&lt;pre name="code" class="python"&gt;
lista_mapeada = [f(e) for e in lista]
lista_filtrada = [e for e  in lista if f(e)]
&lt;/pre&gt;

Si están familiarizados con lenguajes como &lt;a href="http://www.haskell.org"&gt;Haskell&lt;/a&gt;, que utilizan &lt;a href="http://en.wikipedia.org/wiki/Lazy_evaluation"&gt;evaluación &lt;span style="font-style:italic;"&gt;lazy&lt;/span&gt; o perezosa&lt;/a&gt; conocen algunas de las ventajas de esta característica en un lenguaje de programación: la evaluación de algunas expresiones no se hace inmediatamente sino que se pospone hasta que el valor es realmente necesario. &lt;br/&gt;&lt;br/&gt;

En el contexto de las primitivas &lt;span style="font-weight:bold;"&gt;map&lt;/span&gt; y &lt;span style="font-weight:bold;"&gt;filter&lt;/span&gt; que nos conciernen, la ventaja de la evaluación &lt;span style="font-style:italic;"&gt;lazy&lt;/span&gt; es que no se computan hasta tanto no se accede al contenido de las listas resultantes. Con esto en mente, en Python 2.4 se agregaron &lt;a href="http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Generator_expressions"&gt;generadores por comprensión&lt;/a&gt; (simplemente remplazando los corchetes por paréntesis en la sintaxis anterior). El resultado es un &lt;span style="font-weight:bold;"&gt;generador&lt;/span&gt;: un objeto que se comporta parcialmente como una lista, dado que puede iterarse sobre el (aunque no puede indexarse y accededer directamente a su &lt;span style="font-style:italic;"&gt;i-ésimo&lt;/span&gt; elemento).

&lt;pre name="code" class="python"&gt;
gen_mapeado = (f(e) for e in lista)
gen_filtrado = (e for e  in lista if f(e))
&lt;/pre&gt;

La mejora más inmediata es la &lt;span style="font-style:italic;"&gt;performance&lt;/span&gt;: en lugar de aplicar la función completamente en el momento, no se hacen los cómputos requeridos hasta que se accede al resultado. Por esta razón, la librería estándar &lt;span style="font-weight:bold;"&gt;itertools&lt;/span&gt; incluye primitivas &lt;span style="font-weight: bold;"&gt;imap&lt;/span&gt;, &lt;span style="font-weight:bold;"&gt;ifilter&lt;/span&gt; y varias más que devuelven generadores en lugar de listas, puesto que en muchos casos este comportamiento es deseable.&lt;br/&gt;&lt;br/&gt;

En Python 3, sencillamente se cambió el comportamiento de las primitivas convencionales para que devuelvan iteradores en lugar de listas. Así, los nuevos &lt;span style="font-weight:bold;"&gt;map&lt;/span&gt;, &lt;span style="font-weight:bold;"&gt;filter&lt;/span&gt;, &lt;span style="font-weight:bold;"&gt;zip&lt;/span&gt; y compañía se comportan como los anteriores &lt;span style="font-weight:bold;"&gt;itertools.imap&lt;/span&gt;, &lt;span style="font-weight:bold;"&gt;iterators.ifilter&lt;/span&gt; y &lt;span style="font-weight:bold;"&gt;iterators.izip&lt;/span&gt;.

&lt;pre name="code" class="python"&gt;
# Python 3
&gt;&gt;&gt; l = map(f, [1,2,3,4])
&gt;&gt;&gt; l[2]
TypeError: object is unsubscriptable
&lt;/pre&gt;

El nuevo comportamiento no es compatible hacia atrás, pero puede resolverse esta diferencia sencillamente cambiando todas las llamadas &lt;span style="font-weight:bold;"&gt;map(l)&lt;/span&gt; (y los otros) por &lt;span style="font-weight:bold;"&gt;list(map(l))&lt;/span&gt;, lo cual produce un comportamiento casi idéntico en Python 3 y Python 2 (y digo &lt;span style="font-style:italic;"&gt;casi&lt;/span&gt; porque si el map original devolvía una tupla o un string, pueden haber diferencias).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-4442181484416174589?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/4442181484416174589/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2009/04/python-3-iteradores-en-map-y-filter.html#comment-form' title='3 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/4442181484416174589'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/4442181484416174589'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2009/04/python-3-iteradores-en-map-y-filter.html' title='Python 3: iteradores en map y filter'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-517268997268780012</id><published>2009-02-21T02:27:00.001-02:00</published><updated>2009-02-21T02:28:40.593-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>Python 3: codificaciones y más</title><content type='html'>&lt;b&gt;Unicode, bytes y strings&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

Otro punto importante de cambios en Python 3 es como el lenguaje trata el texto, unificando todos los &lt;span style="font-style:italic;"&gt;strings&lt;/span&gt; bajo &lt;a href="http://es.wikipedia.org/wiki/Unicode"&gt;Unicode&lt;/a&gt;. Históricamente, Python tenía un tipo especial de &lt;span style="font-style:italic;"&gt;string&lt;/span&gt; Unicode, con una sintaxis especial, que era hermano del &lt;span style="font-weight: bold;"&gt;str&lt;/span&gt; convencional (dado que ambos heredaban de &lt;span style="font-weight: bold;"&gt;BaseString&lt;/span&gt;).&lt;br /&gt;&lt;br /&gt;

Esta distinción entre &lt;span style="font-weight:bold;"&gt;str&lt;/span&gt; y &lt;span style="font-weight:bold;"&gt;unicode&lt;/span&gt; como tipos básicos se efectuaba en el código fuente con su sintaxis específica:

&lt;pre name="code" class="python"&gt;
str_normal = "Hola Mundo!"
str_unicode = u"Hola Mundo!" # nótese la 'u' antes de la primera comilla
&lt;/pre&gt;

Sin embargo, este paradigma no era muy feliz. Un &lt;span style="font-weight:bold;"&gt;str&lt;/span&gt; convencional carecía de una codificación particular y podía producir una serie de ambigüedades, en particular a la hora de realizar comparaciones entre cadenas de texto Unicode y cadenas de texto de 8 bits convencionales (en muchos casos se pueden producir &lt;span style="font-style:italic;"&gt;Warnings&lt;/span&gt; o incluso excepciones).&lt;br /&gt;&lt;br /&gt;

A partir de Python 3, se utiliza una convención mucho más apropiada. Existen finalmente dos tipos básicos para las finalidades en las que regularmente se utilizan &lt;span style="font-style:italic;"&gt;strings&lt;/span&gt;:&lt;ul&gt;
&lt;li&gt;&lt;span style="font-weight:bold;"&gt;bytes&lt;/span&gt;, que es una secuencia de caracteres de 8 bits sin ninguna codificación particular&lt;/li&gt;
&lt;li&gt;&lt;span style="font-weight:bold;"&gt;texto&lt;/span&gt;, que es una secuencia de caracteres textuales, y como tal representa palabras u otro texto y soporta la noción de &lt;span style="font-style:italic;"&gt;encoding&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;

La sintaxis para éstos es:
&lt;pre name="code" class="python"&gt;
str_bytes = b"Hola Mundo!" # nótese la 'b' antes de la primera comilla
str_texto = "Hola Mundo!"
&lt;/pre&gt;

Esto tiene como consecuencia que al momento de declarar un &lt;span style="font-style:italic;"&gt;string&lt;/span&gt; (y como lo más usual es utilizarlas para almacenar texto), se utilizará una cadena Unicode como corresponde para tener correcto soporte de internacionalización. Para el manejo de datos binarios o &lt;span style="font-style:italic;"&gt;blobs&lt;/span&gt; de caracteres sin interpretación, el tipo &lt;span style="font-weight:bold;"&gt;bytes&lt;/span&gt; está disponible.&lt;br /&gt;&lt;br /&gt;

&lt;b&gt;Codificación del código fuente&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

A su vez, y dado que Python históricamente no soportaba varias codificaciones de fuente, a partir de Python 2.0 (y junto con la introducción del soporte Unicode) se introdujo la posibilidad de declarar la codificación de un archivo mediante un comentario especial. Finalmente, desde 2.5, la aparición de caracteres no-ASCII en un archivo de fuente Python que no tuviera su codificación declarada producía un error.&lt;br /&gt;&lt;br /&gt;

A partir de Python 3, la codificación por defecto es &lt;a href="http://es.wikipedia.org/wiki/UTF-8"&gt;UTF-8&lt;/a&gt; a menos que se indique lo contrario utilizando la declaración que existe desde Python 2. Este valor por defecto es más razonable (ya que UTF-8 es el estándar de facto en la mayoría de los entornos modernos) y evita la necesidad de estar especificando permanentemente la codificación cada vez que debe crearse un nuevo archivo de código fuente.&lt;br /&gt;&lt;br /&gt;

&lt;b&gt;En resumen&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;
Una vez más se reemplazaron algunos casos por defecto para hacerlos más apropiados, rompiendo así la compatibilidad hacia atrás en pos de la sanidad del código fuente. El soporte de cadenas de texto internacionalizadas es ahora transparente y no requiere ningún esfuerzo adicional por parte del programador. Asimismo, la modificación en el &lt;span style="font-style:italic;"&gt;source encoding&lt;/span&gt; ahorra un poco de texto innecesario al comienzo de cada nuevo archivo, manteniendo la esencia minimalista del código fuente de Python.&lt;br /&gt;&lt;br /&gt;

Como siempre, hay &lt;a href="http://docs.python.org/dev/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit"&gt;un resumen completo de los cambios&lt;/a&gt; en el &lt;span style="font-style:italic;"&gt;changelog&lt;/span&gt; de Python 3. Además, hay &lt;a href="http://developers.slashdot.org/comments.pl?sid=1050435&amp;cid=25988007"&gt;una discusión interesante en Slashdot&lt;/a&gt; respecto de este tema, donde además se discuten las diferencias respecto del método adoptado por Ruby para soporte Unicode (es interesante puesto que Ruby tiene sus raíces en países de Asia, que es donde Unicode recibe más críticas por supuestas limitaciones técnicas).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-517268997268780012?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/517268997268780012/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2009/02/python-3-codificaciones-y-mas.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/517268997268780012'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/517268997268780012'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2009/02/python-3-codificaciones-y-mas.html' title='Python 3: codificaciones y más'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-2212668497260552520</id><published>2009-02-20T04:38:00.013-02:00</published><updated>2009-02-20T16:56:14.888-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Educación'/><category scheme='http://www.blogger.com/atom/ns#' term='Ciencia'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><category scheme='http://www.blogger.com/atom/ns#' term='Algoritmos'/><title type='text'>¡Se me pisan los horarios!</title><content type='html'>&lt;span style="font-style:italic;"&gt;Un &lt;span style="font-weight:bold;"&gt;grafo&lt;/span&gt; es esencialmente un dibujito que consta de bolitas y rayitas. Detrás de su inocente aspecto, los grafos ocultan poderosas herramientas matemáticas para resolver problemas muy comunes. Por su naturaleza simple, son una buena muestra de que la matemática no trata solamente de números.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;

El plan de carrera de Ciencias de la Computación es un poco peliagudo. La carrera está prevista para hacerse en 5 años, a los que se suma el Ciclo Básico Común para lograr un total de 6. Este cuatrimestre (el primero de mi cuarto año en la facultad) me toca una situación que hasta ahora vine esquivando por poco.&lt;br /&gt;&lt;br /&gt;

Resulta que si bien el plan indica que en este año deben cursarse 4 materias obligatorias y 2 optativas, mis planes de hacer 3 obligatorias en el primer cuatrimestre se ven frustrados por la superposición de horarios. Las materias se superponen solamente &lt;span style="font-style:italic;"&gt;una hora&lt;/span&gt;, con lo cual parecería ser que alguien intentó (sin éxito) que esto no ocurra.&lt;br /&gt;&lt;br /&gt;

&lt;b&gt;¿Y ahora quién podrá defenderme?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

Con mis planes aparentemente arruinados por la burocracia, me veo en la disyuntiva de elegir qué cursar. Tengo una serie de problemas: hay algunas materias cuyos horarios se superponen, otras que no me interesan, otras que me quiero sacar de encima y al menos una que la tengo que cursar sí o sí porque una materia del segundo cuatrimestre la requiere.&lt;br /&gt;&lt;br /&gt;

Este problema es un caso típico de algo que se resuelve de forma sencilla utilizando grafos. Lo primero que hay que hacer es modelar la información que tengo, que son materias, días y horarios. El grafo que propongo tiene una &lt;span style="font-style:italic;"&gt;bolita&lt;/span&gt; o &lt;span style="font-weight:bold;"&gt;nodo&lt;/span&gt; por cada materia, y una &lt;span style="font-style:italic;"&gt;rayita&lt;/span&gt; o &lt;span style="font-weight:bold;"&gt;eje&lt;/span&gt; une a dos materias cuando los horarios de éstas se superponen.&lt;br /&gt;&lt;br /&gt;

Para construir el grafo voy a usar un pequeño pedazo de código que a partir de los horarios decide si dos materias se superponen o no. A continuación este programa produce un archivo &lt;span style="font-weight:bold;"&gt;dot&lt;/span&gt;, que utilizando &lt;a href="http://www.graphviz.org/"&gt;GraphViz&lt;/a&gt; puedo representar como imagen. Si me limito a las materias que puedo cursar en este momento (obviando las que ya hice, y las que todavía no puedo hacer), me queda algo así:&lt;br /&gt;&lt;br /&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_LQAjBtuUT0w/SZ5eBGUerNI/AAAAAAAAAJk/GtKfe-uj-08/s1600-h/materias_todas.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 176px;" src="http://3.bp.blogspot.com/_LQAjBtuUT0w/SZ5eBGUerNI/AAAAAAAAAJk/GtKfe-uj-08/s400/materias_todas.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5304780784058412242" /&gt;&lt;/a&gt;
&lt;br /&gt;&lt;br /&gt;


Puse de color verde las materias obligatorias, y la única materia que tengo que cursar obligadamente es &lt;span style="font-weight:bold;"&gt;Paradigmas de Lenguajes de Programación&lt;/span&gt;, ya que &lt;span style="font-weight:bold;"&gt;Ingeniería de Software II&lt;/span&gt; depende de ella.&lt;br /&gt;&lt;br /&gt;

El dibujo tiene una complejidad muy alta, ya que hay muchas materias. Si miran con cuidado verán que hay una línea que une a &lt;span style="font-weight:bold;"&gt;Bases de Datos&lt;/span&gt; con &lt;span style="font-weight:bold;"&gt;Teoría de Lenguajes&lt;/span&gt;, ambas de color verde. Por lo tanto, estas dos materias tienen sus horarios superpuestos y no puedo cursar ambas a la vez.&lt;br /&gt;&lt;br /&gt;

Una cualidad interesante es que el dibujo de arriba contiene &lt;span style="font-weight:bold;"&gt;toda la información necesaria&lt;/span&gt; para resolver el problema: los días de la semana, las horas, la aritmética, los feriados y la mar en coche desaparecieron de la vista. Esta &lt;span style="font-weight:bold;"&gt;capacidad de abstracción&lt;/span&gt; es un rasgo fundamental del uso de grafos.&lt;br /&gt;&lt;br /&gt;

&lt;b&gt;¿Qué estamos buscando?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

Necesito encontrar un conjunto de materias (bolitas en el grafo) que no tengan líneas entre sí. Esto se llama, en términos de grafos, un &lt;a href="http://es.wikipedia.org/wiki/Conjunto_independiente"&gt;conjunto independiente&lt;/a&gt;. Existen muchos conjuntos independientes, y uno muy sencillo es simplemente tomar una materia sola. Claro que si curso una sola materia por cuatrimestre no me voy a recibir muy rápido, así que voy a intentar hacer algo mejor.&lt;br /&gt;&lt;br /&gt;

Me gustaría que el conjunto de materias elegidas tenga las siguientes características:
&lt;ul&gt;
&lt;li&gt;Tiene que ser &lt;span style="font-weight:bold;"&gt;independiente&lt;/span&gt; (sin rayitas entre las bolitas), ya que de otro modo dos materias tendrían sus horarios superpuestos.&lt;/li&gt;
&lt;li&gt;Tiene que contener a la materia &lt;span style="font-weight:bold;"&gt;Paradigmas de Lenguajes de Programación&lt;/span&gt; (porque de otro modo me atrasa el cuatrimestre que viene)&lt;/li&gt;
&lt;li&gt;Tiene que contener al menos otra materia obligatoria, sea &lt;span style="font-weight:bold;"&gt;Teoría de Lenguajes&lt;/span&gt; o &lt;span style="font-weight:bold;"&gt;Bases de Datos&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Y por último, tiene que tener al menos 3 materias&lt;/li&gt;
&lt;/ul&gt;

Si escribo el código de esta restricción en Python, me queda lo siguiente:
&lt;pre name="code" class="python"&gt;
def esConveniente(s):
    return esIndependiente(s) and \
           "Paradigmas de Lenguajes de Programación" in s and \
           ("Teoría de Lenguajes" in s or "Bases de Datos" in s) and \
           len(s) &gt;= 3
&lt;/pre&gt;

Para terminar solo es cuestión de dejar que la computadora haga su trabajo, y me diga qué me conviene cursar el cuatrimestre que viene. Tras una (instantánea) corrida, resulta que solo hay 7 combinaciones posibles que reúnen los criterios que describí arriba:
&lt;pre name="code" class="css"&gt;
gomo@ciabatta:~$ python inscripcion.py
Formas válidas de cursar materias: 233
Formas de cursar 'convenientes': 7

Paradigmas de Lenguajes de Programación, Lógicas Modales, Bases de Datos
Paradigmas de Lenguajes de Programación, Computación Gráfica, Bases de Datos
Visualización de la Información, Paradigmas de Lenguajes de Programación, Teoría de Lenguajes
Visualización de la Información, Paradigmas de Lenguajes de Programación, Lógicas Modales, Teoría de Lenguajes
Paradigmas de Lenguajes de Programación, Lógicas Modales, Teoría de Lenguajes
Visualización de la Información, Paradigmas de Lenguajes de Programación, Computación Gráfica, Teoría de Lenguajes
Paradigmas de Lenguajes de Programación, Computación Gráfica, Teoría de Lenguajes
&lt;/pre&gt;

Solo me resta elegir la opción que más me divierta, y &lt;span style="font-style:italic;"&gt;voilà!&lt;/span&gt; Pueden ver el &lt;a href="http://blog.gomox.com.ar/2009/02/se-me-pisan-los-horarios.html#codigopython"&gt;código completo en Python&lt;/a&gt; si siguen leyendo este post.&lt;br /&gt;&lt;br /&gt;

&lt;p align="right"&gt;&lt;a href="http://blog.gomox.com.ar/2009/02/se-me-pisan-los-horarios.html#codigopython"&gt;Ver el código completo &gt;&gt;&lt;/a&gt;&lt;/p&gt;


&lt;span class="fullpost"&gt;
&lt;a name="codigopython"&gt;&lt;/a&gt;
&lt;b&gt;Código completo en Python&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;
&lt;pre name="code" class="Python"&gt;
#!/usr/bin/python
# *-* encoding: utf-8 *-*

materias = {'Estadística y Data Mining':               (('Ma',1800,2100), ('Mie',1800,2100)                   ),
            'Inteligencia Artificial':                 (('Lu',1800,2200), ('Ma', 1900,2200)                   ),
            'Metaheurísticas':                         (('Ma',1800,2100),                                     ),
            'Redes Neuronales':                        (('Ma',1800,2100), ('Jue',1800,2100)                   ),
            'Seguridad de la Información':             (('Lu',1800,2200), ('Mie',1800,2200)                   ),
            'Teoría de Juegos':                        (('Lu',1800,2100), ('Mie',1800,2000)                   ),
            'Paradigmas de Lenguajes de Programación': (('Ma',1700,2000), ('Jue',1700,1930)                   ),
            'Bases de Datos':                          (('Mie',1830,2200),('Vie',1700,2230)                   ),
            'Teoría de Lenguajes':                     (('Lu',1800,2100), ('Mie',1700,1930), ('Jue',1930,2200)),
            'Lógicas Modales':                         (('Lu',1400,1800),                                     ),
            'Computación Gráfica':                     (('Lu',1400,1700), ('Vie',1400,1700),                  ),
            'Procesamiento de Lenguaje Natural':       (('Mie',1900,2200),                                    ),
            'Grafos y Tratabilidad Computacional':     (('Ma',1800,2200), ('Jue',1800,2200),                  ),
            'Programación Orientada a Objetos':        (('Lu',1900,2200), ('Mie',1900,2200),                  ),
            'Simulación de Eventos Discretos':         (('Lu',1800,2200), ('Mie',1800,2200),                  ),
            'Visualización de la Información':         (('Vie',1900,2200),                                    ),
}


def sePisan(h1, h2):
    '''Determina si dos horarios se superponen'''
    for c1 in h1:
        for c2 in h2:
            if c1[0] == c2[0]:

                # cual empieza antes?
                if c1[1] &lt;= c2[1]:
                    prim = c1
                    seg = c2
                else:
                    prim = c2
                    seg = c1

                # si la primera termina despues de 
                # que empiece la otra, se pisan
                if prim[2] &gt; seg[1]:
                    return True

    return False


def getDot():
    '''Devuelve un source DOT para el grafo de materias'''
    buf = []
    
    buf.append('graph grafo {')

    # genero IDs para los nodos
    i = 0
    ids = {}
    mats = {}
    for m in materias:
        ids[m] = i
        mats[i] = m
        i += 1

    for i in ids.values():
        buf.append('node%s [label="%s"]' % (i, mats[i]))
 
    ejes = []

    for m1 in materias:
        for m2 in materias:
            if m1 != m2 and sePisan(materias[m1], materias[m2]):
                if not (ids[m2], ids[m1]) in ejes:
                    buf.append('node%s -- node%s' % (ids[m1], ids[m2]))
                    ejes.append((ids[m1], ids[m2]))

    buf.append('}')

    return '\n'.join(buf)


def subconjuntos(s):
    '''Dada una secuencia s devuelve todos los subconjuntos de sus elementos'''
    if s == []:
        return []
    else:
        e = s[0]
        resto = s[1:]

        sin = subconjuntos(resto)
        con = subconjuntos(resto)

        for sub in con:
            sub.append(e)
        con.append([e])

        return sin + con
            

def esIndependiente(s):
    '''Determina si el conjunto de materias es independiente'''
    for m1 in s:
        for m2 in s:
            if m1 != m2:
                if sePisan(materias[m1], materias[m2]):
                    return False
    return True


def esConveniente(s):
    return esIndependiente(s) and \
           "Paradigmas de Lenguajes de Programación" in s and \
           ("Teoría de Lenguajes" in s or "Bases de Datos" in s) and \
           len(s) &gt;= 3
           

if __name__ == '__main__':
    s = subconjuntos(materias.keys())
    
    print "Formas válidas de cursar materias: %s" % len([x for x in s if esIndependiente(x)])
    print "Formas de cursar 'convenientes': %s" % len([x for x in s if esConveniente(x)])
    print

    for c in [x for x in s if esConveniente(x)]:
        print ', '.join(c)
&lt;/pre&gt;
&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-2212668497260552520?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/2212668497260552520/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2009/02/se-me-pisan-los-horarios.html#comment-form' title='4 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/2212668497260552520'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/2212668497260552520'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2009/02/se-me-pisan-los-horarios.html' title='¡Se me pisan los horarios!'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_LQAjBtuUT0w/SZ5eBGUerNI/AAAAAAAAAJk/GtKfe-uj-08/s72-c/materias_todas.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-1196502410733630984</id><published>2008-12-27T03:14:00.007-02:00</published><updated>2008-12-27T15:11:43.124-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>Python 3: es excepcional</title><content type='html'>El manejo de excepciones en Python 3 recibió también un cambio menor, pero que tiene por objeto evitar una fuente muy común de &lt;span style="font-style:italic;"&gt;bugs&lt;/span&gt; en el código producidos por el descuido del programador.&lt;br /&gt;&lt;br /&gt;

&lt;b&gt;Lanzando&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

En Python 2.x, la sintaxis para lanzar una excepción era usualmente la siguiente:
&lt;pre name="code" class="python"&gt;
raise ValueError, "Error de valor!" # o bien
raise ValueError("Error de valor!")
&lt;/pre&gt;

Sin embargo, existe una cantidad de casos diferentes y poco conocidos que se detallan en &lt;a href="http://docs.python.org/reference/simple_stmts.html#raise"&gt;un largo párrafo del manual de referencia&lt;/a&gt;. Está claro que esto es poco deseable, puesto que introduce complicaciones en el lenguaje con el objeto de facilitar usos poco comunes.&lt;br /&gt;&lt;br /&gt;

Volviendo al caso "usual" citado antes, si bien ambos casos eran equivalentes, se decidió eliminar el primero puesto que la sintaxis resulta un poco confusa. Al leer, no se entiende fácilmente que en realidad el argumento que viene después de la coma en el &lt;span style="font-style:italic;"&gt;raise&lt;/span&gt; se pasa al constructor de la excepción. Por lo tanto, de ahora en más habrá que restringirse a la segunda opción:
&lt;pre name="code" class="python"&gt;
raise ValueError("Error de valor!")
&lt;/pre&gt;

&lt;b&gt;Atajando&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

Del mismo modo, para atrapar una excepción, se utilizaba el siguiente bloque:
&lt;pre name="code" class="python"&gt;
try:
    ... # codigo que lanza una excepción
except ValueError:
    ... # codigo que maneja la excepción
&lt;/pre&gt;

Ahora, si se desea observar el contenido de la excepción para manipularlo de alguna manera, se utiliza el siguiente bloque &lt;span style="font-style:italic;"&gt;except&lt;/span&gt;:
&lt;pre name="code" class="python"&gt;
except ValueError, e:
    print e # "e" referencia la excepción
&lt;/pre&gt;

La dificultad en este caso proviene del uso de la variable &lt;span style="font-style:italic;"&gt;e&lt;/span&gt;, que puede fácilmente confundirse en el contexto siguiente:

&lt;pre name="code" class="python"&gt;
try:
    ... # codigo que lanza una excepción
except (ValueError, TypeError):
    print e
&lt;/pre&gt;

Como se observa, la idea del código es atrapar una excepción de cualquiera de los dos tipos especificados. Sin embargo, muchas veces en las que se desea hacer esto, se termina escribiendo:

&lt;pre name="code" class="python"&gt;
except ValueError, TypeError:
    ... # TypeError es ahora una ref. a la excepción!
&lt;/pre&gt;

Está claro que el comportamiento no es el esperado: el bloque citado no atrapa las excepciones de tipo &lt;span style="font-style:italic;"&gt;TypeError&lt;/span&gt;, sino que únicamente presta atención a los &lt;span style="font-style:italic;"&gt;ValueError&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;

Para evitar esta situación, se introduce a partir de Python 3 una nueva sintaxis que reutiliza el &lt;span style="font-style:italic;"&gt;keyword&lt;/span&gt; &lt;span style="font-weight:bold;"&gt;as&lt;/span&gt;, ya utilizado antes con las inclusiones de módulos externos para establecer un nombre diferente al módulo importado. La nueva sintaxis se ve así:
&lt;pre name="code" class="python"&gt;
except (ValueError, TypeError) as e:
    ... # "e" es lo que uno espera
&lt;/pre&gt;

Gracias a estos cambios se eliminan varias ambiguedades y semánticas complicadas en pos de la simplicidad. Con cambios como estos se hace que el código sea más legible para quienes son nuevos con el lenguaje, y se evitan &lt;span style="font-style:italic;"&gt;bugs&lt;/span&gt; difíciles de detectar en el manejo de excepciones.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-1196502410733630984?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/1196502410733630984/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-es-excepcional.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/1196502410733630984'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/1196502410733630984'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-es-excepcional.html' title='Python 3: es excepcional'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-7457047370828070862</id><published>2008-12-10T21:49:00.010-02:00</published><updated>2008-12-14T03:41:10.693-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>Python 3: es de colección</title><content type='html'>Otro de los &lt;span style="font-style:italic;"&gt;sets&lt;/span&gt; de cambios incluídos en la nueva versión de Python afecta a las colecciones. Entran en esta categoría los diccionarios, conjuntos y secuencias. &lt;br /&gt;&lt;br /&gt;

&lt;b&gt;Sets y Frozensets son ahora &lt;span style="font-style:italic;"&gt;builtin&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;
En Python 3, &lt;b&gt;set&lt;/b&gt; es un &lt;span style="font-style:italic;"&gt;builtin&lt;/span&gt; (ya no es necesario importarlo del módulo &lt;span style="font-style:italic;"&gt;sets&lt;/span&gt;). &lt;span style="font-style:italic;"&gt;Set&lt;/span&gt; es una estructura de datos que representa un conjunto de elementos sin un orden específico. La implementación utiliza una &lt;a href="http://en.wikipedia.org/wiki/Hash_table"&gt;tabla de hash&lt;/a&gt;, al igual que los diccionarios. 
&lt;br /&gt;&lt;br /&gt;
El tipo &lt;span style="font-style:italic;"&gt;set&lt;/span&gt; es &lt;a href="http://docs.python.org/library/stdtypes.html#typesseq-mutable"&gt;mutable&lt;/a&gt; (una misma instancia puede recibir modificaciones), al igual que las listas. Como la listas en Python tienen un equivalente inmutable (las tuplas), los conjuntos lo tienen también en el tipo &lt;span style="font-weight:bold;"&gt;frozenset&lt;/span&gt;. Cabe aclarar que estos tipos ya existían en Python 2.x, pero en Python 3 fueron promovidos a la librería estándar y ya no es necesario importar ningún módulo para utilizarlos.

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; s = set()          # creo un set vacío
&gt;&gt;&gt; for i in [1,2,3]:
...     s.add(i)
...
&gt;&gt;&gt; 3 in s
True
&gt;&gt;&gt; 4 in s
False
&gt;&gt;&gt; s.remove(2)
&gt;&gt;&gt; s
{1, 3}
&gt;&gt;&gt; fs = frozenset()   # creo un frozenset vacío
&gt;&gt;&gt; fs2 = fs.union(s)  # lo uno con el set {1, 3}
&gt;&gt;&gt; fs2
frozenset({1, 3})      # obtengo un nuevo frozenset
&gt;&gt;&gt; fs
frozenset()            # fs se mantiene intacto
&lt;/pre&gt;
Hay más información en la documentación oficial de &lt;a href="http://docs.python.org/3.0/library/stdtypes.html#set-types-set-frozenset"&gt;&lt;span style="font-style:italic;"&gt;set&lt;/span&gt; y &lt;span style="font-style:italic;"&gt;frozenset&lt;/span&gt;&lt;/a&gt;.

&lt;br /&gt;&lt;br /&gt;
&lt;b&gt;Nuevos literales y expresiones por comprensión&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

&lt;i&gt;Set&lt;/i&gt; viene acompañado de un literal para su creación: no es necesario construirlo agregando tediosamente elementos a un set vacío como hice arriba. Sin embargo, como la sintaxis es similar a la del literal de diccionarios, hay que tener cuidado al crear uno vacío! El constructor de &lt;span style="font-style:italic;"&gt;set&lt;/span&gt; vacío es &lt;span style="font-weight:bold;"&gt;set()&lt;/span&gt;, ya que &lt;span style="font-weight:bold;"&gt;{}&lt;/span&gt; está reservado para los diccionarios.

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; s = {1, 2, 3, 4}           # creo un conjunto
&gt;&gt;&gt; d = {1: 'uno', 2: 'dos'}   # creo un diccionario
&gt;&gt;&gt; s = {}                     # ojo! crea un diccionario
&gt;&gt;&gt; type(s).__name__
'dict'
&gt;&gt;&gt; s = set()
&lt;/pre&gt;

Pero todavía hay más! Python 3 finalmente incorpora diccionarios por comprensión, y ya que estamos en la volada, conjuntos por comprensión. La sintaxis es exactamente la que uno esperaría, muy similar a la de listas por comprensión. Es curioso mencionar que la propuesta de diccionarios por comprensión proviene del &lt;a href="http://www.python.org/dev/peps/pep-0274/"&gt;PEP 274&lt;/a&gt; (de 2001!). En 2008, Python 3 incorpora esta deseable funcionalidad: se terminaron los días de usar una lista porque es más cómodo!

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; numeros = [1,2,3,4,5,6,7,8,9,10]
&gt;&gt;&gt; def esPar(x):
...     return x % 2 == 0
...
&gt;&gt;&gt; pares = {x for x in numeros if esPar(x)}
&gt;&gt;&gt; pares
{8, 2, 4, 10, 6}
&gt;&gt;&gt; cuad_impares = {x: x*x for x in numeros if not esPar(x)}
&gt;&gt;&gt; cuad_impares
{1: 1, 3: 9, 9: 81, 5: 25, 7: 49}
&lt;/pre&gt;

&lt;br /&gt;
&lt;b&gt;Novedades en diccionarios&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;

En primer lugar hay que notar que el método &lt;span style="font-weight:bold;"&gt;has_key()&lt;/span&gt; de diccionarios fue eliminado. Este método era redundante; estas dos expresiones eran equivalentes en Python 2.x, introduciendo una ambigüedad innecesaria:
&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; dicc.has_key('clave')
True
&gt;&gt;&gt; 'clave' in dicc
True
&lt;/pre&gt;

En Python 2.x los métodos &lt;span style="font-weight:bold;"&gt;keys()&lt;/span&gt;, &lt;span style="font-weight:bold;"&gt;values()&lt;/span&gt; y &lt;span style="font-weight:bold;"&gt;items()&lt;/span&gt; de un diccionario devolvían listas contiendo los datos del diccionario (sus claves, sus valores, y los pares clave-valor respectivamente). Asimismo, existían funciones análogas &lt;span style="font-style:italic;"&gt;iterkeys&lt;/span&gt;, &lt;span style="font-style:italic;"&gt;iteritems&lt;/span&gt; y &lt;span style="font-style:italic;"&gt;itervalues&lt;/span&gt;. Estas últimas devolvían iteradores sobre las mismas secuencias que las anteriores.&lt;br /&gt;&lt;br /&gt;

Python 3 introduce el concepto de &lt;span style="font-weight:bold;"&gt;vista&lt;/span&gt;. Una vista es un objeto intermedio entre el diccionario y los elementos devueltos por los métodos descritos en el párrafo anterior. Los mismos objetos que usábamos en Python 2.x pueden obtenerse ahora a partir de la vista en lugar del diccionario propiamente dicho. Veamos como:

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; d = {1:'uno', 2:'dos', 3:'tres'}    # armo un diccionario

# Python 2.x
&gt;&gt;&gt; it = d.iterkeys()  # obtengo un iterador sobre las claves
&gt;&gt;&gt; keys = d.keys()    # obtengo una lista de claves
&gt;&gt;&gt; keys
[1, 2, 3]

# Python 3
&gt;&gt;&gt; v = d.keys()    # obtengo la "vista" del diccionario d
&gt;&gt;&gt; type(v).__name__
'dict_keys'
&gt;&gt;&gt; it = iter(v)    # construyo un iterador a partir de la vista (no del dict!)
&gt;&gt;&gt; keys = list(v)  # construyo una lista a partir de la vista
&lt;/pre&gt;

La utilidad de la &lt;span style="font-style:italic;"&gt;vista&lt;/span&gt; no es aparente aún: sin embargo, la &lt;span style="font-style:italic;"&gt;vista&lt;/span&gt; introduce una nueva posibilidad. Es claro que la lista obtenida a partir de &lt;span style="font-style:italic;"&gt;keys()&lt;/span&gt; en Python 2.x está desconectada del diccionario: toda modificación al diccionario posterior a la creación de la lista no se ve reflejada en la misma. Los iteradores se comportan de forma similar: si se modifica el diccionario durante la iteración, se produce una excepción.

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; d = {1:'uno', 2:'dos', 3:'tres'}
&gt;&gt;&gt; it = d.iterkeys()
&gt;&gt;&gt; del(d[2])
&gt;&gt;&gt; for key in it:
...     print key
... 
Traceback (most recent call last):
RuntimeError: dictionary changed size during iteration
&lt;/pre&gt;

Lo curioso de la &lt;span style="font-style:italic;"&gt;vista&lt;/span&gt; es que &lt;span style="font-weight:bold;"&gt;ella también es iterable!&lt;/span&gt; (además del iterador que devuelve al aplicarle &lt;span style="font-style:italic;"&gt;iter()&lt;/span&gt;). Y esta iteración si soporta modificaciones durante su ejecución. De modo que en Python 3, podríamos hacer esto:

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; v = d.keys()
&gt;&gt;&gt; del(d[2])
&gt;&gt;&gt; for key in v:
...     print(key)
...
1
3
&lt;/pre&gt;

Si obtenemos a partir de la vista el iterador "tipo Python 2.x", tendremos el mismo comportamiento que antes (se emite una excepción al modificar el tamaño del diccionario sobre el que se está iterando).&lt;br /&gt;&lt;br /&gt;

En resumen, Python 3 incorpora el tipo &lt;span style="font-style:italic;"&gt;set&lt;/span&gt; a la librería estándar, y notaciones literales y por comprensión tanto para conjuntos como para diccionarios. Estas facilidades hacen más ameno el uso de los tipos de datos correctos para cada tarea, en casos en los que muchas veces se utilizaba una lista por comodidad de notación. Por último, las vistas introducen funcionalidad adicional a los diccionarios, permitiendo modificarlos mientras se los recorre además de mantener las operaciones existentes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-7457047370828070862?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/7457047370828070862/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-es-de-coleccin.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/7457047370828070862'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/7457047370828070862'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-es-de-coleccin.html' title='Python 3: es de colección'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-8818303389218505514</id><published>2008-12-07T21:55:00.008-02:00</published><updated>2008-12-10T21:36:15.498-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><category scheme='http://www.blogger.com/atom/ns#' term='Algoritmos'/><title type='text'>Python 3: diferencias de orden</title><content type='html'>Otro de los cambios introducidos en Python 3 es la abolición del argumento &lt;span style="font-style:italic;"&gt;cmp&lt;/span&gt; en la operación de ordenamiento de secuencias. Este argumento permitía especificar una función de comparación para utilizar como criterio de ordenamiento, lo cual era bastante cómodo en muchas situaciones. La función debía seguir el modelo de &lt;span style="font-style:italic;"&gt;cmp&lt;/span&gt; (la &lt;span style="font-style:italic;"&gt;builtin&lt;/span&gt;). 

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; cmp(1,2)
-1
&gt;&gt;&gt; cmp(2,1)
1
&gt;&gt;&gt; cmp(1,1)
0
&lt;/pre&gt;

Esto se usa mucho para ordenar según el valor de alguna clave particular en el caso de objetos personalizados.

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; def cmp_edad(p1, p2):
...     return cmp(p1.edad, p2.edad)
... 
&gt;&gt;&gt; class Persona:
...     def __init__(self, nombre, edad):
...             self.nombre = nombre
...             self.edad = edad
...     def __repr__(self):
...             return "%s (%s años)" % (self.nombre, self.edad)
... 
&gt;&gt;&gt; personas = [Persona('Juan', 15), Persona('Lautaro', 3), Persona('Ifigenio', 73)]
&gt;&gt;&gt; personas
[Juan (15 años), Lautaro (3 años), Ifigenio (73 años)]
&gt;&gt;&gt; personas.sort(cmp=cmp_edad)
&gt;&gt;&gt; personas
[Lautaro (3 años), Juan (15 años), Ifigenio (73 años)]
&lt;/pre&gt;

Python 3 elimina esta posibilidad para forzar en cambio la utilización del argumento &lt;span style="font-style:italic;"&gt;key&lt;/span&gt;. La diferencia es sutil: el valor de &lt;span style="font-style:italic;"&gt;key&lt;/span&gt; es una función de un único argumento que devuelve una clave que luego se utilizar para ordenar. Esta técnica se llama &lt;b&gt;DSU&lt;/b&gt; (por &lt;span style="font-style:italic;"&gt;Decorate, Sort, Undecorate&lt;/span&gt;) o &lt;a href="http://en.wikipedia.org/wiki/Schwartzian_transform"&gt;transformación de Schwartz&lt;/a&gt;. Es realmente meritorio lo de este tipo Schwartz, porque logró ponerle su nombre a algo muy simple en 1994, uniéndose a otros personajes avivados similares (como Bolzano!).&lt;br /&gt;&lt;br /&gt;

La ventaja está en que la implementación con &lt;span style="font-style:italic;"&gt;cmp&lt;/span&gt; realiza &lt;span style="font-style:italic;"&gt;O(n log n)&lt;/span&gt; llamadas a la función de comparación, mientras que usando DSU la clave se evalúa una única vez para cada elemento de la secuencia, lo cual es &lt;span style="font-style:italic;"&gt;O(n)&lt;/span&gt;. Si la función de cálculo de la clave es costosa, esto puede hacer una diferencia apreciable en el rendimiento del algoritmo.&lt;br /&gt;&lt;br /&gt;

Veamos como quedaría el código anterior:

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; def key_edad(p):
...     return p.edad
... 
&gt;&gt;&gt; personas.sort(key=key_edad)
&gt;&gt;&gt; personas
[Lautaro (3 años), Juan (15 años), Ifigenio (73 años)]
&lt;/pre&gt;

Hay una cuestión más sutil respecto de porqué se decidió eliminar &lt;span style="font-style:italic;"&gt;cmp&lt;/span&gt;. No solo la función se ejecuta más veces, sino que además el uso corriente utiliza una función anónima (&lt;span style="font-style:italic;"&gt;lambda&lt;/span&gt;) para hacerlo. Algo del estilo de:

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; personas.sort(cmp=lambda x,y: cmp(x.edad, y.edad))
&lt;/pre&gt;

Esto tiene el problema añadido de que, si bien la rutina de &lt;span style="font-style:italic;"&gt;sorting&lt;/span&gt; está implementada en C, la función de comparación no. Esto implica que cada vez que hace falta comparar dos elementos, hay además que hacer un &lt;span style="font-weight:bold;"&gt;cambio de contexto&lt;/span&gt; a Python con el costo adicional que esto representa. El &lt;span style="font-style:italic;"&gt;idiom&lt;/span&gt; que utiliza &lt;span style="font-style:italic;"&gt;key&lt;/span&gt; viene con un aditamento: el módulo &lt;span style="font-weight:bold;"&gt;operator&lt;/span&gt; incluye, a partir de 2.4, las funciones &lt;span style="font-style:italic;"&gt;attrgetter&lt;/span&gt; y &lt;span style="font-style:italic;"&gt;itemgetter&lt;/span&gt;. Estas funciones, al estar implementadas en C, son mucho más rápidas que sus contrapartidas en Python. Veamos como quedaría lo anterior utilizando la "forma Python 3":

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; personas.sort(key=operator.attrgetter('edad'))
&lt;/pre&gt;

De esta forma, todo el ciclo de ordenamiento se ejecuta en C y no se repiten cálculos innecesarios, logrando así ordenar de forma muy eficiente. Hay mucha más información sobre ordenamiento de secuencias en Python (pre-3.0) en el &lt;a href="http://wiki.python.org/moin/HowTo/Sorting"&gt;wiki oficial&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;

En resumen, lo que se hizo fue eliminar una forma popular de ordenar secuencias. Las razones son técnicas además de filosóficas: la variante &lt;span style="font-style:italic;"&gt;cmp&lt;/span&gt; no solo introduce una decisión a la hora de ordenar (&lt;span style="font-style:italic;"&gt;¿qué uso? ¿cmp o key?&lt;/span&gt;), sino que una de las opciones produce código naturalmente menos eficiente que la otra.&lt;br /&gt;&lt;br /&gt;

&lt;a name="benchmarksort"&gt;&lt;/a&gt;
&lt;b&gt;Benchmark&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;
A pedido del público, una comparación entre la alternativa típica de Python 2 usando &lt;span style="font-style:italic;"&gt;cmp&lt;/span&gt; con &lt;span style="font-style:italic;"&gt;lambda&lt;/span&gt; contra la versión de Python 3.

En el código de prueba ordeno una larga secuencia de objetos en base al valor de su atributo (entero) &lt;span style="font-style:italic;"&gt;i&lt;/span&gt;. El código que resulta es el siguiente:
&lt;pre name="code" class="python"&gt;
class Pepe:
    def __init__(self, i):
        self.i = i

nums = range(1000000)
random.shuffle(nums)
pepos = map(Pepe, nums)

p1 = pepos[:]
p2 = pepos[:]

t = Timer()

# Version cmp
t.start()
p1.sort(lambda x,y: cmp(x.i, y.i))
print "Version lambda: ",
t.stop()

# Version key
t.start()
p2.sort(key=operator.attrgetter('i'))
print "Version key:    ",
t.stop()
&lt;/pre&gt;

La diferencia es abrumadora, no hay más que ver la salida del programa para una secuencia de 1 millón de elementos:
&lt;pre&gt;
Version lambda:  21.29 segundos
Version key:     3.78 segundos
&lt;/pre&gt;

La versión &lt;span style="font-style:italic;"&gt;key&lt;/span&gt; es unas 6 veces más rápida. Solo porque es divertido, les dejo un gráfico que compara la performance de los dos métodos (tiempo en segundos vs. cantidad de objetos a ordenar).&lt;br /&gt;&lt;br /&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_LQAjBtuUT0w/SUBP4pdnznI/AAAAAAAAAI8/AxjGKj0JQG8/s1600-h/grafico_sort.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 310px;" src="http://4.bp.blogspot.com/_LQAjBtuUT0w/SUBP4pdnznI/AAAAAAAAAI8/AxjGKj0JQG8/s400/grafico_sort.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5278306597899587186" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-8818303389218505514?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/8818303389218505514/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-diferencias-de-orden.html#comment-form' title='9 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/8818303389218505514'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/8818303389218505514'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-diferencias-de-orden.html' title='Python 3: diferencias de orden'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_LQAjBtuUT0w/SUBP4pdnznI/AAAAAAAAAI8/AxjGKj0JQG8/s72-c/grafico_sort.png' height='72' width='72'/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-3610632229762985828</id><published>2008-12-06T23:32:00.018-02:00</published><updated>2008-12-07T20:39:41.251-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>Python 3: pequeños cambios misceláneos</title><content type='html'>&lt;span style="font-weight: bold;"&gt;El operador &lt;&gt;&lt;/span&gt;&lt;br/&gt;
No existe más, debería usarse != en su lugar (más de una manera de hacer lo mismo!)&lt;br /&gt;&lt;br /&gt;

&lt;span style="font-weight: bold;"&gt;El operador !=&lt;/span&gt;&lt;br/&gt;
Por defecto devolverá lo opuesto de ==, a menos que este último lance &lt;span style="font-style:italic;"&gt;NotImplementedError&lt;/span&gt;. Este comportamiento es esperable y por tanto su ausencia era una fuente de bugs. En su momento reporté &lt;a href="http://code.djangoproject.com/ticket/982"&gt;un bug en el ORM de Django&lt;/a&gt; por esta precisa razón.&lt;br /&gt;&lt;br /&gt;

&lt;span style="font-weight: bold;"&gt;print es una función&lt;/span&gt;&lt;br /&gt;
Anteriormente, &lt;span style="font-style: italic;"&gt;print&lt;/span&gt; era un &lt;span style="font-style: italic;"&gt;statement&lt;/span&gt;: una palabra reservada del lenguaje con tratamiento especial (como es el caso de &lt;span style="font-style: italic;"&gt;if&lt;/span&gt; o &lt;span style="font-style: italic;"&gt;class&lt;/span&gt;). En Python 3 &lt;span style="font-style: italic;"&gt;print&lt;/span&gt; pasa a ser una función convencional. Esto es consistente con el hecho de que no hay ninguna razón para que no lo sea! Si bien va a ser necesario agregar paréntesis que antes no hacían falta, también va a ser posible utilizar &lt;span style="font-style: italic;"&gt;print&lt;/span&gt; como antes era imposible.&lt;br /&gt;

&lt;pre name="code" class="python"&gt;
# Python 2.x
print "hola mundo"    # imprime y va a la línea
print "hola mundo",   # imprime y no va a la línea
map(print, [1,2,3,4]) # error!

# Python 3.x
print("hola mundo")
print("hola mundo", end="")
map(print, [1,2,3,4]) # ok!
&lt;/pre&gt;

La mayoría de la sintaxis especial (y muchas veces extraña) de &lt;span style="font-style:italic;"&gt;print&lt;/span&gt; ahora se reemplaza con &lt;span style="font-style:italic;"&gt;keywords&lt;/span&gt; para la función. Hay más información en la &lt;a href="http://docs.python.org/3.0/whatsnew/3.0.html#print-is-a-function"&gt;página de cambios en Python 3&lt;/a&gt; y en la &lt;a href="http://docs.python.org/3.0/library/functions.html#print"&gt;documentación&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-weight: bold;"&gt;La división anda bien&lt;/span&gt;&lt;br/&gt;
El histórico problema de que la división de enteros da entero en Python se eliminó de una buena vez. Esto era un problema clásico al comenzar a programar, y forzaba a &lt;span style="font-style:italic;"&gt;castear&lt;/span&gt; a &lt;span style="font-style:italic;"&gt;float&lt;/span&gt; en muchas ocasiones para asegurarse de que al menos uno de los dos argumentos de la división sea &lt;span style="font-style:italic;"&gt;float&lt;/span&gt; (en ese caso la división puede devolver &lt;span style="font-style:italic;"&gt;float&lt;/span&gt;).

&lt;pre name="code" class="python"&gt;
uno = 1
dos = 2

# Python 2.x
a = uno / dos
b = uno // dos
c = float(uno) / 2
print a # imprime 1
print b # imprime 1
print c # imprime 0.5

# Python 3.x
a = uno / dos
b = uno // dos
c = float(uno) / 2
print(a) # imprime 0.5 :D
print(b) # imprime 1
print(c) # imprime 0.5
&lt;/pre&gt;

&lt;span style="font-weight: bold;"&gt;Unificación de Int y Long&lt;/span&gt;&lt;br/&gt;
En Python 2 existían las dos clases de enteros, y si bien interoperaban entre ellas perfectamente, al imprimir un entero largo por pantalla se imprimía una curiosa &lt;span style="font-weight:bold;"&gt;L&lt;/span&gt; al final.

&lt;pre name="code" class="python"&gt;
&gt;&gt;&gt; import sys
&gt;&gt;&gt; sys.maxint
2147483647
&gt;&gt;&gt; sys.maxint + 1
2147483648L # &lt;- "L" al final
&gt;&gt;&gt; type(sys.maxint).__name__
'int'
&gt;&gt;&gt; type(sys.maxint+1).__name__
'long'
&lt;/pre&gt;

Como la constante &lt;span style="font-style:italic;"&gt;sys.maxint&lt;/span&gt; quedó por lo tanto sin uso, fue eliminada también.&lt;br /&gt;&lt;br /&gt;

&lt;span style="font-weight: bold;"&gt;reduce() no es más &lt;span style="font-style:italic;"&gt;builtin&lt;/span&gt;&lt;/span&gt;&lt;br/&gt;
En Python 1.0 (allá por 1994) se agregaron varias primitivas de programación funcional: &lt;span style="font-style:italic;"&gt;map, filter, reduce&lt;/span&gt;. Estas primitivas permiten, usando una única llamada, reescribir varios bucles simples que se suelen utilizar al programar.&lt;br /&gt;&lt;br /&gt;

Si bien &lt;span style="font-style:italic;"&gt;map&lt;/span&gt; y &lt;span style="font-style:italic;"&gt;filter&lt;/span&gt; quedan casi sin cambios en Python 3, &lt;span style="font-style:italic;"&gt;reduce&lt;/span&gt; se eliminó porque produce &lt;a href="http://code.activestate.com/recipes/148061/"&gt;código difícil de leer&lt;/a&gt; y por lo tanto es más conveniente utilizar un ciclo en lugar de la función. Para los que deseen seguirla utilizando, &lt;span style="font-style:italic;"&gt;reduce&lt;/span&gt; ahora vive en el módulo &lt;span style="font-style:italic;"&gt;functools&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;
Hace un tiempo &lt;a href="http://www.pcmasmas.com/viewtopic.php?t=29863#226192"&gt;expliqué map, filter y reduce&lt;/a&gt; en el foro de PC++.&lt;br /&gt;&lt;br /&gt;

&lt;span style="font-weight: bold;"&gt;Volaron muchos otros builtins&lt;/span&gt;&lt;br/&gt;
Todos los siguientes &lt;span style="font-style:italic;"&gt;builtins&lt;/span&gt; volaron. Los &lt;span style="font-style:italic;"&gt;idioms&lt;/span&gt; que los reemplazan están disponibles en la &lt;a href="http://docs.python.org/3.0/whatsnew/3.0.html#builtins"&gt;documentación sobre cambios en Python 3&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;apply&lt;/li&gt;
&lt;li&gt;callable&lt;/li&gt;
&lt;li&gt;coerce&lt;/li&gt;
&lt;li&gt;reload&lt;/li&gt;
&lt;li&gt;file&lt;/li&gt;
&lt;li&gt;y varios más!&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-3610632229762985828?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/3610632229762985828/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-pequeos-cambios-miscelneos.html#comment-form' title='7 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/3610632229762985828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/3610632229762985828'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-pequeos-cambios-miscelneos.html' title='Python 3: pequeños cambios misceláneos'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-4709833256153615180</id><published>2008-12-06T23:05:00.004-02:00</published><updated>2008-12-06T23:30:44.167-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><title type='text'>Python 3 está entre nosotros</title><content type='html'>El 3 de diciembre &lt;a href="http://www.python.org/download/releases/3.0/"&gt;salió Python 3&lt;/a&gt; (anteriormente llamado Python 3000). Por primera vez en la historia, Python rompe la compatibilidad hacia atrás para mejorar (y en cierta manera, purificar) el lenguaje.

&lt;blockquote&gt;There should be one —and preferably only one— obvious way to do it&lt;/blockquote&gt;

"Debería haber una (y preferentemente solo una) manera obvia de hacerlo". Esa cita proviene del &lt;a href="http://www.python.org/dev/peps/pep-0020/"&gt;Zen de Python&lt;/a&gt;, una suerte de poema-chiste-documento que contiene las filosofías detrás del diseño del lenguaje de programación. La que cito arriba, en particular, se contrapone fuertemente con el &lt;i&gt;slogan&lt;/i&gt; de &lt;a href="http://www.perl.org"&gt;Perl&lt;/a&gt;: "Hay más de una manera de hacerlo". Los programas en Perl se caracterizan por su muy pobre legibilidad - al punto que a veces se dice que Perl es un lenguaje de &lt;i&gt;solo escritura&lt;/i&gt; (una vez que está escrito, nadie podrá leerlo!).&lt;br /&gt;&lt;br /&gt;

Esa cita es una parte esencial de las modificaciones que sufrió Python en su versión 3. Con el paso de los años, Python fue creciendo y acumulando librerías y funcionalidades. En muchos casos, existían varias maneras de hacer lo mismo sin que quedara claro cual era la mejor. Por esta razón, se eliminaron muchas formas redundantes de hacer cosas, y se aprovechó para corregir algunos detalles que &lt;i&gt;vinieron mal de fábrica&lt;/i&gt;. Por supuesto, también hay nuevas funcionalidades.&lt;br /&gt;&lt;br /&gt;

En el sitio web oficial hay una página detallando &lt;a href="http://docs.python.org/3.0/whatsnew/3.0.html"&gt;todos los cambios en Python 3&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;

En los próximos días voy a estar comentando algunos de los cambios que se hicieron en Python 3, sus razones de ser, y sus implicaciones en el uso del lenguaje. Python es más simple, consistente (y lindo!) que nunca.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-4709833256153615180?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/4709833256153615180/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-est-entre-nosotros.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/4709833256153615180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/4709833256153615180'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2008/12/python-3-est-entre-nosotros.html' title='Python 3 está entre nosotros'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-6465840400749293992</id><published>2008-11-05T03:28:00.014-02:00</published><updated>2008-12-14T18:09:23.730-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><category scheme='http://www.blogger.com/atom/ns#' term='Algoritmos'/><title type='text'>Estrellas Mágicas</title><content type='html'>En &lt;a href="http://www.pcmasmas.com"&gt;PC++&lt;/a&gt; hay personajes curiosos. Uno de ellos es DjGera, que hace unos días &lt;a href="http://www.pcmasmas.com/viewtopic.php?t=31866"&gt;propuso este problema&lt;/a&gt;:&lt;br/&gt;&lt;br/&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_LQAjBtuUT0w/SREzL6CUvQI/AAAAAAAAAIk/8y5Lm8IxAOY/s1600-h/estrella.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 365px;" src="http://3.bp.blogspot.com/_LQAjBtuUT0w/SREzL6CUvQI/AAAAAAAAAIk/8y5Lm8IxAOY/s400/estrella.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5265045719023402242" /&gt;&lt;/a&gt;

&lt;b&gt;Ubicar los números del 1 al 16 de forma que todas las columnas, filas y diagonales sumen 34.&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;

Como buen ser humano mi primer instinto es mirar el problema fijo a ver si se me ocurre una solución. Pasados 10 minutos me empieza a dar hambre y recuerdo que para algo estudio Lic. en Computación.&lt;br/&gt;&lt;br/&gt;

Rápidamente, hay &lt;b&gt;16! = 2.1e13&lt;/b&gt; formas de poner los números en el dibujo. Si obviamos las rotaciones y reflexiones de una misma solución, quedan apenas &lt;b&gt;653 837 184 000&lt;/b&gt; formas posibles (seiscientos mil millones aproximadamente). Si bien las computadoras de hoy son rápidas, los problemas con números de esta magnitud todavía no son razonables para tratarlos "a lo bruto".&lt;br/&gt;&lt;br/&gt;
&lt;b&gt;Solución con Programación Lineal&lt;/b&gt;
&lt;br/&gt;&lt;br/&gt;
Al mirar el problema uno piensa bastante rápido (o no) en esas clases de álgebra lineal y empieza a imaginar matrices ralas y triangulaciones. Las restricciones del tipo "&lt;i&gt;esta variable y esta otra deben sumar tanto&lt;/i&gt;" son efectivamente lineales y por lo tanto pueden representarse de forma matricial. Sin embargo, el problema como está planteado no es un sistema lineal convencional, por dos razones:&lt;ul&gt;
&lt;li&gt;Las variables son enteras, no reales&lt;/li&gt;
&lt;li&gt;Las variables deben ser todas distintas&lt;/li&gt;&lt;/ul&gt;&lt;br/&gt;

Con estos añadidos, el problema se sale de la &lt;a href="http://en.wikipedia.org/wiki/Linear_programming"&gt;programación lineal&lt;/a&gt; y entra en la &lt;a href="http://en.wikipedia.org/wiki/Linear_programming#Integer_unknowns"&gt;programación entera&lt;/a&gt;, un área que es computacionalmente mucho más difícil. Estos temas son objeto de la materia &lt;a href="http://www-2.dc.uba.ar/grupinv/invop/integrantes.htm"&gt;Investigación Operativa&lt;/a&gt; que es una de las gemas del Departamento de Computación de la UBA. Desgraciadamente, todavía no tuve oportunidad de cursar esa materia. Pensándolo un poco llegué a la conclusión de que probablemente esta no fuera la ruta más sabia, pero &lt;a href="http://3.bp.blogspot.com/_LQAjBtuUT0w/SRE3G5gwYkI/AAAAAAAAAIs/38q--Rl4r2s/s1600-h/Imagen024.jpg"&gt;Martín&lt;/a&gt;, un poco más piola y sobre todo mucho más optimista, me mostró que estaba equivocado.&lt;br/&gt;&lt;br/&gt;

La técnica para resolver el problema usando programación entera es esencialmente:
&lt;ol&gt;&lt;li&gt;Plantear la matriz asociada a las restricciones lineales&lt;/li&gt;
&lt;li&gt;Triangularla con el método de &lt;a href="http://en.wikipedia.org/wiki/Gaussian_elimination"&gt;eliminación gaussiana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tras este paso, queda una matriz singular de rango 8&lt;/li&gt;
&lt;li&gt;Fijar 8 de las 16 variables en valores enteros de 1 a 16&lt;/li&gt;
&lt;li&gt;Resolver las 8 variables restantes; si sus valores son enteros, están entre 1 y 16 y son diferentes a los 8 valores elegidos en el punto anterior, encontramos una solución.&lt;/li&gt;
&lt;li&gt;Volver al paso 4 para cada valuación distinta de las 8 variables a fijar&lt;/li&gt;&lt;/ol&gt;&lt;br/&gt;

El algoritmo es entonces una combinación de programación lineal con &lt;a href="http://en.wikipedia.org/wiki/Brute-force_search"&gt;fuerza bruta&lt;/a&gt;, pero programado en Mathematica tarda unas 10 horas en hallar todas las soluciones posibles, lo cual es muy respetable.&lt;br/&gt;&lt;br/&gt;

&lt;b&gt;Solución con Algoritmo Genético&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;

Con el rabo entre las piernas frente a la completitud y elegancia de la solución propuesta por Martín, implementé un &lt;a href="http://en.wikipedia.org/wiki/Genetic_algorithm"&gt;algoritmo genético&lt;/a&gt; para resolver el problema. Sin demasiado trabajo el algoritmo encuentra una solución muy rápido (en unos 5 segundos con &lt;a href="http://psyco.sourceforge.net/"&gt;Psyco&lt;/a&gt;). Este tipo de algoritmos no deja de sorprenderme, puesto que si se observa el código las heurísticas de mutación y apareamiento son &lt;span style="font-weight:bold;"&gt;totalmente primitivas&lt;/span&gt;, y sin embargo el rendimiento del mismo es excelente.&lt;br/&gt;&lt;br/&gt;

&lt;pre name="code" class="python"&gt;
#!/usr/bin/python
# estrella.py
# Resuelve la estrella magica de 16 puntas con un algoritmo genetico.

import random
import sys

tam_poblacion = 400
natalidad = 0.15
mutacion = 0.5
max_generacion = 500

# restricciones lineales
m = [[0,1,3,4],
     [4,5,7,8],
     [8,9,11,12],
     [12,13,15,0],
     [14,15,1,2],
     [2,3,5,6],
     [6,7,9,10],
     [10,11,13,14]]

# funcion de aptitud
def fitness(cand):
    fit = 0
    for eq in m:
        fit_local = -34
        for j in eq:
            fit_local += cand[j]
        fit += abs(fit_local)
    
    # esto fuerza que el primer numero sea el 16
    # y filtra las rotaciones de la misma solucion
    if cand[0] != 16:
        fit += 1000

    return fit

# funcion de apareamiento
def cruzar(c1, c2):
    l = [c1,c2]
    random.shuffle(l)
    c1, c2 = l
    c = c1[:8]
    for each in c2:
        if not each in c:
            c.append(each)
    return c

# funcion de mutacion
def mutar(cand):
    # "ejercito" al generador pseudoaleatorio
    # esto mejora bastante la velocidad de convergencia!
    i1 = random.randint(0,15)
    i2 = random.randint(0,15)
    
    i1 = random.randint(0,15)
    i2 = random.randint(0,15)
    
    i1 = random.randint(0,15)
    i2 = random.randint(0,15)

    c = cand[:]
    c[i1], c[i2] = c[i2], c[i1]
    
    return c

def avanzar_gen(pob):
    nacen = int(tam_poblacion * natalidad)
    mutan = int(tam_poblacion * mutacion)

    # unos se aparean
    for i in range(nacen):
        papa, mama = random.sample(poblacion, 2)
        poblacion.append(cruzar(papa, mama))

    # nacen unos mutados
    for i in range(mutan):
        deforme = random.sample(poblacion, 1)[0]
        poblacion.append(mutar(deforme))

    # ordeno por fitness
    poblacion.sort(lambda x,y: cmp(fitness(x), fitness(y)))
    
    # mato a los menos aptos
    while(len(poblacion) &gt; tam_poblacion):
        poblacion.pop()
    

numeros = range(1,17)

# inicializador de poblacion
def init_pob():
    poblacion = []
    for i in range(tam_poblacion):
        cand = numeros[:]
        random.shuffle(cand)
        poblacion.append(cand)
    return poblacion


if __name__ == '__main__':
    while True:
        print
        print "Comenzando simulacion..."
        gen = 0
        poblacion = init_pob()
        min_fit = 1000
        while min_fit !=0:
            avanzar_gen(poblacion)
            gen += 1
            f = fitness(poblacion[0])
            
            if f &lt; min_fit:
                print "Fitness: %s (gen %s)" % (f, gen)
            min_fit = f
            
            if min_fit == 0:
                print "Eureka!"
                print poblacion[0]
                sys.exit(0)
            if poblacion[0] == poblacion[len(poblacion)-1] or gen &gt;= max_generacion:
                break
&lt;/pre&gt;

La ejecución se ve aproximadamente así (con un poco de suerte!):&lt;br/&gt;&lt;br/&gt;

&lt;pre name="code" lang="python"&gt;gomo@ciabatta:~$ time python estrella.py

Comenzando simulacion...
Fitness: 30 (gen 1)
Fitness: 18 (gen 4)
Fitness: 16 (gen 10)
Fitness: 10 (gen 15)
Fitness: 8 (gen 30)
Fitness: 6 (gen 44)
Fitness: 4 (gen 47)
Fitness: 2 (gen 99)
Fitness: 0 (gen 237)
Eureka!
[16, 3, 11, 2, 13, 7, 14, 9, 5, 10, 1, 15, 4, 6, 12, 8]

real 0m2.449s
user 0m2.420s
sys 0m0.028s
&lt;/pre&gt;

Por su naturaleza, el tiempo que insume el algoritmo genético es muy variable y no encuentra siempre la misma solución. Por esta razón el programa hace varios intentos en lugar de perseverar con la misma simulación si observa que está tardando mucho en encontrar una solución.&lt;br/&gt;&lt;br/&gt;

&lt;small&gt;Agregado el 10/12/2008&lt;/small&gt;&lt;br /&gt;
&lt;b&gt;Solución con Backtracking&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;

Tras discutir en un foro, considero que es mejor aclarar lo siguiente: los algoritmos propuestos arriba no son ni de cerca los más eficientes que se conocen para este problema. Un simple algoritmo de &lt;a href="http://en.wikipedia.org/wiki/Backtracking"&gt;backtracking&lt;/a&gt; encuentra una solución de forma certera y bastante más veloz que lo que propongo arriba.&lt;br/&gt;&lt;br/&gt;

El interés del post era mostrar dos técnicas interesantes para resolver el problema, sin ánimos de indicar que eran particularmente aptas o buenas para la tarea. A modo de ilustración, adjunto el código del algoritmo de &lt;span style="font-style:italic;"&gt;backtracking&lt;/span&gt;.&lt;br/&gt;&lt;br/&gt;

&lt;pre name="code" class="python"&gt;
def buscarSolucion(actual, restantes):
    if restantes == []:
        return actual
    else:
        for r in restantes:
            nuevo_act = actual + [r]
            nuevo_rest = restantes[:]
            nuevo_rest.remove(r)
            
            s = None
            if esValidoParcial(nuevo_act):
                s = buscarSolucion(nuevo_act, nuevo_rest)
                
            if not s is None:
                return s
            

def esValidoParcial(perm):
    l = [[0,1,3,4],
         [4,5,7,8],
         [8,9,11,12],
         [12,13,15,0],
         [14,15,1,2],
         [2,3,5,6],
         [6,7,9,10],
         [10,11,13,14]]

   
    for rest in l:
        s = 0
        c = 0
        for k in rest:
            if len(perm) &gt; k:
                s += perm[k]
                c += 1
        
        if c == 4 and s != 34:
            return False
        elif c &lt; 4 and s &gt;= 34:
            return False
    
    return True

if __name__ == "__main__":
    numeros = range(1,17)
    print buscarSolucion([], numeros)
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-6465840400749293992?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/6465840400749293992/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2008/11/estrellas-mgicas.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/6465840400749293992'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/6465840400749293992'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2008/11/estrellas-mgicas.html' title='Estrellas Mágicas'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_LQAjBtuUT0w/SREzL6CUvQI/AAAAAAAAAIk/8y5Lm8IxAOY/s72-c/estrella.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1085745093237715146.post-1683868668124924792</id><published>2008-11-01T21:23:00.006-02:00</published><updated>2008-11-05T19:20:04.473-02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><category scheme='http://www.blogger.com/atom/ns#' term='Computación'/><category scheme='http://www.blogger.com/atom/ns#' term='Algoritmos'/><title type='text'>3 implementaciones de Fibonacci</title><content type='html'>En la &lt;a href="http://blog.gomox.com.ar/2008/11/charla-sobre-tcnicas-algortmicas.html"&gt;charla sobre técnicas algorítmicas&lt;/a&gt; usé el algoritmo para calcular &lt;a href="http://en.wikipedia.org/wiki/Fibonacci_number"&gt;números de Fibonacci&lt;/a&gt; para mostrar como se podía mejorar la eficiencia de un programa utilizando la técnica de programación dinámica.

El algoritmo trivial de Fibonacci utiliza recursión múltiple. Esto produce una cantidad exponencial de llamadas, lo que resulta en un algoritmo de complejidad &lt;span style="font-weight:bold;"&gt;O(2&lt;sup&gt;n&lt;/sup&gt;)&lt;/span&gt;.

&lt;pre name="code" class="python"&gt;
def fibo1(n):
    if n == 0 or n == 1:
        return 1
    else:
        return fibo1(n-1) + fibo1(n-2)
&lt;/pre&gt;

La técnica de &lt;a href="http://en.wikipedia.org/wiki/Dynamic_programming"&gt;Programación Dinámica&lt;/a&gt; consiste en resolver primero subproblemas que están contenidos dentro del problema total, y luego servirse de las soluciones a los subproblemas para resolver el problema total.&lt;br /&gt;&lt;br /&gt;

Un ejemplo sencillo es el problema de obtener un camino mínimo entre dos puntos. El popular &lt;a href="http://en.wikipedia.org/wiki/Dijkstra_algorithm"&gt;algoritmo de Dijkstra&lt;/a&gt; para caminos mínimos utiliza la técnica de programación dinámica.&lt;br /&gt;&lt;br /&gt;

Para el caso de Fibonacci, los subproblemas son los números de Fibonacci más pequeños que el que queremos calcular. En la implementación anterior, estos números se obtienen con llamadas recursivas. Sin embargo, si comenzamos calculando desde los números de Fibonacci más pequeños y con ellos vamos construyendo los más grandes, se puede realizar el cálculo en &lt;span style="font-weight:bold;"&gt;O(n)&lt;/span&gt;.

&lt;pre name="code" class="python"&gt;
def fibo2(n):
  if n == 0 or n == 1:
      return 1
  else:
      nm1 = 1
      nm2 = 1
      i = 1
      while i &lt; n:
          nm1, nm2 = nm2, nm1 + nm2
          i = i+1
      return nm2
&lt;/pre&gt;

Un tiempo después &lt;a href="http://www.fotolog.com/fede_firefly"&gt;Fede&lt;/a&gt; me comentaba sobre un algoritmo de &lt;a href="http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm"&gt;Divide &amp; Conquer&lt;/a&gt; que permite calcular aún más rápido los números de Fibonacci.&lt;br /&gt;&lt;br /&gt;

El algoritmo se basa en la siguiente identidad (donde F&lt;sub&gt;n&lt;/sub&gt; es el enésimo término de la sucesión o secuencia de Fibonacci):&lt;br&gt;&lt;br&gt;
&lt;div align="center"&gt;&lt;img src="http://upload.wikimedia.org/math/a/6/0/a6083f85f39b468210f5715a8e30d572.png" /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;


Como existe un algoritmo de &lt;span style="font-style:italic;"&gt;Divide &amp; Conquer&lt;/span&gt; para elevar un número (o en este caso, una matriz) a una cierta potencia, nos podemos servir de él. Este &lt;a href="http://www.datastructures.info/the-divide-and-conquer-algorithmmethod/"&gt;algoritmo de potenciación&lt;/a&gt; es la función &lt;span style="font-weight:bold;"&gt;aLaN()&lt;/span&gt; en el código que sigue. Gracias al mismo, podemos calcular números de Fibonacci en tiempo &lt;span style="font-weight:bold;"&gt;O(log n)&lt;/span&gt;.

&lt;pre name="code" class="python"&gt;
def fibo3(n):
    # esta funcion multiplica 2 matrices de 2x2
    def multiplicar(m, n):
        return [m[0]*n[0]+m[1]*n[2],
                m[0]*n[1]+m[1]*n[3],
                m[2]*n[0]+m[3]*n[2],
                m[2]*n[1]+m[3]*n[3]]
    # esta funcion eleva una matriz de 2x2 a la k
    # usando el algoritmo de D&amp;C
    def aLaN(m, k):
        if k == 0:
            return [1,0,0,1]
        elif k == 1:
            return m
        else:
            if k % 2 == 0:
                return aLaN(multiplicar(m,m),k/2)
            else:
                return multiplicar(aLaN(multiplicar(m,m),k/2),m)
    
    inicial = [1,1,1,0]
    return aLaN(inicial, n)[0]
&lt;/pre&gt;

Esto permite ver que el correcto uso de técnicas algorítmicas puede mejorar sustancialmente el rendimiento de los algoritmos. Si bien el programa presentado es &lt;span style="font-style:italic;"&gt;de juguete&lt;/span&gt;, el conocimiento de estas técnicas puede abrir nuevas posibilidades a la hora de desarrollar software.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1085745093237715146-1683868668124924792?l=blog.gomox.com.ar' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.gomox.com.ar/feeds/1683868668124924792/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.gomox.com.ar/2008/11/prueba-source-higlight.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/1683868668124924792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1085745093237715146/posts/default/1683868668124924792'/><link rel='alternate' type='text/html' href='http://blog.gomox.com.ar/2008/11/prueba-source-higlight.html' title='3 implementaciones de Fibonacci'/><author><name>GomoX</name><uri>http://www.blogger.com/profile/16605161706896272573</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='14447421456491791623'/></author><thr:total>1</thr:total></entry></feed>