miércoles, 28 de noviembre de 2007

El reto de Chuck Norris‏

Un importante proyecto de decodificacion de mensajes para el gobierno que fúe
desarrollado por el mismisimo Chuck Norris (que escribe cualquier programa en una sola linea)
necesita ser puesto en marcha lo antes posible. Lamentablemente, Chuck Norris eliminó del control
de codigo fuente su libreria de utilidades antes de irse de la empresa para probar
nuevos retos
, por lo que el código ya no compila.
Es necesario que alguien reconstruya las partes necesarias de esta libreria para volver a hacer
funcionar el decodificador. Una pista, nadie hace librerias tan simples como Chuck Norris.

Ánimo y gracias por tu interés y tiempo. Espero te diviertas

Console.WriteLine(
new long[]{
292805444303323170,
-4455112766532738942,
579137309091315972,
1207248581508040306,
7033655460037132288 }
.SelectMany(l => 64.DownTo(0)
.Select(i => (l & ((long)1 << i)) != 0))
.Take(285)
.Select((b, i) => new { b, i })
.GroupBy(p => p.i / 3, p => p.b)
.Select(g => g.ToArray())
.Select(a => 0.To(3)
.Sum(i => a[i] ? (1 << (2 - i)) : 0))
.Select((a, i) => new { c = @" _()/'\,"[a], i })
.GroupBy(p => p.i / 19, p => p.c)
.Select(g => new string(g.ToArray()))
.Aggregate((s, t) => s + "\r\n" + t));

public static class ExtensionesChuckNorris
{
public static IEnumerable.int To(this int a, int b)
{
//TODO: Completar código
}
public static IEnumerable.int DownTo(this int a, int b)
{
//TODO: Completar código
}
}

La oferta, original como pocas, recibió sus 15 segundos de fama (estoy seguro de que si Warhol hubiera conocido Internet, la frase hubiera sido en segundos) siendo vinculada por unos cuantos blogs y foros. A mí me interesaba más resolverla, la verdad. Así que llegué a casa el viernes por la noche y me puse a ello. Si le echamos un vistazo por encima (y somos de los que, al menos, leemos blogs) podemos ver que en ese pedazo de bloque de código aparentemente ilegible se declara un array de tipo long, que se rellena con un montón de números aparentemente sin sentido a los que a continuación y sin un pobre punto y coma que llevarse a la boca se le ejecutan un montón de operaciones con nombres como Select, GroupBy, o Aggregate. Esto tiene toda la pinta de ser LINQ. Primer problema: no tengo ni repajolera idea de LINQ.


Don't panic


Bueno, me dije, ignora ese bloque de momento. Sabes que es un array de tipo long al que se le hacen un montón de transformaciones mediante LINQ. Puedes suponer que el resultado no van a ser números sino caracteres, por la presencia de un sospechoso cast a String y un par de cadenas. Bien. ¿Qué pide exactamente el problema? Concretamente, nos piden que implementemos dos de esas transformaciones: To(int a, int b) y DownTo(int a, int b). Mirando en el código principal, vemos que las llamadas a estas funciones son 64.DownTo(0) y 0.To(3). Es una sintaxis un tanto extraña, y aparentemente no tiene nada que ver con la firma de las funciones, pero puedo suponer que 64.DownTo(0) es equivalente a DownTo(64, 0). DownTo se podría traducir por Hasta, y teniendo en cuenta que el tipo que debe devolver la función es un IEnumerable<int>, podemos suponer que DownTo debe devolver un rango de números desde a hasta b en orden descendente, y que la función To debe devolver un rango de números desde a hasta b en orden ascendente. Vale, ya entendemos el problema. Intentemos implementarlo.


Intento y error


LINQ está disponible a partir de la versión 3.5 del .NET Framework, y sinceramente no tengo ganas de ponerme a buscarlas e instalarlas. El problema de las betas, CTPs, RTMs y demás versiones de early-adopters es que suelen ser algo puñeteras en su instalación y configuración, y el problema ahora era de código, no tenía ganas de comerme la cabeza con configuraciones. Virtual PC 2007 con la imagen completa de Windows 2003 Server y la última Beta de VS2008 al rescate. Sí, es una descarga de 11 Gbs, pero para hacer pruebas locas es perfecto. Una vez tenemos las herramientas necesarias (aunque eché bastante de menos a mi ReSharper, a ver cuándo hacen versión para el 2008), nos ponemos a currar.


Intenté primero hacer las cosas a lo sencillo: un simple bucle for que creara un array de integers desde a hasta b, convertirlo a IEnumerable<int> y a tirar. Pero no hubo manera humana de hacerlo, o yo no la encontré... y menos mal, porque aunque hubiera funcionado ésa no era la respuesta. Tenía que haber una manera de hacerlo mediante LINQ.


Después de mucho googlear, me encontré con unos cuantos recursos valiosos, entre ellos esta wiki sobre LINQ, y sobre todo este artículo de MSDN, que me pusieron sobre la pista correcta. Concretamente, conocí la existencia de la clase System.Linq.Enumerable, concretamente de su operador Range que, mira por dónde, devuelve un rango IEnumerable<int>. Parece que la función To estaba resuelta. ¿Y la función DownTo?



Al principio intenté unas cuantas manipulaciones groseras de Range, pero recordé que el reto me pedía soluciones sencillas, pues tal es el estilo de programación de Chuck... cosa que, viendo sus películas, dudo mucho. Pero ésa es otra cuestión. Seguí leyendo e investigando y me encontré con otro operador de System.Linq.Enumerable, el Reverse, que efectivamente era la solución que andaba buscando. Un poquito de intento y error para hacer las cosas lo más limpias posibles y voilá!: el reto de Chuck está solucionado.


Aquí tenéis el código completo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ilitia
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(
new long[]{
292805444303323170,
-4455112766532738942,
579137309091315972,
1207248581508040306,
7033655460037132288 }
.SelectMany(l => 64.DownTo(0)
.Select(i => (l & ((long)1 << i)) != 0))
.Take(285)
.Select((b, i) => new { b, i })
.GroupBy(p => p.i / 3, p => p.b)
.Select(g => g.ToArray())
.Select(a => 0.To(3)
.Sum(i => a[i] ? (1 << (2 - i)) : 0))
.Select((a, i) => new { c = @" _()/'\,"[a], i })
.GroupBy(p => p.i / 19, p => p.c)
.Select(g => new string(g.ToArray()))
.Aggregate((s, t) => s + "\r\n" + t));

Console.ReadLine();
}
}

public static class ExtensionesChuckNorris
{
/// <summary>
/// Devolvemos un rango enumerable desde inicio a fin.
/// </summary>
/// <param name="a">Valor inicial</param>
/// <param name="b">Valor final</param>
/// <returns>Un rango ascendente</returns>
public static IEnumerable<int> To(this int a, int b)
{
return Enumerable.Range(a, b);
}

/// <summary>
/// Devolvemos un rango inverso enumerable desde fin a inicio.
/// </summary>
/// <param name="a">Valor inicial</param>
/// <param name="b">Valor final</param>
/// <returns>Un rango descendente</returns>
public static IEnumerable<int> DownTo(this int a, int b)
{
//Invocamos To y le damos la vuelta
return Enumerable.Reverse(b.To(a));
}

Y éste es el resultado por pantalla:

Y esto es todo, amigos. Gracias a Fernando Lozano, gerente de Ilitia, que amablemente me ha dado permiso para escribir este post al haber terminado el reto y no aceptar más candidaturas para esa oferta.

Fuente noticia: http://www.picacodigos.com

No hay comentarios: