El blog de Juan Palómez

10 noviembre 2020

NULL no es algo malo

Filed under: programming, Uncategorized — Etiquetas: , , , — thisisoneball @ 19:08

Ejemplos de casos reales donde alguien quiere evitar los valores NULL a toda costa en su BD y hubiera sido mejor usarlos:

  • En un cajero automático no hay billetes y me intenta mostrar el cajero más cercano de la misma cadena. Me muestra un mapa de África con un punto en el mar (la famosa Null Island https://es.wikipedia.org/wiki/Null_Island) donde se supone que está el cajero más cercano. Alguien ha considerado que cuando la posición de un cajero es desconocida era buena idea poner en la BD latitud = 0 y longitud = 0 en vez de latitud = NULL y longitud = NULL
  • En un campo «Edad» en algunas de las filas de la tabla no se puede rellenar el dato, y por no ponerlo a NULL se acuerda usar -1 como «desconocido» (ya que el campo es de tipo entero):
    • Diferencia de edad entre 30 y NULL (edad desconocida) = NULL (diferencia desconocida)
    • Diferencia de edad entre 30 y -1 = 31, es decir, la diferencia de edad de una persona de 30 años y otra de la cual desconozco su edad, es de 31 años
    • Media de edad de 30, 40 y NULL = 35
    • Media de edad de 30, 40 y -1 = 23
  • En la estación de autobuses de Avenida de América una vez oí la locución «el autobús procedente de (silencio) va a hacer su entrada en la estación». Seguramente un campo de tipo String que está vacío. Si hubiera estado a NULL, dependiendo de cómo esté implementado, puede que no se oyera la frase directamente (o puede que se oyera la misma frase, o «autobús procedente de NULL»). Y aparecería por algún sitio en el sistema algún error que ayudara a detectar y localizar el problema, en lugar de «evitar» el error sin más.
  • Campos fecha en formato Unix Time (https://es.wikipedia.org/wiki/Tiempo_Unix), con valor 0 o -1 para indicar que la fecha es desconocida:
    • 0 en este formato significaría el 1 de enero de 1970 a las 00:00. Espero que nadie esté calculando intervalos de fechas con este dato
    • -1 en este formato podría significar el 19 de enero de 2038 o el 13 de diciembre de 1901
  • Campos fecha en formato YYYY-MM-DD: los «haters» del NULL suelen intentar poner cuando se usa este formato, la fecha 0000-00-00, y el sistema no suele permitirlo ya que no es un día válido del año, así que simplemente le ponen 0000-01-01, 1900-01-01 o cualquier otra cosa, con lo que estamos en las mismas.

Otros problemas independientes del tipo de dato:

  • Campos en los que se necesita que todos los valores sean únicos pero que permitan nulos porque en algunos casos no vamos a conocer el dato: esto está permitido en cualquier SGBD: UNIQUE NULL. Sin embargo, si es UNIQUE NOT NULL, a la segunda vez que metas un dato desconocido (con los famosos valores 0, -1, cadena vacía o similar) tendrás una violación de la constraint UNIQUE que seguramente hará que falle un INSERT o UPDATE de la fila completa y llevará a pérdida del dato completo o un error en la aplicación

29 marzo 2015

Recopilación de frikadas

Filed under: Uncategorized — Etiquetas: , , , , — thisisoneball @ 13:49

 
 

AAlib: reproducir vídeo o jugar a videojuegos en ASCII-art:

Abre las imágenes en una pestaña nueva para verlas a tamaño original:

 

 
 

 
 

 
 

Windows 95 en un smartwatch:

 
 

 
 

Jugar al Quake en un osciloscopio:

 
 

 
 

psDooM: matar procesos del sistema jugando al DooM:

 
 

 
 
Tempest: reproducir música en una radio utilizando las frecuencias que emite un monitor de tubo:

 
 

 
 

Algoritmo que dibuja bitmaps jugando al Tetris:

 
 

 
 

Reproducir música con unidades de floppy disk:

 
 

 
 

Tatuaje de pantalla azul de Windows:

 
 

18 octubre 2009

La ranita feliz

Filed under: programming, Uncategorized — Etiquetas: , , , , , — thisisoneball @ 15:00

Este es un programa que resuelve una de las pruebas del juego del MAME Puzzle & Action: Tant-R, en concreto la de la rana que tiene que ir pisando todas las casillas del tablero sin dejar ninguna sin pisar. Estas son algunas de las pantallas que te pueden salir:

tant0000 tantr tant0001

El programa muestra un tablero como el del juego pero vacío. Si pinchas en una casilla sitúas la rana, y las siguientes que pinches serán las piedras. Cuando hayas terminado de poner piedras pulsas Solución. Si hay más de una solución te seguirán saliendo cada vez que pulses el botón hasta que ya no queden más:

captura

Pincha aqui para bajar el ejecutable y el código

Puedes usar los casos que salen en el juego, como en las tres capturas de arriba, o probar otros.

Si vas a probar los tuyos propios muchas veces pasará que no haya ninguna solución, o que haya tantas que se te quede el programa prácticamente colgado pensando. Poniendo 5 o 6 piedras normalmente saca las soluciones rápido.

Realmente son dos programas separados:

  • Un programa en Lisp que es el que calcula las soluciones (rana.lsp). Recibe por la entrada estándar las posiciones de las piedras y de la rana: cada par de números son la coordenada X y la Y de una piedra, empezando de 0,0 que es la esquina superior izquierda. Luego una linea que ponga FIN, y luego las coordenadas de la rana.

    Al ejecutarlo imprime las soluciones con un dibujo en ascii, donde < > ^ v significa izquierda, derecha, arriba, abajo.

    Se puede ver todo esto en los ficheros entrada.txt, salida.txt y ejecutar.bat

  • Un frontend para el programa anterior, hecho en C + API de Windows
    Puedes usar ranita.exe para ejecutarlo o puedes abrir los ficheros de Visual Studio para editarlo. en ambos casos hace falta el clisp, que he incluido en una carpeta en el ZIP.

    Genera el fichero entrada.txt a partir de lo que dibuje el usuario, llama al programa Lisp para que calcule las soluciones, lee el fichero salida.txt y lo dibuja en la ventana.

  • 26 enero 2009

    Dependencias entre ficheros fuente

    Filed under: Uncategorized — Etiquetas: , , , — thisisoneball @ 17:25

    Otra de las múltiples carencias de MySQL es que no tiene un sistema de dependencias entre objetos. Por ejemplo, un procedimiento almacenado que isnerta en una tabla, o lee de una vista, o una vista que lee de otras tablas o vistas, etc…

    Te permite borrar o cambiar de nombre un objeto aun cuando está siendo referenciado desde otro sitio. Esto hay quien lo ve como una ventaja (es más cómodo siempre que sepas lo que estás haciendo). Pero para un proyecto medianamente grande y en el que trabajen varias personas se hace inmantenible.

    Siempre queda la opción de hacer un programa que lea los ficheros fuente, y sepa encontrar ahí dónde se referencian otros objetos o ficheros fuente. He estado buscando en Internet algo hecho y solo he visto alguna cosa para ficheros .c y .h. Asi que he preparado este script como solución rápida. De momento solo busca en ficheros fuente de Perl y de procedimientos almacenados de MySQL.

    Va dibujando un arbol de las llamadas entre unos y otros. Por ejemplo:

    perl1.pl
    	perl2.pl
    	mysql1.sql
    	mysql2.sql
    		mysql4.sql
    	mysql5.sql

    en este caso el fichero perl1.pl llama a un programa Perl (perl2) y a 3 procedimientos almacenados (mysql1, mysql2 y mysql5), y mysql2 a su vez llama a mysql4

    Los parámetros del programa son el fichero fuente a partir del cual se empieza a buscar, y la lista de directorios en los que buscar, y se ejecuta desde el directorio que contiene a éstos. Ej:

    --raiz/
        |--perl/
            |-- perl1.pl
    	|-- perl2.pl
        |--mysql/
            |-- mysql1.pl

    En este caso se llamaría desde ‘raiz’, por ejemplo con:

    dependencias.pl perl1.pl {perl,mysql}

    Requiere el módulo File::Grep, que sirve para lo mismo que el comando grep. Es bastante incómodo de utilizar y probablemente habría sido mejor utilizar simplemente grep en un script de shell o de Perl.

    El programa es fácilmente adaptable a otros lenguajes. Solo hay que especificar cómo se hacen las llamadas a otros scripts (do, call, system, …)

    Se agradecen ampliaciones y correcciones.

    #!/usr/bin/perl
    
    use File::Grep qw( fgrep );
    
    die "Uso: dependencias.pl <archivo fuente inicial> {directorio1,directorio2}...\n\n" unless @ARGV == 2;
    
    my $directorios = $ARGV[1];
    
    busca($ARGV[0], "");
    
    sub busca {
    
    	my $ocurrencia;
    	my %matches;
    	my $match_key;
    	foreach $ocurrencia (fgrep { /(call|do).*\(/i } glob "$directorios/$_[0]") {
    		%matches = %{${$ocurrencia}{'matches'}};			# matches es un hash que contiene las lineas encontradas
    		foreach (sort keys %matches) {
    			if ($matches{$_} =~ m/call\s+(.+)\(/i) {		# llamadas a procedimientos almacenados MySQL
    				print "$_[1]$1.sql\n";
    				busca("$1.sql", $_[1]."    ");
    			} elsif ($matches{$_} =~ m/do.*\'(.+)\'/i) {		# llamadas a scripts Perl
    				print "$_[1]$1\n";
    				busca("$1", $_[1]."    ");
    			}
    		}
    	}
    
    }

    30 noviembre 2008

    Extraer y reemplazar texto con expresiones regulares en MySQL

    Filed under: programming, Uncategorized — Etiquetas: , , , , — thisisoneball @ 17:46

    Extracting and replacing text with MySQL regular expressions

    Entre las múltiples carencias de MySQL se encuentran (en parte) las expresiones regulares. Tiene el operador REGEXP que permite saber si una cadena casa con una expresión regular concreta, pero no permite extraer fragmentos de esa cadena ni reemplazar la cadena encontrada con otro texto.

    Esta es una función para MySQL que hace precisamente eso valiéndose del propio operador REGEXP.

    Es una chapuza rápida, probablemente no sea conveniente utilizarlo en determinados casos ya que debería ser bastante lenta (hace una llamada a REGEXP por cada subcadena contenida dentro de la cadena a buscar , por ejemplo, para buscar en ‘abc’, comprobaría la expresión regular con:

    a

    ab

    abc

    b

    bc

    c

    Si llamas a esta función dentro del select o el where de una consulta con muchos registros puede ser realmente ineficiente. He añadido DETERMINISTIC ya que la función es determinista, y en algunos casos de esta manera se puede ahorrar bastante tiempo de proceso.

    Hay otras opciones, como instalar UDFs de expresiones regulares. Las UDFs son código compilado y son mucho más rápidas. La ventaja de esta función es que es más fácil de instalar y es más portable.

    Uso:

    Devuelve la cadena más grande dentro de ‘cadena’ que coincida con ‘re’, por ejemplo

    SELECT substring_regexp(‘jket5657twr’, ‘[0-9]+’, NULL) –> 5657

    En este caso hay varias cadenas que coinciden con [0-9]+, por ejemplo 565, 5 , 657, … He decidido hacerlo de forma que devuelva la mayor de ellas.

    Para sustituir, se pasa como parámetro la cadena de sustitución, en vez de NULL:

    SELECT substring_regexp(‘jket5657twr’, ‘[0-9]+’, ‘HOLA’) –> jketHOLAtwr

    Código:

    DELIMITER $$
    
    DROP FUNCTION IF EXISTS `substring_regexp` $$
    CREATE FUNCTION `substring_regexp`(cadena text, re text, sustitucion text) RETURNS text CHARSET latin1 DETERMINISTIC
    BEGIN
    
    declare a, b, tam int;
    declare subcadena, retorno text;
    
    set tam = 0;
    set retorno = '';
    set a = 1;
    inicio: WHILE a <= length(cadena) DO
    	set b = a;
    	final: WHILE b <= length(cadena) DO
    		set subcadena = substring(cadena, a, b-a+1);
    		if subcadena regexp concat('^', re, '$') and length(subcadena) > tam then
    			set tam = length(subcadena);
    			if sustitucion is null then
    				set retorno = subcadena;
    			else
    				set retorno = replace(cadena, subcadena, sustitucion);
    			end if;
    		end if;
    		set b = b+1;
    	END WHILE final;
    	set a = a+1;
    END WHILE inicio;
    
    return retorno;
    
    END $$
    
    DELIMITER ;

    22 octubre 2008

    Timoconcurso «cerillas»

    Filed under: programming, Uncategorized — Etiquetas: , , , — thisisoneball @ 23:32

    Este es un programa en C que resuelve uno de los problemas de los «timoconcursos» de los canales de la TDT. En concreto el que aparece un número de 4 cifras hecho con cerillas y hay que conseguir el número más alto posible moviendo dos cerillas.

    Realmente este programa es algo más genérico: se puede especificar el número de cerillas que habría que mover, se pueden hacer con números de cualquier número de cifras, y devuelve todas las soluciones, no sólo el más alto.

    Esto es una curiosidad, no es para llamar al programa y tener la solución, porque el timo por lo visto está en que te tienen media hora al teléfono esperando para dar la solución, y eso en el caso de que cojan la llamada.

    NO LLAMES AL TIMOCONCURSO.

    #define	TRUE	1
    #define	FALSE	0
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <math.h>
    
    /*	tipo de datos "segmentos":
    		vector de 7 shorts, almacena un dígito en forma de segmentos, de la siguiente forma:
    			|-0-|
    			1   2
    			|-3-|
    			4   5
    			|-6-|
    		por ejemplo el 0 se almacenaría como:
    			segmentos numero0 = {1,1,1,0,1,1,1};
    
    	conjuntos de segmentos:
    		permiten almacenar un número completo compuesto de varios dígitos, por ejemplo para almacenar números de 4 dígitos:
    			segmentos numero[4];
    		para hacer lo mismo en tiempo de ejecución:
    			segmentos *numero;
    			numero = (segmentos *) calloc(4, sizeof(segmentos));
    */
    typedef short segmentos[7];
    
    //	especificación de los dígitos del 0 al 9 en forma de segmentos (vector de 10x7 shorts)
    segmentos digitos[10] = {
    	{1,1,1,0,1,1,1},
    	{0,0,1,0,0,1,0},
    	{1,0,1,1,1,0,1},
    	{1,0,1,1,0,1,1},
    	{0,1,1,1,0,1,0},
    	{1,1,0,1,0,1,1},
    	{1,1,0,1,1,1,1},
    	{1,0,1,0,0,1,0},
    	{1,1,1,1,1,1,1},
    	{1,1,1,1,0,1,1}
    };
    
    //	copia los 7 segmentos de un dígito
    void CopiarSegmentos(segmentos destino, segmentos origen)
    {
    	memcpy(destino, origen, 7 * sizeof(destino[0]));
    }
    
    //	transforma un entero "origen" a un vector de segmentos "destino" (no un solo dígito sino un número completo compuesto de "tam" dígitos)
    void IntSegmento(segmentos *destino, int origen, int tam)
    {
    	while (tam >= 1) {
    		tam--;
    		CopiarSegmentos(destino[tam], digitos[origen % 10]);
    		origen = origen / 10;
    	}
    }
    
    /*	compara dos números de "tam" dígitos
    	devuelve TRUE si entre los dos se han movido "ndiferencias" segmentos
    */
    int CompararSegmentos(segmentos *origen, segmentos *destino, int tam, int ndiferencias)
    {
    	int i;
    	int diferencias = 0, total = 0;
    
    	for (i=0; i < tam*7; i++) {
    		total += (*origen)[i] - (*destino)[i];
    		if ((*origen)[i] != (*destino)[i])
    			diferencias++;
    	}
    
    	// total es la diferencia en el total de segmentos, y debe ser 0 (mismo nº de cerillas en los dos casos)
    	// diferencias es el total de segmentos cambiados (es el doble del nº de cerillas movidas)
    	if (diferencias == ndiferencias*2 && total == 0)
    		return TRUE;
    	else
    		return FALSE;
    }
    
    /*	parámetros del programa: el número inicial y el número de cerillas que hay que mover
    
    	retorno:
    		0	sin errores
    		1	error en el número de parámetros pasados
    		2	error en el formato de los parámetros
    */
    int main(int argc, char *argv[])
    {
    	int inicial, ndiferencias, tam; segmentos *s_inicial;
    	int candidato; segmentos *s_candidato;
    
    	// gestión de parámetros de entrada
    	if (argc != 3) {
    		fprintf(stderr, "Uso: %s\nEjemplo: %s 5912 2\n", argv[0], argv[0]);
    		return 1;
    	}
    
    	inicial = atoi(argv[1]);		// ej: 5912
    	ndiferencias = atoi(argv[2]);		// ej: 2
    	if (inicial == 0 || ndiferencias == 0) {
    		fprintf(stderr, "Los parámetros tienen que ser numéricos y distintos de 0\nEjemplo: %s 5912 2\n", argv[0]);
    		return 2;
    	}
    
    	tam = strlen(argv[1]);			// ej: 4
    	candidato = (int) pow(10,tam) - 1;	// ej: 9999
    
    	s_inicial = (segmentos *) calloc(tam, sizeof(segmentos));
    	s_candidato = (segmentos *) calloc(tam, sizeof(segmentos));
    
    	// pasar el nº inicial a segmentos y comparar con cada nº candidato decrecientemente
    	IntSegmento(s_inicial, inicial, tam);
    	while (candidato >= 0) {
    		IntSegmento(s_candidato, candidato, tam);
    		if (CompararSegmentos(s_inicial, s_candidato, tam, ndiferencias))
    			printf("%d\n", candidato);
    		candidato--;
    	}
    
    	return 0;
    }

    Crea un blog o un sitio web gratuitos con WordPress.com.