Skip to content

dart

dart-challenges (2)

Sigo trabajando en completar todos los retos de programación de www.codingame.com.

Hace unos días terminé todos los de nivel 3 y dejé todo el código subido a dart-challenges, aprovechando además para añadir un montón de comentarios en todas las soluciones de nivel 1 y 2, que ya había subido anteriormente, además de pasarlas por el formateador de código de Dart Editor.

En total llevo casi 40 problemas resueltos. Y de todo tipo, que es lo más interesante. Bastante de ellos recursivos, pero siempre con algún detalle que se aparta del algoritmo clásico y que te obliga a pensar la estrategia adecuada en cada caso particular.

Mi siguiente objetivo son los retos de nivel 4. ¡Ya sólo me quedan cinco!

dart-challenges

DartEn las últimas semanas he estado trabajando en escribir una solución en Dart para cada uno de los retos propuestos en http://www.codingame.com. Una web que organiza competiciones de programación cada mes, en las que se plantea una serie de problemas y se da un tiempo para resolverlos en el lenguaje que se quiera entre los disponibles (unos 16 a día de hoy).

La web está bien diseñada, y los problemas se resuelven a través de la propia interface de la misma. Aunque lo que más llama la atención creo que son los problemas en sí, bastante imaginativos algunos y utilizando personajes de la cultura popular en muchos casos: Terminator, Indiana Jones, Thor, Doctor Who, Bender, …

Una vez terminada cada competición se hacen públicos los problemas planteados, todas las soluciones entregadas, y el resultado de las pruebas ejecutadas para comprobar la corrección de las mismas. Adicionalmente cada problema se añade a una página en la que puede intentar solucionarlo cada cual por su cuenta, sin límite de tiempo, y sólo por pura satisfacción.

Los problemas están divididos en niveles de dificultad, siendo 1 el correspondiente a los problemas más sencillos y 4 a los más difíciles. Por ahora sólo he resuelto los de de nivel 1 y 2, y uno de nivel 3 que fue con el que realmente empecé a plantearme resolver todos los demás. Para que las soluciones que he ido escribiendo no se pierdan en el fondo de mi disco duro he creado el proyecto dart-challenges donde estoy subiendo todo el código generado. Aunque ahora que es público creo que debería intentar añadirle más comentarios para que resulte realmente útil para los demás.

Mi idea es intentar resolver ahora todos los problemas de nivel 3, aunque ya he tenido alguna que otra dificultad con los de nivel 2, así que no prometo nada. Para que me resulte de verdad un reto no estoy mirando las soluciones. Bueno, al menos no hasta que escribo mi propia solución, entonces si he mirado alguna que otra para comparar, e incluso escribir una nueva solución si he visto alguna más simple que la mía o que me ha parecido los suficientemente interesante como para portarla a Dart.

La verdad es que me estoy entreteniendo bastante con estos pequeños retos de programación, en vez de estar trabajando en una librería o aplicación más grande.

dart-lzw

DartAyer publiqué dart-lzw, una implementación de LZW en Dart. Un algoritmo de compresión bastante antiguo, superado por otros algoritmos más populares como gzip, pero que sigue siendo utilizado ampliamente hoy en día en la medida que es el algoritmo que usan las imágenes en formato GIF.

LZW es un algoritmo sencillo de implementar. El típico proyecto para estudiantes de segundo año, o para los que tratan de ganar algo de soltura con un lenguaje nuevo, como es mi caso. No lo tenía en mi lista de cosas por hacer, pero encontré por casualidad una referencia al algoritmo y me pareció que el proyecto tenía el tamaño adecuado como para poder hacerlo tranquilamente en mi tiempo libre. Sobre todo aprovechando que ya tenía cierta experiencia con algoritmos de compresión, de cuando porté LZMA, primero a JavaScript y luego a Dart, aunque aquellas fueran traducciones directas del código original en Java, línea por línea, sin llegar a entender el mismo. Experiencias ambas que me gustaron, pero que no me convencieron del todo, al no llegar a entender realmente lo que estaba haciendo, por lo que he aprovechado esta nueva oportunidad para resarcirme de aquello.

Los algoritmos de compresión más comunes se basan en analizar la información a comprimir, intentando detectar secuencias de símbolos que se repiten dentro de la misma, y asignando códigos a dichas secuencias. La compresión se produce de una manera natural en el momento que los códigos son más cortos que las secuencias que representan. Los códigos forman una suerte de diccionario que el programa compresor crea y el programa descompresor utiliza para recuperar la información original. Lo que varía de un algoritmo a otro es la forma en la que construye y almacena el diccionario. LZW es peculiar en este sentido, ya que no incluye el diccionario dentro de los datos comprimidos, sino que define un mecanismo que permite que el compresor y descompresor creen el mismo diccionario de forma independiente.

LZW establece que se tiene que definir un tamaño para los símbolos de entrada, un tamaño mínimo y máximo para los códigos a emitir, y crear un diccionario inicial relleno con los todos los posibles símbolos. En las implementaciones habituales se suele utilizar 8 bits para los símbolos, 9 bits como tamaño de código mínimo, 12 bits como tamaño de código máximo, y un diccionario relleno inicialmente con 256 secuencias de un símbolo cada una, de forma que a la secuencia formada por el símbolo 0 se le asigna el código 0, a la secuencia formada por el símbolo 1 se le asigna el código 1, y así sucesivamente.

El primer símbolo (byte) que lee el compresor se emite tal cual. El segundo símbolo leído se emite tal cual también, pero además se concatena al anterior construyendo una primera secuencia que se inserta en el diccionario asignándole el primer código disponible. A partir de ahí se sigue el mismo procedimiento, concatenando el último símbolo leído a la secuencia anterior. Si la secuencia no se encuentra en el diccionario se emite el último símbolo leído y se inserta la secuencia en el diccionario asignándole el siguiente código disponible. Y si la secuencia se encuentra en el diccionario no se hace nada, simplemente se espera al siguiente símbolo. Con este sencillo proceder, los símbolos que aparecen por primera vez se emiten tal cual, y el diccionario se va poblando con secuencias potencialmente cada vez más largas.

El descompresor funciona de forma análoga al compresor, leyendo códigos, creando secuencias, y comprobando si se encuentran ya en el diccionario. Como los diccionarios se inicializan de una misma forma, tanto en el compresor como en el descompresor, el primer código leído se emite tal cual. El segundo código se emite también tal cual y además se concatena con el anterior para formar una secuencia que se añade al diccionario. A partir de ahí se sigue una lógica similar a la del compresor. Si el código existe en el diccionario se emite la secuencia de símbolos asociada al mismo. Y si el código no existe en el diccionario se crea y se le asigna la secuencia de símbolos en curso. De esta forma tanto compresor como descompresor construyen el mismo diccionario de forma sincronizada.

En la práctica hay otros detalles a tener en cuenta, como el hecho de que cuando se alcanza el tamaño de código máximo se tiene que borrar el diccionario y volver a empezar con el primer código de tamaño mínimo. Para señalar esto se reserva un código, normalmente el primero, que se conoce como “Clear Table”. De igual forma se suele reservar otro código, normalmente el siguiente al anterior, para indicar el fin de los datos comprimidos, que se conoce como “End of Data”. Algunas implementaciones utilizan estos dos códigos, algunas sólo uno de ellos, y otras siempre empiezan emitiendo el “Clear Table” como primer código. Por no mencionar el hecho de que algunas implementaciones emiten el código en LSB y otras en MSB. Y que algunas incluso alinean los códigos a la primera dirección múltiplo de 8 después de emitir un “Clear Table”. De hecho me he encontrado con tantas variaciones que al final desistí de intentar soportarlas todas después de haber añadido a la librería una clase de configuración con siete opciones distintas para permitir controlar el proceso por parte de los clientes de la librería.

Integración continua: drone.io

Los servidores de integración continua se utilizan principalmente para garantizar que todo el código que se sube a los repositorios de un proyecto es correcto. Entendiendo como “correcto” que el nuevo código permite seguir compilando el proyecto sin errores y que pasa las pruebas automatizadas (tests unitarios, de integración, …)

Su funcionamiento es sencillo. Se componen de procesos que corren automáticamente y comprueban cada poco tiempo si ha habido cambios en los repositorios de código, aunque normalmente también admiten que se les programe para, por ejemplo, que se ejecuten a una determinada hora. Pero por lo general se configuran para que cuando se detecte un cambio se descarguen el código, lo compilen si es preciso, ejecuten las pruebas, y si todo fue correcto generen los binarios, empaquetados, o lo que requiera el lenguaje de programación utilizado y la configuración del proyecto.

El resultado de cada ejecución de cada proceso de un servidor de integración continua normalmente se puede consultar a través de una web que proporciona el propio software del servidor. De esta forma cualquiera puede ver si el código actual subido al repositorio compila antes de descargárselo a su máquina local.

Otras de las ventajas, cuando se trata con lenguajes de programación que tienen que compilarse, como C/C++ o Java por ejemplo, es que la generación del ejecutable o empaquetado se realiza siempre en una misma máquina, en un entorno estable y controlado, en vez de en la máquina de un desarrollador cualquiera del equipo.

Los proyectos de código abierto tradicionalmente se limitaban a ofrecer el código fuente y las instrucciones para su compilación en algún repositorio público gratuito, y dependiendo del lenguaje de programación y de las características del proyecto, los binarios y recursos resultantes para los sistemas operativos más extendidos que cada proyecto generaba según sus posibilidades, aunque no todos podían permitirse generar todo para todos los entornos.

Hoy en día, en el desarrollo de cualquier proyecto, medianamente serio, en cualquier empresa, también medianamente seria, los servidores de integración continua son, como se suele decir, un “must”. Es decir, no se concibe el desarrollo de un proyecto sin ellos. Lo único que tiene que decidirse es que software utilizar, ya sea uno gratuito, como Jenkins, o uno comercial, como TeamCity, por poner un par de ejemplos.

Sin embargo, para los proyectos pequeños que algunos desarrollamos en casa y luego liberamos como código abierto esto no suele ser una opción. No tanto por la instalación y configuración del software del servidor, sino por el propio servidor. A la mayoría no nos resulta rentable invertir en la adquisición, mantenimiento y actualización de un servidor de integración continua propio. Afortunadamente, como ocurre con los repositorios gratuitos, también existen servidores de integración continua gratuitos.

Yo en particular estoy ahora utilizando drone.io, que permite utilizarlo de forma gratuita para tantos proyectos de código abierto como se quiera, aunque también tiene tarifas para repositorios privados. Yo lo utilizo principalmente porque tiene un buen soporte para Dart, el lenguaje que llevo utilizando para mis proyectos personales en los últimos años.

La configuración de los proyectos se realiza a través de una interface web muy clara y bien organizada. Los parámetros básicos que se tienen que indicar para cada proyecto son la url del repositorio, el lenguaje de programación (C/C++, Dart, Erlang, Go, Groovy, Haskell, Node, Java, PHP, Python, Ruby, Scala, …), las variables de entorno, y los comandos (Linux) a ejecutar para compilar el proyecto y ejecutar las pruebas.

Si el proyecto utiliza una base de datos, la configuración permite indicar el tipo de base de datos que se necesita para la ejecución de las pruebas (MySQL, PostgreSQL, MongoDB, Redis, Memcache, …) El servicio creará automáticamente una instancia de base de datos del tipo seleccionado para poder ejecutar las pruebas y la destruirá también de forma automática una vez acabadas. La url, usuario y password son siempre los mismos, y están perfectamente documentados en las páginas de ayuda de la propia web.

Otra característica interesante del servicio es que permite realizar deploys automáticos. Es decir, copiar el resultado de la compilación a otro servidor y desplegar la nueva versión si es preciso.

Por último, comentar que el servicio ofrece urls que se resuelven como imágenes y con las que se puede comprobar de un sólo vistazo el estado que se encuentra un proyecto:

La imagen muestra el resultado del último build, de color verde si se ejecutó con éxito, o de color rojo si falló. Pinchando sobre la imagen se accede a una página web donde se muestra el detalle de la ejecución de cada comando como si se tratara de una consola de línea de comandos.

dart-ssrp: SQL Server Resolution Protocol

DartSSRP (SQL Server Resolution Procotol) es un protocolo de Microsoft que permite descubrir todas las instancias de base de datos SQL Server que se encuentran disponibles en una red o en un servidor concreto. Proporciona los nombres de los servidores disponibles, los nombres de las instancias que se encuentran ejecutándose en dichos servidores, sus versiones, y los parámetros de conexión que un cliente necesita para poder conectarse a dichas base de datos.

El protocolo se basa en enviar un mensaje a través de UDP a una dirección IP tipo broadcast/multicast para obtener todas las instancias de base de datos que se encuentran en una red, o a una dirección IP de un servidor concreto conocido para obtener las instancias que se encuentran ejecutándose en dicho servidor.

Cuando un mensaje SSRP enviado desde un cliente llega a un servidor SQL Server, este responde con otro mensaje en el que se listan todas las instancias de base de datos que hay en el servidor. En dicho mensaje de respuesta se incluyen todos los protocolos de comunicación soportados por cada una de las instancias, así como los parámetros concretos de conexión para cada protocolo que un cliente necesita conocer para poder abrir una conexión. Por ejemplo, si una instancia admite conexiones a través de TCP/IP, en el mensaje de respuesta se retorna el número del puerto TCP en el que está escuchando la instancia. O, por ejemplo, si la instancia admite conexiones a través de una pipe, en el mensaje de respuesta se retorna el nombre de la pipe que mantiene abierta la instancia.

Además de esto, el protocolo también permite obtener el número de puerto TCP en el que se encuentra escuchando el SQL Server DAC (Dedicated Administrator Connection), que es un servicio especial de SQL Server que permite conectarse directamente a una instancia, sobre todo para realizar labores de administración y diagnóstico, y que es totalmente independiente del resto de conexiones ordinarias que se abren habitualmente para ejecutar sentencias SQL contra base de datos.

dart-ssrp es una librería cliente que implementa este protocolo y he escrito en Dart, como viene siendo mi norma últimamente, ya que sigo encontrando muy atractiva y productiva la experiencia de desarrollo con este lenguaje. En la práctica no creo que el paquete sea de mucha utilidad, es más bien un ejercicio de programación, en particular con UDP, que es un protocolo que Google ha añadido en la última release de Dart, concretamente la 1.1.1.

El protocolo es bastante sencillo de implementar, ya que los mensajes que se intercambian son muy simples. La parte más complicada que he encontrado en la especificación es el formato que utiliza para intercambiar cadenas de caracteres. La especificación se basa en el uso de MBCS (MultiByte Character Set), sin especificar ningún estándar de representación en concreto, y presuponiendo además que el codepage de la máquina del cliente y del servidor es el mismo. Dart utiliza UTF-16 para representar internamente las cadenas de caracteres, así que la solución ideal sería realizar una conversión de UTF-16 a MBCS para las cadenas enviadas desde el cliente al servidor, y de MBCS a UTF-16 para las cadenas enviadas desde el servidor al cliente. Los algoritmos están descritos en [MS-UCODEREF], pero es bastante material por si solo como para hacer otra librería. De momento me he conformado con transformar directamente los code units de UTF-16 en arrays de bytes y viceversa. UPDATE: Después de escribir esta entrada en el blog he estado buscando un rato y he visto que la clase SystemEncoding parece hacer exactamente lo que necesito.

En otro orden de cosas, comentar que en estos últimos tres meses he trabajado en otros tres proyectos personales distintos. Uno de ellos lo publiqué hace un par de meses (dart-fed), aunque nunca he hablado de él en el blog. Y de los otros dos, uno está en una fase muy temprana de desarrollo, y el otro aunque está casi terminado posiblemente no salga nunca de mi disco duro, ya que el código no está todo lo bien escrito que me gustaría. Ya veremos.