Archivo

Entradas Etiquetadas ‘funcion’

Callbacks, retrollamadas o delegados o cómo crear código más flexible en C

Lunes, 17 de Enero de 2011 Gaspar Fernández Sin comentarios

Uno de los grandes objetivos de la programación es la de evitar repetirse. Ok, depende del contexto en el que estemos trabajando. Pero en general, querremos escribir poco cuando trabajamos en un proyecto (y así terminamos antes) y de paso que lo que escribamos para un programa nos sirva para otro (reaprovechamiento del código).

Un callback nos permite llamar a una función u otra dependiendo de la ocasión. Imaginemos dos funciones y la función principal (la sintaxis utilizada nos es correcta, pero mi objetivo es que en este punto se vea claramente)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void funcion_a()
{
  printf("Has llamado a la función A");
}  

void funcion_x(funcion ayudante)
{
  printf("Aquí procesamos información");
  ayudante();
  printf("Aquí seguimos procesando información");
}

int main()
{
  funcion_x(funcion_a);
}

Lo que hcemos es llamar a función_x() y, como parámetro, le decimos que llame a funcion_a(), lo que conseguimos es que cuando funcion_x() esté ejecutándose, necesitará ayuda en algún momento de otra función, y desde main() decimos que esa función sea funcion_a().

Esto, por ejemplo nos puede servir cuando implementemos un algoritmo de ordenación (basado en el intercambio de elementos), es decir, el algoritmo va a ser uno de los que elijamos, pero podemos implementar la ordenación con varios criterios, ascendente y descendente y para ello no tenemos que crear decenas de funciones que implementen la ordenación; podemos crear una función de ordenación y que esta se ayude de otra función que dependiendo del criterio elegido diga si debemos intercambiar los elementos o no). De hecho algo parecido está implementado en stdlib.h en la función qsort(), la cual en uno de sus parámetros debemos indicar qué función llamar para hacer las comparaciones; la función debe ser muy general y existen infinitos casos específicos ( por ejemplo si se ordenan registros, cadenas, etc).

O por ejemplo, para el caso en el que nuestro programa tenga varios tipos de salida, por ejemplo: por pantalla, a fichero y por e-mail; cada uno deberá tener un formato diferente, por pantalla será simple, pero a fichero debemos incluir una serie de cabeceras y por e-mail tendremos otras necesidades. Una forma intuitiva sería hacer un case siempre que toque hacer una salida (con lo que estaríamos repitiéndonos un poco); pero también podemos introducir una función callback y comprobar el tipo de salida una sola vez).

Dejo aquí un ejemplo con la sintaxis de una función que admitirá como parámetro otra función; debemos tener en cuenta que una función puede tener salida de un tipo y N parámetros de tipos determinados, por lo que antes de decir a qué función B (llamada) debe llamar una función A (llamadora), debemos decidir qué forma tendrá esa función B.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>

int llamada(char *cadena, int numero)
{
  return (strlen(cadena)+numero);
}

void llamadora(int (*func)(char*, int))
{
  int num;
  printf("Voy a llamar a una función con el texto HOLA y el numero 20\n");
  num=func("HOLA", 20);
  printf("La he llamado y me ha devuelto: %d\n", num);
}

int main()
{
  llamadora(llamada);
}

En el ejemplo anterior, podemos implementar funciones llamada() diferentes, pero siempre tendrán que devolver un int, y necesitarán como parámetro un char* y un int y debemos pasarle el parámetro a llamadora() antes de invocarla.

Por tanto, el parámetro que equivale a una función es:

tipo_devolución (*nombre)(tipo param1, tipo param2, …)

En este punto, no nos hace falta darle nombre a los parámetros, lo que necesitamos saber es el tipo de los parámetros y su tipo.

Una buena práctica es crear un typedef que defina la función, de la siguiente forma:

typedef tipo_devolución (*nombre)(tipo param1, tipo param2, …);

Exactamente igual que para declarar el parámetro. Con lo que el código anterior quedaría de la sigueinte forma (lo único que cambia es el typedef y la declaración de llamadora):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>

/* TYPEDEF */
typedef int (*funcion_callback)(char*, int);

int llamada(char *cadena, int numero)
{
  return (strlen(cadena)+numero);
}

/* DECLARACIÓN DE LLAMADORA */
void llamadora(funcion_callback func)
{
  int num;
  printf("Voy a llamar a una función con el texto HOLA y el numero 20\n");
  num=func("HOLA", 20);
  printf("La he llamado y me ha devuelto: %d\n", num);
}

int main()
{
  llamadora(llamada);
}

Por último, indicar un ejemplo con qsort() como se dijo antes. En este ejemplo, podemos ver cómo generamos un array con números aleatorios, y lo ordenamos según ciertos criterios (hay ciertas líneas comentadas, por defecto la ordenación es ascendente, pero si quitamos los comentarios de alguna otra ordenación y comentamos la activa podremos ver cómo el array se va reordenando con diferentes criterios.

La función que se llamará desde qsort() deberá devolver un número mayor, igual o menor a 0, indicando si un objeto A es mayor, menor o igual a un objeto B (en este caso, los objetos será números).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <stdlib.h>     /* qsort() */
#include <time.h>       /* time() - Para generar la semilla aleatoria */
#define MAX_NUMEROS 10

/* Genera números aleatorios del 1 al 100 y los almacena en el array */
/* numeros, esta función generará como máximo max_numeros */
/* max_numeros suele coincidir con el tamaño del array */
void genera_numeros(int numeros[], int max_numeros)
{
  int i;
  srand(time(NULL));
  for (i=0; i<max_numeros; i++)
    {
      numeros[i]=rand()%100+1;
    }
}

/* Pone en pantalla un array de números */
void print_numeros(int numeros[], int max_numeros)
{
  int i;
  for (i=0; i<max_numeros; i++)
    {
      printf("Número %d = %d\n", i, numeros[i]);
    }
}

int ordena_ascendente(const void *a, const void* b)
{
  return (*(int*)a>*(int*)b);
}

int ordena_descendente(const void *a, const void* b)
{
  return (*(int*)a<*(int*)b);
}

int ordena_aleatorio(const void *a, const void* b)
{
  return (rand()%5<3);
}

int ordena_curioso(const void *a, const void* b)
{
  /* Para quitarnos punteros y typecasting */
  int n_a=*(int*)a;
  int n_b=*(int*)b;

  if (n_a>50)
    {
      /* Si n_b>50 (igual que n_a), como ordenamos de mayor a menor, */
      /* la comparación resultará 1 si n_a es menor */
      /* Si no, lo dejamos tal cual */
      return (n_b>50)?(n_a<n_b):0;
    }
  else
    {
      /* Si n_b es menor o igual de 50 (igual que n_a), al ordenar de menor a mayor, */
      /* la comparación resultará 1 si n_a es mayor */
      /* Si no, la comparación será 1 (para separar los mayores de 50 de los menores */
      /* o iguales */
      return (n_b<=50)?(n_a>n_b):1;
    }

}

int main()
{
  int numeros[MAX_NUMEROS];

  genera_numeros(numeros, MAX_NUMEROS);

  qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_ascendente);
  /* qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_descendente); */
  /* qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_aleatorio); */
  /* qsort(numeros, MAX_NUMEROS, sizeof(int), ordena_curioso); */

  print_numeros(numeros, MAX_NUMEROS);

  return 0;
}

Como vemos, esto se suele utilizar en funciones de librería, por el hecho de que son muy generales, es más el comportamiento de la función es siempre el mismo, con pequeñas variaciones en medio del algoritmo, lo que nos obligaría (sin esta técnica) a crear funciones casi iguales, sólo con pequeñas variaciones.

Por eso, aunque quede muy abstracto, si tenemos una función A que en su transcurso ejecuta un fragmento de código que puede variar, podemos hacer que en su ejecución llame una función B y ésta función pueda ser variable.

El método más rápido para traer un valor pasado por $_GET [ PHP ]

Miércoles, 15 de Diciembre de 2010 Gaspar Fernández 1 comentario

Tal vez por paranoia, por ganas de perder el tiempo, o por optimizar aún más el código fuente nos encontremos ante esta cuestión.

He realizado una serie de pruebas para ver cuál es el método que mejor funciona para traer un valor de $_GET basándome en el peor de los casos: cuando este valor no existe. Tal vez las pruebas no tengan excesiva validez por el método utilizado, aunque he intentado hacer todas las pruebas en las mismas condiciones.

Las medidas de tiempo han sido tomadas con el comando time de la siguiente manera:

$ time php myscript.php

Se han hecho un total de 7 pruebas, 1 de ellas de control. Las pruebas se han basado en la generación de cadenas aleatorias (que más tarde serán nuestro parámetro $_GET), además, la obtención del parámetro ha sido hecha unas 500000 veces con un bucle for. Se detallan las pruebas realizadas.
Cada prueba se ha repetido tres veces y se ha capturado el tiempo de las tres veces. Estamos usando un software complicado que hará muchas cosas mientras se realiza el test, es más, el sistema operativo puede requerir hacer una tarea que puede influir en nuestro resultado, por lo que así minimizamos el error (y si en alguna ocasión sale un dato desorbitado podemos descartarlo).

Prueba de control [TEST 0]

La generación de parámetros es lo que más tarda. Se ha utilizado uniqid() para obtener cadenas de la misma longitud y contenido arbitrario y diferente cada vez. Además, tanto la obtención del parámetro (en las siguientes pruebas) como la generación del nombre del parámetro se hace un número N de veces gracias a un bucle (que también invierte un tiempo).

Con esta prueba de control podremos ver en qué grado son significativos los valores obtenidos luego (cuando hagamos la obtención de datos), ya que vemos que el bucle y la generación tardan de media 4.493 segundos.

El código fuente del script para esta prueba es el siguiente:

1
2
3
4
5
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
}

Anulando error_reporting() [TEST 1]

Lo que nos impide hacer un $variable=$_GET['parametro']; es el error_reporting, ya que si éste no existe, nos devolverá un feo error por pantalla. Así que probemos primero desactivando esto y obteniendo el valor de la variable.

1
2
3
4
5
6
7
echo "Probando 500000 peticiones GET";
error_reporting(E_NONE);
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=$_GET[$getVar];
}

Con @$_GET[...] [TEST 2]

Otra manera de impedir que PHP muestre sus errores en pantalla, es colocando una @ delante de lo que creemos que puede fallar, y en este caso es la sentencia de $_GET[$getVar].

1
2
3
4
5
6
7
echo "Probando 500000 peticiones GET";

for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=@$_GET[$getVar];
}

if…else [TEST 3]

No es nada intuitivo, PHP es un lenguaje de scripting, así que cuanto más escribamos, peor, pero merece la pena hacer la prueba, y nos podemos llevar una sorpresa. Ahora, hacemos la obtención del dato, pero primero comprobamos que éste existe con isset().

1
2
3
4
5
6
7
8
9
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
if (isset($_GET[$getVar]))
$dato=@$_GET[$getVar];
else
$dato=false;
}

Operador ternario ? [ TEST 4 ]

Tal vez vaya mejor que el if…else (TEST 3), por aquella razón de escribir menos que comentaba…

1
2
3
4
5
6
echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=(isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

Creando una función auxiliar gval() [ TEST 5 ]

Tal vez escribimos más que con el TEST 2 pero, vamos a probar, qué tal va. Tal vez si la diferencia de tiempo no es significativa podemos utilizar este método…

1
2
3
4
5
6
7
8
9
10
11
function gval($getVar)
{
return (isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=gval($getVar);
}

Función auxiliar y parámetro por referencia [ TEST 6 ]

Una nueva prueba, ya que hemos hecho una función auxiliar, veamos cómo influye el pasar el parámetro por valor o pasarlo por referencia. En este caso podríamos hacerlo sin problema.

1
2
3
4
5
6
7
8
9
10
11
function gval(&amp;$getVar)
{
return (isset($_GET[$getVar]))?$_GET[$getVar]:false;
}

echo "Probando 500000 peticiones GET";
for ($i=0; $i<500000; $i++)
{
$getVar=uniqid();
$dato=gval($getVar);
}

Resultados obtenidos

Tipo de prueba Primer resultado Segundo resultado Tercer Resultado Media
TEST 0 4,4530 4,5960 4,4310 4,4933
TEST 1 6,7560 6,1390 5,8350 6,2433
TEST 2 7,3880 7,3350 7,3120 7,3450
TEST 3 5,5270 4,9900 5,0610 5,1927
TEST 4 5,4480 4,9820 5,1060 5,1787
TEST 5 6,2860 6,4050 5,8160 6,1690
TEST 6 5,9840 5,6290 5,7440 5,7857

Con estos resultados podemos observar que:

  • Efectivamente, el test de control es el más lento, incluye generación del nombre aleatorio del parámetro, el bucle y la carga del programa (PHP), pero más o menos partimos de que el tiempo mínimo para esta prueba son cerca de 4.5 segundos. El tiempo de obtención de dato, será más o menos la diferencia entre el tiempo total del test y los 4.5 segundos aproximadamente obtenidos aquí.
  • TEST 1: error_reporting(E_NONE) nos entorpecería en tareas de depuración (cuando realmente deseemos tenerlo en otro valor), de todas formas no es una opción, comparado con los demás. Deberíamos estar pendientes del valor de error_reporting() cuando queramos depurar el script, no ahorraríamos mucho código y tampoco es el mejor con respecto al tiempo.
  • TEST 2: Estaría bien que un carácter nos solucionara la vida, pero no es así… conseguimos el peor tiempo de todos. Se ve que a PHP le sienta mal cuando no encuentra una variable…
  • TEST 3 y TEST 4: Parece que un if…else nos puede ayudar a hacer nuestro código más rápido, un poco más elegante y funcionar mejor. También vemos que el operador ? puede ayudar. Y vemos, además, que el tiempo entre if…else y entre ? es el mismo (la diferencia es despreciable en este caso)
  • TEST5: Visto lo visto con TEST3 y TEST4 en los que escribimos más que con TEST2, vamos a crear una función auxiliar, a ver cómo se comporta con respecto a lo que tenemos. Comprobamos que el uso de una función, hace nuestro código más claro (punto positivo) y aún tarda menos que TEST1 y TEST2 aunque tarda algo más que TEST3 y TEST4. TEST5 tarda aproximadamente lo que TEST1.
  • TEST6: Parece que el hecho de no copiar el valor de una variable de verdad influye significativamente en el tiempo cerca de medio segundo; podemos seguir utilizando un método elegante con nuestra función auxiliar y al mismo tiempo aproximarnos a un tiempo mínimo.
  • Sé que 500000 iteraciones puede parecer exagerado aunque en momentos de carga de servidor y cuando esperamos que nuestra página tenga un número importante de visitas tal vez se agradezca un poco de agilidad, sobre todo en recepción de formularios (con variables $_POST). De todas formas, queda claro con estas pruebas que el hecho de poner una @ en un comando o asignación, sólo hace que PHP se trague los errores, pero es un método lento, con lo que podemos empezar a optimizar nuestro código por ahí.

Función curiosa: preg_replace_callback (PHP)

Lunes, 15 de Marzo de 2010 Gaspar Fernández 2 comentarios

A veces tenemos la necesidad de reemplazar un texto dentro de una cadena larga. Por ejemplo, el uso principal que le doy a esta función es el procesamiento de plantillas para páginas web, en donde tengo la página en HTML puro por un lado, y en su interior hay ciertas palabras clave, por ejemplo —seccionA—, —fotoUsuario—, —menuSesion—, etc; y en la web definitiva, aparecerá otra cosa en lugar de ese texto, aparecerá el objeto al que hace referencia.
Aunque cada cadena de texto seccionA, fotoUsuario, requiere un procesamiento distinto y es posible que no tengamos por qué procesar siempre todas las equivalencias de todos los textos. Queremos en definitiva reemplazar —seccionA— por el resultado de una función que ejecutaremos cuando encontremos ese texto. Sería como:

1
2
3
4
$disparadores=array("---seccionA---", "---fotoUsuario---", "---menuSesion---");
$reemplazos=array(dibuja_seccionA(), get_fotoUsuario(), dibuja_menuSesion);

$web=str_replace($disparadores, $reemplazos, $web);

Aunque, como dijimos, en el ejemplo anterior, se tienen que ejecutar dibuja_seccionA(), get_fotoUsuario() y dibuja_menuSesion().

Pero podemos aprovecharnos de las expresiones regulares, para extraer texto que esté entre “—” y “—” y pasar lo que hay entre medias a otra función que decidirá qué hacer; eso hace preg_replace_callback().

1
2
3
4
5
6
7
8
9
10
function quehacer($texto)
{
  switch ($texto[1]) // [0] devuelve: ---texto---, [1] devuelve texto
  {
     case 'seccionA': return dibuja_seccionA();
     case 'fotoUsuario': return get_fotoUsuario();
     case 'menuSesion': return dibuja_menuSesion();
  }
}
$web=preg_replace_callback('/---([a-zA-Z0-9_]*)---/', 'quehacer', $web);

Hasta aquí bien, el primer parámetro es una expresión regular (es un tema muuuy extenso), el segundo es la función a la que llamamos cuando encontramos el texto, y el tercero es de dónde lo sacamos. Bueno, ahora surge un problema, imaginad que necesito pasarle parámetros a esa función callback. Lo podemos hacer así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function guardar_datos($data=null)
{
  static $guardados; // Una variable estática, muy importante

  // Si se especifica $data, lo guardamos en $guardados, al ser estática, aunque salgamos de la función, el valor de la variable no se perderá.
  if ($data)
    $guardados=$data;
 
  return $guardados;
}

function quehacer($texto)
{
  $datos = guardar_datos();

  switch ($texto[1]) // [0] devuelve: ---texto---, [1] devuelve texto
  {
     case 'seccionA': return dibuja_seccionA();
     case 'fotoUsuario': return get_fotoUsuario($datos);
     case 'menuSesion': return dibuja_menuSesion($datos);
  }
}

guardar_datos(array($datos_usuario, $datos_sesion));
$web=preg_replace_callback('/---([a-zA-Z0-9_]*)---/', 'quehacer', $web);

Nota: las variables $datos_usuario, $datos_sesion y $web no están definidas, como esto es parte de un programa, hay que imaginar un poco cuál será la información que contendrán.

trim(), un gran amigo (PHP, C++, C)

Miércoles, 10 de Marzo de 2010 Gaspar Fernández 2 comentarios

A mi entender, es una de las funciones más útiles que se han inventado, como programador de PHP estoy harto de utilizarlo para filtrar información (caracteres a la derecha y a la izquierda, ya sean espacios, caracteres especiales o algún carácter que yo utilice para el control de la información).
Sabemos que el usuario final no nos va a dejar las cosas fáciles, puesto que a veces, nos llena un campo con “intros” al principio y al final; o la información, después de pasar por HTTP, lectura de un archivo XML o por otros tratamientos, tal vez tenga un retorno de carro al final; por eso, a veces es útil hacer:

1
$cadena=trim($cadena);

Pero ahora estamos en C++, bien quería postear también el código en C++, así que decidí googlear un poco para ver esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <string>
const std::string whiteSpaces( " \f\n\r\t\v" );

void trimRight( std::string& str,
      const std::string& trimChars = whiteSpaces )
{
   std::string::size_type pos = str.find_last_not_of( trimChars );
   str.erase( pos + 1 );
}

void trimLeft( std::string& str,
      const std::string& trimChars = whiteSpaces )
{
   std::string::size_type pos = str.find_first_not_of( trimChars );
   str.erase( 0, pos );
}

void trim( std::string& str, const std::string& trimChars = whiteSpaces )
{
   trimRight( str, trimChars );
   trimLeft( str, trimChars );
}

Con esto podemos jugar con nuestros textos…

Ahora vamos al caso de C, muchos se siguen preguntando… pero si C es un lenguaje muy antiguo, ¿aún se sigue usando? Pues sí ! Aunque muchas cosas sean horribles de programar en C, a pelo, se sigue usando, y aún le queda mucha vida a este lenguaje. Por eso, veremos dos funciones trim() especiales para C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <string.h>

char *trim(char *s)
{
  char *start = s;

  /* Nos comemos los espacios al inicio */
  while(*start && isspace(*start))
    ++start;

  char *i = start;
  char *end = start;

  /* Nos comemos los espacios al final */
  while(*i)
  {
    if( !isspace(*(i++)) )
      end = i;
  }

  /* Escribimos el terminados */
  *end = 0;

  return start;
}

char *trim2(char *s, const char *trimChars)
{
  char *start = s;

  /* Nos comemos los caracteres al principio */
  while(*start && strpbrk((const char*)start, trimChars)==start)
    ++start;

  char *i = start;
  char *end = start;

  /* Nos comemos los caracteres al final */
  while(*i)
  {
    if( strpbrk(i++, trimChars)!=i-1 )
      end = i;
  }

  /* Coloramos el perminador */
  *end = 0;

  return start;
}

int main()
{
  char mi_cadena[30]="\f\n Cadena; \tSucia \n\t ;";
  printf("Mi cadena inicialmente: \"%s\"\n", mi_cadena);
  printf("Mi cadena tras trim(): \"%s\"\n", trim(mi_cadena));
  printf("Mi cadena tras trim2(): \"%s\"\n", trim2(mi_cadena,";\f\n\t"));
}

En C también podremos tener nuestro trimleft y nuestro trimright y creo que es fácil sacarlo desde esta función general, ya que *start se mueve indicando el principio de la cadena, y con end, y es ese movimiento (que aunque perdemos algo de memoria) el que hace que se recorte la cadena; además, como buena función de C, perdemos la cadena que inicialmente teníamos.

Categories: C/C++ Tags: , , ,

Visita otras webs de la red