alchemy: jugando a ser dios (ó cómo hacer trampa con Python)

Creo que esto de usar Python para hacer trampa se me está volviendo vicio. Es que hay veces que las cosas te piden a gritos que tomes un atajo. Y esto fue lo que me pasó esta semana.

Primero un poco de contexto. La semana pasada conocí, a través de Microsiervos, un juego llamado Alchemy. Consiste en combinar los cuatro elementos clásicos griegos (tierra, agua, fuego y aire) para generar nuevos elementos y a su vez, otros con estos.

Se los comenté a varios conocidos y se engancharon. El tope (al menos de momento) es 200 elementos y, como se trata de un juego muy divertido y adictivo, en un tiempo razonable se puede superar los 100. Los elementos son cada vez más complejos, incluso uno se empieza a obsesionar con la idea de crear human :P

El problema llega cuando uno se estanca y la tentación de hacer trampa se hace inevitable.

Atención: a partir de este momento el post es una especie de spoiler en donde se explica cómo llegar al final del juego sin jugarlo. Si estás jugándolo y lo disfrutas, deja de leer inmediatamente.

Con mirar rápidamente el código de la página, uno puede descubrir cómo funciona. Parece haber un archivo en donde se definen las relaciones de los elementos por un número identificador y otro donde está se mapean estos identificadores a nombres. El siguiente script me ayudó a organizar la información muy rápidamente:

Así, si quiero saber cómo obtener el elemento egg:

$ ./alchemy.py -g egg
egg = life + stone
egg = bird + bird
egg = turtle + turtle
egg = lizard + lizard

Si quiero saber para que me pueden servir los elemetos dough y sea:
$ ./alchemy.py -f dough sea
bread = fire + dough
cookie = fruit + dough
ocean = water + sea
seaweed = plant + sea
beach = sand + sea
wave = wind + sea
tides = moon + sea
seasickness = sickness + sea
algae = plant + sea
horizon = sky + sea

Para obtener todas las combinaciones:
$ ./alchemy.py -g all
que da este listado. Me pareció interesante hacer algunos grafos con estos datos. Así se puede ver la sección local de light bulb:
./alchemy.py -c "light bulb" | dot -Tpng > light_bulb.png ; display light_bulb.png

Claro que también se puede hacer el grafo completo (si se tiene memoria y tiempo):
$ ./alchemy.py -c all | neato -Tpng > all.png ; display all.png
Así se genera una imagen enorme de la que se puede ver un fragmento a continuación (click acá para verla completa, son 13MB 12027x 13593).

Fue divertido, incluso cuando evidentemente no fui el primero en notar esto y basta con googlear un poco para conocer las soluciones. En lo personal, consideré parte del juego escribir el código que lo resuelve y lo usé de excusa para hacer algo con optparse, que lo tenía pendiente.

parser para fórmulas de lógica proposicional (o una somera introducción a pyparsing)

La semana pasada empecé a cursar, de oyente, dos materias en Exactas: Lógica y Computabilidad y Teoría de Lenguajes. En la primera, empezamos a estudiar algunos conceptos de cálculo proposicional. Al final de la clase, el profesor sugirió escribir un pequeño parser que reconozca fórmulas de lógica proposicional. Dado que venía con ganas de entender mejor pyparsing, me pareció que podía ser una buena forma de empezar.

Primero, la teoría. Dado el alfabeto $latex A$, $latex A^*$ es el conjunto de palabras que pueden formarse combinando sus elementos.
$latex A=\{ \to , \wedge , \vee , \neg , ( , ) , p , \prime \}$
Existe un tipo particular de palabras, a las que llamamos variables.
$latex Var=\{p , p\prime, p\prime\prime , \ldots \}$
Es un conjunto infinito y, dado que puede ser tedioso contar la cantidad de primas, las variables pueden ser referenciadas como $latex p_n$, donde n es la cantidad de primas. Así, parece razonable pensar que no todas las palabras son válidas a la hora de escribir una fórmula (que es como llamaremos a las palabras válidas).

Ejemplos de fórmulas:

  • $latex p_2$
  • $latex ( ( p_3 \wedge p_5 ) \to ( \neg p_2 \vee p_5 ) )$
  • $latex \neg \neg ( p\prime \vee p_{1232} )$

Ejemplos de no-fórmulas (palabras que no forman una fórmula):

  • $latex \neg\prime$
  • $latex ( p_2 )$
  • $latex p_3 \vee \wedge $

Si bien es más o menos intuitivo qué es una fórmula y qué no, es necesario definirlo en un sentido formal. Así $latex Form \subset A^* $ y es el conjunto de las palabras que cumplen:

  1. si $latex \alpha \in A^*$ y $latex \alpha \in Var$, entonces $latex \alpha \in Form$
  2. si $latex \alpha \in Form$, entonces $latex \neg \alpha \in Form$
  3. si $latex \alpha,\beta \in Form$, entonces $latex ( \alpha \circledast \beta)\in Form$. Donde $latex \circledast=\vee,\wedge,\to $

Nada más es una fórmula.

Ahora, a la práctica. Queremos escribir un parser que, dada una palabra, reconozca si es una fórmula. Y para esto vamos a jugar con el módulo pyparsing, para python.

Lo primero es definir el conjunto de variables.

>>> from pyparsing import Word
>>> variable=Word('Pp',"0123456789'")
>>> variable.parseString('p1')
(['p1'], {})
>>> variable.parseString("P'")
(["P'"], {})

Así, variable reconoce los posibles nombres de variables. Toda expresión que sea parseable por variable, es una formula. Para el punto ii. hay que definir una estructura recursiva. Utilizaremos el bang (!) para la negación.
from pyparsing import Forward
formula=Forward()
ii='!' + formula

Caso similar en el punto iii.. Para esto hay que definir los operadores, que son and (&), or (|) y then (<).

operador=Word('&|>',max=1)
iii='('+formula+operador+formula+')'

Por último, definimos una fórmula cómo una variable (i.) o una negación de una fórmula (ii.) o una operación entre fórmulas (iii.).

formula << ( variable | ii | iii )

Y esto es, básicamente, nuestro parser:

>>> formula.parseString("(p1 | p4)")
(['(', 'p1', '|', 'p4', ')'], {})

Lo dicho, con un poco más de contexto y en un único archivo, a continuación:

how to hack a h4ckc0nt3st

Hace unas horas acabo de volver de A Coruña, donde pasé unos excelentes días entre charlas y talleres de excelente nivel técnico y amigos en las Jornadas de Seguridad Informáticas organizadas por GSIC.

Pero este post tiene otro objetivo que contar lo lindo que fue. Así que si esperabas algo blando, puedes dejar de leer acá :P (las fotos estarán online en breve). Lo que sigue es la respuesta larga a la pregunta que me realizaron varias veces en los últimos días: ¿Estas jugando al h4ckc0nt3st?. La respuesta corta era sí, lo estoy haciendo en este exacto momento, aunque no de la forma tradicional.

El contexto

Durante la conferencia, Miguel organizó un muy divertido hack contest. El objetivo era llegar a una respuesta a través de la resolución de diferentes desafíos. Dicha respuesta se metía en un formulario web (que escuchaba en http://10.20.63.1:666) y de esta forma se acumulaban puntos.

El hack

Dado que la red sobre la que se hacía el concurso era una wifi abierta y que el servidor recibía las respuestas de los participantes en texto plano (no usaba SSL), pensé que sería divertido escuchar las respuestas de los demás concursantes. El paso siguiente de enviar dichas respuestas a mi nombre resultó inevitable. De esta forma, cada vez que alguien enviaba una posible respuesta a un desafío, yo también respondía lo mismo. Logré automatizar ello con las siguientes líneas:

tshark -T fields -e data -i mon0 -R 'ip.dst == 10.20.63.1 and tcp.dstport == 666 and ip.src != mi.propia.ip ' | ./loro.py

Donde loro.py este archivo:

loro.py   

La explicación

Tshark es la versión de consola de wireshark. Este toma los paquetes de una interfaz en modo monitor (lee esto para aprender cómo). El modificador -f es el encargado de darme solo aquellos paquetes que vayan al servidor. Excluir del filtro a mi propia IP evita que el sniffer vea mis replayes y entre en loop (tardé en darme cuenta de esto, perdona Miguel por floodear tus logs). El script loro.py recibe el campo data de los por stdin (línea (#) 5), lo decodifica en ascii (#7), y chequea si es una respuesta (#8). Después lo divide y busca la cookie (# 9 a 11). Al encontrarla, la reemplaza por mi propia cookie (#12, fue una suerte práctica que no caducara). Luego vuelve a juntar todo (#13), lo imprime en stderr (#14, con fines de logging, nada más) y lo envía de nuevo al server (# 15 a 18).

Como dicen por acá, ¡a que mola!

exportando cumpleaños de Facebook a Google Calendar

Ocurre que soy realmente malo para recordar eventos. Muchas veces me comprometí a estar en dos lados simultáneamente y me olvido de hacer tal o cual cosa. Mi desorden se extiende a cosas repetitivas, como los cursos o cumpleaños. Para intentar apalear este mal, utilizo Google Calendar intensamente. Es por esto que intenté exportar las fechas de cumpleaños a mis calendarios. Pero cuando intenté hacerlo out-of-the-box me encontré con unos incordios:

  • No hay forma de exportar solo un subconjunto de las fechas
  • Con los cambios de DST, hay eventos que duran dos días
  • Los eventos no son de días completos, sino que van de 12am a 12am
  • No quedan como eventos editables normales

Los eventos se ven algo así:

Y la verdad que están horribles. Así que realicé estos 5 sencillos pasos:

Paso 1

Fui a los eventos de cumpleaños en mi perfil, donde puse exportar cumpleaños.

Así, facebook me proveyó una URL, que empieza con webcal://

Paso 2

Esta URL, reemplazando webcal por http, la utilicé para obtener los eventos, en formato CSV:
wget "http://www.facebook.com/ical/b.php?uid=2XXXXXX4&key=3XXXXXXX8" -O facebook.csv

Paso 3 (opcional)

Dada mi política de aceptar-todo-contacto en Facebook hay muchos eventos que no quisiera incluir en mi calendario. Por lo que generé una lista de los que sí quiero tener:
rgrep "^SUMMARY" facebook.csv > lista.txt
Y después borré de lista.txt aquellos que quería excluir.

Paso 4

Escribí el siguiente script:

fb2gc.py   

El cual convierte los eventos de facebook en eventos lindos. Se ejecuta así:
./fb2gc.py facebook.csv lista.txt > miscumples.csv
El parámetro lista.txt es optativo, si pasaste por el Paso 3.

Paso 5

El archivo generado se pueden importar en Google Calendar, en un calendario existente.


Y ahora, tengo agendados los cumpleaños de forma mucho más agradable.

secure information flow analysis: my first steps

During the last months and have been reading a lot about information flow analysis, with the remarkable Eduardo Bonelli's guidance.

Some months ago, as an exercise, I wrote two analyzers for a really short command set of Python (while, if and assign). Before remove that directory, it occurred to me that may exists a remote possibility that someone might find it interesting. So here it is, with a quick and dirty introduction to secure information flow.

The goal, in short words, is to avoid that variables tagged as secret (high confidential level) doesn't leak to public variables (low confidential level). This may happen in two ways:

  • Explicit: A high variable is assigned to a low variable
  • public:=secret

  • Implicit: A low variable content depends on the content of a high variable
  • if secret == true
    then public:=true
    else public:=false

If there is no leak, we said that the code satisfies non-interference (wikipedia link). You can learn more about secure information flow analysis in the web. In my humble opinion, this is a good introduction.

A typical way (certainly not the only one) to detect these leaks is with type systems. This was the approach in both analyzers. The first one is a sort of an implementation of a fundation paper, by Volpano et.al.. I made an algorithm version (probably wrong) of the typing rules exposed in the paper. The code is here. This type of analyzers are called Denning-style, because Denning and Denning introduced those concepts in a 1977 paper.

The second analyzer (the code is here) is based on the formalism presented by Hunt and Sands in this paper. It's a dynamic analyzer (Denning-style analyzers are static), which means that the non-interference can be broken in subprograms and still be good as a whole. This may be a little tricky. For example, this code is secure (the leak was overwritten with a 0) even when a subprogram (without the last line) is insecure:
public:=secret
public:=0

Anyway, that's all for now. The analyzers are written in Python, using the Abstract Syntax Trees module and python-lattice (yes, this is what that stupid library is for). If you want to play more, here is a tarball with the code, the papers and few examples to analyze.

operating elements of a finite lattice is now easy(?)

In the context of my recent readings about Information Flow analysis, I wrote a little (tiny) Python module to operate elements of a finite lattice. Here is the code and usage tutorial. Comments are welcome. Patches to my broken English in the main page are very welcome.

eventé, eventando y eventaré

He estado (y lo estaré) de evento en evento. Así que acá va un pequeño resumen. Tal vez a alguien le sea útil o pueda lamentarse de no haber ido a aquellos que ya ocurrieron. Si tenés pensado ir a alguno en donde nos encontremos, no dudes en inscribirme para tomar una cerveza.

En el pasado:

En el futuro:

  • 10º Jornadas Regionales de Software Libre: En San Luis, el 28, 29 y 30 de Octubre. Este año no podré asistir, pero se corre la bola de que va a estar muy muy buena.
  • Google DevFest 2010 Argentina: En Buenos Aires, 1 y 2 de Noviembre. Si bien la inscripción ya debería haber terminado, yo lo hice fuera de termino y parece que tengo la confirmación. El procedimiento requiere dar respuesta un breve quiz.
  • BSDday Argentina 2010 (una web muy geek): En Buenos Aires, 5 y 6 de Noviembre. Me inscribí hace meses y no voy a poder ir (por la razón que se comenta en el siguiente item). El año paso la pasé muy bien y este año las charlas realmente prometen.
  • 6º Jornadas de Software Libre: En Junín, 5 y 6 de Noviembre. Hablaré Linux Capabilities y Hardening. Muchísimas gracias a los organizadores por invitarme. El programa se ve muy interesante y será una excelente oportunidad para reencontrarme con amigos y visitar la ciudad.

removing your facebook photo tags automagically

Este post también está escrito en español aquí.

Privacy at Facebook is heavy-duty. As a big fan of the Worlds Collide Theory I hate be tagged compulsively. I would like to select in which photos appear in my profile and feed. Since I couldn't find that option in the setting menu, I looked for the answer in my favorite scripting language: Python.

This 60-lines-long script removes your tag from the latests photos where you has been labelled. You can download it from here. You may run it hourly (or every 15 minutes, or every 5 minutes, depends how paranoid you are) via cron or whatever.

Any improvement is welcome. It probably runs on Windows too. If you managed to do it, leave a comment for the others.

NEW VERSION! (available here).

remover tu etiqueta de las fotos de facebook automágicamente

This post has been written in English too.

La privacidad en Facebook es un asunto complejo. Como gran suscriptor a la Teoría de Colisión de Mundos es que odio ser etiquetado en fotos de forma compulsiva. Me gustaría tener alguna forma de elegir en que fotos aparezco en mi perfil y actualizaciones. Dado que no pude encontrar tal opción entre la configuración, busqué la respuesta en mi lenguaje de scripting favorito: Python.

Este script de 60 lineas remueve tu etiqueta de las últimas fotos donde te hayan tagueado. Puede ser descargado desde aquí. Hay que correrlo cada hora (o cada 15 minutos, o cada 5, dependiendo de que tan paranoico seas) a través de cron o como sea.

Cualquier mejora es bienvenida. Posiblemente también corra en Windows. Si lograste hacer esto, deja un comentario que pueda serle útil a otros.

¡NUEVA VERSIÓN! (disponible aquí).

φbonacci

Mi conjunto de números favoritos son los enteros en general y los naturales en particular. Son los primeros números que aprendemos en la vida, sencillos, de apariencia intuitiva pero con poderosas propiedades. Dado que mi amateur aproximación a la matemática se dio por el camino de la teoría de números, fueron mi primer gran amor matemático.


Sin embargo, no es difícil encontrar fascinante a otros conjuntos de números. Muchas de estos patean el tablero de formas agradables, como los complejos, que se escaparon de la recta real para irse de vacaciones al plano. Entre los más impetuosos contra la intuición están, obviamente, los irracionales. Y a uno de ellos va dedicado este post.

Hace un par de meses divagué con π. Este semana terminé de leer el libro La Proporción Áurea de Mario Livio. Este libro, que evidentemente trata sobre la constante φ (phi), lo compré en Madrid, atraído por la promesa de desasnarme con respecto a la historia de este número.

Primero me gustaría hablarles sobre el libro como tal, el cual resultó sumamente entretenido, aunque tal vez le sobren unas 50 o 100 páginas. Es un paseo por la historia del arte plástico, arquitectónico, escultural y matemático con φ como eje central hilador. Además de dar ejemplos de como se expresan los números en la naturaleza, explica con profundo detalle ciertas propiedades matemáticas en sus numerosos apéndices e invita al lector a pensar sobre porque la matemática encaja también como explicación del universo. Por momentos se va por las ramas pero casi siempre de forma afable y atrapante. Son altamente rescatables los pasajes donde el autor trata con ácida ironía los esfuerzos de los numerólogos de meter a φ lugares arbitrarios cualquiera, como en las dimensiones de Panteón. En mi opinión es un libro altamente recomendable para el aficionado a la matemática y mi calificación (hace mucho que no califico libros con estrellas) es .

En segundo lugar quisiera comentar algunas nerdeadas matemáticas inspiradas en el la lectura de este libro. Empezando por lo principio, presentar a quienes no conozcan el número φ, en las palabras de Euclides:

Se dice que una línea recta está dividida en el extremo y su proporcional cuando la línea entera es al segmento mayor como el mayor es al menor

La frase dividida en el extremo y su proporcional hace referencia a la proporción de la que estamos hablando. La definición para mortales es:

La longitud total a+b es al segmento más largo a como a es al segmento más corto b

El valor en cuestión se puede calcular despejando algebraicamente tomando b=1, y es:

Puede verse que, al igual que π(pi), es un número irracional. Pero, a diferencia de π, es un número algebraico, lo que lo emparenta bastante con teoría de números. Sin embargo, su expresión como fracción continua compuesta de solo 1s (unos), hace que converja muy lentamente, convirtiéndolo en el más irracional entre todos los irracionales. En términos llanos, φ puede expresarse como la siguiente fracción continua:

Nótese que, cada iteración de esta fracción continua puede expresarse como (los primeros 15 valores, esta secuencia fue resultado de este script):

2 / 1 = 2
3 / 2 = 1.5
5 / 3 = 1.666666666666666666666666667
8 / 5 = 1.600000000000000000000000000
13 / 8 = 1.625
21 / 13 = 1.615384615384615384615384615
34 / 21 = 1.619047619047619047619047619
55 / 34 = 1.617647058823529411764705882
89 / 55 = 1.618181818181818181818181818
144 / 89 = 1.617977528089887640449438202
233 / 144 = 1.618055555555555555555555556
377 / 233 = 1.618025751072961373390557940
610 / 377 = 1.618037135278514588859416446
987 / 610 = 1.618032786885245901639344262

Aquel lector atento notará que las fracciones tiene cierto patrón. Por un lado, el numerador de cada reglón pasa a ser el denominador en el siguiente. Los números son:
1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,....
¡Exacto! ¡Es la sucesión de Fibonacci! Cada número es la suma de los dos anteriores. En efecto, siendo F(n) el termino n-ésimo de la serie de Fibonacci:

Así es como puede verse una fuerte relación entre φ y Fibonacci. Y si con Fibonacci se puede calcular la proporción áurea... ¿cómo es la relación a la inversa?

Con esta formula se puede calcular el valor del término n-ésimo de la serie de Fibonacci:

Es fácil reconocer que dentro de esa fórmula está φ.

La propoción áurea φ tiene un montón de raras y divertidas propiedades. Entre las más atractivas se encuentran:


En lo personal aprendí muchas cosas nuevas sobre la proporción áurea. Pero en el apéndice 9 (como dije, es un libro con muchos apéndices) se comenta un concepto que me voló la cabeza: la ley de Benford. El tema se toca tangencialmente, promediando el último capítulo, como un ejemplo de como las matemáticas sorprenden. Y sí que lo hacen.

Pero ese será tema del siguiente post.

Asimov, Leibniz, pi, python, floats y evadirse de la realidad

La matemática tiene cosas sorprendentemente lindas. Y una de las que siempre me gustó de forma especial es la formula de Leibniz para el cálculo de π. También conocida como la serie de Gregory-Leibniz es una simpática manera de expresar el más famoso de los números irracionales:

O para aquellos no gustan de las notación sigma-grande:

Como verán, se trata de una serie sumamente simple y elegante. Y como suele ocurrir con estas cosas, aparecen en la vida de uno en momentos extraños, casi que al rescate.

Anoche me hallé frente a la biblioteca buscando nada en espacial. Necesitaba despejar la cabeza, olvidarme de los asuntos terrenales asociados con las complejas interacciones y relaciones humanas.

Fue en esa circunstancia que me encuentro con De los números y su historia de Isaac Asimov (que puede ser descargado desde aquí). Mientras lo hojeaba vi la formula de Leibniz, promediando el capítulo 6 e inmediatamente atrajo mi atención.

Después de expresar la serie, Asimov explica:

<<Ustedes podrán condenar mi falta de perseverancia, pero los invito a calcular la serie de Leibniz simplemente hasta donde la hemos escrito más arriba, es decir hasta 4/15. Incluso pueden enviarme una postal para darme el resultado. Si al terminar se sienten desilusionados al descubrir que su respuesta no está tan cerca de π como lo está el valor 355/113, no se den por vencidos. Sigan sumando términos. Sumen 4/17 al resultado anterior, luego resten 4/19, después sumen 4/21 y resten 4/23, etcétera. Pueden seguir hasta donde lo deseen, y si alguno de ustedes descubre cuántos términos se requieren para mejorar el valor 355/113, escríbanme unas líneas y no dejen de decírmelo. >>
En efecto, la fracción 355/113 parece ser la forma que mejor balancea precisión y simpleza a la hora de arrimarse a π desde lo números racionales, quedando a solo 2.66764189 × 10-7 de distancia. Mucha gente también utiliza la relación 22/7, aunque con menor precisión.

El desafío propuesto parecía interesante. Resultaba una entretenida oportunidad de jugar con floats en python, que siempre a resultado ser bastante tricky (además de ser un excusa para despejar la cabeza).

Empecé por escribir una versión relativamente obvia de la solución, que me dijo que al llegar al cociente 7497257 (n=3748629 en la sumatoria de Leibniz) el valor de la sumatoria estaría en 3.14159292035, quedando a 2.66764081935 x 10-7 de π. Dado que los decimales (floats, representaciones de punto flotante) en Python no se comportan de forma intuitiva, decidí hacer una segunda versión.

En esta versión traté de mudarme al predecible mundo de los enteros. Y obtuve valores levemente distintos. En el cociente 7497247 (n=3748624) se obtiene la primera aproximación mejor que 355/113, que es 3.141592386825... (a 2.66764162 x 10-7 de π)

Tampoco estoy seguro de que la segunda solución sea la correcta. Me pregunto cual será el promedio de valores en las postales que la viuda Asimov seguramente guarda en una caja de zapatos.

El hecho es que fue entretenido intentarlo y tal vez continúe con la experiencia la próxima vez que quiera desconectarme del mundo. Después de todo, como dijo el mismo Asimov: “Los hombres que se acostumbran a preocuparse por las necesidades de unas máquinas, se vuelven insensibles respecto a las necesidades de los hombres”, y hay veces que volverse insensible se ve, erróneamente, como una propuesta seductora. Hay gente que ahoga penas en alcohol, mucho más sano es ahogarlas matemáticas...

BTW, ya que tenía los datos armé un plot de los primeros 1000 valores de la serie para mostrar de forma gráfica la convergencia.

Ahora es momento de volver a la realidad, que buena o mala, irracional o racional (seguro que real al menos), inexacta o precisa, después de este recreo metal ya no se ve tan mal :-)

UPDATE: Sat, 20 Sep 2008 22:18:18 -0300: La solución estaba realmente cerca (en mi misma ciudad) y se llama Facundo, quien dejó un comentario. Bah.. se llama decimal, pero existe gracias a Facundo :). La biblioteca decimal permite manejar de forma exacta una cantidad arbitraria de decimales. La respuesta correcta es........ (redoblantes de suspenso) con n=(7497259+1)/2! aproxima a Π en 3.141592386825668744985771256 a solo 2.66764125 × 10-7 del número irracional. El nuevo script.. aquí.

Gracias Facundo, gracias decimal, gracias Asimov, gracias Leibniz....

sudoku, algorítmos genéticos, la estrategia backtracking, programación lineal y R

/* Habiendo leído El curioso incidente del perro a medianoche le tomé cierto buen gusto a los títulos largos :-) */

Hace unas semanas, Mahoo me regaló un pequeño libro con un montón de sudokus para resolver. Si bien conocía el juego, nunca me atrajo particularmente. En esos días, yo estaba buscando algún tema original para uno de los trabajos prácticos de Inteligencia Artificial. La consigna se refería al uso de algoritmos genéticos para la resolución de algún problema. Lo importante no era el programa, sino el análisis de su comportamiento: estudiando, por ejemplo, qué método de cruzamiento se aplica mejor o qué función de aptitud es más representativa.

Viendo como Mahoo se entretenía con alguno de ellos (práctica en la que también se iniciaba) se me ocurrió que un resolver de sudokus podría ser una linda propuesta para mi TP.

Los algoritmos genéticos intentan emular a la naturaleza, a base de prueba y error, favoreciendo a las soluciones más aptas y castigando a las que no lo son tanto. Suponiendo que el cruzamiento de las buenas-soluciones generan aún-mejores-soluciones. Se trata de la búsqueda probabilística, muy parecida a meter muchas soluciones en un cubilete gigante y agitarlas hasta encontrar algo útil.

Al tratarse de un problema determinístico, la solución dista muchísimo de ser óptima. De hecho, ni siquiera se asegura que vaya a haber una solución. Pero me pareció algo simpático de intentar.

El resultado, escrito en mi rústico Python, puede ser bajado de aquí (incluye instrucciones para correrlo en Windows). No, no esperen que funcione bien, advertidos están. Es uno de los peores métodos posibles para este tipo de problemas. Sólo llega a una solución cuando la cantidad de incógnitas es (muy) limitada. Se plantean soluciones a base de llenar de random cada uno de los casilleros vacíos y chequeando cuantos valores se repiten en cada fila, columna y región. Cuando este chequeo da 0, se llega a una solución. Bastante cavernícola como notarán.

Es natural pensar en cuál sería la forma correcta de llegar a una solución. Recordé las clases de Investigación Operativa y la utilización del método simplex para la solución a problemas con restricciones. De hecho, es una solución que muchos proponen. No se si alguno de ustedes lo ha intentado. Yo sí, durante las clases aburridas me senté a pensar como sería el conjunto de ecuaciones y me encontré con una demencial y frustrante cantidad de ecuaciones. Acá hay una linda explicación, la que resumo, haciendo énfasis en las complicaciones:
Se necesitan variables binarias de la forma xijk donde 1 significa que el símbolo k (de 1 a 9) va en la celda (i,j) de la solución. 0 significa que no está. Esto genera un total de 729 variables (93) lo que, como mínimo, asusta. Veamos las ecuaciones:

(1) Para que cada celda (i,j) tenga un símbolo y éste sea único. Se necesitan 81 ecuaciones como éstas (una por cada celda).
(2) Para que en la fila i cada símbolo esté una vez y que en todas las columnas sea distinto. Se necesitan 81 ecuaciones como éstas(una por cada columna y cada símbolo posible).
(3) Para que en la columna j cada símbolo esté una vez y que en todas las filas sea distinto. Se necesitan 81 ecuaciones como éstas(una por cada fila y cada símbolo posible).
(4) Para que en cada región cada símbolo esté una y solo una vez. Se necesitan 81 ecuaciones como éstas (una por cada región y cada símbolo posible).
(5) Representa el enunciado, es decir, el problema a resolver. Por ejemplo, x115=1 significa que en la celda (1,1) hay un 5. Dependiendo cuantos casilleros vengan asignados es la cantidad de igualdades como estas que se necesitan; típicamente, ~30.
(6) Restringe las variables al conjunto binario.

¡En total son más de 350 ecuaciones! Hay propuestas con menos ecuaciones, pero siempre serán muchísimas. Evidentemente, ésta tampoco es la solución óptima (aunque es mucho mejor que los algoritmos genéticos). Y su problema radica en que, desde algún punto de vista, el método simplex no se ajusta al problema. Gráficamente, las ecuaciones representan semiplanos en un hiperespacio multidimencional (de 9 dimensiones?) cuya intersección es, en caso que haya una única solución, un único punto. /* Puede que le esté pifiando en esta conclusión final y cualquier ratificación o rectificación es bienvenida */

Simplex está pensado para restricciones de máximos y mínimos donde el objetivo es maximizar o minimizar y las variables binarias intentan ampliar el método para restricciones de este tipo. Da soluciones múltiples en forma de un polígono, donde a una o más se las llama óptima, porque maximiza o minimiza la función objetivo. Es claro que el problema del sudoku no usa sus ventajas y abusa de sus debilidades.

Curiosamente, cuando comenté este problema a un reciente conocido, surgió que también él tuvo que hacer un trabajo práctico que resolvía sudokus. En este caso, el TP giraba entorno a la gestión de la pila para recorrer árboles. Particularmente, árboles empleados en la estrategia de backtracking. Así pues, éste es su programita, sencillo, simpático y rápido. Gracias Nacho :).

Los métodos basados en búsqueda combinatoria sobre árboles parecen ser particularmente buenas para el problema del sudoku. En la wikipedia figuran dos interesantes formas: backtracking y ramificación y poda.

Para terminar, una última casualidad: Por razones que no vienen al caso ahora y que seguramente provocarán un nuevo post en el futuro estoy aprendiendo un lenguaje para el procesamiento de señales llamado R. Estoy trabajando mucho con una biblioteca llamada sound. Buscándola en el repositorio de bibliotecas, me encuentro con una llamada sudoku. No resistí la tentación de probarla :). Un ejemplo de su uso:

> install.packages("sudoku")
> # es necesario instalar tcltk si se quiere la parte gráfica
> install.packages("tcltk")
> library(sudoku)
> library(tcltk)
> library(tkrplot)
> miSudoku <- generateSudoku(Nblank=50, print.it=TRUE)
  +-------+-------+-------+
  |   6   |   7   |   8 2 |
  | 4 8   |     1 | 7     |
  |       | 8 2   | 5     |
  +-------+-------+-------+
  |       | 1     | 9 7   |
  |       |     2 |   5 1 |
  | 5 1 6 |   9 7 |       |
  +-------+-------+-------+
  |       |   4 8 |       |
  | 6 5   | 7   9 | 8     |
  |       |       |   9 7 |
  +-------+-------+-------+
> # la variable miSudoku es una matriz
> miSudoku
       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
 [1,]    0    6    0    0    7    0    0    8    2
 [2,]    4    8    0    0    0    1    7    0    0
 [3,]    0    0    0    8    2    0    5    0    0
 [4,]    0    0    0    1    0    0    9    7    0
 [5,]    0    0    0    0    0    2    0    5    1
 [6,]    5    1    6    0    9    7    0    0    0
 [7,]    0    0    0    0    4    8    0    0    0
 [8,]    6    5    0    7    0    9    8    0    0
 [9,]    0    0    0    0    0    0    0    9    7
> #La biblioteca incluye una función para para solucionarlo
> solveSudoku(miSudoku)
       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
 [1,]    1    6    5    9    7    3    4    8    2
 [2,]    4    8    2    5    6    1    7    3    9
 [3,]    3    7    9    8    2    4    5    1    6
 [4,]    2    4    8    1    5    6    9    7    3
 [5,]    7    9    3    4    8    2    6    5    1
 [6,]    5    1    6    3    9    7    2    4    8
 [7,]    9    3    7    2    4    8    1    6    5
 [8,]    6    5    1    7    3    9    8    2    4
 [9,]    8    2    4    6    1    5    3    9    7
> # Incluso trae soporte gráfico para resolverlo en una ventanita :)
> playSudoku()

Así fue como, un simple regalo, un TP universitario y un montón de simpáticas casualidades tuvieron su punto de conexión. Y que divertido que resultó ser :). Ahora tengo que escribir el informe sobre los algoritmos genéticos en el sudoku. La semana que viene espero publicarlo aquí.

UPDATE August 6th: El informe y los archivos relacionados ya está publicados.