Palíndromo en C desde un fichero


Introducción

Estos días he tenido que hacer un pequeño ejemplo de un programa en C, aparentemente sencillo, pero del que no he encontrado buenas soluciones en Google, ni siguiera en otros idiomas más dados.

Casi todos los ejemplos que veía por internet eran poco elegantes, no cuidaban el tratamiento de errores y además todos pedían la entrada de datos por línea de órdenes. Asi que me ha parecido útil ponerlo en el blog, para que gente que se encuentre con el problema en un futuro, tenga una buena referencia o al menos aproximada a su solución.

Descripción del problema

El problema consiste en crear un programa en C que pida el nombre de un archivo de texto y devuelva el número y el palíndromo de todos los posibles palíndromos encontrados en dicho archivo.

Un palíndromo segun Wikipedia es:

Una palabra, número o frase que se lee igual hacia adelante que hacia atrás.

Por ejemplo algunas palabras: ala, bob, solos, reconocer, …

Y alguna frases como:

Dabale arroz a la zorra el abad

En nuestro caso por simplicidad y caso común detectaremos solo las palabras y no frases o textos enteros, ya que si no esto podría complicarse hasta un nivel mayor, aunque tampoco sería demasiado complicado.

Resolución del problema

Supongamos que tenemos un fichero de ejemplo con algunos palíndromos como el siguiente:

Este es un archivo de prueba
que contiene palíndromos como
pueden ser: ala anilina reconocer solos otto bob
pero por ejemplo no son palindromos
solas ola

El siguiente programa, detectara los palíndromos según nuestros propósitos (lo explico más detalladamente después):

#include < stdio.h>;
#include < stdlib.h>;
#include < string.h>;

int searchPalin(char *cad)
{
    unsigned int i = 0, d = (int) strlen(cad)-1;
    int palin=1;
    while ((palin == 1) && (i < ((int)strlen(cad))))
    {
        if (cad[i] != cad[d]) palin=0;
        else
        {
            i++;
            d--;
        }
    }
    return palin;
}

int main(int argc, char** argv)
{
    if(argc < 2)
    {
        fprintf(stderr,"palin: opción inválida\nEl primer argumento debe ser un nombre de archivo.\n");
        return 1;
    }

    char *namefile=argv[1];
    FILE *fd;

    if((fd=fopen(namefile,"r")) == NULL)
    {
        fprintf(stderr,"palin: Error al abrir el archivo.\n");
        return 1;
    }
    else
    {
        printf("Buscando palíndromos en archivo: %s\n",namefile);

        fseek(fd, 0, SEEK_END);
        long length = ftell(fd);
        fseek(fd, 0, SEEK_SET);
        char *w;
        w = (char *) malloc(length*sizeof(char *));
        int i,j,p=0;

        fread(w, sizeof(unsigned int), length, fd);
        fclose(fd);

        char *aux;
        aux = (char *) malloc(strlen(w)*sizeof(char *));
        for(i=0,j=0;i

Bien pasemos a la explicación detallada:

Las tres primeras lineas incluyen en las bibliotecas necesarias para el funcionamiento del programa, la de entrada/salida estandar, la biblioteca estandar y la biblioteca de cadenas (útil para estos propósitos).

La función searchPalin es la que puedes encontrar en la mayoría de los sitios de internet, se encarga de contar los caracteres de la cadena e ir viendo desde el principio al final si van coincidiendo por el principio y por el final. Si es así retorna un valor booleano verdadero (líneas 5 a 19).

En la función main primero comprobamos que hemos recibido un número correcto de argumentos, al menos los necesarios, ya que más de los estrictos, serán ignorados (líneas 23 a 27).

Como segunda comprobación tenemos que asegurarnos que el parámetro del nombre de fichero dado, existe y se puede abrir para lectura, si es así ya podemos procesar el archivo en busca de palíndromos (líneas 29 a 36).

Por comodidad para realizar la lectura del archivo utilizamos la función fread() (línea 48), por lo que será necesario saber la longitud del fichero. La longitud de un fichero no es difícil de calcular, pero hay que tener un aspecto en cuenta.

La función que tenemos disponible para el cálculo de la longitud de un fichero es ftell(), pero dicha función retorna el indicador de posición de fichero, que al comenzar la lectura será 0, luego, necesitamos posicionarnos al final del fichero, después llamar a ftell() y por último volver al principio del fichero para empezar a leer caracteres mediante fread(). Para posicionarnos al principio y al final utilizamos fseek() consiguiendo nuestro propósito (lineas 41 a 43) .

Después cerraremos el archivo con fclose() (línea 49), ya que habremos leído todos los caracteres y los habremos almacenado en una variable llamada w, con la peculiaridad, de que se hace una reserva dinámica de memoria con la cantidad exacta de caracteres del archivo, aprovechando que ya habíamos calculado la longitud del archivo (línea 44 y45), por lo que no nos limitamos a una cantidad en un archivo (como hacen otras posibles implementaciones).

También necesitaremos una cadena auxiliar (aux) en la que reservaremos la cantidad de carácteres que tenga w (línea 51 y 52).

Posteriormente procesaremos nuestra cadena y si leemos un carácter normal que no sea un espacio en blanco o un tabulador, lo almacenaremos en nuestra cadena auxiliar (líneas 61 a 68). Si por el contrario encontramos un salto de línea o un espacio en blanco (lineas 55 a 60) , entonces sera una palabra distinta, por lo que procesaremos el contenido que tengamos en aux, ademas de añadir el carácter '\0' de final de cadena (línea 57). Esto último es importante, ya que si no añadimos ese carácter no se interpretara bien la cadena al no estar completa y ser una sucesión de caracteres (peculiaridades de C).

Llamaremos a la función searchPalin para que nos verifique si la cadena aux es un palindromo y si es, mostraremos el número de palindromo y el palíndromo en sí.

Compilación y ejecución

Para compilar el archivo, se puede hacer a pelo, o bien podemos utilizar un makefile (mucho más cómodo) como el siguiente:

BINARY = palin
CFLAGS = -o
CC = gcc
RM = rm

all:
    @echo Compilando...
 $(CC) $(BINARY).c $(CFLAGS) $(BINARY)
   @echo Hecho.

clean:
  @echo Limpiando...
  $(RM) -f *.o *~
 @echo Hecho.

Para compilar escribimos:

$ make

Para ejecutar el binario con el fichero de ejemplo.txt:

$ ./palin ejemplo.txt

Y debe dar una salida como:

Buscando palíndromos en archivo: ejemplo_old.txt
1. ala
2. anilina
3. reconocer
4. solos
5. otto
6. bob

Para limpiar la compilación:

$ make clean

Y eso es todo, no es muy complicado, pero requiere varios detalles en los que se debe prestar atención.

Cualquier sugerencia de mejora o comentario adicional es bienvenido.

Happy coding!


2 thoughts on “Palíndromo en C desde un fichero”

  1. Pues a mi me da errores de compilación, te los dejo aquí:

    [email protected]:~/Escritorio$ make
    Compilando...
    gcc palin.c -o palin
    palin.c:1:20: error: stdio .h: No existe el fichero ó directorio
    palin.c:2:21: aviso: elementos extra al final de la directiva #include
    palin.c:2:22: error: stdlib .h: No existe el fichero ó directorio
    palin.c:3:21: aviso: elementos extra al final de la directiva #include
    palin.c:3:22: error: string .h: No existe el fichero ó directorio
    palin.c: En la función ‘searchPalin’:
    palin.c:7: aviso: declaración implícita incompatible de la función interna ‘strlen’
    palin.c: En la función ‘main’:
    palin.c:25: aviso: declaración implícita incompatible de la función interna ‘fprintf’
    palin.c:25: error: ‘stderr’ no se declaró aquí (primer uso en esta función)
    palin.c:25: error: (Cada identificador no declarado solamente se reporta una vez
    palin.c:25: error: para cada funcion en la que aparece.)
    palin.c:30: error: ‘FILE’ no se declaró aquí (primer uso en esta función)
    palin.c:30: error: ‘fd’ no se declaró aquí (primer uso en esta función)
    palin.c:32: error: ‘NULL’ no se declaró aquí (primer uso en esta función)
    palin.c:34: aviso: declaración implícita incompatible de la función interna ‘fprintf’
    palin.c:39: aviso: declaración implícita incompatible de la función interna ‘printf’
    palin.c:41: error: ‘SEEK_END’ no se declaró aquí (primer uso en esta función)
    palin.c:43: error: ‘SEEK_SET’ no se declaró aquí (primer uso en esta función)
    palin.c:45: aviso: declaración implícita incompatible de la función interna ‘malloc’
    palin.c:52: aviso: declaración implícita incompatible de la función interna ‘strlen’
    make: *** [all] Error 1

  2. @Daniel: el plugin de wordpress para código no me admite muy bien los caracteres del include y mete algun espacio, comiendose también el primer punto y coma.

    Prueba a sustituir las primeras lineas por:
    [code]
    #include <stdio.h>;
    #include <stdlib.h>;
    #include <string.h>;
    [/code]
    Observa que he añadido el ; en la primera linea y quitado los espacios en el .h.
    Prueba ahora y me dices a ver que tal.

    PD: Tambien es posible que te haga falta instalar el paquete build-essential, para Ubuntu o Debian pon en terminal:
    sudo apt-get install build-essential
    O en Synaptic busca dicho paquete.

Deja un comentario