compresor/descompresor de archivos

Iniciado por m@o_614, 3 Octubre 2013, 20:22 PM

0 Miembros y 1 Visitante están viendo este tema.

m@o_614

Saludos

Tengo el siguiente codigo que me tiene que comprimir un archivo cuando aparecen las siguientes palabras: de , para , con , desde , al ,y se tienen que sustituir con los siguientes signos: *, /, + ,- ,@ y escribir el texto comprimido(con las sustituciones) en otro archivo. El programa compila correctamente y me hace lo que le pido pero tiene algunos fallos, por ejemplo cuando utilizo sizeof() en unos for para sacar el numero de elementos me aparecen warnings, y tambien por ejemplo, yo se que el tamanio del texto que voy a comprimir es de 96 caracteres, por eso lo puse asi en los arreglos, pero si mi maestra quiere que me abra un archivo mas grande o mas chico no se si funcionara, como puedo corregir esto??como puedo hacer que funcione para cualquien tamanio de texto??ahh!! y otra duda, mi profesora nos dijo algo de que no queria que el archivo se sobreescribiera y que checaramos eso, no se bien a que se referia si alguien me pudiera ayudar tambien con eso se lo agradeceria

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 100

char ver_signos(char signo[],int i);
char *comprimir_archivo(char signo[],char arreglo[]);

int main()
{
    int i,tam,ini;
    char nombre[MAX],archivo[MAX],arreglo[96],*palabra,*producto,car,opcion,*ptr,sig;
    char *p[]={" de "," para "," con "," desde "," al "};
    char signo[]={'*','/','+','-','@'};
    FILE *fd,*ap;
    printf("Dame el nombre del archivo: ");
    fgets(nombre,MAX,stdin);
    if((ptr = strchr(nombre,'\n')) != NULL)
       *ptr = '\0';
    sprintf(archivo,"F:\\%s.txt",nombre);
    ap = fopen("F:\\Archivo_producto.txt","w");
    if((fd = fopen(archivo,"r"))!= NULL)
    {
        printf("Elige una opcion\n");
        printf("a) Comprimir archivo\n");
        printf("b) Descomprimir archivo\n");
        switch(opcion = getchar())
        {
            case 'A':case'a':
               fgets(arreglo,96,fd);
               for(i=0;i < 5;i++)
               {
                   fseek(fd,0,SEEK_SET);
                   while((car = fgetc(fd))!= EOF)
                   {
                       if(car == ' ')
                       {
                           ini = ftell(fd);
                           tam = strlen(p[i]);
                           palabra = (char*)malloc((tam+1)*sizeof(char));
                           fseek(fd,-1,SEEK_CUR);
                           fgets(palabra,tam+1,fd);
                           if((strcmp(palabra,p[i]))== 0)
                           {
                               sig = ver_signos(signo,i);
                               fseek(fd,ini,SEEK_SET);
                               arreglo[ini-1] = sig;
                           }
                           free(palabra);
                       }
                   }
               }
               producto = comprimir_archivo(signo,arreglo);
               fwrite(producto,sizeof(char),strlen(producto),ap);
               break;
               // me gustaria ponerle aqui el fwrite(arreglo,sizeof(char),strlen(arreglo),ap);
            case 'B': case 'b':/*
               for(i = 0;i < sizeof(signo);i++)
               {
                   while((car = fgetc(ap)))
               }
               break;*/
            default:
               printf("La opcion no existe\n");
        }
    }
    else
       printf("No se pudo abrir el archivo");
    return 0;
}

char ver_signos(char signo[],int i)
{
    int j;
    for(j = 0;j < sizeof(signo);j++)// warning por usar sizeof
    {
        if(j == i)
           return(signo[j]);
    }
}

char *comprimir_archivo(char signo[],char arreglo[])
{
    int tam[]={4,6,5,7,4},i,j,k;
    char a[96];
    for(i=0; i < sizeof(signo);i++)//warning por usar sizeof
    {
        for(j = 0,k = 0;j < 96;k++)
        {
            if(arreglo[j] == signo[i])
            {
                a[k] = arreglo[j];
                j = j+tam[i];
            }
            else
            {
                a[k] = arreglo[j];
                j++;
            }
        }
        memset(arreglo,0,96);
        strcpy(arreglo,a);
    }
    return arreglo;
}


Estas son las unicas fallas que le encontre al codigo, alomejor hay mas que no he notado, las sugerencias para hacerlo mas eficientes son bienvenidas

de antemano gracias

eferion

sizeof no funciona por arte de magia. sizeof no es una función normal de c... es más como una directiva de precompilador.

sizeof únicamente puede calcular el tamaño de arrays si éstos se le pasan completos, es decir:


char array[20];
char *array2[] = &array;

int size1 = sizeof( array ); // size1 = 20
int size2 = sizeof( array2 ); // size2 = 4... es un puntero
int size3 = sizeof( *array2 ); // warning, no se puede calcular


Si necesitas conocer el tamaño del array, o bien lo tienes que pasar como parámetro, o bien, en el caso de cadenas de caracteres, terminas la cadena con '\0' y lo que haces es buscar la posición de ese carácter para determinar su tamaño... por ejemplo con strlen.

Si no quieres que se sobreeescriba el fichero, utiliza un tercer fichero para descomprimir... pide el nombre o bien ponle un prefijo / sufijo al archivo que introduzca el usuario. Entonces, para comprimir pasas del fichero base al que te facilita el usuario, como hasta ahora, y para descomprimir pasas del facilitado por el usuario a este nuevo que te digo.

Para comprobar el funcionamiento de tu programa con archivos más grandes prueba a crearte tus propios archivos, no tengas miedo... además es la mejor forma de comprobar si tu programa funciona correctamente en todas las circunstancias.

PD.: si pones un de, para, con, desde o al al principio o al final de una línea no te va a funcionar el programa... deberías mirar eso.

m@o_614

gracias eferion por tu respuesta, tienes razon estuve checando con varios archivos y si me di cuenta que tiene mas fallas, tal vez tenga que cambiar todo el algoritmo porque por ejemplo cuando tengo el texto:

El libro de Mary es para la clase de mate con la maestra Ana

el codigo va a fallar cuando llegue al 'para', porque el algoritmo cuando se encuentra un ' ' en el archivo lo que va a hacer es tomar los ese caracter vacio y los siguientes 5 caracteres en este caso,( 5 porque " para "tiene 6 caracteres), entonces ese para lo va a pasar por alto y no la va a hacer correctamente, entonces voy a tener que cambiar casi todo el algoritmo :s

eferion

No desesperes... son cosas que pasan.

rir3760

Cita de: m@o_614 en  3 Octubre 2013, 20:22 PMTengo el siguiente codigo que me tiene que comprimir un archivo cuando aparecen las siguientes palabras: de , para , con , desde , al ,y se tienen que sustituir con los siguientes signos: *, /, + ,- ,@ y escribir el texto comprimido(con las sustituciones) en otro archivo.
Leer las lineas de texto del archivo y comprimirlas son dos operaciones que deberías separar en funciones, esto para hacer mas fácil el desarrollo del programa.

En la lectura del archivo sigues con algunos errores que ya te había comentado como almacenar el valor de retorno de fgetc en una variable de tipo char cuando debería ser de tipo int, si no lo haces puedes tener problemas para detectar el estado de fin de archivo.

En cuanto a la compresión puedes verificar cada carácter de la linea y si este es un espacio comparar el resto utilizando la función strncmp, un programa de ejemplo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
   char linea[] = "El libro de Mary es para la clase de mate con la maestra Ana";
   char *palabra[] = {" de "," para "," con "," desde "," al "};
   size_t num_pals = sizeof palabra / sizeof palabra[0];
   char ch[] = "*/+-@";
   size_t i;
   size_t j;
   
   i = 0;
   while (linea[i] != 0){
      if (linea[i] == ' ')
         for (j = 0; j < num_pals; j++)
            if (strncmp(linea + i, palabra[j], strlen(palabra[j])) == 0)
               break;
     
      if (linea[i] == ' ' && j != num_pals){
         putchar(ch[j]);
         i += strlen(palabra[j]);
      }else
         putchar(linea[i++]);
   }
   putchar('\n');
   
   return EXIT_SUCCESS;
}


También hay que considerar que la entrada puede tener alguno de los caracteres utilizados para la compresión ('*', '/', etc.), de existir lo usual es escapar el carácter agregando un carácter especial antes de este, por ejemplo en C para imprimir la diagonal invertida se debe utilizar '\\'.

Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language

m@o_614

muchas gracias rir3760 tratare de corregir eso ultimo y la variable de retorno del fgetc, una ultima duda, si tambien tengo que hacer el descompresor seria conveniente usar una lista doblemente ligada para almacenar cada uno de los caracteres del archivo en un nodo, y que cuando me aparezca un *, /, -, + ,cree otro nodo con la palabra completa y ya hacer los enlaces en la lista??? no se si me di a entender lo que quiero hacer, por ejemplo si tengo

El libro*Mary es/la clase*mate-la maestra Ana

y que despues me queden las palabras en vez de los signos.

El libro de Mary es para la clase de mate con la maestra Ana

saludos :)

rir3760

En tu caso descomprimir es todavía mas fácil ya que solo debes verificar si el carácter se debe imprimir tal cual o en su lugar se debe imprimir una cadena. Para eso lo mas sencillo es utilizar una tabla de valores.

Por ejemplo:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h> /* UCHAR_MAX */

int main(void)
{
   char linea[] = "El libro*Mary es/la clase*mate+la maestra Ana";
   
   char ch[UCHAR_MAX] = {0};
   char set[] = "*/+-@";
   char *cadena[] = {" de "," para "," con "," desde "," al "};
   int i;
   int pos;
   
   /* Los caracteres especiales toman valores mayores que cero */
   for (i = 0; set[i] != '\0'; i++)
      ch[set[i]] = i + 1;
   
   for (i = 0; linea[i] != '\0'; i++){
      pos = ch[linea[i]];
     
      if (pos)
         printf("%s", cadena[pos - 1]);
      else
         putchar(linea[i]);
   }
   putchar('\n');
   
   return EXIT_SUCCESS;
}


Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language

m@o_614

#7
Saludos rir3760 nunca se me hubiera ocurrido lo de la tabla de valores, lo unico que se me ocurrio fue hacer una lista doblemente ligada que es mas laboriosa, pero ps ya la empece, ahora tengo que terminarla. lo que hice fue leer el archivo al reves para que pudiera ingresar cada uno de los caracteres del archivo por la cabeza de una lista, porque si lo hubiera leido desde el principio tendria que buscar el fin de lista para ingresarle el caracter en su posicion correcta y era mas complicada por eso mejor alreves para que me ingresara en orden

el problema que tengo es que en el typedef struct el dato lo tengo declarado como caracter, para ir ingresando caracter por caracter a la lista enlazada, pero despues una vez que recorro la lista busco  ' *',' + ', '/ ' y lo encuentra le pido que me haga un nodo que contenga la palabra que le corresponde, por ejemplo si es * la palabra sera _de_ pero esta seria una cadena y en el typedef la tengo declarada como un solo caracter, un char, como puedo solucionar esto??? y el fgetc ya se que tiene que ser int en vez de char, pero si lo pusiera como int ya no me regresaria el caracter que le voy a enviar a la funcion de insertar_cabeza


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 100

typedef struct elemento
{
   char dato;
   struct elemento *sig;
   struct elemento *ant;
}Nodo;

typedef struct pal
{
    char *dato;
    struct pal *sig;
    struct pal *ant;
}Palabra;

Nodo *crear_nodo(char car);
Palabra *crear_palabra(char *p[],int i);
void insertar_cabeza(Nodo **cabeza,char car);
void recorrer_lista(Nodo *cabeza);
void buscar_elemento(char signo[],char *p[],Nodo *cabeza);

int main()
{
   int pos;
   Nodo *cabeza = NULL;
   char nombre[MAX],archivo[MAX],car,opcion,*ptr,*p[]={" de "," para "," con "," desde "," al "};
   char signo[]={'*','/','+','-','@'};
   FILE *ap;
   printf("Dame el nombre del archivo: ");
   fgets(nombre,MAX,stdin);
   if((ptr = strchr(nombre,'\n')) != NULL)
      *ptr = '\0';
   sprintf(archivo,"F:\\%s.txt",nombre);
   if((fd = fopen(archivo,"r"))!= NULL)
   {
       printf("Elige una opcion\n");
       printf("a) Comprimir archivo\n");
       printf("b) Descomprimir archivo\n");
       switch(opcion = getchar())
       {
           case 'A':case'a':
             // Aqui va el compresor
              break;
           case 'B': case 'b':
              if((ap = fopen("F:\\Archivo_producto.txt","r"))!=NULL)
              {
                  fseek(ap,0,SEEK_END);
                  pos = ftell(ap);
                  for(pos-=1;pos >= 0;pos--)
                  {
                      fseek(ap,pos,SEEK_SET);
                      car = fgetc(ap);
                      insertar_cabeza(&cabeza,car);
                  }
                  buscar_elemento(signo,p,cabeza);
                  //recorrer_lista(cabeza);
              }
              else
                 printf("No se pudo abrir archivo");
              break;
           default:
              printf("La opcion no existe\n");
       }
   }
   else
      printf("No se pudo abrir el archivo");
   return 0;
}

Palabra *crear_palabra(char *p[],int i)
{
    Palabra *x;
    x = (Palabra*)malloc(sizeof(Palabra));
    x->dato = p[i];
    x->sig = x->ant = NULL;
    return x;
}

Nodo *crear_nodo(char car)
{
   Nodo *x;
   x = (Nodo*)malloc(sizeof(Nodo));
   x->dato = car;
   x->sig = x->ant = NULL;
   return x;
}

void insertar_cabeza(Nodo **cabeza,char car)
{
   Nodo *nuevo;
   nuevo = crear_nodo(car);
   nuevo->sig = *cabeza;
   nuevo->ant = NULL;
   *cabeza = nuevo;
}

void recorrer_lista(Nodo *cabeza)
{
   Nodo *indice;
   for(indice = cabeza;indice != NULL;indice = indice->sig)
      printf("%c",indice->dato);
}

void buscar_elemento(char signo[],char *p[],Nodo *cabeza)
{
   int i;
   Nodo *indice;
    Palabra *ptro;
   for(i=0;i < 5;i++)
   {
       for(indice = cabeza;indice != NULL;indice = indice->sig)
       {
           if(indice->dato == signo[i])
           {
               ptro = crear_palabra(p,i);
               printf("%s",ptro->dato);
               /*
               indice->sig->ant = ptro; aqui tengo warnings
               ptro->ant = indice;
               ptro->sig = indice->sig;
               indice->sig = ptro;*/
           }
       }
   }
}


muchas gracias y disculpa las molestias por tanta preguntadera

rir3760

Cita de: m@o_614 en  5 Octubre 2013, 04:57 AMlo unico que se me ocurrio fue hacer una lista doblemente ligada que es mas laboriosa, pero ps ya la empece, ahora tengo que terminarla.
Si en tu centro de estudios te piden que lo hagas de esa manera bueno, no tienes mas mas opción que hacerlo así pero no deja de ser una solución extremadamente complicada.

Cita de: m@o_614 en  5 Octubre 2013, 04:57 AMlo que hice fue leer el archivo al reves para que pudiera ingresar cada uno de los caracteres del archivo por la cabeza de una lista, porque si lo hubiera leido desde el principio tendria que buscar el fin de lista para ingresarle el caracter en su posicion correcta y era mas complicada por eso mejor alreves para que me ingresara en orden
Ya te había comentado en otro tema que no deberías utilizar fseek para posicionarte (en la forma que lo haces) en un stream en modo texto.

Soluciones realmente sencillas hay varias, supongamos que el archivo contiene los caracteres "ABCDE" y tu los insertas de la forma usual en la lista, esta contiene "EDCBA". Opciones:
A) Creas una función que invierta la lista, no mas de diez lineas de código.
B) Ya que la lista es de vinculo doble simplemente los procesas al revés empezando por el ultimo y terminando con el primero, para ello te mueves por la lista utilizando el puntero al nodo previo.

Cita de: m@o_614 en  5 Octubre 2013, 04:57 AMel problema que tengo es que en el typedef struct el dato lo tengo declarado como caracter, para ir ingresando caracter por caracter a la lista enlazada, pero despues una vez que recorro la lista busco  ' *',' + ', '/ ' y lo encuentra le pido que me haga un nodo que contenga la palabra que le corresponde, por ejemplo si es * la palabra sera _de_ pero esta seria una cadena y en el typedef la tengo declarada como un solo caracter, un char, como puedo solucionar esto?
Estas enfocando mal el problema. Supongamos que tu lista contiene los caracteres "Casa*Jose" y se debe expandir a "Casa de Jose" ahi los pasos son:
1) Iteras por cada nodo en la lista verificando si es un carácter especial.
2) Si el carácter es especial sustituyes el nodo (siguiendo el ejemplo) '*' por los nodos ' ', 'd', 'e' y ''.
En resumen no tienes que manejar distintos tipo de datos, solo expandir o contraer la lista.

Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language