Welcome to collectivesolver - Programming & Software Q&A with code examples. A website with trusted programming answers. All programs are tested and work.

Contact: aviboots(AT)netvision.net.il

Semrush - keyword research tool

Turn ChatGPT, Claude, Gemini, And CoPilot Into Your Personal Assistant, Business Coach, Content Creator, And More

AFFILIATE MARKETING Your all-in-one performance engine Manage affiliates, creators, and customer referrals in one unified platform—turning every partnership into measurable growth
Secure & Reliable Web Hosting, Free Domain, Free SSL, 1-Click WordPress Install, Expert 24/7 Support

Boost your online presence with premium web hosting and servers

Disclosure: My content contains affiliate links.

42,844 questions

55,671 answers

573 users

How to perform high‑performance reversible text compression using a word dictionary in C

2 Answers

0 votes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define INITIAL_CAPACITY 16

/**
 * Structure representing a Dictionary that maps unique words to integer indices.
 */
typedef struct {
    char **words;    // Array of dynamically allocated word strings
    size_t count;    // Current number of unique words stored
    size_t capacity; // Allocated capacity of the dictionary array
} Dictionary;

/* Function prototypes */
Dictionary *dict_create(void);
int dict_find_or_add(Dictionary *dict, const char *word);
void dict_free(Dictionary *dict);
char *compress_string(const char *input, Dictionary *dict);
char *decompress_string(const char *compressed, const Dictionary *dict);

/**
 * Creates and initializes a new empty Dictionary.
 */
Dictionary *dict_create(void) {
    Dictionary *dict = malloc(sizeof(Dictionary));
    if (!dict) return NULL;

    dict->capacity = INITIAL_CAPACITY;
    dict->count = 0;
    dict->words = malloc(dict->capacity * sizeof(char *));
    
    if (!dict->words) {
        free(dict);
        return NULL;
    }
    return dict;
}

/**
 * Searches for a word in the dictionary.
 * If found, returns its existing index.
 * If not found, adds the word to the dictionary and returns the new index.
 */
int dict_find_or_add(Dictionary *dict, const char *word) {
    for (size_t i = 0; i < dict->count; i++) {
        if (strcmp(dict->words[i], word) == 0) {
            return (int)i;
        }
    }

    if (dict->count >= dict->capacity) {
        size_t new_cap = dict->capacity * 2;
        char **new_words = realloc(dict->words, new_cap * sizeof(char *));
        if (!new_words) return -1;
        dict->words = new_words;
        dict->capacity = new_cap;
    }

    dict->words[dict->count] = strdup(word);
    if (!dict->words[dict->count]) return -1;

    return (int)(dict->count++);
}

/**
 * Frees all memory allocated for the dictionary and its contained words.
 */
void dict_free(Dictionary *dict) {
    if (!dict) return;
    for (size_t i = 0; i < dict->count; i++) {
        free(dict->words[i]);
    }
    free(dict->words);
    free(dict);
}

/**
 * Compresses a string by replacing unique words with dictionary tokens (e.g., @0, @1).
 * Preserves non-alphanumeric punctuation and spacing intact.
 */
char *compress_string(const char *input, Dictionary *dict) {
    if (!input || !dict) return NULL;

    size_t out_capacity = strlen(input) + 64;
    size_t out_len = 0;
    char *output = malloc(out_capacity);
    if (!output) return NULL;
    output[0] = '\0';

    const char *ptr = input;

    while (*ptr != '\0') {
        if (!isalnum((unsigned char)*ptr)) {
            if (out_len + 2 >= out_capacity) {
                out_capacity *= 2;
                output = realloc(output, out_capacity);
            }
            output[out_len++] = *ptr++;
            output[out_len] = '\0';
            continue;
        }

        const char *word_start = ptr;
        while (*ptr != '\0' && isalnum((unsigned char)*ptr)) {
            ptr++;
        }
        size_t word_len = (size_t)(ptr - word_start);

        char temp_word[128] = "";
        if (word_len >= sizeof(temp_word)) word_len = sizeof(temp_word) - 1;
        memcpy(temp_word, word_start, word_len);
        temp_word[word_len] = '\0';

        int word_id = dict_find_or_add(dict, temp_word);

        char token[32] = "";
        int token_len = snprintf(token, sizeof(token), "@%d", word_id);

        if (out_len + (size_t)token_len >= out_capacity) {
            out_capacity = (out_len + (size_t)token_len) * 2;
            output = realloc(output, out_capacity);
        }

        memcpy(output + out_len, token, (size_t)token_len);
        out_len += (size_t)token_len;
        output[out_len] = '\0';
    }

    return output;
}

/**
 * Decompresses a tokenized string (@0, @1, ...) using the provided Dictionary.
 * Reconstructs the original text by substituting tokens back into words.
 */
char *decompress_string(const char *compressed, const Dictionary *dict) {
    if (!compressed || !dict) return NULL;

    size_t out_capacity = strlen(compressed) * 2 + 64;
    size_t out_len = 0;
    char *decompressed = malloc(out_capacity);
    if (!decompressed) return NULL;
    decompressed[0] = '\0';

    const char *ptr = compressed;

    while (*ptr != '\0') {
        // Step 1: Detect token start indicator '@' followed by digits
        if (*ptr == '@' && isdigit((unsigned char)*(ptr + 1))) {
            ptr++; // Skip '@'
            
            // Parse token index
            int index = 0;
            while (isdigit((unsigned char)*ptr)) {
                index = index * 10 + (*ptr - '0');
                ptr++;
            }

            // Look up corresponding word from dictionary
            if (index >= 0 && (size_t)index < dict->count) {
                const char *word = dict->words[index];
                size_t word_len = strlen(word);

                // Resize buffer if word exceeds current allocation
                if (out_len + word_len >= out_capacity) {
                    out_capacity = (out_len + word_len) * 2;
                    decompressed = realloc(decompressed, out_capacity);
                }

                memcpy(decompressed + out_len, word, word_len);
                out_len += word_len;
                decompressed[out_len] = '\0';
            }
        } else {
            // Step 2: Pass non-token characters (spaces, punctuation) through directly
            if (out_len + 2 >= out_capacity) {
                out_capacity *= 2;
                decompressed = realloc(decompressed, out_capacity);
            }
            decompressed[out_len++] = *ptr++;
            decompressed[out_len] = '\0';
        }
    }

    return decompressed;
}

int main(void) {
    const char *original = "this is is a test test compression string string test this is a test compression";

    Dictionary *dict = dict_create();
    if (!dict) {
        fprintf(stderr, "Failed to initialize dictionary.\n");
        return EXIT_FAILURE;
    }

    char *compressed = compress_string(original, dict);

    if (compressed) {
        size_t orig_size = strlen(original);
        size_t comp_size = strlen(compressed);

        printf("Original String  : \"%s\"\n", original);
        printf("Original Size    : %zu bytes\n\n", orig_size);

        printf("Dictionary Mapping:\n");
        for (size_t i = 0; i < dict->count; i++) {
            printf("  [@%zu] => %s\n", i, dict->words[i]);
        }

        printf("\nCompressed String: \"%s\"\n", compressed);
        printf("Compressed Size  : %zu bytes\n", comp_size);
        printf("Space Reduction  : %.2f%%\n\n", 
               (1.0 - ((double)comp_size / (double)orig_size)) * 100.0);

        // Print original text decompressed from the token stream
        char *decompressed = decompress_string(compressed, dict);
        if (decompressed) {
            printf("Decompressed Text: \"%s\"\n", decompressed);
            free(decompressed);
        }

        free(compressed);
    }

    dict_free(dict);
    
    return EXIT_SUCCESS;
}


/*
run:

Original String  : "this is is a test test compression string string test this is a test compression"
Original Size    : 80 bytes

Dictionary Mapping:
  [@0] => this
  [@1] => is
  [@2] => a
  [@3] => test
  [@4] => compression
  [@5] => string

Compressed String: "@0 @1 @1 @2 @3 @3 @4 @5 @5 @3 @0 @1 @2 @3 @4"
Compressed Size  : 44 bytes
Space Reduction  : 45.00%

Decompressed Text: "this is is a test test compression string string test this is a test compression"

*/

 



answered Jul 31 by avibootz
0 votes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/*
    ================================================================
    HIGH‑PERFORMANCE WORD COMPRESSION USING A HASH TABLE
    ---------------------------------------------------------------
    This program compresses text by replacing repeated words with
    compact tokens (@ID). It preserves punctuation and spacing and
    can fully decompress back to the original text.

    OPTIMIZATION:
    -------------
    The original version used a linear search (O(n)) for each word.
    This version uses a HASH TABLE for O(1) average lookup time.

    RESULT:
    -------
    • Much faster for large inputs
    • Same reversible compression
    • Clean, idiomatic, safe C code
    ================================================================
*/

/* ----------------------------- */
/* CONFIGURATION CONSTANTS       */
/* ----------------------------- */

#define INITIAL_CAPACITY 16
#define HASH_SIZE 4096        /* Large enough for fast hashing */
#define MAX_WORD_LEN 256      /* Safety limit for temporary buffers */


/* ----------------------------- */
/* HASH TABLE ENTRY STRUCTURE    */
/* ----------------------------- */

typedef struct Entry {
    char *word;          /* Stored word */
    int index;           /* Dictionary index */
    struct Entry *next;  /* Linked list for collisions */
} Entry;


/* ----------------------------- */
/* DICTIONARY STRUCTURE          */
/* ----------------------------- */

typedef struct {
    char **words;        /* Array of unique words */
    size_t count;        /* Number of words */
    size_t capacity;     /* Allocated capacity */
    Entry *table[HASH_SIZE]; /* Hash table buckets */
} Dictionary;


/* ----------------------------- */
/* HASH FUNCTION (FNV‑1a)        */
/* ----------------------------- */

static unsigned hash_word(const char *s) {
    unsigned hash = 2166136261u;
    while (*s) {
        hash ^= (unsigned char)*s++;
        hash *= 16777619u;
    }
    return hash % HASH_SIZE;
}


/* ----------------------------- */
/* CREATE DICTIONARY             */
/* ----------------------------- */

Dictionary *dict_create(void) {
    Dictionary *dict = malloc(sizeof(Dictionary));
    if (!dict) return NULL;

    dict->capacity = INITIAL_CAPACITY;
    dict->count = 0;
    dict->words = malloc(dict->capacity * sizeof(char *));
    if (!dict->words) {
        free(dict);
        return NULL;
    }

    /* Initialize hash table buckets */
    memset(dict->table, 0, sizeof(dict->table));

    return dict;
}


/* ----------------------------- */
/* FIND OR ADD WORD (FAST)       */
/* ----------------------------- */

int dict_find_or_add(Dictionary *dict, const char *word) {
    unsigned h = hash_word(word);
    Entry *e = dict->table[h];

    /* 1. Search bucket chain */
    while (e) {
        if (strcmp(e->word, word) == 0)
            return e->index;   /* Found */
        e = e->next;
    }

    /* 2. Not found → add new word */
    if (dict->count >= dict->capacity) {
        dict->capacity *= 2;
        dict->words = realloc(dict->words, dict->capacity * sizeof(char *));
    }

    int new_index = (int)dict->count;
    dict->words[dict->count++] = strdup(word);

    /* 3. Insert into hash table */
    Entry *new_entry = malloc(sizeof(Entry));
    new_entry->word = dict->words[new_index];
    new_entry->index = new_index;
    new_entry->next = dict->table[h];
    dict->table[h] = new_entry;

    return new_index;
}


/* ----------------------------- */
/* FREE DICTIONARY               */
/* ----------------------------- */

void dict_free(Dictionary *dict) {
    if (!dict) return;

    for (size_t i = 0; i < dict->count; i++)
        free(dict->words[i]);

    free(dict->words);

    /* Free hash table chains */
    for (size_t i = 0; i < HASH_SIZE; i++) {
        Entry *e = dict->table[i];
        while (e) {
            Entry *next = e->next;
            free(e);
            e = next;
        }
    }

    free(dict);
}


/* ----------------------------- */
/* COMPRESS STRING               */
/* ----------------------------- */

char *compress_string(const char *input, Dictionary *dict) {
    size_t out_cap = strlen(input) * 2 + 64;
    size_t out_len = 0;
    char *out = malloc(out_cap);
    out[0] = '\0';

    const char *p = input;

    while (*p) {

        /* Pass punctuation/spaces directly */
        if (!isalnum((unsigned char)*p)) {
            if (out_len + 2 >= out_cap) {
                out_cap *= 2;
                out = realloc(out, out_cap);
            }
            out[out_len++] = *p++;
            out[out_len] = '\0';
            continue;
        }

        /* Extract word */
        const char *start = p;
        while (isalnum((unsigned char)*p)) p++;
        size_t len = p - start;

        char word[MAX_WORD_LEN];
        if (len >= MAX_WORD_LEN) len = MAX_WORD_LEN - 1;
        memcpy(word, start, len);
        word[len] = '\0';

        /* Get dictionary index */
        int id = dict_find_or_add(dict, word);

        /* Write token */
        char token[32];
        int tlen = snprintf(token, sizeof(token), "@%d", id);

        if (out_len + tlen >= out_cap) {
            out_cap = (out_len + tlen) * 2;
            out = realloc(out, out_cap);
        }

        memcpy(out + out_len, token, tlen);
        out_len += tlen;
        out[out_len] = '\0';
    }

    return out;
}


/* ----------------------------- */
/* DECOMPRESS STRING             */
/* ----------------------------- */

char *decompress_string(const char *compressed, const Dictionary *dict) {
    size_t out_cap = strlen(compressed) * 2 + 64;
    size_t out_len = 0;
    char *out = malloc(out_cap);
    out[0] = '\0';

    const char *p = compressed;

    while (*p) {

        /* Token? */
        if (*p == '@' && isdigit((unsigned char)p[1])) {
            p++; /* skip '@' */

            int id = 0;
            while (isdigit((unsigned char)*p)) {
                id = id * 10 + (*p - '0');
                p++;
            }

            if (id >= 0 && (size_t)id < dict->count) {
                const char *w = dict->words[id];
                size_t wl = strlen(w);

                if (out_len + wl >= out_cap) {
                    out_cap = (out_len + wl) * 2;
                    out = realloc(out, out_cap);
                }

                memcpy(out + out_len, w, wl);
                out_len += wl;
                out[out_len] = '\0';
            }
        }
        else {
            /* Pass punctuation */
            if (out_len + 2 >= out_cap) {
                out_cap *= 2;
                out = realloc(out, out_cap);
            }
            out[out_len++] = *p++;
            out[out_len] = '\0';
        }
    }

    return out;
}


/* ----------------------------- */
/* MAIN                          */
/* ----------------------------- */

int main(void) {
    const char *text =
        "this is is a test test compression string string test "
        "this is a test compression";

    Dictionary *dict = dict_create();

    char *compressed = compress_string(text, dict);
    char *decompressed = decompress_string(compressed, dict);

    printf("Original:      \"%s\"\n", text);
    printf("Compressed:    \"%s\"\n", compressed);
    printf("Decompressed:  \"%s\"\n\n", decompressed);

    printf("Dictionary:\n");
    for (size_t i = 0; i < dict->count; i++)
        printf("  @%zu => %s\n", i, dict->words[i]);

    free(compressed);
    free(decompressed);
    dict_free(dict);

    return 0;
}


/*
run:

Original:      "this is is a test test compression string string test this is a test compression"
Compressed:    "@0 @1 @1 @2 @3 @3 @4 @5 @5 @3 @0 @1 @2 @3 @4"
Decompressed:  "this is is a test test compression string string test this is a test compression"

Dictionary:
  @0 => this
  @1 => is
  @2 => a
  @3 => test
  @4 => compression
  @5 => string

*/

 



answered Jul 31 by avibootz
edited Jul 31 by avibootz

Related questions

...