Archivo para la categoría ‘Informática’

Función en Javascript para filtrar filas de una tabla

Como en la entrada anterior de Javascrip para crear solapas, para filtrar las filas de una tabla también existen multitud de ejemplos en la Web, incluidos plug-ins de jQuery.

Pero como a mi me gusta experimentar para aprender, aquí está mi versión del filtrado de filas de una tabla. De momento no es plug-in de jQuery pero, como la vez anterior, no creo que sea demasiado difícil adaptarlo. Y, por supuesto que tiene sus fallos, pero se irán subsanando con el tiempo.

function tablefilter(table_selector, input_selector, search_level, colspan) {
	var table = $(table_selector);
	if(table.length == 0)
		return;

	var input = $(input_selector);
	if(input.length == 0)
		return;

	if(search_level == "undefined" || search_level < 1)
		search_level = 3;

	if(colspan == "undefined" || colspan < 0)
		colspan = 2;

	$(input).val("Buscar…");

	$(input).focus(function() {
		if($(this).val() == "Buscar…") {
			$(this).val("");
		}
		$(this).select();
	});

	$(input).blur(function() {
		if($(this).val() == "") {
			$(this).val("Buscar…");
		}
	});

	$(input).keyup(function() {
		if($(this).val().length >= search_level) {
			// Ocultamos las filas que no contienen el contenido del edit.
			$(table).find("tbody tr").not(":contains(\"" + $(this).val() + "\")").hide();
			
			// Si no hay resultados, lo indicamos.
			if($(table).find("tbody tr:visible").length == 0) {
				$(table).find("tbody:first").append('<tr id="noresults" class="aligncenter"><td colspan="' + colspan + '">Lo siento pero no hay resultados para la búsqueda indicada.</td></tr>');
			}
		} else {
			// Borramos la fila de que no hay resultados.
			$(table).find("tbody tr#noresults").remove();
			
			// Mostramos todas las filas.
			$(table).find("tbody tr").show();
		}
	});
}

Para hacerlo funcionar basta con llamar a la función con dos, tres o cuatro parámetros (a elegir; si no se pasan se usan los predeterminados). El primer parámetro es el selector de la tabla a aplicar; el segundo es el input que se usará para el filtro; el tercero es el número de caracteres mínimos para comenzar la búsqueda (si no se especifica se usan tres); y el cuarto es el número de columnas que se debe extender la fila de información. Un ejemplo de llamada sería:

$(document).ready(function() {
  tablefilter("table#mi_tabla", "table thead tr input#filtro", 2, 3);
});

Internamente esta función usa el método contains() de jQuery para buscar. Este método distingue entre mayúsculas y minúsculas, así que, para hacer mejores búsquedas, se puede indicar que este método no distinga de la siguiente forma:

jQuery.expr[':'].contains = function(a, i, m) { 
  return jQuery(a).text().toUpperCase().indexOf(m[3].toUpperCase()) >= 0; 
};

En caso de que no funcione correctamente, se debe añadir exactamente lo mismo otra vez pero cambiando “.contains =” por “.Contains =“.

Y, cómo no, podéis ver un ejemplo del funcionamiento de la misma así como descargarlo.

Función en Javascript para crear solapas (tabs)

Existen multitud de snippets y plug-ins de jQuery para hacer solapas con diferentes partes de una misma página, pero ninguno de ellos con las características que he implementado en esta función (que —todavía— no es un plug-in de jQuery). La principal característica que quería conseguir era la simplicidad.

La mayoría de estos plug-ins o funciones requieren que se haga un poco de marcado en HTML para delimitar tanto el conjunto de solapas como cada una de ellas, con lo que, al menos, habrá que marcar un <div> general y un <div> por cada solapa.

Con esta función lo que se pretende, como he comentado antes, es que ese marcado adicional sea innecesario usando sólo lo que ya existe dentro de la página para crear las solapas.

Es por esto que esta función lo que hace es, dentro de una caja (o del cuerpo completo del HTML), convertir en solapa todo el contenido que hay entre diferentes encabezados del mismo nivel (<h1>..<h6>), es decir, si existe una página bien formada con sus correspondientes encabezados, mediante esta función es trivial mostrarla como solapas.

El código de la función —y, repito, no es un plug-in para jQuery pero no sería demasiado difícil adaptarla— es el siguiente:

function Tabs(element, tabElement) {
    // Si no existe la caja de donde coger los elementos, nos salimos.
    if($(element).length == 0)
        return;

    // Si ya las hemos creado, no lo volvemos a hacer. De momento sólo se soportan unas tabs por página.
    if($(element).find("ul#tabs").length > 0)
        return;

    // Creamos una lista con los elementos de las solapas (serán los botones).
    var tabList = $("<ul>", { id: "tabs" });

    // Cogemos lo que hay entre cada 'tabElement' y lo metemos en un div
    var counter = 1;
    $(element).find(tabElement).each(function() {

        // Creamos una solapa para cada elemento y asignamos el evento "click".
        $(tabList).append($("<li>", { text: $(this).html(), id: "tab_" + counter }).click(function() {
            // Cuando hacemos click en la solapa, quitamos la selección.
            $(element).find("ul#tabs li").removeClass("selected");
            $(this).addClass("selected");
            var id = $(this).attr("id").substring(4);   // de "tab_1" nos quedamos con "1".
            $(element).find("div.tab_content").removeClass("selected");
            $(element).find("div.tab_content#tab_content_" + id).addClass("selected");

            // Reemplazamos la URL para que se refleje el cambio y, si se recarga la página, se recargue en el sitio correcto.
            var newurl = window.location.pathname;
            newurl = newurl.substring(newurl.indexOf("#") + 1) + "#tab_" + id;
            window.location.replace(newurl);

            // Volvemos a la parte de arriba de la página para que no queden ocultas las solapas por culpa de Drupal y su barra de administrador.
            // Otra opción podría ser volver un poco, en lugar de al principio. Por ejemplo: $(document).scrollTop($(document).scrollTop() - 10);
            $(document).scrollTop(0);
        }));

        // Metemos lo que hay entre los 'tabElement' en un div.
        $(this).nextUntil(tabElement).wrapAll("<div class='tab_content' id='tab_content_" + counter + "'></div>");

        // Borramos el elemento.
        $(this).remove();

        // Estos divs se ocultan con CSS.
        counter++;
    });

    // Añadimos la lista al div principal (pasado como parámetro).
    $(element).prepend(tabList);

    // Obtenemos el fragment (o hash) de la URL.
    var hash = window.location.hash;

    // Obtenemos el número de tab que sabemos está detrás de "_".
    hash = hash.substring(hash.indexOf("_") + 1);

    // Mostramos la primera solapa con el primer contenido (div) si no hay fragment. Si hay fragment
    // mostramos el que toca según el mismo (el fragment es #tab_x donde x es el número de tab [1..n]).
    if(hash != "" && $(tabList).find("li#tab_" + hash).length != 0) {
        $(tabList).find("li#tab_" + hash).addClass("selected");
        $(element).find("div.tab_content#tab_content_" + hash).addClass("selected");
    } else {
        $(tabList).find("li:first").addClass("selected");
        $(element).find("div.tab_content:first").addClass("selected");
    }
}

Como las solapas se crean con una lista (<ul>), se debe poner un poco de CSS para que se adapte, igual que se debe poner CSS para ocultar las solapas que no están visibles (en lugar de hacerlo con el propio Javascript modificando los atributos directamente, prefiero hacerlo con CSS añadiendo y/o quitando clases). El CSS básico (con un poco de estilo gráfico también) es el siguiente:

ul#tabs {
    margin: 10px 0;
    padding: 0;
    border-bottom: 1px solid silver;
    line-height: 29px;
}

ul#tabs li {
    display: inline-block;
    margin: 0 0 -1px -1px;
    padding: 0 10px;
    cursor: pointer;
    border: 1px solid silver;
    background: #DDDDDD;
    color: gray;
}

ul#tabs li.selected {
    background: white;
    color: #444444;
}

ul#tabs li:hover {
    background: #EEEEEE;
}

div.tab_content {
    display: none;
}

div.tab_content.selected {
    display: block;
}

Podéis ver un ejemplo de su funcionamiento y también podéis descargarlo para vuestro uso y disfrute (sin olvidarse de la licencia, claro).

Actualización 2012-06-07: Antonio me comenta que en IE9 (cómo no) no se ven bien las solapas. La lista que hará las solapas, al tener el CSS inline-block para que se muestren en una sola fila pero no se corten las solapas por la mitad, no se interpreta bien, por lo que salen en una lista normal. La solución sería que floten dichas solapas a la izquierda (anulando todo lo anterior del inline-block), más o menos así:

ul#tabs { overflow: auto; }
ul#tabs li { float: left; }

El porqué de los menús globales y su falta de usabilidad

Lo que fueron un par de comentarios en Menéame sobre la usabilidad de Unity —los menús globales en general— en un artículo que nada tiene que ver, lo he convertido en una entrada que llevaba tiempo queriendo escribir, aunque algo menos densa.

El caso es que todo empezó con que las últimas versiones de Ubuntu cuentan con un nuevo entorno de escritorio llamado Unity, que se asemeja más al de Mac OS X que a la forma antigua —más tirando a entornos Windows— y por ello cuenta con una barra de aplicaciones en el lateral izquierdo y un menú de aplicación global en el borde superior de la pantalla.

Son precisamente estos cambios lo que provocan las quejas de no pocos usuarios, pero no por el hecho de cambiar la interfaz, sino porque dichos cambios, precisamente, hacen de este software algo inusable. Aunque bueno, por ser un poco menos radicales lo dejaremos en “poco usable”. Y es poco usable porque ha sido pensado para pantallas pequeñas y para ejecutar aplicaciones a pantalla completa dejando muy de lado las pantallas grandes (de 20 pulgadas en adelante) con aplicaciones con ventanas pequeñas distribuidas a lo largo y ancho de todo el escritorio. Además de que es muy poco configurable y personalizable (si no es con software de terceros).

¿Qué es lo que nos lleva a decir que es poco usable? Empecemos por un par de pequeñas historias. La primera: Mac OS X, allá por sus inicios, justificó su menú global superior argumentando que ubicándolo de esta forma no te sales con el ratón, es decir, por muy rápido que vayas hacia un menú, el borde de la pantalla evitará que te salgas y la probabilidad de acierto de clic en el menú correspondiente es muy alta. Esto no ocurre, por ejemplo, con los menús en Windows, que suelen estar por debajo de la barra de título y puede que, si no eres hábil con el ratón, en lugar de hacer clic en dicho menú lo hagas en la barra. En Mac OS X puedes hacer clic en el menú equivocado, pero por arriba nunca te saldrás, mientra que en Windows tienes los cuatro costados para fallar en la acción.

La segunda historia es algo más moderna, exactamente de Google Chrome: la idea detrás de la ubicación de las pestañas en Google Chrome con la ventana maximizada es la misma que la del menú global. A la hora de cambiar de pestaña haciendo clic con el ratón, por mucho que avances o muy rápido que vayas, no te saldrás de la pantalla y siempre darás en una pestaña. Que puede que falles, sí, pero la probabilidad de acierto es mucho más alta que si las pestañas estuvieran como en el antiguo Firefox, justo por debajo del menú y este por debajo de la barra de título. Por cierto, en Firefox ya se puede hacer lo mismo con las pestañas arriba del todo ocultando el menú.

Con estas dos historias en mente, tenemos que usar un menú global impide que se usen las pestañas de la forma para lo que han sido diseñadas. En Mac OS X y en Unity, por ejemplo, toda esta funcionalidad de Chrome se pierde, como se puede ver en la imagen siguiente:

Pestañas de Chrome en Ubuntu con Unity

Y no sólo eso, un usuario experimentado ¿cuántas veces usa los menús? En general, los usuarios experimentados usan el ratón, usan las barras de herramientas, usan los menús contextuales y, sobre todo, usan las teclas de acceso rápido. Incluso en, por ejemplo, Photoshop, que es un software complejo con muchas opciones, muchos menús, muchas herramientas… el usuario experimentado rara vez usará los menús ya que está totalmente habituado a usar las teclas y el menú contextual.

Como decía antes, en un navegador, ¿para qué sirve el menú? ¿Cuántas veces se usa? La primera vez para configurarlo. Y ya. Si te fijas, Chrome desde un principio ya lo ha sustituido por un sólo botón donde sale un menú contextual. Firefox, con la configuración compacta, ha hecho exactamente lo mismo con su botón naranja. El explorador de Windows (el de los archivos, no el navegador) tampoco tiene menú —que se activa presionando la tecla Alt—. Microsoft Office lo ha sustituido por una barra de herramientas y un botón con menú contextual. Y así con mucho otro software… Pero no es que sea una moda, es que hay estudios que dicen que un menú es poco usable y hay alternativas mucho mejores, entre ellas, las ya nombradas teclas de acceso rápido los gestos en el trackpad que Mac OS X usa con destreza.

Además, otra de las cosas criticables de los menús globales es que, en el caso de querer usar el menú —porque se siguen usando, obviamente, aunque su uso se reduzca con las nuevas interfaces gráficas—, si tenemos una aplicación con ventana pequeña ubicada en el lugar más cómodo de uso para nosotros, puede que sea bastante tedioso usar dicho menú. Por ejemplo, la calculadora:

Cambiar el modo de la calculadora en Unity

En caso de querer cambiar el modo, funcionalidad sólo accesible a través de menús, tendremos que recorrer toda la pantalla para acceder al mismo y luego volver para hacer uso de la calculadora.

Aún podemos seguir con esta falta de usabilidad de Unity: ¿por qué si hay una aplicación maximizada y otra en ventana normal delante, el menú que se muestra en el menú global es el de la aplicación que tiene el foco, excepto cuando haces clic, que es el menú de la aplicación maximizada trayendo esta al frente? Es decir, el menú de arriba es el de la ventana que tiene el foco excepto cuando haces clic en él, que se convierte por arte de magia en el menú de la ventana maximizada. Muy coherente todo, sí.

Pero, venga, no nos quedemos aquí. Unity también tiene cosas buenas. O casi…

La barra y lanzador de aplicaciones a la izquierda está muy bien. Es muy usable porque no te sales hacia la izquierda (el mismo principio que usa el menú global) porque a la izquierda de las aplicaciones no hay ningún control… bueno, eso no es cierto del todo. En Unity los botones de minimizar, maximizar y cerrar ventana están, ¡oh, sorpresa!, a la izquierda. Cuando vas a cerrar una ventana es muy fácil que te salgas hacia la barra de aplicaciones. Una solución es cambiar los botones a la derecha de la ventana.

O también, ¿qué pasaría si la pudiéramos poner el lanzador a la derecha? Digo “pudiéramos” porque Unity no lo permite (de ahí mis quejas por la falta de configuración). En caso de poderse, alguien podría pensar que ahí interfiere con la barra de desplazamiento… pero no. ¿Todavía queda alguien que no use la rueda del ratón? La única queja es el trackpad de los portátiles, pero hoy en día la mayoría soporta scroll don dos dedos o cualquier forma similar.

En conclusión, y ya para terminar este ladrillo, se podría decir, en mi opinión, que la mejor configuración para un lanzador de aplicaciones, menú principal y bandeja del sistema en una interfaz gráfica de usuario es en la parte inferior —donde lo tienen Windows, KDE y Gnome 2.x— ya que no interfiere con ningún control de la ventana; o también en los laterales, siempre y cuando los controles de la ventana no estén en el mismo lado.

Como nota respecto a esta ubicación de elementos y a mi conocida afición por el sistema operativo BeOS/Haiku, tengo que decir que allá por el año 1990, BeOS ya contaba con una interfaz gráfica de usuario donde el lanzador y la lista de aplicaciones abiertas estaba en la parte superior derecha del escritorio, y los controles de las ventanas estaban a la izquierda de la barra de título —teniendo en cuenta que esta barra de título no ocupaba todo el ancho de la ventana sino sólo lo que ocupaba el texto, de forma similar a solapas—, tónica de diseño de interfaces que ha tardado veinte años en llegar al gran público.

Si has llegado a este punto (vaya ganas que tenías de leer :tongue: ), que quede claro que esta es mi opinión y sólo mía, totalmente criticable por supuesto, pero que luego cada uno usará lo que quiera siempre y cuando esté a su gusto.

Galaxy Nexus

Después de dos años y medio de mi primer Android en un HTC Magic, ha llegado la hora de cambiarlo, exactamente por un Galaxy Nexus. Porque, la verdad, que un teléfono de estos, teniendo en cuenta la obsolescencia programada, dure dos años y medio es todo un logro. Pero ya empezaba a fallar, sobre todo la batería —que no llegaba ya al 100 % de carga— y porque cada vez que salía de la aplicación de Twitter tardaba un minuto en mostrarme el escritorio… 528 MHz de procesador y 192 MB de RAM ya dan para poco con las nuevas aplicaciones, a parte de seguir con Android 2.2.

El Galaxy Nexus lo compré a través de Vodafone mediante su programa de puntos. En total, 1000 puntos y 239 € con una oferta de 120 € de descuento. Teniendo en cuenta que libre cuesta más de 500 € no está nada mal. Eso sí, 24 meses de permanencia. ¿Mucho tiempo? Bueno, sería mucho si no estuviera contento con ellos pero, hasta el momento, sólo tengo una queja que quizá comente otro día.

En cuanto al terminal, me quejaba con el Magic que era un poco grande con 3,2 pulgadas de pantalla… y he pasado a este con ¡4,65 pulgadas! Esperemos que no supere la medida estándar del bolsillo de mis pantalones.

En cuanto al hardware, poco hay que decir. Que si un buen procesador, que si mucha memoria, que si bastante memoria de almacenamiento… ¡ah! ¿Que soy poco preciso? Vaya, Apple hace lo mismo y no os quejáis tanto… :troll: . Bueno, venga: procesador ARM Cortex-A9 a 1,2 GHz, 1 GB de memoria RAM y 16 GB de memoria de almacenamiento. ¿Así mejor?

La pega. La gran pega de todos los smartphones: la batería. Todavía no he hecho un cálculo preciso de cuanto podría llegar a durar, pero dudo mucho que pase de un día o día y medio con un uso normal 1.

Pero lo más importante de este cambio de terminal, lejos de todo eso de los megaherzios y gigabytes, es el software. El Galaxy Nexus llega con Android 4.0 ICS, que es la evolución del nunca lanzado Android 3.0 para tablets. Y esto es lo que hace bueno a un dispositivo, con todas las mejoras de dos años de evolución y la ya comentada por aquí actuación de Matias Duarte en cuanto a la interfaz gráfica.

A partir de ahora esperemos que dure, como mínimo, lo mismo que el HTC Magic —que sigue vivo, pero un cajón—. Y a disfrutar :grin: .

1 El uso normal podría ser diariamente un par de llamadas de 10 minutos, la sincronización total (correo y calendario), media hora de Twitter y otra media hora de navegación. ¿Sería más o menos así? ¿Cambiamos algo?

Script en BASH para mostrar las entradas de GRUB2 tal y como salen en el menú

¿No os ha pasado alguna vez que actualizáis un sistema Linux y, después de reiniciar, queréis quitar los kernels antiguos pero no sabéis cuáles son?

La solución más rápida era buscar el archivo /boot/grub/grub.cfg de GRUB 2 y mirar en la zona de abajo cuáles eran las entradas que había y cuáles sobraban. Y luego, ya, desinstalarlas con apt-get purge.

Pero como al hacer un cat sobre grub.cfg se muestra mucha información que cuesta descifrar de un sólo vistazo, he hecho un script (ya sabéis, prefiero hacer un script que hacer las cosas a mano :grin: ) que muestra las entradas de GRUB tal y como salen en el menú de inicio con el fin de identificarlas igual de rápido que al iniciar el sistema.

#!/bin/bash

FILE=/boot/grub/grub.cfg
PATTERN='menuentry'

cat $FILE | grep $PATTERN | while read; do
	LINE=${REPLY:11}
	PROCESSED=`echo $LINE | rev | cut -s -d"'" -f2 | rev`
	if [ "$PROCESSED" == "" ]; then
		PROCESSED=`echo $LINE | rev | cut -s -d'"' -f2 | rev`
	fi
	echo $PROCESSED
done

Si tampoco tenéis ganas de copiarlo y pegarlo, también lo podéis descargar. No os olvidéis de quitarle la extensión .txt (si queréis) para que se parezca más a un comando de Linux y de darle permisos de ejecución con chmod 755 ;) .

Hacer copia de seguridad de tus tweets

Twitter es ese sistema de microblogging que ahora es tan famoso, pero no por lo que puedas escribir, sino por lo que te puedan contestar. De ahí lo de red social.

Uno de los problemas carencias de este servicio, aunque creo que hay más, es que no queda constancia de lo que escribes más allá de sus servidores y ¿qué pasa si quieres hacer copia de seguridad?

Pues tienes dos opciones: o usar una de las múltiples aplicaciones que hay para ello (una búsqueda en Internet dará con muchas de ellas aunque, eso sí, la mayoría tienen características de pago) o usar las aplicaciones necesarias para descargarlos por ti mismo. Más concretamente, usar cURL.

Antes de nada, debes tener en cuenta que, como máximo, sólo se pueden recuperar los últimos 3200 tweets. Esto es debido a una restricción en el API de Twitter. ¿Por qué? Ni idea, pero ahí está.

Y luego hay que tener en cuenta que sólo se pueden recuperar 100 tweets en cada petición, por lo que se harán 32 peticiones. Después de esto y de revisar un poco su API, ya podemos recuperar nuestros 3200 últimos tweets mediante este comando (que se puede ejecutar en Linux o Windows siempre que se use la aplicación cURL):

$ curl -s --user-agent "Mozilla" --insecure "https://twitter.com/statuses/user_timeline/beoxman.xml?count=100&page=[1-32]" > beoxman@twitter.com.2012-03-20.xml

Obviamente hay que sustituir el nombre de usuario (beoxman en este caso) por el vuestro. Después de esto, tendréis en el archivo de salida vuestros últimos 3200 tweets en formato XML.

El siguiente paso es buscar la forma de descargarse, también en formato XML, todos los retweets que hemos hecho (que no están incluidos en el TL) y luego, ya para rematar, descargar todas las menciones y retweets que nos han hecho. Pero bueno, eso ya lo preparamos para otro día después de revisar el API de descargas :) .

Algoritmo de cálculo de la sucesión de Fibonacci en C++

Seguro que todos los que habéis tenido matemáticas en el instituto o universidad conocéis la sucesión de Fibonacci. Y seguro que todos los que habéis hecho algún curso, módulo o carrera de informática la habéis tenido que implementar alguna vez, ¿verdad?

Lo más fácil era seguir las instrucciones del profesor e implementar, sin salirse del guión, la definición matemática de esta sucesión:

f0 = 0
f1 = 1
fn = fn-1 + fn-2 para todo n = 2, 3, 4…

Siguiendo esto estrictamente, en C++ os quedaría:


unsigned long long
fibonacci(int value) {
  if(value == 0) {
    return 0;
  } else if(value == 1) {
    return 1;
  } else {
    return fibonacci(value - 1) + fibonacci(value - 2);
  }
}

Este algoritmo, como podréis observar, es recursivo, es decir, se llama a sí mismo con parámetros diferentes a los que se pasa inicialmente para calcular la solución.

Pero este algoritmo tiene un problema bastante más grave que el de la recursividad (que, dependiendo del número de llamadas recursivas, corremos el riesgo de desbordar la pila) y es el de la duplicidad de llamadas a la función con el mismo valor del parámetro. Es decir, ¿te has parado a pensar cuántas veces se llama a la función fibonacci(int) con el mismo parámetro? Pues te lo digo yo: para un valor inicial de 8, esta función se llama 13 veces con el 0, 21 veces con el 1, 13 veces con el 2, 8 veces con el 3, 5 veces con el 4, 3 veces con el 5, 2 veces con el 6 y 1 vez con el 7. En total 66 veces. 66 veces cuando el valor a calcular es 8. Si tenemos que calcular con el 20, la función se llama un total de ¡21 890 veces!

Digamos que esta función, implementada directamente como se define matemáticamente, es totalmente ineficiente. Probad, si queréis, a calcular el tiempo de cálculo para 20, 40, 50… si tenéis tiempo, claro.

¿Y cómo solucionarlo? Pues mediante programación dinámica. Resumiendo, se trata de reducir el problema principal en problemas más pequeños, calcular la solución para cada problema pequeño y, finalmente, aplicar las soluciones de estos problemas pequeños para solucionar el problema grande.

Para la sucesión de Fibonacci específicamente, de lo que se trata es de no tener que calcular el mismo valor cada vez que se llama a la función, sino de tener una tabla donde están los valores previamente calculados. Si, cuando se llama a esta función el valor no está calculado, se calcula, pero si lo está, se recoge directamente de la tabla evitando toda esa recursividad y cálculos innecesarios.

Una posible implementación en C++ podría quedar así:


unsigned long long
fibonacci_alt(int value) {
	// Variables estáticas con la tabla de valores precalculados.
	static unsigned long long* prev_values = new unsigned long long[value];
	static bool init = true;
	if(init) {
		// Inicializamos sólo una vez.
		for(int i = 0; i < value; i++) {
			prev_values[i] = -1;
		}
		init = false;
	}
	
	// Implementación de Fibonacci comprobando si hay algún valor precalculado.
	if(value == 0) {
		return 0;
	} else if(value == 1) {
		return 1;
	} else if(prev_values[value - 1] != -1 || prev_values[value - 2] != -1) {
		prev_values[value] = prev_values[value - 1] + prev_values[value - 2];
		return prev_values[value];
	} else {
		if(prev_values[value-1] == -1) {
			prev_values[value-1] = fibonacci_alt(value-1);
		}
		if(prev_values[value-2] == -1) {
			prev_values[value-2] = fibonacci_alt(value-2);
		}
		prev_values[value] = prev_values[value-1] + prev_values[value-2];
		return prev_values[value];
	}
}

Usando esta función, ésta sólo se ejecuta el número de veces indicado en el valor. Cierto es que usa más memoria, pero la reducción de tiempo es tan impresionante que merece la pena. De hecho, con este algoritmo, podréis calcular sin problemas la sucesión de Fibonacci para valores mayores de 200. Probad con el algoritmo anterior…

Y, ya de estar, aquí tenéis un programa para probar estos dos algoritmos con alguna funcionalidad extra, como la de la presentación de estadísticas de llamadas a las funciones y demás. Y con un fallo: con el argumento “2” produce una violación de segmento. Pero en general funciona.

Para compilarlo podéis usar el comando:

$ g++ -o fibonacci fibonacci.cpp

Y para usarlo podéis hacerlo así (o usando el comando time para calcular cuánto tarda con cada método):

$ ./fibonacci
fibonacci <number> [-a|-v|-av]
Opciones:
  -a   Usa el algoritmo alternativo.
  -v   Muestra más información en pantalla.
  -av  Usa el algoritmo alternativo mostrando más información.
$ ./fibonacci 20
6765
$

Funciones de comprobación de tipos de datos en C++

Haciendo uso de la STL de C++ y viendo que no tiene ninguna función “sencilla” de comprobación de tipos de datos, he implementado estas funciones para uso y disfrute de todo aquel que las quiera y las necesite. Con sus comentarios y todo.

Por resumir, tenemos la función T StringTo<T>(string,T) (con sus diferentes variantes) para convertir una cadena al tipo determinado en la plantilla. Por ejemplo:


int i = StringTo<int>("42",0);  // i valdrá 42

bool error;
int a = StringTo<int>("42a",-1,error);  // a valdrá -1 y error será true

float f = StringTo<float>("123.45",-1);  // f valdrá 123.45

También está la función bool StringIs<T>(string) (también con sus variantes sobrecargadas) que comprueba si el valor pasado en la cadena como parámetro es del tipo indicado en la plantilla:


cout << boolalpha << StringIs<int>("42") << endl;  // la salida será true
cout << boolalpha << StringIs<int>("42a") << endl;  // la salida será false
cout << boolalpha << StringIs<float>("42") << endl;  // la salida será true
cout << boolalpha << StringIs<float>("123.45") << endl;  // la salida será true

Finalmente tenemos la función string StringFrom<T>(T) que devuelve una cadena con el valor pasado como parámetro convertido:


string value = StringFrom<int>(42);  // "value" contendrá la cadena "42"
string bvalue = StringFrom<bool>(true,boolapha);  // "bvalue" contendrá la cadena "true"

Y, ahora ya sí, la implementación:


#include <string>
#include <sstream>

using std::string;
using std::ios_base;
using std::stringstream;

/**
 * Convierte una cadena en el tipo dado en la plantilla.
 *
 * @param string value Valor a convertir.
 * @param T defaultValue Valor por defecto en caso de error.
 * @param bool& error Esta variable se fija a true si hay error, a false si
 * todo_ está correcto.
 * @param ios_base f Si son números, se puede especificar una base de
 * conversión.
 * @return T Valor devuelto según la cadena pasada.
 */
template <typename T>
T
StringTo(const string value, T defaultValue, bool& error, ios_base& (*f)(ios_base&) = NULL) {
	stringstream iss(value);
	T result;
	if(f == NULL) {
		iss >> result;
	} else {
		iss >> f >> result;
	}
	error = iss.fail() || iss.rdbuf()->in_avail() > 0;
	return (iss.fail() || iss.rdbuf()->in_avail() > 0 ? defaultValue : result);
}

/**
 * Función sobrecargada de la anterior donde se omite el parámetro "error".
 * @see template <typename T> T StringTo(const string, T, bool&, ios_base& (*)(ios_base&));
 */
template <typename T>
T
StringTo(const string value, T defaultValue, ios_base& (*f)(ios_base&) = NULL) {
	bool result;
	return StringTo<T>(value, defaultValue, result, f);
}

/**
 * Función sobrecargada de la anterior con un "const char*" en lugar de
 * un string.
 *
 * @param const char* value Valor a convertir.
 * @param T defaultValue Valor por defecto.
 * @param ios_base& f En caso de que sea un número y haya una base de
 * conversión.
 * @return T Tipo de dato a partir de la cadena.
 */
template <typename T>
T
StringTo(const char* value, T defaultValue, ios_base& (*f)(ios_base&) = NULL) {
	return StringTo<T>(string(value),defaultValue,f);
}

/**
 * Devuelve si una cadena contiene el tipo de dato indicado en la plantilla.
 *
 * @param string value Valor a controlar.
 * @param ios_base& f En caso de que sea un número, la base de conversión.
 * @return bool True si la cadena es del tipo indicado, false en otro caso.
 */
template <typename T>
bool
StringIs(const string value, ios_base& (*f)(ios_base&) = NULL) {
	stringstream iss(value);
	T result;
	if(f == NULL) {
		iss >> result;
	} else {
		iss >> f >> result;
	}
	return !iss.fail() && iss.rdbuf()->in_avail() <= 0;
}

/**
 * Convierte un valor del tipo indicado en la plantilla a cadena de caracters.
 *
 * @param T value Valor.
 * @param ios_base& f En caso de que sea numérico se puede especificar la base
 * de conversión.
 * @return string Cadena con el valor convertido.
 */
template <typename T>
string
StringFrom(const T value, ios_base& (*f)(ios_base&) = NULL) {
	stringstream result;
	if(f != NULL) {
		result << f;
	}
	result << (T)value;
	return result.str();
}

Actualización 2012-03-07: Releyendo un poco el código y habiendo leído algo de documentación adicional, creo que quedaría más elegante si llamamos a las funciones de otra manera (con la misma implementación), por eso de simplificar. Más o menos así:


namespace string {

template <typename T> T to(const string) {…}

template <typename T> T is(const string) {…}

template <typename T> const string from(T value) {…}

}

Con lo que los ejemplos quedarían:


int i = string::to<int>("42");  // 42
bool isint = string::is<int>("42");  // true
string value = string::from<float>(123.45);  // "123.45"

Por simple elegancia :grin: , ya que la funcionalidad es la misma. Pues quizás lo implemente…

Generar diccionarios de palabras en texto plano

Un diccionario, en la jerga informática, es un archivo de palabras en texto plano —es decir, sin formato— donde generalmente hay una palabra por línea. Este tipo de diccionarios tiene muchos usos, entre ellos la de actuar como base de correctores ortográficos como, por ejemplo, el diccionario de Mozilla Firefox.

En Linux existe un paquete de software llamado Aspell que es la base de la mayoría de correctores ortográficos que se usan en este sistema operativo. Y, Aspell, obviamente, también tiene muchas palabras de diferentes idiomas que usa para su cometido, sólo que los tiene en su formato interno.

Pero, ¿qué pasa cuando queremos obtener un diccionario para nuestros propios fines? Por ejemplo, para instalarlo como corrector ortográfico de Eclipse. Pues para ello, haremos uso, en Linux, del nombrado paquete Aspell en su forma de comando de consola. El famoso comando, que luego explicaré, es este:

$ aspell --lang=es dump master | aspell --lang=es expand | tr ‘ ‘ ‘\n’ > spanish-dict.txt

Este gran comando, en realidad son cuatro (separados por ‘|’ y el último por ‘>’):

  1. aspell --lang=es dump master: Vuelca todas las palabras del diccionario interno de Aspell a formato de texto plano y una por línea, con la salvedad de que dichas palabras tienen códigos específicos para poder expandirlas (por ejemplo, poder expandir el infinitivo de un verbo en todas sus formas).
  2. aspell --lang=es expand: Dada una entrada (que en el comando es la salida del comando anterior), expande cada palabra de cada línea en todas las que puede generar. Por ejemplo, lo que he comentado antes, un verbo en todas sus formas. Esta expansión se hace en la misma línea, es decir, todas las formas verbales, por ejemplo, aparecerán en la misma línea separadas por espacios.
  3. tr ' ' '\n': La entrada de este comando es la salida del anterior, transformando todos los espacios en retornos de carro. Con esto se consigue que, ahora sí, cada palabra esté en una línea.
  4. > spanish-dict.txt: Finalmente, la salida del comando anterior se redirige a un archivo cuyo nombre es “spanish-dict.txt”.

Ahora, en el archivo spanish-dict.txt tenemos 1 250 789 palabras, una por línea, que podremos usar para lo que queramos.

P.D.: Llegados a este punto, os podéis preguntar para qué sirven, además, estos diccionarios, porque sólo como corrector ortográfico podría parecer poco, ¿no? Pues lleváis razón. Una de las cosas donde más se utilizan estos diccionarios, a parte de corregirnos las faltas, es para crackear contraseñas. El método se llama ataque de diccionario y consiste en coger cada una de las palabras del archivo e ir probando si coinciden con la contraseña. Y, como la mayoría de las contraseñas que usamos son débiles, pues suele ser bastante rápido el descifrado.

Sesión remota gráfica en Linux

Cuando accedemos a un ordenador, generalmente lo hacemos en modo consola, es decir, estamos delante de la pantalla, el teclado y el ratón y todo lo que hagamos mediante la interfaz lo hacemos en la máquina que tenemos delante.

Pero ¿qué pasa cuando queremos trabajar en otro ordenador pero sin movernos del nuestro? Bien porque está lejos, bien porque somos unos vagos… :grin:

En Windows es de sobra conocido el Protocolo de Escritorio Remoto, donde nos conectamos a cualquier ordenador con Windows cuyo equipo esté compartido (que acepte conexiones remotas) y tenemos, en una ventana o en pantalla completa, el escritorio completo del ordenador remoto con el que podemos trabajar como si estuviéramos delante.

Y, en Linux, ¿podemos hacer algo similar? La respuesta corta es: por supuesto. La respuesta larga incluye la siguiente explicación:

El sistema operativo GNU/Linux cuenta con un servidor gráfico (la aplicación que gestiona la interfaz gráfica de usuario) llamado X.org, que es una implementación del sistema X Window System. Este servidor es el programa encargado de gestionar las pantallas, las ventanas y, también, las conexiones remotas a otros equipos para tener interfaz gráfica remota. El protocolo usado para estas conexiones remotas, que es lo que nos interesa, se llama XDMCP.

Este servidor gráfico se apoya en otra aplicación, llamada Display Manager, encargada de iniciar dicho servidor gráfico a la vez que muestra la pantalla de acceso, es decir, donde se pone el usuario y la contraseña. Y seguro que los usuarios de Linux conocéis más de un gestor de pantalla: xdm, gdm, kdm, lightdm

A partir de este punto donde ya sabemos, más o menos, cómo funciona esto de la gestión de la interfaz gráfica, lo que nos queda para habilitar el acceso remoto en Linux es configurar el gestor de pantalla (display manager) del ordenador remoto al que queremos acceder para que tenga activado el protocolo XDMCP.

Hay que tener en cuenta que cada gestor usado (como hemos dicho: gdm, kdm, lightdm…) tiene su propia configuración, pero para el más moderno y usado en la última versión de Ubuntu, lightdm, la configuración está en el archivo /etc/lightdm/lightdm.conf (para el resto se puede ver su configuración usando el manual o, en una consola, con el comando man <dm>). En este archivo de configuración hay que activar el protocolo XDMCP añadiendo las siguientes líneas en dicho archivo:

[XDMCPServer]
enable=true

Y luego reiniciar el servidor lightdm —teniendo en cuenta que se cerrarán todas las sesiones— con el comando:

$ sudo service lightdm restart

Ya tenemos nuestro servidor funcionando y aceptando conexiones remotas. Y, ahora, para acceder a él, lo podemos hacer de dos formas:

La primera es usando el programa Xnest que se instala escribiendo el comando sudo apt-get install xnest. Luego, para lanzar la sesión remota hay que lanzar el comando:

$ Xnest :1 -query remote.server

En el caso de que tengáis en vuestro equipo más de una sesión gráfica iniciada, hay que cambiar el :1 por un número de pantalla que no está usado. Además, remote.server puede ser tanto el nombre de un servidor como su IP.

Con este comando se nos abrirá una ventana donde aparecerá la pantalla de acceso a nuestro servidor remoto como si estuviéramos delante de él.

Ventana de Xnest

La segunda opción es lanzar directamente las X (otra instancia del servidor X.org) desde un terminal mediante el comando:

$ X :1 -query remote.server

Hay que tener en cuenta lo mismo sobre el número de pantalla (:1) y el nombre del servidor que con Xnest.

Con este comando, en lugar de abrir una ventana, lo que hará será cambiar de terminal virtual (VT) —de forma similar a cuando cambiamos entre la interfaz gráfica y una consola con las teclas Ctrl+Alt+F1/F7— a, generalmente, el número 8 (Ctrl+Alt+F8) mostrándonos la pantalla de acceso al sistema remoto.

Con el segundo método realmente parece que estamos delante de la máquina remota ya que no hay ventanas de por medio que aparenten estar en un sistema virtualizado. Además, con las combinaciones de teclas Ctrl+Alt+Fx bastará para cambiar entre equipos de forma muy cómoda y muy rápida, teniendo tanto la sesión local como la sesión remota totalmente activas y funcionales.

Y realmente es muy cómodo no tener que desplazarse para trabajar en otros equipos cuando necesitas la interfaz gráfica. Aunque, bueno, ya sabéis que para administrar sistemas no hay nada mejor ni más rápido que una consola en modo texto, por supuesto :grin: .

Implementación de la clase de sincronización Semaphore con la librería boost (y alguna cosa más)

Una de las primitivas clásicas de la sincronización de procesos e hilos —hablando de informática— es el semáforo. La diferencia entre este y el mutex es que el semáforo se puede bloquear y liberar desde diferentes hilos/procesos mientras que el mutex no; es para proteger una zona de código (el acceso a variables por un sólo hilo).

La librería boost —una de las mejores librerías generalistas para C++ que hay, por no decir la mejor— no hay un semáforo de este tipo propiamente dicho, sino que se usan variables de condición para implementarlo; o semáforos con nombre, aunque estos son más específicos para sincronización entre procesos en lugar de entre hilos. Esta implementación se basa mucho en el estándar POSIX de hilos (pthreads) pero orientándolo a objetos.

Es por esto que, para no tener que usar un variable de condición, un mutex y un contador cada vez que se quiere implementar un semáforo, se puede usar esta clase:

#include <boost/thread.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>

class Semaphore {
	private:
    	mutable boost::mutex fMutex;
	    boost::condition_variable fCondVar;
    	unsigned long fCounter;
    	
	public:
    	Semaphore(unsigned long counter)
          : fMutex(),
            fCondVar(),
            fCounter(counter)
        {
    	}

	    void post() {
    	    boost::mutex::scoped_lock lock(fMutex);
    	    ++fCounter;
    	    fCondVar.notify_one();
	    }

    	void wait() {
    	    boost::mutex::scoped_lock lock(fMutex);
    	    while(fCounter <= 0)
    	        fCondVar.wait(lock);
    	    --fCounter;
    	}
    	
    	bool isLocked() const {
    		return fCounter == 0;
    	}
};

Un buen ejemplo de uso de un semáforo es implementar una cola de datos usada para el famoso problema del productor/consumidor, es decir, tenemos dos procesos, uno que produce datos y los mete en la cola, y otro que los consume sacándolos de dicha cola teniendo en cuenta que, cuando la cola está vacía, el consumidor está bloqueado esperando a que haya más valores.

Para ello, la implementación de la cola podría ser la siguiente:

#include <deque.hpp>

using std::deque;

template <typename T>
class Queue {
	private:
		deque<T> fQueue;
		mutable boost::mutex fAccessLock;
		mutable Semaphore fSem;
		
	public:
		Queue() : fQueue(), fAccessLock(), fSem(0) {}
		
		virtual ~Queue() {}
		
		virtual void push(T elem) {
			fSem.post();
			boost::mutex::scoped_lock lock(fAccessLock);
			fQueue.push_front(elem);
		}
		
		virtual T pop() {
			fSem.wait();
			boost::mutex::scoped_lock lock(fAccessLock);
			T elem = fQueue.back();
			fQueue.pop_back();
			return elem;
		}
};

Y el programa de prueba implementando dos funciones, una el productor y otra el consumidor, y usando hilos de boost sería:

#include <boost/ref.hpp>
#include <boost/date_time.hpp>
#include <iostream>

template <typename T>
void
producer(Queue<T>& q) {
	int counter = 0;
	int times = 5;
	while(!boost::this_thread::interruption_requested() && times-- > 0) {
		for(int i = 0; i < 5; i++) {
			cout << "producer: inserting " << counter << endl;
			q.push(counter++);
		}
		sleep(4);
	}
}

template <typename T>
void
consumer(Queue<T>& q) {
	while(!boost::this_thread::interruption_requested()) {
		cout << "consumer: extracting " << q.pop() << endl;
		boost::this_thread::sleep(boost::posix_time::milliseconds(100));
	}
}

int
main() {
	Queue<int> q(7);
	
	boost::thread p(producer,boost::ref(q));
	sleep(1);
	boost::thread c(consumer,boost::ref(q));
	
	p.join();
	c.join();

	return 0;
}

En este ejemplo, el hilo productor inserta cinco números cada 4 segundos (a ráfagas) mientras que el productor saca un número cada 100 milisegundos. Cuando la cola está vacía, gracias al semáforo el hilo consumidor espera hasta que el productor inserte más números, y así sucesivamente.

Pues sí, pues vale, muy bonito, pero ¿para qué se usa esta cola y el productor/consumidor? Pues, en informática, en multitud de software. Por ejemplo, en el reproductor multimedia que usáis todos los días. El hilo productor es el que hilo que lee del disco duro la música insertando los datos leídos en un buffer (en la cola), y el consumidor es el hilo que saca los datos de ese buffer, los decodifica y los envía a la tarjeta de sonido. Y con el vídeo funciona igual (aparte, claro, de los mecanismos de sincronización entre sonido y vídeo).

Pero en esta implementación hay una cosa que falta: el productor debería parar de insertar elementos cuando la cola esta llena hasta que el consumidor saque algún elemento. Pero esa implementación será para el próximo programa ;) .

11 características que Twitter debería tener

Mucha gente usa Twitter, esa red social tan “rara” que sólo permite 140 caracteres en cada mensaje. Y quizás sea por eso su gran éxito a pesar de que sus características técnicas no han cambiado desde… bueno, casi desde que se creó. Por eso creo que, desde el punto de vista de un usuario (no de un desarrollador), Twitter necesita, al menos, que se implementen estas 11 características:

  1. Edición de tuits: Sería bueno poder editar los tuits, al menos durante un tiempo después de la publicación, para corregir, por ejemplo, faltas de ortografía. No sería bueno poder editarlos siempre ya que con la sucesivas ediciones podrías hacer totalmente incoherente la conversión con el resto de usuarios.
  2. Bloqueo de usuarios sin necesidad de dejar de seguirlos: ¿Sabéis eso de “te sigo por compromiso”? Pues eso. Además, he visto algunos tuits donde ya se demanda esta característica.
  3. Omisión de tuits en el timeline que contengan un determinado hashtag: No estaría demás que se pudiera, en el TL principal, omitir de forma automática los tuits que contengan algún hashtag determinado, por eso de ser, en ocasiones, bastante cansinos.
  4. Poder recuperar todos tus tuits: En caso de tener un cliente de Twitter que no sea el de Web (y creo que con el de Web también), sólo podrás recuperar tus 3000 últimos tuits. Aunque el resto siguen en la base de datos, su API no soporta recuperar más de esta cantidad.
  5. Posibilidad de copia de seguridad de tus tuits: No hay ninguna forma de recuperar todos tus tuits si no es con aplicaciones externas. Y a esto hay que aplicarle el límite del punto anterior. Twitter debería implementar una forma de hacer copia de seguridad de tus tuits.
  6. Respuesta pública: Posibilidad de responder a un tuit de forma pública (que lo vean todos los usuarios que te siguen en lugar de los usuarios que te siguen y los que siguen al que respondes) para así no tener que poner un carácter antes del nombre de quien estás respondiendo y tener que desaprovecharlo.
  7. Implementación de un sistema para “leer más tarde”: Yo, al menos, uso los favoritos como marcadores para leer luego. No tengo ningún tuit favorito, simplemente en mi cliente del móvil los marco (los que tienen imágenes, alguna URL…) para leer cuando esté en un ordenador o tablet. Recordemos las limitaciones de las pantallas pequeñas y, sobre todo, las limitaciones de las conexiones 3G.
  8. Formato del texto: Esta quizás sería la más rompedora con la filosofía de Twitter, pero ayudaría mucho a expresar justamente lo que queremos decir —“emociones” incluidas— con, al menos, negrita, cursiva y tachado.
  9. Enlaces contextualizados: Los enlaces deberían ir como en HTML, enlazando un texto en lugar de que vaya la URL directamente. El contexto es muy importante en la Web y Twitter está haciendo un esfuerzo enorme —entiendo que inconscientemente— para eliminarlo. Lo mismo que los acortadores URL, donde se pierde toda la semántica de las URLs amigables. Pero ese es otro tema.
  10. Estadísticas: Sería buena idea que se proporcionaran estadísticas —principalmente informativas— de uso de Twitter, tanto de los usuarios (tuits por día, horarios de tuiteo, retuits…) como generales (tuis diarios, tuits diarios por países, usuarios activos en tiempo real…).
  11. Avance automático en el cliente Web: En el cliente Web, cuando sale el aviso de que hay nuevos tuits, al hacer clic, la página debería avanzar automáticamente hasta el último que has leído. Además, en la nueva interfaz, se debería marcar de alguna forma el último tuit que has leído (en la antigua se hacía).

Y una característica extra, la duodécima: más caracteres. Aquí hay muchísima controversia. Quizás, como he dicho antes, el éxito de Twitter sea precisamente el de tener un número de caracteres limitados. Pero, al menos a mi, muchas más veces de las que me gustaría se me quedan muy cortos para escribir lo que quiero decir. Y a veces no soy capaz de hacerle caso al famoso mensaje “Tu Tweet contiene mas de 140 caracteres. Tendrás que ser más ingenioso.”.

No estoy diciendo que se ponga un número ilimitado (o muy grande) de caracteres como ocurre con Google+ o Facebook, pero sí alguno más, por ejemplo —por aventurarme con un número— 200; o, mejor, 256 (28, por eso de ser friki :tongue: ). Por ejemplo, las notas de Menéame tienen 1000 caracteres y creo que la mayoría de ellas no pasan ni de los 500, pero cuando hay algo más largo que decir, se puede hacer sin ningún problema.

Pero bueno, esta es sólo mi opinión. Y vosotros ¿qué opináis de estas 11 características que Twitter debería tener? ¿Alguna más? ¿Alguna menos?

¿Pagarías por usar tu distribución de Linux favorita?

Me ha llamado mucho la atención esta encuesta del blog Usemos Linux: “¿Pagarías por usar tu distribución favorita de Linux?”.

Mi respuesta corta: No.

Mi respuesta larga: No porque todas las distribuciones usan el mismo software. Todas. Todas usan X Window System (de momento Wayland está en desarrollo). Todas usan Gnome o KDE con alguna disidente que usa Xfce, LXDE o similares. Todas tienen las mismas librerías. Todas tienen la misma arquitectura multimedia. Y, como no, todas usan, en esencia, el mismo Kernel.

¿Por qué pagar por algo que haga una empresa cuando cualquier otra empresa o particular lo puede hacer sin ningún coste? Incluso, si eres un poco aficionado al tema, te puedes hacer tu propia distribución sin más coste que tu tiempo. Y, ojo, que no estamos hablando del servicio técnico.

Yo, personalmente, pagaría por tener algo diferente (y mejor, se entiende). Algo que haya hecho una empresa para mejorar lo que hay en lugar de ser una mera agrupación de distintos elementos de software, todo con el mismo Kernel, y ponerle un nombre rimbombante.

Si Windows no es de mi agrado a nivel técnico (y lo uso como usuario), Linux tampoco lo es (y también lo uso como usuario). Ya sabéis que yo soy más de BeOS/Haiku :P .

C#

Por necesidades del servicio voy a empezar a programar en C#, así que me he leído un par de manuales y he practicado un poco con ello. Y, la verdad, no me ha convencido demasiado. Así que, por petición popular (si por una persona se le puede llamar “popular”), estas son algunas de las conclusiones que he sacado, aunque si eres muy fan de C# quizás no deberías seguir leyendo :P :

Declaración de tipos

Extrañamemente se usan formas diferentes para declarar lo mismo. Porque declarar una variable o un nuevo tipo de dato debería ser consistente. Por ejemplo, para declarar una variable se usa <tipo de dato> <identificador>;. En cambio, para declarar un alias de un tipo de dato (no hay nuevos tipos) se usa using <identificador> = <tipo de dato>;. Y, ya para rematar, para declarar un delegate se usa delegate <valor devuelto> <identificador> (<parámetros>);.

¿No sería más sencillo y consistente tener una sintaxis común? Por ejemplo: <tipo de dato> <descripción> <identificador>;, con lo que la declaración quedaría:

// Variable:
int i;

// Tipo de dato:
typedef int MyInt;

// Usando 'using' (sin introducir ninguna palabra reservada adicional):
using int as MyInt;

// Delegate:
delegate void(int) MyDelegate;

Con esto la sintaxis siempre es consistente. Tenemos a la izquierda los tipos y a la derecha, siempre, el nombre tanto de la variable como del tipo nuevo como del delegate (que no deja de ser un tipo nuevo).

Nuevos tipos de datos

En C#, al igual que en C++, no se pueden declarar nuevos tipos de datos. Sí, en serio. Lo que estáis pensando ahora mismo es que con typedef sí se puede… pues no. En realidad es un alias del tipo básico. En C++, este código:

typedef int myint_t;
void overloaded_function(int value);
void overloaded_function(myint_t value);  // Error de compilación.

Daría error de compilación porque las dos funciones son iguales. myint_t no es un tipo de datos nuevo, sino un alias de int. En C# ocurre lo mismo, sólo que en lugar de declarar los nuevos tipos con typedef (que no tiene esa palabra reservada) se usa using:

using myint_t = int;

class Main {
  void overloaded_funcion(int value) {...}
  void overloaded_funcion(myint_t value) {...}  // Error de compilación.
}

También se puede hacer alguna triquiñuela, como hacer una clase MyInt que herede de System.Int32. Pero ya habría que implementar ciertas cosas para que funcionase.

Los métodos y su tipo de acceso

Es bastante enrevesado el uso de virtual, override y new para controlar el polimorfismo de las clases. Por ejemplo, en el código:

class A {
	public virtual void Who() { Console.WriteLine("A"); }
}

class B : A {
	public override void Who() { Console.WriteLine("B"); }
}

class C : B {
	public new virtual void Who() { Console.WriteLine("C"); }
}

class D : C {
	public override void Who() { Console.WriteLine("D"); }
}

C c = new D();
c.Who();	// Escribe "D"; lógico ¿no?

A a = new D();
a.Who();	// ¡Escribe "B"! ¿Dónde está la lógica de funciones virtuales aquí?

Es obligatorio que en todos los métodos que se podrán sobreescribir en clases derivadas se ponga virtual; que en todos los métodos que sobreescriben se ponga override; y que si un método oculta a otro se ponga new.

¿No sería más sencillo que todos los métodos fuesen virtuales por defecto (porque cuando haces clases, lo más probable es que las vayas a heredar)? ¿No sería más lógico que cuando declaras un método con el mismo nombre en la clase base, automáticamente se sobreescriba el de la base y, en caso de querer llamar al método de la base, uses super, base o similar?

Por cierto, decidme un caso real donde se use esta funcionalidad.

El problema de la clase base frágil

En este manual se menciona el problema de la clase base frágil, que habla de que en Java podría suceder esto, teniendo en principio estas clases:

class BaseClass {
	public void CleanUp() {
		System.out.println("BaseClass.CleanUp()");
	}
}

class DerivedClass extends BaseClass {
	public void Delete() {
		System.out.println("DerivedClass.Delete()");
	}
	public void CleanUp() {
		Delete();
	}
}

public class Test {
	public static void main(String[] args) {
		BaseClass k = new DerivedClass();
		k.CleanUp();
	}
}

La salida de este programa sería:

$ javac Test.java && java Test
DerivedClass.Delete();
$

Si luego implementamos el método Delete() en la clase base de esta forma:

class BaseClass {
	public void CleanUp() {
		System.out.println("BaseClass.CleanUp()");
	}
	public void Delete() {
		System.out.println("Delete all the world!");
	}
}

Según el manual, ¡podríamos borrar el mundo!

Esto sería un verdadero problema… si existiese. Pero es muy fácil comprobar que no se produce. La salida con la clase modificada es la misma que sin ella. Sólo usando super.Delete() se podría borrar el mundo.

Ámbito global o local

En C#, si una clase oculta un espacio de nombres, para acceder a dicho espacio introduce una nueva palabra que no está reservada, pero que hay que recordar: global. Por ejemplo:

class Main {

  public class System {}

  static void Main(string[] args) {
    System.Console.WriteLine("¡Hola mundo!");  // Error: la clase 'System' oculta
                                               // el espacio de nombres 'System'.
    global::System.Console.WriteLine("Esto sí funciona.");
  }
}

¿No sería más sencillo, en lugar de introducir otro identificador nuevo, usar sólo el operador ::, como hace C++ o, incluso, usar el operador .?

Comportamientos extraños de las variables

Resulta que si declaras una variable de tipo delegate, esta se comporta como una lista de delegates que se llaman todos seguidos:


class Test {
	void Notify1(string text) {
		System.Console.WriteLine("Hello " + text);
	}
	void Notfiy2(string text) {
		System.Console.WriteLine("Goodbye " + text);
	}
}

Test test = new Test();

delegate void Notifier(string text);

Notifier notifier;
notifier = new Notifier(test.Notify1);
notifier += new Notifier(test.Notify2);

notifier("Manolito");

// La salida será:
// Hello Manolito
// Goodbye Manolito

¿Nos aclaramos? ¿Notifier es una variable o es una lista de variables? Y si todos los delegates asignados devuelven algún valor, sólo se devuelve el último ejecutado. ¿Y el resto?

Recorriendo listas

C# añade el foreach, que no es más que lo que se conoce como syntactic sugar para el bucle for de toda la vida evitando que el programador use variables índice:


string[] stringArray = new string[] { /* Inicialización */ };

foreach(var s in stringArray) {
  System.Console.WriteLn(s);
}

Pero no estoy criticando ese syntactic sugar, todo lo contrario, eso me parece muy bien. Cuanto menos código se escriba y haga más cosas, mejor.

Lo que ya no me parece tan bien es que en ese bucle foreach sólo se tenga acceso a los valores de la lista a recorrer y no se tenga acceso al índice de dicha lista. Sí, simplemente a la famosa i que se omite para que el programador escriba menos código.

Y por culpa de esto, en caso de necesitarla, pues tienes que declararla fuera e incrementarla manualmente. Vamos, con en un bucle for.

Anidamiento

No se permiten métodos anidados, es decir, declaración e implementación de métodos dentro de otros métodos. Sí, venga, vale, se puede “solucionar” mediante el uso de delegates, pero eso no es permitir métodos anidados, eso es una chapuza.

Y además, ya para rematar, tampoco se permite declarar estructuras ni clases dentro de métodos. Como está orientado a objetos, pues toda declaración adicional tiene que ir dentro de la clase.

Actualización 2011-12-05: C y C++ tampoco permiten métodos anidados como me indican en un comentario. Esto lo puse porque yo los he usado para resolver algún problema, sólo que no recuerdo ni el lenguaje ni el proyecto en el que lo hice. ¿Pascal quizás?

Atributos

Los atributos del lenguaje también son otra cosa que está muy bien dentro de un lenguaje. Permiten extender la funcionalidad del compilador, por ejemplo, para hacer compilación condicional, controlar entre diferentes versiones o arquitecturas, marcar elementos como obsoletos… pero, me pregunto, ¿por qué la sintaxis no es coherente con el resto del lenguaje y hay que añadir triquiñuelas del estilo [Conditional("Debug")] al código?

Conclusión

Después de despotricar un poco toca una pequeña conclusión:

Si Microsoft tuvo la oportunidad de crear un nuevo lenguaje de programación desde cero, para ellos solos, sin que nadie se pueda meter, sin que nadie le diera lecciones, con la posibilidad de innovar, de mejorar lo que había hasta el momento ¿por qué lo ha hecho tan retorcido e incoherente? Porque sí, en serio, es retorcido. Hasta C++ —que mira que a Bjarne Stroustrup se le fue la olla cuando lo diseñó— es más consistente y coherente que C#. C# parece un batiburrilo de Java, C++ y alguna funcionalidad adicional como los delegates que más que ser un lenguaje nuevo es lo mismo de siempre con otro nombre. Y propietario, claro.

Y, bueno, de la compilación a código intermedio en lugar de a código máquina directamente ya no hablamos. Eso es una decisión que han tomado pero que tampoco es que importe demasiado. Recordemos que esta generación de código es una de las fases de compilación que se puede cambiar sin afectar a la sintaxis.

Concluyendo, a mi, personalmente, no me convence en absoluto. Pero claro, si quieres hacer un proyecto decente para sistemas Windows, no queda otra que hacerlo en C# o pelearte con los punteros en C++.

O usar Pascal ;) .

P.D.: Yo, en realidad, soy más del lenguaje de programación D.

Representar un número en binario en C++

Puede que alguna vez en tu vida como desarrollador de software te veas en la necesidad de representar números en su forma binaria. Hay infinidad de algoritmos para ello, por eso no me voy a enrollar mucho. Aquí te muestro tres en C++:

La forma sencilla e intuitiva

template <typename T>
char*
ValueToBits(T value) {
	int length = sizeof(T)*8;
	char* result = new char[length];
	result[length] = '\0';
	for(int i = 0; i < length; i++) {
		result[length-1-i] = (value & (1 << i) ? '1' : '0');
	}
	return result;
}

Este algoritmo es fácilmente adaptable a C estándar. Basta con quitar la plantilla, sobrecargar esta función con más versiones, una por cada tipo de dato que se quiera convertir, y usar malloc() en lugar de new. Y, por supuesto, no hay que olvidarse de liberar la memoria del valor devuelto, o bien con delete[] si usas new, o bien con free() si usas malloc().

Usando std::strings

Este algoritmo es similar al anterior pero usando std::strings, con lo que evitamos el uso de char* y la reserva manual de memoria.

#include <string>

using std::string;

template <typename T>
const string
ValueToBits(T value) {
	string result;
	for(int i = 0; i < sizeof(T)*8; i++) {
		result = (value & (1 << i) ? "1" : "0") + result;
	}
	return result;
}

La forma elegante

Y ya que estamos en C++, ¿por qué no usar toda la potencia que nos proporciona la STL?

#include <bitset>

using std::bitset;

template <typename T>
const string
ValueToBits(T value) {
	return bitset<sizeof(T)*8>(value).to_string();
}

No me digáis que esta forma no es muy elegante ;) .

¿Qué significan los iconos de inicio de HaikuOS?

Iconos de inicio de HaikuOS

Cuando se inicia el sistema operativo Haiku se presentan una serie de iconos para indicar en qué estado del inicio nos encontramos. Estos iconos significan lo siguiente:

  • Haiku boot: Atom Inicializa los módulos del kernel.
  • Haiku boot: Disk with magnifier glass Monta el sistema de archivos principal en / (rootfs) y monta el sistema de archivos de los dispositivos en /dev (devfs).
  • Haiku boot: Plug-in card Inicializa el gestor de dispositivos.
  • Haiku boot: Disk with leaf Monta el disco del sistema (/boot).
  • Haiku boot: Chip Activa los módulos para controlar las características específicas de la CPU.
  • Haiku boot: Folder Finalización del inicio de los subsistemas (que cargan módulos del disco principal en /boot).
  • Haiku boot: Rocket Carga en memoria e inicia el script (BootScript) que carga los servidores de inicio como el app_server o el input_server.

En una máquina virtual con VirtualBox (en un equipo moderno, todo hay que decirlo) tarda algo menos de 10 segundos en iniciar el sistema. Supongo que en hardware real tardará menos ;) .