Archivo

Entradas Etiquetadas ‘codigo’

Coloreando código con GeSHi

Sábado, 4 de Febrero de 2012 Gaspar Fernández Sin comentarios

geshiCuando queremos publicar código fuente por Internet (como en esta web), debemos hacer que los usuarios se sientan bien con el código y que sea agradable de leer. Además del indentado, es de agradecer la introducción de colores, que separen palabras clave, identifiquen cadenas, funciones de biblioteca, funciones propias, números y demás elementos que encontramos en un fragmento de código.

Por Internet, podemos encontrar una biblioteca muy útil para este propósito: GeSHi. Vamos a intentar hacer algo sencillo con ella.

Para instalarla simplemente tenemos que descargarla y descomprimirla en uno de los directorios de nuestra web (no tiene por qué ser el principal), a continuación vamos a hacer un pequeño programa que mostrará el código fuente de un fichero PHP. Suponemos que geshi.php está situado en el mismo directorio que este ejemplo:

1
2
3
4
5
6
7
8
9
10
<?php
// Basado en los ejemplos de la página oficial
require_once("geshi.php");

$codigo=file_get_contents('mi_programa.php');

$ges=new GeSHi($codigo, 'php');

echo $ges->parse_code();
?>

Con este pequeño ejemplo, se mostrará de forma coloreada el código seleccionado, aunque es conveniente el uso de cachés, es decir, es una buena idea almacenar el código coloreado, es decir, el contenido que devuelve $ges->parse_code() en un archivo aparte y cargar directamente este archivo en la siguiente petición de la página. Por ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
include_once 'geshi.php';

function color_source_file($source_file, $language)
{
  $source_cache='cache/'.basename($source_file).'_'.$language.'.cache';
  if (@filemtime($source_cache)<filemtime($source_file))
    {
      $geshi= new GeSHi(file_get_contents($source_file), $language);
      $highlighted_source=$geshi->parse_code();
      file_put_contents($source_cache, $highlighted_source);
      return $highlighted_source;
    }
  return file_get_contents($source_cache);
}

$source = 'mi_programa.php';

echo color_source_file($source, 'php');
?>

Este código es capaz de guardar en un fichero el html generado por GeSHi en un archivo de caché (se guardará en un directorio llamado cache, el nombre del fichero será el mismo que el espeficado y su formato será [nombre]_[lenguaje].cache . La decisión se toma en base a las fechas de modificación de los archivos. Si el archivo de caché no existe (por eso la @ para que no devuelva fallos PHP) o su fecha de modificación es anterior a la fecha de modificación del archivo fuente, se generará un nuevo archivo de caché y se devolverá el código coloreado. Si no, se devolverá el código coloreado leyendo directamente del archivo de caché.

Activar números de línea

Lo podemos hacer así:

1
$ges->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS);

Si ponemos GESHI_FANCY_LINE_NUMBERS cada 5 líneas saldrá el número en negrita, si ponemos GESHI_NORMAL_LINE_NUMBERS no sucederá así.

Resaltar líneas

Para resaltar líneas, debemos crear un array con los números de línea que queremos resaltar:

1
$lineas=array(5,6,7,43,53,54)

Y luego escribir:

1
$ges->hightlight_lines_extra($lineas);

Personalizando los estilos

Para ello podemos utilizar CSS. Por defecto GeSHi personaliza el style=”" de cada etiqueta, aunque genera mucho código html que podemos ahorrar. Para ello, podemos utilizar lo siguiente:

1
2
$ges->enable_classes();
file_put_contents('codigo.css', $ges->get_stylesheet(false));

Así crearemos un archivo .css que contendrá todos los estilos que puede tener un código fuente en un determinado lenguaje (en el cual hemos inicializado la clase), luego podemos incluir ese CSS ya generado en nuestro html resultante, y cambiar los colores, tipos de letra, etc.

GeSHi tiene muchísimas más opciones, para saber más, basta con un vistazo a la documentación oficial (en inglés) para descubrir todas las posibilidades de esta clase.

C.I. IX: KGPU, M$ Skype, NOSQL, Twitter VS frustración

Viernes, 13 de Mayo de 2011 Gaspar Fernández 2 comentarios

Os dejo algunos enlaces interesantes recopilados estos últimos días:

  • Speeding Up The Linux Kernel With Your GPU. Lo que leéis, hay un proyecto para acelerar el kernel con la ayuda de la GPU. Patrocinado por NVIDIA y la Universidad de Utah, puede hacer las lecturas/escrituras en sistemas de archivos cifrados 3 ó 4 veces más rápidos. En principio sólo vale para cifrado, pero bueno, sólo es cuestión de pensar qué tareas del kernel se podrán paralelizar y compensa hacer en GPU (vamos, que la transmisión de datos de y a la memoria gráfica no sea más lenta que procesar la tarea en CPU; y que la tarea sea paralelizable).
  • Microsoft adquiera Skype, ¡es hora de usar Ekiga! Ha sido la noticia de la semana, y es que no sabemos qué pasará con Skype a partir de ahora, esperemos que los clientes se sigan manteniendo y la forma de funcionar actual. Aunque es un buen momento para que los desarrolladores de proyectos libres de VoIP se pongan las pilas y mejores bastante sus proyectos, para llegar a ser verdaderos rivales para este servicio. Al final del artículo vemos una imagen que me encanta (sky .net).
  • ¿Has leído la licencia de Microsoft Windows? (Esa que todo el mundo acepta con los ojos cerrados) . De vez en cuando aparece algún artículo similar, pero es cierto que el 99% de los usuarios de Windows no lee la licencia y la acepta, aunque los fabricantes nos dan pocas opciones si se da el caso de que no aceptamos la licencia.
  • ¿Qué son las bases de datos NOSQL? Es un artículo muy interesante sobre este tipo de bases de datos, nos puede servir como un buen punto de referencia para empezar a adentrarnos en este nuevo mundo (muchos enlaces para devorar información).
  • Samsung libera código fuente del software usado en el Galaxy S II . Enhorabuena a Samsung por este movimiento, animará a muchos a tunear el sistema libremente y seguro que se promociona este modelo (y modelos futuros y derivados).
  • Las estrategias de Twitter para minimizar la frustración de los usuarios . Cualquiera diría que Twitter está reduciendo la desesperación de los usuarios, son algunos detalles subliminales curiosos.

Cronometrando en C

Domingo, 5 de Diciembre de 2010 Gaspar Fernández 2 comentarios

Puede que queramos hacer una comparativa de cuánto tarda nuestro código en ejecutarse, o que tengamos varios algoritmos y queramos saber cuál es el más rápido. O que estemos haciendo un programa que mida el tiempo de reacción de un usuario en una cierta tarea. Aquí vemos ejemplos con diferentes precisiones.

Minutos

Este método viene bien para ver el tiempo transcurrido en un proceso que puede durar varios minutos. Si algo tarda 20 minutos, suele darnos igual segundo arriba, segundo abajo.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <time.h>

int main(int argc, char *argv[])
{
int anterior;
anterior=time(NULL);
sleep(1);
printf("Transcurrido: %ld\n", time(NULL)-anterior);
}

Sí, simplemente es esto, pero no voy a hacer un post sólo para esto, vamos a crear una función que llamemos cuando queremos empezar a cronometrar y volvamos a llamar cuando queramos parar:

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

int cronosec(int startstop)
{
  static int pre_time;

  if (startstop)
    pre_time=time(NULL);
  else
    return time(NULL)-pre_time;

  return 0;
}

int main(int argc, char *argv[])
{
  cronosec(0);
  sleep(1);
  printf("Transcurrido: %ld\n", cronosec(1));
}

Con cronosec cuando empezamos a cronometrar, pasamos el parámetro 0, y cuando queremos ver el tiempo transcurrido llamamos al parámetro 1.

Precisión de milisegundo y microsegundo

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
#include <unistd.h>
#include <sys/time.h>

long long cronomsec(int startstop)
{
  static long long pre_time;
  struct timeval tv;

  if (startstop)
    {
      gettimeofday(&tv, NULL);      
      pre_time=tv.tv_sec*1000+tv.tv_usec/1000;
    }
  else
    {      
      gettimeofday(&tv, NULL);      
      return tv.tv_sec*1000+tv.tv_usec/1000 - pre_time;
    }
    return 0;
}

long long cronousec(int startstop)
{
  static long long pre_time;
  struct timeval tv;

  if (startstop)
    {
      gettimeofday(&tv, NULL);      
      pre_time=tv.tv_sec*1000000+tv.tv_usec;
    }
  else
    {      
      gettimeofday(&tv, NULL);      
      return tv.tv_sec*1000000+tv.tv_usec - pre_time;
    }
    return 0;
}

Las dos se usan igual que cronosec(), cronomsec() tiene precisión de milisegundos y cronousec() de microsegundos; si las observamos las dos son iguales, sólo que en una, como queremos contar milisegundos, multiplicamos los segundos por mil y dividimos microsegundos entre 1000 (struct timeval es capaz de devolvernos microsegundos también).
En cronousec sólo multiplicamos los segundos por 1000000 (un millón).

Nanosegundo

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

unsigned long long crononsec(int startstop)
{
  static unsigned long pre_time;
  static unsigned long pre_secs;
  struct timespec ts;

  if (startstop)
    {
      clock_gettime(CLOCK_MONOTONIC, &ts);
      pre_time=ts.tv_nsec;
      pre_secs=ts.tv_sec;
    }
  else
    {      
      clock_gettime(CLOCK_MONOTONIC, &ts);
      return (ts.tv_sec-pre_secs)*1000000000+ts.tv_nsec - pre_time;
    }

    return 0;
}

Con esta función, gracias a clock_gettime(), obtenemos precisión de nanosegundos. Utilizo CLOCK_MONOTONIC porque este reloj no cambia, es decir, existe la función clock_settime, y este reloj no se podrá cambiar, de hecho no queremos que nos hagan trampa en nuestras aplicaciones, cambien la hora del ordenador y nuestro programa no funcione bien. Si no, podemos usar CLOCK_REALTIME, aunque este se puede cambiar, aunque necesita privilegios.
En este último ejemplo, vemos que no multiplico los segundos desde el principio como hacía antes, ahora sólo acumulo segundos, y luego multiplico la diferencia; esto es, porque el número de segundos ya acontecidos desde 1970 (Epoch) es demasiado grande, y si lo multiplico por 1000000000 (mil millones) para saber cuántos microsegundos han pasado desde entonces, el número es demasiado grande y desborda la variable (mira que es long long y tiene 64bits), así que aplico este método, mucho más inteligente y menos costoso (dos multiplicaciones frente a una), también podía hacer todos los métodos anteriores así, aunque el precio de hacer menos operaciones es la reserva de una variable más.

Un ejemplo para probarlo todo

Una vez colocadas todas las funciones en un archivo .c y todos los includes correspondientes arriba, añadimos este main():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char *argv[])
{
  cronosec(1);
  cronomsec(1);
  cronousec(1);
  crononsec(1);
  usleep(4123789);      /* 4 segundos y pico*/
  printf("segundos: %d\n", cronosec(0));
  printf("milisegundos: %lld\n", cronomsec(0));
  printf("microsegundos: %lld\n", cronousec(0));
  printf("nanosegundos: %llu\n", crononsec(0));
 
  return EXIT_SUCCESS;
}

Una advertencia, las funciones que tienen más precisión de segundos son dependientes de la plataforma, además, para compilarlos necesitamos añadir la librería rt al compilar, de la siguiente forma:

$ gcc -lrt -o crono crono.c

La importancia de conocer atajos de teclado en nuestro IDE/Editor favorito

Lunes, 29 de Noviembre de 2010 Gaspar Fernández Sin comentarios

Siempre aconsejo a alguien que esté aprendiendo a programar que lo primero es sentirte bien con el entorno o IDE que manejas. A veces, en muchos centros de enseñanza se impone un entorno, por ejemplo Dev-Cpp o Borland C, pero cuando nos toca ponernos a programar algo en serio, ya sea para nosotros o para trabajar lo primero es elegir un IDE con el que nos encontremos cómodos.

Tanto para personas que están empezando como para expertos suelo hacer que prueben alguno de estos tres: Eclipse, Netbeans o Code::Blocks y que echen un rato intentando familiarizarse con el entorno, sus caprichos y su metodología.

Aunque si ya es importante familiarizarse con el entorno, ahora tenemos que hacer que valga la pena estar programando con un IDE frente a un editor de texto plano. Los IDEs tendrán muchas opciones que nos facilitarán la tarea como programadores, por ejemplo, lo primero que destacamos es que la mayoría de ellos colorean el código que escribimos; es posible que a algunas personas (conozco algún ejemplo) odien que aparezca el código en colores, incluso no lo entiendan; pero es una de las grandes herramientas que tenemos para detectar errores de un vistazo y para saber si una palabra es función/variable/texto/etc.

Pero a medida que vamos programando, tenemos la necesidad de hacer las cosas más rápido, y para ello están los atajos de teclado, por ejemplo:

  • Compilar un programa o ejecutarlo (aunque tengamos un botón para eso, tardamos mucho más tiempo en coger el ratón y hacer click que en pulsar una tecla o una combinación).
  • Copiar/cortar/pegar/salvar, es una tarea muy común, aunque no sea específica de programación, pero aún veo programadores experimentados que hacen click con el derecho o buscan el menú editar y escogen la opción que necesitan. Lo más común aquí es Control+X (cortar), Control+C (copiar), Control+V (pegar), Control +S (salvar), aunque depende del editor.
  • Auto-indentado, también llamado auto-anidado, auto-sangrado y nos permite insertar espacios o tabulaciones delante de cada línea para el correcto sangrado de cada línea de código; en ocasiones cuando copiamos o analizamos código de diversas fuentes, tal vez no esté correctamente indentado, también nos puede valer, en el proceso de aprendizaje, para ver cómo sería la forma correcta.
  • Alineado automático. A veces separamos la inicialización de un array, o los argumentos que pasamos a una función en varias líneas, pero queremos que queden todos alineados. Algunos lo hacen automáticamente, otros necesitan pulsar una tecla o elegir una opción. A veces es la misma que el auto-indentado.
  • Búsqueda de texto. Es importante localizar rápidamente un texto (variable, función, macro, etc) dentro de nuestro código y luego regresar al punto donde estábamos.
  • Ir a una línea determinada. El hecho de dirigirnos a una línea determinada es importante a la hora de ver los errores producidos y a la hora de comentar el código con otras personas
  • Comentarios automáticos. Una buena opción cuando se trata de comentar una región de texto o introducir comentarios al lado de una línea de código ya escrita.
  • Rellenado automático. Muchos IDEs permiten completar automáticamente lo que queremos escribir gracias a lo que ya hemos escrito o a los archivos / librerías que incluimos, esto nos ayuda a escribir menos y hacerlo todo más rápido.
  • División de pantalla. Algunos IDEs permiten dividir la pantalla y editar varios ficheros a la vez, o varias zonas de un mismo archivo.
  • Borrado de texto. En ocasiones nos hace falta borrar una línea entera o una palabra. Podemos seleccionar y borrar, aunque a veces lo podemos hacer con una sola pulsación de teclado.
  • Navegación de texto. Es importante familiarizarnos con las teclas Av-pag (Page Up), Re-pag (Page Down), Inicio, Fin, las flechas, y esto nos vale tanto para navegar como seleccionar texto; aunque en ocasiones, en muchos sistemas podemos utilizar Control para modifcar el comportamiento de estas, por por ejemplo, lo más común es:
    • Control + (izq/der): Navegamos entre palabras
    • Control + (arriba/abajo): Navegamos entre bloques de texto (párrafos)
    • Control + Inicio: Inicio del documento
    • Control + Fin: Fin del documento

En muchos casos no he incluido las teclas en cuestión ya que depende del IDE utilizado. Al principio puede que gastemos un tiempo investigando las teclas rápidas (muchos IDEs nos dicen la tecla rápida cuando vamos a acceder a la opción vía menú o vía comando), y tal vez al principio cuando las empezamos a usar también nos cueste trabajo, pero a la larga ahorraremos tiempo y programaremos mucho más cómodamente.

Programar en C++ puede llegar a ser frustrante

Miércoles, 20 de Octubre de 2010 Gaspar Fernández Sin comentarios

cuchilloSiempre se dice que una de las reglas de la programación es “escribir poco”, tenemos que aunque en ciertos lenguajes, tenemos que escribir dos veces las cosas. Por ejemplo, para un sencillo hola mundo con clases en C++ (se puede hacer todo en el mismo archivo, pero queremos el código bien organizado):
[ hwclass.h ]

1
2
3
4
5
6
7
8
class HolaMundo
{
 public:
  HolaMundo();
  ~HolaMundo();

  void coutVersion();
};

[ hwclass.cpp ]

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

using namespace std;

HolaMundo::HolaMundo()
{
  cout<<"Hola Mundo!!"<<endl;
}

HolaMundo::~HolaMundo()
{
  cout<<"Adiós Mundo!!"<<endl;
}

void HolaMundo::coutVersion()
{
  cout<<"HolaMundo Versión 1.0"<<endl;
}

[ main.cpp ]

1
2
3
4
5
6
7
#include "hwclass.h"

int main()
{
  HolaMundo hm;
  hm.coutVersion();
}

Si queremos compilar el proyecto podemos hacer:

$ g++ -o holamundo main.cpp hwclass.cpp

Bueno, a lo que voy, como veis en hwclass.cpp y hwclass.h se repiten algunas cosas, el nombre de la clase, y el nombre de los métodos cuando vamos a definir el código de cada uno… cuando en el archivo .h se definen decenas de métodos resulta muy repetitivo teclear una y otra vez lo mismo y más aún mantener el orden de los métodos.

Para ello quiero presentar dos aplicaciones:

lzz

Podemos descargarlo desde aquí, tiene multitud de opciones y es bastante potente. Aunque yo veo un gran defecto, tenemos que crear un fichero lzz, que luego el programa lo convertirá a cpp y hpp extrayendo la información según corresponda. Por otra parte, para generar el ejecutable, necesitamos el binario de lzz (como si de un compilador se tratara)

stubgen

Lo encontramos aquí, y aunque le falta alguna que otra cosilla como personalización de la documentación, es bastante potente y nos permite trabajar normalmente con nuestro .h y generar el .cpp con todos los métodos empezados, justo para que nosotros completemos el código.

Foto: ~Brenda_Starr~ (Flickr)
¿Por qué un cuchillo? Un stub en programación es un trozo de código incompleto (tal vez simula lo que va a hacer, o sólo es una pincelada)… stub se parece a stab (puñalada).

OSWC ha muerto de forma sospechosa, pero OSWCalt sí que tiene vida

Lunes, 11 de Octubre de 2010 Gaspar Fernández Sin comentarios

Ya os comenté que la Open Source World Conference (OSWC) se canceló a unos 20 días de celebrarse y han dejado de dar señales de vida por completo… por otra parte, vemos que la cancelación es sospechosa (Barrapunto, Juantomas - Hacking the Planet). Y Microsoft está entre medias, luego tenemos la filtración en Wikileaks de un documento para entorpecer el crecimiento del software libre en Europa.

Afortunadamente, la gente de ASOLIF se ha puesto las pilas a una velocidad de vértigo y mantiene vivas nuestras esperanzas. Por eso, están organizando la OSWCalt para los días 27 y 28 de este mes. Ahora más que nunca debemos permanecer unidos por el software libre.

Si tenéis Facebook podéis entrar aquí.

Seguiré posteando más información en cuanto la tenga.

Comentarios en el código fuente

Domingo, 19 de Septiembre de 2010 Gaspar Fernández 2 comentarios

Leo en Stack Overflow los mejores comentarios que se han encontrado muchos desarrolladores en el código fuente. Merece la pena leerlos, y echar un ratillo. Advertencia… está en inglés.

Podemos encontrar muchísimas disculpas, y es cierto que muchas veces recurrimos a soluciones rápidas, ininteligibles para los demás humanos, no demasiado optimizadas y mucho menos elegantes, pero que nos hacen el apaño.

También encontramos algunos como:

1
return 1; # returns 1
1
long long ago; /* in a galaxy far far away */
1
double penetration; // ouch
1
return 0; // Happy ending
1
2
// Added because boss changed his mind : 20020111,20020501,20020820, ...
// Commented out because boss changed his mind : 20020201,20020614,20020908, ...
1
// This code worked before, but my cat decided to take a trip across my keyboard...
1
2
// Since today's CPUs are really fast, this is dedicated to those who said:
// " You can't use Moore's Law as an excuse to write bad software. "

Bueno… y mucho más…

Sustituyendo texto con expresiones regulares en EMACS

Domingo, 18 de Julio de 2010 Gaspar Fernández Sin comentarios

regular_expression

Una de las herramientas más utilizadas (por mí al menos) es la de reemplazar texto. En EMACS la podemos encontrar con:

M-x replace-string

Con esta orden podemos cambiar un texto por otro dentro de un buffer o una selección. Hasta aquí bien. Pero alguna vez nos podemos encontrar con un texto que debemos reemplazar por otro, y aunque no es exactamente igual en todos los reemplazos que tenemos que hacer sigue una cierta lógica.

Imaginemos que tenemos este código en PHP (Gracias Antonio):

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
switch ($pregunta) {
    case 0: $preg="<br><br>¿Qué significa PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Power H Processor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. PHP: Hypertext Preprocessor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Para Hacer Poco</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. PHP: Harvard Preprocessor</a><br>";
            break;

    case 1: $preg="<br><br>¿Dónde se ejecuta PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. En el Cliente</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. En algún lugar de la Mancha...</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. En el Servidor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. Ninguna de las anteriores</a><br>";
            break;

    case 2: $preg="<br><br>¿Cuál de las siguientes variables NO existe como predefinida en PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. PTTS_BOOK</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. PHPSESSID</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. REQUEST_METHOD</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. REMOTE_ADDR</a><br>";
            break;

    case 3: $preg="<br><br>¿Quién creó PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Rasmus Lerdorf</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. Obi One Kenobi</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Bill Gates</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. Andrew S. Tanenbaum</a><br>";
            break;

    case 4: $preg="<br><br>¿Con qué <i>marca</i> habrá un salto de línea en el código fuente?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. \\t</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. \\n</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. &lt;br&gt;</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. \\g</a><br>";
            break;

    case 5: $preg="<br><br>¿Cuál de las siguientes funciones NO se puede acceder al servidor de datos MySQL?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. mysql_query</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. mysql_connect</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. mssql_connect</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. mysql_pconnect</a><br>";
            break;

    case 6: $preg="<br><br>¿Cuál es la frase correcta?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. setcookie() puede ser llamada desde cualquier parte del documento HTML</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. Las cookies sólo se pueden mandar antes de mandar cualquier otra cabecera</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Las cookies no son parte de la cabecera HTTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. El perro de San Roque no tiene rabo, porque el Chiquito se lo ha robado</a><br>";
            break;

    case 7: $preg="<br><br>¿La función ftp_get?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Sube un fichero al servidor FTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. Establece una conexión FTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Descarga un fichero del servidor FTP</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. Devuelve el nombre del directorio actual</a><br>";
            break;
}

Y queremos sustituirlo por este otro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
addPregunta($pregresp, "¿Qué significa PHP?", "a. Power H Processor", "b. PHP: Hypertext Preprocessor", "c. Para Hacer Poco", "d. PHP: Harvard Preprocessor");

    addPregunta($pregresp, "¿Dónde se ejecuta PHP?", "a. En el Cliente", "b. En algún lugar de la Mancha...", "c. En el Servidor", "d. Ninguna de las anteriores");

    addPregunta($pregresp, "¿Cuál de las siguientes variables NO existe como predefinida en PHP?", "a. PTTS_BOOK", "b. PHPSESSID", "c. REQUEST_METHOD", "d. REMOTE_ADDR");

    addPregunta($pregresp, "¿Quién creó PHP?", "a. Rasmus Lerdorf", "b. Obi One Kenobi", "c. Bill Gates", "d. Andrew S. Tanenbaum");

    addPregunta($pregresp, "¿Con qué <i>marca</i> habrá un salto de línea en el código fuente?", "a. \\t", "b. \\n", "c. &lt;br&gt;", "d. \\g");

    addPregunta($pregresp, "¿Cuál de las siguientes funciones NO se puede acceder al servidor de datos MySQL?", "a. mysql_query", "b. mysql_connect", "c. mssql_connect", "d. mysql_pconnect");

    addPregunta($pregresp, "¿Cuál es la frase correcta?", "a. setcookie() puede ser llamada desde cualquier parte del documento HTML", "b. Las cookies sólo se pueden mandar antes de mandar cualquier otra cabecera", "c. Las cookies no son parte de la cabecera HTTP", "d. El perro de San Roque no tiene rabo, porque el Chiquito se lo ha robado");

    addPregunta($pregresp, "¿La función ftp_get?", "a. Sube un fichero al servidor FTP", "b. Establece una conexión FTP", "c. Descarga un fichero del servidor FTP", "d. Devuelve el nombre del directorio actual");

    addPregunta($pregresp, "¿Con qué función se puede eliminar una variable de sesión?", "a. session_unregister", "b. session_destroy", "c. session_register", "d. jam_session");

    addPregunta($pregresp, "¿Con que harías un buen bocadillo...?", "a. Con pavo de ese que tiene trozos de colores", "b. Con un milagro", "c. Con habichuelas", "d. Con las manos");

El switch podemos quitarlo a mano, y en este ejemplo, tal vez para 7 preguntas, podríamos hacerlo a mano, aunque si fuera alguna más, nos pensaríamos este nuevo método.

Bien, en EMACS, tenemos la siguiente herramienta:

M-x regexp-builder

Que nos seleccionará el texto que corresponde con la expresión regular que estemos construyendo, y nos ayudará a generar una expresión más compleja, en definitiva queremos que la expresión cubra todos los cases, para continuar la lectura del post, nos fijamos en el primero:

1
2
3
4
5
6
case 0: $preg="<br><br>¿Qué significa PHP?<br>";
            $resp="<a href=".$_SERVER['PHP_SELF']."?respuesta=1>a. Power H Processor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=2>b. PHP: Hypertext Preprocessor</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=3>c. Para Hacer Poco</a><br>".
                  "<a href=".$_SERVER['PHP_SELF']."?respuesta=4>d. PHP: Harvard Preprocessor</a><br>";
            break;

Para construir la expresión, hay mucho copy-paste, además, desde Linux podemos seleccionar texto y pegarlo con el botón central del ratón, lo que nos facilita mucho la vida. Por otra parte, hay que tener en cuenta que tenemos que escapar muchos caracteres (como “, [, (, ), ], ?), de todas formas, nos vamos dando cuenta de eso al construir la expresión, si EMACS no selecciona un carácter como correspondiente a la expresión, lo más normal es que tengamos que escaparlo, también, tenemos que tener en cuenta que un espacio en blanco se representa aquí como [:space:]. Con todo esto, construimos la siguiente expresión:

“case [0-9]: $preg=\”

[a-zA-Z_.\<\>\/\¿\?áéíóú[:space:]]*
\”;
[[:space:]]*$resp=\”[\\_()a-zA-Z,\:áéíóú&;[:space:]\.]+
\”.
[[:space:]]*\”[\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+
\”.
[[:space:]]*\”[\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+
\”.
[[:space:]]*\”[\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+
\”.
[[:space:]]*break;

Se seleccionarán todos los case, ahora copiamos la expresión para luego pegarla para reemplazarla. Aunque antes, tenemos que fijarnos en una cosa, en cada línea que queremos sustituir hay fragmentos de texto que queremos que figuren en la cadena sustituida, queremos hacerlo también automáticamente, eso lo conseguimos poniendo entre paréntesis en la expresión anterior las cadenas que vamos a aprovechar del texto que vamos a sustituir. La dejamos así:

case [0-9]: $preg=\”

\([a-zA-Z_.\<\>\/\¿\?áéíóú[:space:]]*\)
\”;
[[:space:]]*$resp=\”\([\\_()a-zA-Z,\:áéíóú&;[:space:]\.]+\)
\”.
[[:space:]]*\”\([\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+\)
\”.
[[:space:]]*\”\([\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+\)
\”.
[[:space:]]*\”\([\\_()a-zA-Z,&áéíóú\:;[:space:]\.]+\)
\”.
[[:space:]]*break;

Ahora llamamos a la siguiente orden:

M-x replace-regexp

Introducimos como expresión la última que hemos hecho (la de los paréntesis, marqué en negrita los textos que queremos conservar). Y la sustituimos por lo siguiente:

addPregunta($pregresp, “\1″, “\2″, “\3″, “\4″, “\5″);

Vemos que es una función a la que le pasamos una variable y después cinco textos, el primer texto (\1), coincidirá con la pregunta (en el código de los cases), y desde el segundo (\2) hasta el quinto (\5) coincidirá con cada una de las cuatro posibles respuestas.

Una vez hecho esto se sustituirán todo el código de los cases, por el de la función addPregunta() con los textos correspondientes.

Tal vez este ejemplo haya sido un poco grande, pero las expresiones regulares son muy útiles, seguro que casi a diario encontráis la necesidad de hacer algo parecido a esto en vuestro editor de texto.

Referencias:
Text Pattern Matching in EMACS
Regexp Replace - GNU Emacs

Foto: fdecomite (Flickr)

Sed… de venganza (1): Sustituyendo cadenas en múltiples archivos

Lunes, 28 de Junio de 2010 Gaspar Fernández 1 comentario

3846929292_60721fb24e

Es uno de los grandes desconocidos y tan temidos comandos de que disponemos. Y es cierto que a veces da pereza mirarse el manual cuando queremos hacer algo que sed podría hacer rápidamente.

Lo que cuento hoy es su uso más popular (porque sed se puede usar para muuuuuchas cosas) y es muy simple, sustituir en un stream un texto por otro (Donde dije digo, digo Diego).

Imaginemos un fichero de texto, para ser originales llamémosle README, y en el texto queremos cambiar la palabra “Ireland” por “Spain”. Podemos hacer lo siguiente:

$ sed ’s/Ireland/Spain/g’ README

y veremos en pantalla el texto. Para guardarlo en el mismo archivo, si buscamos por Internet, veremos cómo la gente se complica la vida (que no digo que sea malo, yo también me la complico un poco más abajo), pero podemos usar el modificador -i (que tiene algunas funciones curiosas).

Si ahora hacemos:

$ sed -i ’s/Ireland/Spain/g’ README

Los cambios se guardarán automáticamente en el fichero README. Pero si investigamos un poco más, y queremos rematar la faena podemos hacer:

$ sed -i~ ’s/Ireland/Spain/g’ README

En este caso, guardaremos el cambio en el fichero README y grabaremos en README~ una copia de seguridad del fichero antiguo. La extensión de la copia de seguridad podemos cambiarla haciendo por ejemplo:

$ sed -i.bak ’s/Ireland/Spain/g’ README

Para que la copia sea README.bak

Pero ahora viene algo interesante, con el parámetro -i podemos modificar todos los archivos que queramos, sed acepta en la entrada múltiples archivos, por lo que si en un directorio con muchos archivos queremos cambiar un texto (imaginad que habéis hecho un proyecto relativamente grande, y hay una función con un nombre un poco ridículo, curiosamente es la que más veces llamáis, y como vamos a enseñar el código fuente, no queremos que nadie lea eso), podremos hacer:

$ sed -i ’s/nombre_ridiculo/nombre_elegante/g’ *

Ahora bien, si el proyecto está en múltiples directorios, siempre podemos usar find para localizar los archivos de la siguiente forma:

find -name ‘*.c’ -exec sed -i ’s/nombre_ridiculo/nombre_elegante/g’ {} \;

Con toda esta línea, buscaremos todos los archivos con extensión .c dentro del directorio actual y subdirectorios y se los pasaremos a sed, con el modificador -exec de find, ejecutaremos el comando que especificamos, donde {} indica el nombre de archivo (que nos lo da find) y con \; indicamos el fin del comando y sus parametros.

Pero esto no termina aquí, sed soporta expresiones regulares, y si por ejemplo queremos coger todas las imágenes de un fichero html, cambiarlas de directorio y añadirles un class (inicialmente encontramos <img src=”foto.jpg” alt=”" /> , y queremos que salga <img class=”imagen” src=”/static/foto.jpg” alt=”" />) podemos hacer lo siguiente:

sed ’s/src=”\(.[a-zA-Z\.\_\/]*\)”/src=”\/static\/\1” class=”imagen”/g’ fichero.html

Lo que hay en negrita, corresponde a la expresión regular que determina el nombre del archivo (caracteres de la a a la z, de la A a la Z, puntos, guiones bajos, y barras (en la cadena de origen), en la cadena de destino escribimos \1 donde queremos que coloque el texto correspondiente a la expresión anterior, es decir donde queremos que coloque el nombre del archivo.

Ni que decir tiene que podemos hacer una mezcla de todo lo dicho en este post ( expresiones regulares, y sustitución en múltiples archivos dentro de múltiples subdirectorios y guardando backups ), y estaremos delante de una potente herramienta.

Lo malo de ejecutar sed, es que tenemos que escapar muchos caracteres,por lo menos ), (, ., \, / y seguro que encontramos alguno más; para ello, siempre que queramos introducir un carácter de esos, debemos poner una contra barra (\) delante.

Foto: albertopveiga (Flickr)

Fibonacci recursivo en C [ intentemos no repetir operaciones! ]

Lunes, 21 de Junio de 2010 Gaspar Fernández Sin comentarios

fibo
Es un ejercicio muy típico cuando se está aprendiendo con funciones recursivas es la sucesión de Fibonacci, aquella en la que F(n)=F(n-1)+F(n-2).
Aunque esta técnica podemos (y deberíamos) utilizarla para una gran cantidad de algoritmos. A veces es necesario sacrificar un poco la memoria del sistema (sin pasarnos) para agilizar y hacer más rápido el programa. Debemos adoptar una solución que beneficie al usuario, no es plan de dejarlo sin memoria, pero tampoco es plan de que la solución se eternice.

Una primera implementación puede ser la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int fibonacci_simple(int n)
{
  /* La función normal, recursiva */
  if (n>2)
    return fibonacci_simple(n-1) + fibonacci_simple(n-2);
  /* Este es el caso más común,  */
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)       
    return 1;
  else if (n==0)
    return 0;
  else
    return -1;          /* Error */
}

Si creamos un programa de ejemplo como este:

1
2
3
4
5
6
7
8
9
10
int main(int argc, char *argv[])
{
    unsigned int numero;

    /* Generamos Fibonaccis */
    for (numero=0; numero<48; numero++)
      printf("%u\n", fibonacci_simple(numero));

    return 0;
}

Podemos observar que para generar la secuencia de 48 números (he escogido este valor, porque a partir de ahí no tenemos suficiente de 32bits para almacenar el valor obtenido), tardamos varios minutos, en concreto, en mi equipo (Pentium M 1.73MHz), echó algo más de 10 minutos. ¡ 10 minutos ! Es un buen rato para lo que ha hecho.
Si lo estudiamos detenidamente (en negrita pongo las veces que llamamos a fibonacci_simple() sin necesidad):

  • fibonacci_simple(0)
  • fibonacci_simple(1)
  • fibonacci_simple(2)
  • fibonacci_simple(3) (que llama a fibonacci_simple(2) y fibonacci_simple(1) )
  • fibonacci_simple(4) (que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) )
  • fibonacci_simple(5) (que llama a fibonacci_simple(4) ( que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) ) y a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) )

Yo creo que con esto nos hacemos una idea de lo que quiero decir, se repiten demasiadas operaciones, seguro que hay alguna forma de solucionar esto.

Propongo lo siguiente:

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
int fibonacci_buffer(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0; /* Tamaño de buffer */
  int n3;           /* n-2 */
  int i;            /* Recorre buffers */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      for (i=0; i<n3; i++)
        fbuffer[i]=0;   /* Ponemos buffer a 0 */

/*    printf("Creo buffer: %d -- %d\n", n3*sizeof(int), errno); */

      bufsize=n3;
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
/*    printf("Amplio buffer\n"); */

      for (i=bufsize; i<n3; i++) /* Ponemos a 0 la memoria nueva */
        fbuffer[i]=0;

      bufsize=n3;
    }
/*       printf("-%d--%d\n", n, fbuffer[n3-1]); */
      if (fbuffer[n3-1]!=0) /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      //      printf("He generado con éxito fbuffer[%d]\n", n3-1);
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Es algo más enrevesado, ya que usamos variables estáticas (podríamos hacer variables globales, con cuidado), así evitamos que la información que vamos almacenando se pierda entre ejecuciones de la función; por otra parte, utilizamos malloc() y realloc() para reservar sólo la memoria que vamos necesitando.
Por otra parte para reducir la memoria utilizada, hacemos que fibonacci(3) se almacene en la posición 0 del buffer (porque el de 2, 1 y 0, ya nos los sabemos); por otra parte… la variable n3 vale n-2; esto es simplemente porque si tenemos que calcular fibonacci(5), y sabemos que fibonacci(3) está almacenado en fbuffer[0], fibonacci(4) estará en fbuffer[1] y fibonacci(5) estará en fbuffer[2]; es decir tenemos que almacenar 3 valores de fibonacci (5-2=3).
Por otra parte, siempre que reservamos memoria ponemos los nuevos espacios a 0, ya que comprobamos que no hay ningún valor almacenado si éstos son 0.

Con el almacén de los números conseguimos que el esquema anterior se quede en:

  • fibonacci_buffer(0)
  • fibonacci_buffer(1)
  • fibonacci_buffer(2)
  • fibonacci_buffer(3) (que llama a fibonacci_buffer(2) y fibonacci_buffer(1)
  • fibonacci_buffer(4) (que llama a fibonacci_buffer(3) ( que saca el valor del buffer ) y a fibonacci_simple(2) )
  • fibonacci_buffer(5) (que llama a fibonacci_buffer(4) (que saca el valor del buffer) y a fibonacci_buffer(3) (que saca el valor del buffer)

Con este algoritmo consigo generar todos los números en 4ms.

¿Será posible simplificarlo un poco más?
Sólo hay que ver cómo se hace la generación de valores de forma recursiva. Si lo pensamos bien, cuando llamamos a fibonacci_buffer(5) la primera vez y lo queremos almacenar en fbuffer[2] llamaremos a fibonacci_buffer(4) (que se almacenará en fbuffer[1], y éste llamará a fibonacci_buffer(3) (que se almacenará en fbuffer[0]); es decir, lo primero que se generará será fbuffer[0], luego fbuffer[1] y por último fbuffer[2]… ¡ vamos por orden ! Sabiendo esto, nos podremos ahorrar el código de poner a 0 los elementos del buffer sabiendo cuál es el último elemento generado.

Para los incrédulos, podemos eliminar el comentario de la línea:

1
//    printf("He generado con éxito fbuffer[%d]\n", n3-1);

y ejecutar… veremos con qué orden se van generando los números.

Bien, intentemos eliminar las puestas a 0 de los elementos del buffer.

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
int fibonacci_buffer2(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0;         /* Tamaño de buffer */
  static int bpos=0;            /* Recorre buffers */
  int n3;           /* n-2 */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      bufsize=n3;   /* Mantenemos bufsize por si redimensionamos el buffer */
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
      bufsize=n3;      
    }

      /* Utilizamos bpos para saber cuál es el último elemento del buffer */
      /* que hemos rellenado. */
      if (bpos>=n3)         /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      bpos=n3;
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Con este algoritmo, consigo generar la secuencia en 2ms. Bastante más rápido que con el primer algoritmo, parece que la puesta a 0 del buffer llevaba su tiempo.

Para concluir, quiero proponer otra solución basada en arrays, supongo que muchos estaréis más familiarizados con estos:

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
int fibonacci_buffer3(int n)
{
  static int fbuffer[MAX_FIB];
  static int bufsize=0;         /* Tamaño de buffer */
  int n3;           /* n-3 */

  if (n>2)
    {
      n3=n-3;

      if (bufsize>n3)           /* Si se encuentra el fibonacci del número que buscaos  */
    return fbuffer[n3]; /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2); /* y lo guardamos */
      bufsize=n3+1;
      /* Al ser fibonacci recursivo, y asignar el tamaño después de la generación */
      /* el buffer se rellenará por orden, es decir, el primero que se rellenará */
      /* será el [0], luego el [1], luego el [2]... */
      return fbuffer[n3];
    }

    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;

  return -1;            /* Error, seguramente me hayan pasado un n negativo*/
}

Esta solución es muy parecida a las anteriores, aunque nos quitamos del medio la reserva de memoria, reducimos las líneas de código, ya que no vamos a alojar tantos valores diferentes… para esta secuencia serían unos 48 * 4 (sizeof(int)) = 192 bytes, y bufsize la utilizamos para ver cuál es el último elemento del buffer con información válida (como vimos antes, se rellenarán por orden, así que sólo tendríamos que llevar la cuenta). Las variables estáticas, como dije, podemos hacerlas globales (y quitarles el static, aunque queda más elegante como static).

Con esta solución tardo unos 5ms de media en generar la secuencia de números.

Foto: Eustaquio Santimano (Flickr)

Visita otras webs de la red