Saturday, July 11, 2009

Solving simple International Mathematical Olympiad problems.

Solving simple International Mathematical Olympiad problems by programming is a sadistic stress reliever. They were originally thought to be solved using only paper, pencil and lots of young and smart neurons. Let's amuse ourselves solving them with a bit of code. Afterall, Why not to use the nukes? :D Follow the links for the solutions in AS3, but if you felt tempted give them first a try, some of these problems can even make a nice programming interview exercise (if the position at hand requires minimal mathematical skills from your candidates).

IMO 1960 Problem 01
Determine all three-digit numbers N having the property
that N is divisible by 11, and N/11 is equal to the sum
of the squares of the digits of N.






It's a simple loop with known limits. I guess the average programmer can solve this quickly.

public function solve():void {
txtResult.text = "";
var count:int = 1;
var k:int = 10;
while (k * 11 < 1000) {
// Calculate sum of the squares of the digits
var sumSquares:int = 0;
var n:int = k * 11;
while (n > 0) {
var digit:int = n % 10;
sumSquares += digit * digit;
n /= 10;
}
// Check
if (sumSquares == k) {
txtResult.text += count + ") " + (k * 11) + "\n";
++count;
}
++k;
}
}

IMO 1962 Problem 01

Find the smallest natural number n which has the following
properties:

(a) Its decimal representation has 6 as the last digit.
(b) If the last digit 6 is erased and placed in front of the
remaining digits, the resulting number is four times as
large as the original number n.




A bit involved, because now your loop doesn't have a limit:

public function solve():void {
txtResult.text = "";
var k:int = 1;
var k_up:int = 10; // Minimum (10^p) greater that k.
while (true) {
var n:int = (10 * k) + 6;
var m:int = (6 * k_up) + k;
// Check
if (m == 4 * n) {
txtResult.text += "ANSWER: " + n;
break;
}
++k;
if (k >= k_up) {
k_up *= 10;
}
}
}

IMO 1963 Problem 06

Five students, A,B,C,D,E, took part in a contest.
One prediction was that the contestants would finish in the
order ABCDE. This prediction was very poor. In fact no
contestant finished in the position predicted, and no two
contestants predicted to finish consecutively actually did so.
A second prediction had the contestants finishing in the
order DAECB. This prediction was better. Exactly two of the
contestants finished in the places predicted, and two disjoint
pairs of students predicted to finish consecutively actually
did so. Determine the order in which the contestants finished.






Now we are talking.... Raise your hand if you know what a permutation is. Ok. Now every one of you have to code a working version in five minutes. Counting! :D
I couldn't do it. As always the optimized permutation generator slips through the holes in my mind. So I had to resort to my simple but inefficient recursive generator, it has the advantage that if I forget it, I can deduce it again. Here my ruby version:

def permutations(array)
n = array.length
return [array] if n < 2

rt = Array.new

for i in 0...n
## Create a new array from [array] where the
## [i]-th term has been ignored.
t = Array.new(array)
t.delete_at(i)

tz = permutations(t)
tz.each do |tp|
rt << tp.unshift(array[i])
end
end
return rt
end

Once you have a working generator of permutations the rest is just loop through all the possible outcomes and checking if the conditions are satisfied.
Hmm... pretty boring for human minds, even if there are only 5! = 120 possibilities. It's no wonder that the solution for this problem in my book is a one liner without explanation. I guess this problem deserves the nukes!

public function solve():void {
txtResult.text = "";

// Create array of permutations of students.
var p:Array = permutations(students.split(""));

// Check every possibility.
for (var k:int = 0; k < p.length; ++k) {
var s1:int = singleCorrectPredictions(p[k], guess1.split(""))
var d1:int = doubleConsecutivePredictions(p[k], guess1.split(""))
var s2:int = singleCorrectPredictions(p[k], guess2.split(""))
var d2:int = doubleConsecutivePredictions(p[k], guess2.split(""))
if (s1 == 0 && d1 == 0 && s2 == 2 && d2 == 2) {
// Conditions satisfied, print solution!
txtResult.text += p[k] + "\n";
}
}
}

private function singleCorrectPredictions(master:Array, guess:Array):int {
var rt:int = 0;
for (var k:int = 0; k < guess.length; ++k) {
rt += (guess[k] == master[k])? 1 : 0;
}
return rt;
}

private function doubleConsecutivePredictions(master:Array, guess:Array):int {
var rt:int = 0;
for (var i:int = 0; i < guess.length - 1; ++i) {
for (var j:int = 0; j < master.length - 1; ++j) {
if (guess[i] == master[j] && guess[i + 1] == master[j + 1]) {
++rt;
break; // breaking here because elements are different.
}
}
}
return rt;
}

Take care!

Sunday, July 5, 2009

IMO 1959 Problem 04

The year is 1959. The place: Braşov, Romania, where the first International Mathematical Olympiad is being held. The problem is short, simple and sweet:

Construct a right-angled triangle whose hypotenuse c is given if
it is known that the median from the right angle equals the
geometric mean of the remaining two sides of the triangle.

You can solve it like every high-schooler can do:


Or you can brute force it! :D





Cheers.

Friday, July 3, 2009

Particiones enteras

Celebrando el hecho de que ya termino con mi primer ciclo de la maestría, y que ademas he conseguido el titulo de experto en ProjectEuler haré un post acerca de un problema que me parece interesante. El enunciado es bien simple:

Dado un numero entero "n" ¿De cuantas maneras diferentes se puede descomponer "n" en números enteros positivos?.

La solución aunque simple no es evidente.

Por ejemplo, sea n = 4.
4 puede descomponerse en: ( 1 + 1 + 1 + 1 ), ( 1 + 2 + 1 ), ( 3 + 1 ), ( 2 + 2 ) y ( 4 ). Cada una de estas sumas es una partición entera de 4. Como buscamos solo particiones diferentes hay algunas que no se consideran, por ejemplo, no se esta considerando ( 2 + 1 + 1 ) porque es lo mismo que ( 1 + 1 + 2 ) si ignoramos el orden.

Es fácil entender de donde viene el nombre de partición entera, lo que estamos haciendo es "partir" el numero 4 en agrupaciones diferentes:

Vemos que el numero de formas diferentes en que se puede descomponer 4 es 5.

La función que devuelve el numero total de particiones diferentes de un numero "n" se denota como p(n), entonces: p(4) = 5. ¿Como hacemos para calcular p(n) para un "n" cualquiera? Antes de resolver este problema resolveremos el problema mas general:

Dado un numero entero "n", listar todas las particiones enteras diferentes de "n".

La clave para resolver este problema es considerar particiones ascendentes y ordenadas de menor a mayor.

Una partición ascendente (que abreviaremos: PA) es aquella en la cual se cumple que cada termino es igual o mayor que el termino a su izquierda; así ( 1 + 1 + 2 ) es una PA, pero ( 1 + 2 + 1 ) no lo es.

Una partición es mayor que otra partición si su n-ésimo termino es mayor que el n-ésimo termino de la otra partición (cuando se lee de izquierda a derecha), siendo todos los términos anteriores al n-ésimo iguales (si existen). De aquí que ( 1 + 3 ) sea mayor que ( 1 + 1 + 2 ) porque su segundo termino es mayor que el segundo termino de la otra.

Ordenando las particiones ascendentes para n = 4:

  1. pa1 = 1 + 1 + 1 + 1
  2. pa2 = 1 + 1 + 2
  3. pa3 = 1 + 3
  4. pa4 = 2 + 2
  5. pa5 = 4

Iterativamente no se cómo se pueda generar esta secuencia así que iremos por el camino recursivo. De la lista de arriba podemos ver que las particiones ascendentes de 4 son:

PA de 4 = (pa1 + pa2 + pa3) + (pa4) + (pa5)

o dicho de otra manera:

PA de 4 = (PA de 4 que empiezan con 1) + (PA de 4 que empiezan con 2) + (PA de 4 que empiezan con 4)

Sea f una función recursiva que devuelva particiones ascendentes. Si esta función recibiera como primer parámetro una partición ascendente con la que queremos que empiecen todas las particiones ascendentes devueltas por esta función, podríamos escribir la igualdad anterior como:

PA de 4 = f({1}, ...) + f({2}, ...) + f({4}, ...) -- (1)

Donde usamos las llaves { } para indicar los términos de una partición. Estamos considerando que f puede recibir otros parámetros (que aun no hemos descubierto) y por eso le agregamos los puntos consecutivos como segundo parámetro.

Para resolver este problema es vital darse cuenta que todas las particiones de 4 pueden a su vez ser generadas por la misma función pero enviándole como primer parámetro una partición vacía:

PA de 4 = f({}, ...) -- (2)

Examinando la ecuación (1) vemos que falta el termino f({3}, ...) que correspondería a las particiones ascendentes de 4 que empiezan con 3. De nuestra lista de particiones (al inicio) vemos que no existe ninguna, pero razonemos porque; si existiera alguna PA esta sería de la forma: ( 3 + ... ), ya que 3 es menor que 4 entonces aun tenemos que agregarle mas términos, pero los únicos valores que podemos agregar como siguientes términos serian números iguales o mayores que 3 (porque tiene que ser una PA) entonces la lista de posibles particiones seria: ( 3 + 3 ), ( 3 + 4 ), etc. Todas ellas con sumas mayores que 4, lo que no puede ser, de aquí que:

f({3}, ...) = (conjunto vacío)

Agregando esto a (1):

PA de 4 = f({1}, ...) + f({2}, ...) + f({3}, ...) + f({4}, ...) -- (3)

Remplazando (2) en (3):

f({}, ...) = f({1}, ...) + f({2}, ...) + f({3}, ...) + f({4}, ...) -- (4)

Nos falta una forma de pasar la información del número de quien estamos generando particiones (en nuestro ejemplo de 4). Para esto podemos pasar como segundo parámetro el valor que queremos que sumen los términos que se agreguen a la PA que pasamos como primer parámetro, agregando esto a (4):

f({}, 4, ...) = f({1}, 3, ...) + f({2}, 2, ...) + f({3}, 1, ...) + f({4}, 0, ...) -- (5)

en donde:

f({1}, 3, ...) = ( 1 + 1 + 1 + 1 ), ( 1 + 1 + 2 ), ( 1 + 3 )
f({2}, 2, ...) = ( 2 + 2 )
f({3}, 1, ...) = (conjunto vacío)
f({4}, 0, ...) = ( 4 )

Aquí f({1}, ...) se ha convertido en f({1}, 3, ...) porque si cada PA empieza con 1 (y buscamos las PA de 4), solo necesitamos agregar términos hasta que sumen 3:

PA de 4 que empiezan con 1 = f({1}, 3, ...) = ( 1 + 1 + 1 + 1 ), ( 1 + 1 + 2 ), ( 1 + 3 )

Ya estamos preparados para investigar el comportamiento recursivo de f, vemos que:

f({1}, 3, ...) = [( 1 + 1 + 1 + 1 ), ( 1 + 1 + 2 )], [ ], [( 1 + 3 )]

o en palabras:

PA de 4 que empiezan con {1} = (PA de 4 que empiezan con {1, 1}) + (PA de 4 que empiezan con {1, 2}) + (PA de 4 que empiezan con {1, 3})

en donde:

f({1, 1}, 2, ...) = ( 1 + 1 + 1 + 1 ), ( 1 + 1 + 2 )
f({1, 2}, 1, ...) = (conjunto vacio)
f({1, 3}, 0, ...) = ( 1 + 3 )

o en términos de f :

f({1}, 3, ...) = f({1, 1}, 2, ...) + f({1, 2}, 1, ...) + f({1, 3}, 0, ...) -- (6)

De (5) y (6) podemos obtener pistas de como terminar la recursion, observemos que:

f({4}, 0, ...) = ( 4 )
f({1, 3}, 0, ...)
= ( 1 + 3 )

Es simple deducir de estos ejemplos que si nos pasan como segundo parámetro cero, significa que no necesitamos agregar nada a la lista que nos pasaron como primer parámetro (pues ya suma lo que estábamos buscando), y solo debemos retornar esa lista como respuesta y terminar la recursión, entonces:

f(pa, 0, ...) = pa -- (7)

en donde:

pa = alguna partición ascendente

Además de:

f({3}, 1, ...) = (conjunto vacio)
f({1, 2}, 1, ...) = (conjunto vacio)

se puede deducir que si el segundo parámetro es un numero menor que el último termino de la PA pasada como primer parámetro, entonces la función debe retornar nada y terminar:

f(pa, m, ...) = (conjunto vacío) Si (m < z) ∧ (m > 0) -- (8)

en donde:

z = último termino de pa.

Es fácil demostrar esto ya que los únicos elementos que podemos agregar a pa = {a, b, ... , z} son números iguales o mayores que z, y como m > 0 aún tenemos que agregar términos a pa (pues no esta completa), pero agregando cualquiera de los términos válidos nos dará una suma mayor que m (porque los términos validos son por lo menos iguales a z y z > m) lo que no se quiere.

De (5) y (6):

f({}, 4, ...) = f({1}, 3, ...) + f({2}, 2, ...) + f({3}, 1, ...) + f({4}, 0, ...)
f({1}, 3, ...)
= f({1, 1}, 2, ...) + f({1, 2}, 1, ...) + f({1, 3}, 0, ...)

Es posible deducir que:

f(pa, m, ...) = f({pa, z}, m - z, ...) + f({pa, z + 1}, m - z - 1, ...) + ... + f({pa, m}, 0, ...) -- (9)

en donde:

pa = alguna partición ascendente
z = último termino de pa, o 1 si pa esta vacía.

m > 0 ∧ m ≥ z

La ecuación (9) dice que todas las PA que empiezan con pa (y a las que hay que agregar términos hasta que sumen m) son iguales a la sumatoria de todas las PA que empiezan con pa mas un termino (que va desde z hasta m, donde z es el ultimo termino de pa) en donde se resta el nuevo termino a la suma a la que deben llegar los nuevos términos de las nuevas particiones. Como vemos pa crece en tamaño y m disminuye en valor, siendo evidente por (7) y (8) que este proceso recursivo debe llegar a un fin.

Ademas de (7), (8) y (9) vemos que ya no serán necesarios mas parámetros para esta función pues ya hemos establecido su proceso iterativo y condiciones para detener la recursión.

Resumiendo:

f(pa, m) = f({pa, z}, m - z) + f({pa, z + 1}, m - z - 1) + ... + f({pa, m}, 0) Si (m z) (m > 0)
f(pa, m) = Si (m < z) (m > 0)
f(pa, m)
= pa Si (m = 0)

en donde:

pa = alguna partición ascendente
z = máximo termino de pa, o 1 si pa esta vacía.

La relación recursiva puede escribirse en forma compacta:

Listo!, ya tenemos todo lo necesario para programar.
Usaremos arreglos para representar las particiones e iremos imprimiendo las particiones que vayamos encontrando (no las devolveremos). En código simplificado:

void f(pa, m) {
// Determinar valor de [z].
z = (pa.longitud() > 0)? pa.ultimo() : 1

// Proceso recursivo
for (k = z; k <= m; ++k) {
// Crear nueva particion temporal [pt] agregando
// elemento [k] a la particion [pa].
pt = pa.agregarAlFinal(k)

if (m == k) {
// Si (m - k == 0) imprimir [pt]
imprimir(pt)
}
else {
// Continuar recursion
f(pt, m - k)
}
}
}
Puedes revisar la implementación online en AS3 aquí, los comentarios están en ingles, pero son similares a los comentarios de arriba.




Lamentablemente la solución que he implementado es bastante lenta para valores de n grandes. Los métodos de optimización serán motivo de otro post, incluyendo la historia de como ese genio entre genios llamado Euler encontró una respuesta genial a esta pregunta... después de buscarla por 11 años!

Cuídense.

Saturday, March 22, 2008

Chess problems in your phone

This little thing can make you waste serious time if you like chess problems :)
Only tested in my SonyEricsson W810i, it should run in most of the phones with MIDP 2.0 + CLDC 1.1 support. It requires a phone with at least 176x176 pixels in screen resolution.
It supports UNDO and REDO and has the first move of the solution as a hint (if you feel impatient).



The problems belong to the books:

J. W. Abbott: 121 Chess Problems (1887)
F. Healey: 200 Chess Problems (1866)
[link]

I must thank the guy who converted those PDF files to PGN files, without that I would have been spent more time translating the positions to FEN, I only needed to prune off the bad problems and add the hints, the original PGN files can be found here:
[link]

You can choose from 4 different sets of pieces, every set has been composed by me, so every one of those pixels belong to me!! :D
To say the truth, the "Da Vinci" set was inspired in this article:
[link]


However I guess I couldn't capture the spirit and interchanged the knight by bishop.

DOWNLOAD:
chessProblems.jar
chessProblems.jad

Have a nice day!

Sunday, March 2, 2008

Futuro movil

Me parece que el futuro será móvil.

La epoca de las grandes y ruidosas PC de escritorio esta llegando a su fin :D. Y no me refiero a las costosas y pesadas laptops de ahora sino mi idea va mas por el lado de los celulares de ultima generación que están saliendo al mercado.

Ya estamos viendo laptops cada vez mas pequeñas con discos de estado solido y Linux como SO:








Por otro lado los celulares de ahora son cada vez mas potentes.

Consideremos el iPhone por ejemplo, fue el primero que me demostró que un celular podía ser utilizado para navegar internet. Antes de eso seguro que muchos celulares tenían web browsers, pero la calidad de navegación era deprimente o incómoda, pantallas muy pequeñas, dificultad para mover la pagina, etc. Súmale a eso la tarifa de la llamada y no era muy tentadora la idea tampoco. Tenia que ser Apple quien demostrara que el tema de la web en tu celular es posible y práctico.

El futuro parece converger en un híbrido de las laptops actuales y tu celular. Con el dispositivo ideal:

  • Puedes comunicarte fácilmente con tus amigos.
  • Puedes guardar tu agenda, revisar tu correo, responder tu correo.
  • Puedes escuchar música, ver videos.
  • Puedes jugar juegos.
  • Puedes escribir reportes (o programar) en el camino.

Todo eso es posible en las laptops actuales, pero no en los celulares, actualmente no existe siquiera un celular que sirva realmente para jugar, al menos que yo sepa. Trata de jugar cualquier juego de acción en tu celular y veras que es bastante difícil, e incómodo, los botones son pequeños y duros o están muy cerca uno del otro y el pad de dirección esta en un lugar donde no es fácil de usar.

Necesito un gameboy-celular!! Aunque nada impide soñar que algún día tendremos un iGameBoy.

En cuanto a herramientas de desarrollo, J2ME es lento. Concuerdo plenamente con John Carmack (padre de Doom). El dice que no entiende como un celular con hardware suficiente para ser mejor que un GameBoy Advance con Java se convierte en un CPU con el poder de una IBM PC de 4.77 MHz y encima con controles que apestan!

Google mismo se ha dado cuenta de esto y le ha lanzado una artera puñalada a Sun con su Android. Android hasta ahora es solo una copia del J2ME, claro, sin todas las restricciones del consorcio J2ME y open source, pero sigue siendo Java (aunque usando una máquina virtual completamente diferente). Aun falta algo que permita acceder al procesador directamente, algo tan importante para desarrollar juegos de acción. Google ha dicho que esta en sus planes. Esperemos pues.

Un tercer contendor seria: FlashLite.

Aunque Flash ya tiene en su récord haber desaparecido (para efectos practicos) los applets de la web, su incursion en el mercado de celulares es aun muy limitada y aunque mejora el hecho de la compatibilidad (por el mismo hecho de que solo corre en pocos equipos) los primeros reportes no indican un aumento en el desempeño gráfico importante.
El tiempo dirá, de todos modos el tiempo corre a favor de FlashLite y en contra de J2ME.

Y bueno esta semana decidi portar mis problemas de ajedrez de la semana pasada de AS3 a J2ME como un ejercicio y este es el resultado:

JAR
JAD

Demás esta decir que tuve que experimentar en carne propia el ya famoso "write once, run everywhere", que para efectos prácticos se convierte en "compila en windows, falla donde sea". En mi caso habían dos errores que en el emulador no se notaban pero si en mi celular. Claro que los dos errores eran mi culpa (en este caso), pero se de otros que se han jalado los cabellos por errores esotéricos en la implementación del fabricante. De nuevo, no es culpa de Java tampoco, pero eso les pasa a los chicos de Sun por crear un lema tan pegajoso :).

Solo lo he probado en mi celular W810i y supuestamente debería correr en cualquier dispositivo con soporte CLDC 1.1 y MIDP 2.0.

Notas finales:

- No quiero hacer juegos de accion para celulares, pero puzzles esta bien.
- Java sera por fin WORE? O sera desplazado de nuevo por Flash?
- Microsoft existira en el futuro? Creo que ninguno de esos celulares corre Windows ni IE.... :D
- Cuando sale mi iGameBoy????

Hasta la proxima!

Saturday, February 23, 2008

Problemas de ajedrez

Cuando estaba en los primeros años de la universidad me gustaba jugar ajedrez, hace poco me entro un poco de nostalgia cuando encontré un programita para jugar ajedrez que entraba en mi celular :D. Asi que me hice un pequeño visor de problemas en AS3.

Los problemas pertenecen al libro:
J. W. Abbott: 121 Chess Problems (1887)

Pero use la version PGN que se encuentra aqui.

He quitado los problemas que no tenían solución o múltiples soluciones o soluciones mas cortas que las estipuladas.

Se pueden mover las fichas del tablero pero aun no soporta las reglas de juego (me lazy...) Agregué la primera jugada de la solución como hint. Solo se controla con el teclado.





Espero que te diviertas!

Wednesday, December 26, 2007

Ruby 1.8.6 *stripped* documentation

I have been playing for a while with Ruby, and I'm enjoying it. However I was missing an up to date CHM version of the documentation for the release I'm using (1.8.6). My only source of documentation was the "Programming Ruby" book that comes with the Windows installation, and it was a bit outdated: Ruby 1.6. So I googled for one but only found a CHM version for Rails. Instead of looking more I downloaded the source and used RDoc for generating the CHM file, it was not straightforward: the CHM compiler was crashing, I needed to use the flag: --inline-source and after that I got my flashy new CHM file. But it was full of many empty entries and libraries I don't use right now. I selected some entries and created a *stripped* version. If this helps others like me it would be nice.