C.J
C.J

Reputation: 169

RLE algorithm should compress bytes in c

I know there are too many questions about this algorithm but I couldn't really find a good answer for compressing the bytes. I am kind of a newbie in C. I have the following code:

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

//compress function here...

int main(int argc, char **argv) {

    if(argc != 2){
        fprintf(stderr, "Wrong argument number\n");
        exit(1);
    } 

    FILE *source = fopen(argv[1], "rb");
    if(source == NULL){
        fprintf(stderr, "Cannot open the file to be read\n");
        exit(1);
    }

    FILE *destination;
    char name = printf("%s.rle", argv[1]);
    while((destination = fopen(&name, "wb")) == NULL){
        fprintf(stderr, "Can't create the file to be written\n");
        exit(1);
    }

    compress_file(source, destination);

    int error;
    error = fclose(source);
    if(error != 0){
        fprintf(stderr, "Error: fclose failed for source file\n");
    }
    error = fclose(destination);
    if(error != 0){
        fprintf(stderr, "Error: fclose failed for destination file\n");
    }
}

If this is test.c and the executable is test. I need to make this work on terminal/command prompt as "./test file.txt". My file.txt includes something like (bytes):

20 21 20 20 8F 8F 21 21 64 60 70 20 21 90 90

and the desired output is:

01 20 01 21 02 20 02 8F 02 21 01 64 01 60 01 70 01 20 01 21 02 90

My code create a file and that file includes:

0b00 0000 0106 0000 0000 0000 0000 0000 0000 0000 0a

instead what I want. What do I miss?

Also I want my file to be named as file.txt.rle but it has no name.

EDIT:

char name[30];
sprintf(name, "%s.rle", argv[1]);

solved the problem for naming.

Upvotes: 1

Views: 759

Answers (2)

4386427
4386427

Reputation: 44329

Also I want my file to be named as file.txt.rle but it has no name.

Well, this code

char name = printf("%s.rle", argv[1]);
while((destination = fopen(&name, "wb")) == NULL){

doesn't give you a string like "file.txt.rle". Instead try something like:

size_t len = strlen(argv[1]) + 4 + 1;
char name[len];
sprintf(name, "%s.rle", argv[1]);
while((destination = fopen(name, "wb")) == NULL){

instead what I want. What do I miss?

Well, you miss that you need to put data into str

This code

char str[BUF_SIZE];
fwrite(str, sizeof(str), 1, destination);

just writes an uninitialized variable to the file.

I'll not give you a complete solution but here is something that you can start with and then figure the rest out yourself.

void compress_file(FILE *source, FILE *destination){

    char str[BUF_SIZE];
    int index = 0;

    int repeat_count = 0;
    int previous_character = EOF;
    int current_character;

    while((current_character = fgetc(source)) != EOF){
        if(current_character != previous_character) {

            if (previous_character != EOF) {
                // Save the values to str
                str[index++] = repeat_count;
                str[index++] = previous_character;
            }

            previous_character = current_character;
            repeat_count = 1;
        }
        else{
            repeat_count++;
        }
    }
    if (repeat_count != 0)
    {
        str[index++] = repeat_count;
        str[index++] = previous_character;
    }

    fwrite(str, index, 1, destination);
}

EXAMPLE 1:

Let's say a file.txt is:

ABBCCC

On linux it can be displayed hexadecimal like this:

# hexdump -C file.txt
00000000  41 42 42 43 43 43                                 |ABBCCC|

After running the program, you have:

hexdump -C file.txt.rle
00000000  01 41 02 42 03 43                                 |.A.B.C|

EXAMPLE 2:

Let's say that file.txt is like

# hexdump -C file.txt
00000000  20 21 20 20 8f 8f 21 21  64 60 70 20 21 90 90     | !  ..!!d`p !..|

the result will be

# hexdump -C file.txt.rle
00000000  01 20 01 21 02 20 02 8f  02 21 01 64 01 60 01 70  |. .!. ...!.d.`.p|
00000010  01 20 01 21 02 90                                 |. .!..|

Upvotes: 2

Mathieu
Mathieu

Reputation: 9649

As pointed in comments, you have two problems:

  • Usage of printf instead of sprintf,
  • Writting to file what you've counted.

Name Creation

char name = printf("%s.rle", argv[1]);
destination = fopen(&name, "wb");

The first line will store the number of characters in argv[1] plus 4 into name. Since, from man printf:

Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings).

The second line is more problematic : you ask fopen to open a file giving it a pointer to char instead of a read string.

One correct way to do what you want is:

/* reserve memory to store file name
   NOTE: 256 here might not large enough*/
char name[256];
/* fill name array with original name + '.rle' 
   The return of sprintf is tested to assert that its size was enough */    
if (snprintf(name, sizeof name, "%s.rle", argv[1]) >= sizeof name)
{
    fprintf(stderr, "name variable is not big enough to store destination filename");
}
       

Writting to file

The code

char str[BUF_SIZE];
fwrite(str, sizeof(str), 1, destination);
     

reserve a big array, and writes it to file, without initializing it. To do what you want, you can have this approach:

  • make a function that will only write two characters in file: the number of character found and the character itself
  • call this function each time needed (at character changing, but not when one of character is EOF...)

Let's look at :

void write_char_to_file(FILE *f, int count, char car)
{
    /* char array to be stored in file */
    char str[2];   
    /* number of repeating characters */
    str[0] = count;
    /* the character */
    str[1] = car;
    /* write it to file */
    fwrite(str, sizeof str, 1, f);    
}

This function has two potential problems:

  • It doesn't handle char overflow (what if count is over 256?),
  • It doesn't test the return of fwrite.

Then, when this function should be called, when the current character changes:

EOF A A B C C EOF

In this example, we have 4 characters changes, but we want only 3 writting in the file, so:

  • Character changing when previous is EOF must be ignored (else we would write something like 0 (char)EOF at file starting),
  • One writting must be added after while loop since, when the last reading gives EOF, we still have 2 C to write to file.

Let's look at the code:

while((current_character = fgetc(source)) != EOF) {
    if(current_character != previous_character) { 
        /* ignore initial change */            
        if (previous_character != EOF) {  
            write_char_to_file(destination, repeat_count, previous_character);
        }
        previous_character = current_character;
        repeat_count = 1;
    } else {
        repeat_count++;
    }
}
/* write last change */
write_char_to_file(destination, repeat_count, previous_character);

This code have a problem too: what if the input file is empty? (first read gives EOF)


The complete code:

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

#define BUF_SIZE 5096

void write_char_to_file(FILE *f, int count, char car)
{
    /* char array to be stored in file */
    char str[2];   
    /* number of repeating characters */
    str[0] = count;
    /* the character */
    str[1] = car;
    /* write it to file */
    fwrite(str, sizeof str, 1, f);    
}

void compress_file(FILE *source, FILE *destination)
{
    int repeat_count = 0;
    int previous_character = EOF;
    int current_character;

    while((current_character = fgetc(source)) != EOF) {
        if(current_character != previous_character) {            
            if (previous_character != EOF) {  
                write_char_to_file(destination, repeat_count, previous_character);
            }
            previous_character = current_character;
            repeat_count = 1;
        } else {
            repeat_count++;
        }
    }
    write_char_to_file(destination, repeat_count, previous_character);
}

int main(int argc, char **argv) {
    if(argc != 2) {
        fprintf(stderr, "Wrong argument number\n");
        exit(1);
    } 

    FILE *source = fopen(argv[1], "rb");
    if(source == NULL) {
        fprintf(stderr, "Cannot open the file to be read\n");
        exit(1);
    }

    FILE *destination;
    /* reserve memory to store file name
       NOTE: 256 here might not large enough*/
    char name[256];
    /* fill name array with original name + '.rle' 
       The return of sprintf is tested to assert that its size was enough */    
    if (snprintf(name, sizeof name, "%s.rle", argv[1]) >= sizeof name)
    {
        fprintf(stderr, "name variable is not big enough to store destination filename");
    }
    
    /* while is not needed here, if do the job */
    if((destination = fopen(name, "wb")) == NULL) {
        fprintf(stderr, "Can't create the file to be written\n");
        exit(1);
    }
    
    compress_file(source, destination);
    
    int error;
    error = fclose(source);
    if(error != 0) {
        fprintf(stderr, "Error: fclose failed for source file\n");
    }
    error = fclose(destination);
    if(error != 0) {
        fprintf(stderr, "Error: fclose failed for destination file\n");
    }
    
    /* main must return a integer */
    return 0;
}

Upvotes: 1

Related Questions