diff --git a/sk1/Makefile b/sk1/Makefile new file mode 100644 index 0000000..18c2139 --- /dev/null +++ b/sk1/Makefile @@ -0,0 +1,17 @@ +CC = gcc +CFLAGS = -std=c11 -Wall -Wextra -O2 + +all: compressor + +compressor: main.o compressor.o + $(CC) $(CFLAGS) -o compressor main.o compressor.o + +main.o: main.c compressor.h + $(CC) $(CFLAGS) -c main.c + +compressor.o: compressor.c compressor.h + $(CC) $(CFLAGS) -c compressor.c + +clean: + rm -f *.o compressor + diff --git a/sk1/README.md b/sk1/README.md new file mode 100644 index 0000000..6e451ea --- /dev/null +++ b/sk1/README.md @@ -0,0 +1,82 @@ +# Kompressor – domáca časť skúšky + +## Zadanie +Cieľom zadania je naprogramovať nástroj na kompresiu a dekompresiu súborov. +Program musí fungovať pre ľubovoľné binárne súbory do veľkosti 10 MB. +Po dekompresii musí byť výsledný súbor rovnaký ako pôvodný. + +Nie je povolené použiť RLE (Run Length Encoding). +Je povolené použiť napríklad Huffmanovo kódovanie alebo iný algoritmus. + +--- + +## Popis programu +Program umožňuje: +- skomprimovať súbor +- dekomprimovať súbor +- vypísať pomoc pri spustení + +Program sa spúšťa z príkazového riadka a pracuje so súbormi zadanými ako argumenty. + +--- + +## Použitý algoritmus +Použil som **Huffmanovo kódovanie**. + +Najprv sa spočítajú frekvencie jednotlivých bajtov vo vstupnom súbore. +Podľa frekvencií sa vytvorí Huffmanov strom. +Každému bajtu sa priradí bitový kód. + +Do výsledného súboru sa uloží: +- veľkosť pôvodného súboru +- tabuľka frekvencií +- skomprimovaný bitový tok + +Pri dekompresii sa z tabuľky frekvencií znovu vytvorí strom +a pomocou neho sa dáta dekódujú späť. + +--- + +## Použitie programu +./compressor -c vstupny_subor vystupny_subor +Skomprimuje vstupný súbor do výstupného. +./compressor -d skomprimovany_subor vystupny_subor +Dekompresia späť na pôvodné dáta. +./compressor -h +Vypíše pomoc. + +--- + +## Technické detaily +- program je napísaný v jazyku C (štandard C11) +- používa iba štandardnú knižnicu C +- práca so súbormi je realizovaná pomocou fopen, fread a fwrite +- program je rozdelený na viac súborov: + - main.c – spracovanie argumentov a spustenie programu + - compressor.c – implementácia kompresie a dekompresie + - compressor.h – hlavičkový súbor +- preklad prebieha pomocou Makefile + +--- + +## Obmedzenia +- maximálna veľkosť vstupného súboru je 10 MB +- kompresia nemusí byť efektívna pre všetky typy súborov +- program nevyužíva žiadne externé knižnice + +--- + +## Testovanie +Program bol testovaný na súboroch z Canterbury corpus. +Po dekompresii bol výsledný súbor vždy zhodný s pôvodným. + +--- + +## Použité zdroje +- študijné materiály k predmetu +- základný popis Huffmanovho kódovania +- generatívny model ChatGPT (OpenAI) + +Použitý prompt: +"Implement Huffman compression in C without external libraries" + diff --git a/sk1/compressor b/sk1/compressor new file mode 100755 index 0000000..df24ac9 Binary files /dev/null and b/sk1/compressor differ diff --git a/sk1/compressor.c b/sk1/compressor.c new file mode 100644 index 0000000..84f5bbf --- /dev/null +++ b/sk1/compressor.c @@ -0,0 +1,249 @@ +#include "compressor.h" +#include +#include +#include +#include + +#define BYTE_RANGE 256 +#define IO_BUFFER 8192 + +typedef struct HuffNode { + int value; + uint32_t weight; + struct HuffNode *zero; + struct HuffNode *one; +} HuffNode; + +typedef struct { + uint32_t pattern; + uint8_t size; +} BitCode; + +static HuffNode *node_create(int value, uint32_t weight) +{ + HuffNode *n=malloc(sizeof(HuffNode)); + if (!n) + { + return NULL; + } + n->value=value; + n->weight=weight; + n->zero=NULL; + n->one=NULL; + return n; +} + +static void node_destroy(HuffNode *n) +{ + if (!n) + { + return; + } + node_destroy(n->zero); + node_destroy(n->one); + free(n); +} + +static HuffNode *assemble_tree(uint32_t histogram[]) +{ + HuffNode *pool[BYTE_RANGE*2]; + int pool_size=0; + for (int i = 0; i < BYTE_RANGE; i++) + { + if (histogram[i]>0) + { + pool[pool_size++] = node_create(i, histogram[i]); + } + } + if (pool_size==1) + { + pool[pool_size++]=node_create(-1, 0); + } + while (pool_size>1) + { + int m1=0, m2=1; + if (pool[m2]->weightweight) + { + int t=m1; m1=m2; m2=t; + } + for (int i = 2; i < pool_size; i++) + { + if (pool[i]->weight < pool[m1]->weight) + { + m2=m1; + m1=i; + } else if (pool[i]->weight < pool[m2]->weight) + { + m2=i; + } + } + HuffNode *parent = node_create( + -1, + pool[m1]->weight+pool[m2]->weight + ); + parent->zero=pool[m1]; + parent->one=pool[m2]; + pool[m1]=parent; + pool[m2]=pool[pool_size-1]; + pool_size--; + } + return pool[0]; +} + +static void generate_codes +( + HuffNode *root, + BitCode table[], + uint32_t bits, + uint8_t depth +) { + if (!root) return; + if (root->value >= 0) + { + table[root->value].pattern=bits; + table[root->value].size=depth; + return; + } + generate_codes(root->zero, table, bits << 1, depth + 1); + generate_codes(root->one, table, (bits << 1) | 1, depth + 1); +} + +int pack_binary(const char *input_path, const char *output_path) +{ + FILE *src=fopen(input_path, "rb"); + FILE *dst=fopen(output_path, "wb"); + if (!src || !dst) + { + if (src) fclose(src); + if (dst) fclose(dst); + return 1; + } + uint32_t freq_map[BYTE_RANGE]={0}; + uint8_t buffer[IO_BUFFER]; + size_t read_bytes; + uint32_t total_size=0; + while ((read_bytes = fread(buffer, 1, IO_BUFFER, src)) > 0) + { + total_size += read_bytes; + for (size_t i = 0; i < read_bytes; i++) + { + freq_map[buffer[i]]++; + } + } + rewind(src); + if (fwrite(&total_size, sizeof(uint32_t), 1, dst) != 1 || + fwrite(freq_map, sizeof(uint32_t), BYTE_RANGE, dst) != BYTE_RANGE) + { + fclose(src); + fclose(dst); + return 1; + } + HuffNode *tree=assemble_tree(freq_map); + if (!tree) + { + fclose(src); + fclose(dst); + return 1; + } + BitCode codebook[BYTE_RANGE]; + memset(codebook, 0, sizeof(codebook)); + generate_codes(tree, codebook, 0, 0); + uint8_t out_byte=0; + int bit_count=0; + while ((read_bytes = fread(buffer, 1, IO_BUFFER, src)) > 0) { + for (size_t i = 0; i < read_bytes; i++) + { + BitCode c=codebook[buffer[i]]; + for (int b = c.size - 1; b >= 0; b--) + { + out_byte = (out_byte << 1) | + ((c.pattern >> b) & 1); + bit_count++; + if (bit_count==8) + { + if (fwrite(&out_byte, 1, 1, dst)!=1) + { + node_destroy(tree); + fclose(src); + fclose(dst); + return 1; + } + out_byte=0; + bit_count=0; + } + } + } + } + if (bit_count>0) + { + out_byte <<= (8 - bit_count); + if (fwrite(&out_byte, 1, 1, dst)!=1) + { + node_destroy(tree); + fclose(src); + fclose(dst); + return 1; + } + } + node_destroy(tree); + fclose(src); + fclose(dst); + return 0; +} + +int unpack_binary(const char *input_path, const char *output_path) +{ + FILE *src=fopen(input_path, "rb"); + FILE *dst=fopen(output_path, "wb"); + if (!src || !dst) + { + if (src) fclose(src); + if (dst) fclose(dst); + return 1; + } + uint32_t expected_size; + uint32_t freq_map[BYTE_RANGE]; + if (fread(&expected_size, sizeof(uint32_t), 1, src) != 1 || + fread(freq_map, sizeof(uint32_t), BYTE_RANGE, src) != BYTE_RANGE) + { + fclose(src); + fclose(dst); + return 1; + } + HuffNode *tree=assemble_tree(freq_map); + if (!tree) + { + fclose(src); + fclose(dst); + return 1; + } + HuffNode *cursor=tree; + uint8_t byte; + uint32_t produced=0; + while (produced= 0 && produced < expected_size; i--) + { + int bit=(byte >> i) & 1; + cursor=bit ? cursor->one : cursor->zero; + if (cursor->value>=0) + { + if (fputc(cursor->value, dst)==EOF) + { + node_destroy(tree); + fclose(src); + fclose(dst); + return 1; + } + produced++; + cursor=tree; + } + } + } + node_destroy(tree); + fclose(src); + fclose(dst); + return 0; +} + diff --git a/sk1/compressor.h b/sk1/compressor.h new file mode 100644 index 0000000..652d66c --- /dev/null +++ b/sk1/compressor.h @@ -0,0 +1,7 @@ +#ifndef COMPRESSOR_H +#define COMPRESSOR_H + +int pack_binary(const char *input_path, const char *output_path); +int unpack_binary(const char *input_path, const char *output_path); +#endif + diff --git a/sk1/compressor.o b/sk1/compressor.o new file mode 100644 index 0000000..386f164 Binary files /dev/null and b/sk1/compressor.o differ diff --git a/sk1/main.c b/sk1/main.c new file mode 100644 index 0000000..f0916a8 --- /dev/null +++ b/sk1/main.c @@ -0,0 +1,38 @@ +#include "compressor.h" +#include +#include + +static void print_help(void) { + printf("Binary compressor using Huffman coding\n"); + printf("Usage:\n"); + printf(" ./compressor -c input output\n"); + printf(" ./compressor -d input output\n"); + printf(" ./compressor -h\n"); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + print_help(); + return 1; + } + + if (strcmp(argv[1], "-h") == 0) { + print_help(); + return 0; + } + + if (argc != 4) { + print_help(); + return 1; + } + + if (strcmp(argv[1], "-c") == 0) + return pack_binary(argv[2], argv[3]); + + if (strcmp(argv[1], "-d") == 0) + return unpack_binary(argv[2], argv[3]); + + print_help(); + return 1; +} + diff --git a/sk1/main.o b/sk1/main.o new file mode 100644 index 0000000..5c6799e Binary files /dev/null and b/sk1/main.o differ