Skip to main content
2 of 3
added 150 characters in body

Optimize Huffman Compressor in C++

I'm currently writing a Huffman compressor for educational purposes.

I want it to compress/decompress files less then 5mb. The time limit is 5 seconds.

Now it compresses a 5mb file in almost 8 seconds. The decompression speed is really awful. It requires almost 6 seconds on a file of the size approx 0.1mb.

The problem is in reading/writing. I can get all code tables in less than 1 second, but then writing subroutine works about 6 seconds on 5mb file.

How can I improve speed of compression/decompression?

main.cpp

#include <iostream>
#include <bits/stdc++.h> 
#include "huffman.h"
#include "archiver.h"

using namespace std::chrono;

int main() {
     high_resolution_clock::time_point t1 = 
     high_resolution_clock::now();
     Archiver ar;

     //encoding

     /*

     std::map<char, int32_t> m;
     ar.createFreqTable("test.pdf", m);
     HuffmanTree t(m);
     std::ifstream ifs("test.pdf");
     std::ofstream ofs("test.out");
     ar.compress(ifs, ofs, &t);

     */

     //decoding

     // /*

     //test.out - 123 930 bytes
     HuffmanTree nt;
     std::ifstream ifs2("test.out");
     std::ofstream ofs2("result.pdf");
     ar.decompress(ifs2, ofs2, &nt);

     //*/

     high_resolution_clock::time_point t2 = high_resolution_clock::now();
     auto duration = duration_cast<milliseconds>( t2 - t1 ).count();
     std::cout << duration;  // 5349
     return 0;
 }

archiver.h

#include "huffman.h"
#include "bitstring.h"
#ifndef HUFFMAN_ARCHIVER_H
#define HUFFMAN_ARCHIVER_H

class Archiver{
private:
     std::map<std::vector <bool>, char> codes;

     std::map<char, std::vector<bool> > lookup;

public:
     Archiver(){};

     void compress(std::ifstream&, std::ofstream&, HuffmanTree*);

     void decompress(std::ifstream&, std::ofstream&, HuffmanTree*);

     void encodeTree(BitStringWrite&, TreeNode*);

     TreeNode* decodeTree(BitStringRead&);

     void buildCodes(TreeNode*, std::vector<bool>);

     std::map<std::vector <bool>, char>& getCodes(){
         return codes;
     };

     std::map<char, int32_t>& createFreqTable(const std::string&, std::map<char, int32_t>&);

     void buildTable(TreeNode*);

     std::map<char, std::vector<bool> >& getTable(){
         return lookup;
     };

};
#endif //HUFFMAN_ARCHIVER_H

archiver.cpp

#include "archiver.h"
#include <fstream>
#include <deque>

std::map<char, int32_t>& Archiver::createFreqTable(const std::string &name, std::map<char, int32_t>& freq){
    std::ifstream file(name);
    int next = 0;

    while ((next = file.get()) != EOF) {
        char uc = static_cast <char> (next);
        std::map<char, int32_t>::iterator iter;
        iter = freq.find(uc);
        if (iter != freq.end())
            iter->second += 1;
        else
            freq[uc] = 1;
    }

    return freq;
 };

void Archiver::encodeTree(BitStringWrite& bw, TreeNode* node){
    if (node -> isLeaf()) {
        bw.writeBit(1);
        bw.writeByte(node->getChar());
        return;
    }
    else {
        bw.writeBit(0);
        encodeTree(bw, node->getLeftTree());
        encodeTree(bw, node->getRightTree());
    }
}

TreeNode* Archiver::decodeTree(BitStringRead& br){
    if (br.readBit()) {
        return new TreeNode(br.readByte(), 0, true, NULL, NULL);
    }
    else {
        TreeNode* left = decodeTree(br);
        TreeNode* right = decodeTree(br);
        return new TreeNode(0, 0, false, left, right);
    }
}

void Archiver::buildCodes(TreeNode* n, std::vector<bool> cur) {
    if (n -> isLeaf()) {
        codes[cur] = n->getChar();
        return;
    }

    cur.push_back(0);
    buildCodes(n->getLeftTree(), cur);
    cur.pop_back();
    cur.push_back(1);
    buildCodes(n->getRightTree(), cur);

    return;
}

void Archiver::buildTable(TreeNode* root) {
    std::deque< std::pair<TreeNode *, std::vector<bool> > > q;
    q.push_back(make_pair(root, std::vector<bool>()));

    while (!q.empty()) {
        TreeNode *node, *lc, *rc;
        std::vector<bool> code;
        node = q.front().first;
        code = q.front().second;
        q.pop_front();
        lc = node->getLeftTree();
        rc = node->getRightTree();
        if (lc) {
             std::vector<bool> code_cp(code);
             q.push_back(make_pair(lc, (code.push_back(0), code)));
             q.push_back(make_pair(rc, (code_cp.push_back(1), code_cp)));
        }
        else
             lookup.insert(make_pair(node->getChar(), code));
        }
   }

void Archiver::compress(std::ifstream &ifs, std::ofstream &ofs, HuffmanTree *tree) {
    ifs.clear();
    ifs.seekg(0, ifs.beg);
    BitStringRead br(ifs);
    BitStringWrite bw(ofs);
    buildTable(tree->getRoot());
    encodeTree(bw, tree -> getRoot());

    while(!ifs.eof()){
        br.readByte();
        int sz = getTable()[br.getByte()].size();
        std::vector<bool> out = getTable()[br.getByte()];
        for(int i = 0; i < sz; i++)
            bw.writeBit(out[i]);
    }
}

void Archiver::decompress(std::ifstream &ifs, std::ofstream &ofs, HuffmanTree *tree) {
    ifs.clear();
    ifs.seekg(0, ifs.beg);
    BitStringRead br(ifs);
    BitStringWrite bw(ofs);
    TreeNode* t = decodeTree(br);
    std::vector<bool> cur;
    buildCodes(t, cur);
    std::vector<bool> v;
    bool b = false;

    while(!ifs.eof()) {
         while (!(getCodes().count(v)) && !ifs.eof()) {
              b = br.readBit();
              v.push_back(b);
         }
        if (ifs.eof())
            break;
        char s = getCodes()[v];
        v.clear();
        bw.writeByte(s);
    }
}

huffman.h

#ifndef HUFFMAN_HUFFMAN_H
#define HUFFMAN_HUFFMAN_H

#include <sys/param.h>
#include <iostream>
#include <map>
#include <vector>

class TreeNode{
public:
    TreeNode(char c, int cnt, bool l, TreeNode* lc, TreeNode* rc): character(c), count(cnt), is_leaf(l), left(lc), right(rc){};

    TreeNode(): character(0), count(0), is_leaf(false), left(NULL), right(NULL){}

    int getCount() const{
        return this -> count;
    };

    char getChar() const{
        return this -> character;
    };

    TreeNode* getLeftTree() const{
        return this -> left;
    };

    TreeNode* getRightTree() const{
        return this -> right;
    };

    void setLeftTree(TreeNode* n){
        this -> left = n;
    };

    void setRightTree(TreeNode* n){
        this -> right = n;
    };

    void setChar(char c){
        this -> character = c;
    };

    bool isLeaf(){
        return is_leaf;
    }

    void setLeaf(bool num){
        this->is_leaf = num;
    }

private:
    char character;
    int count;
    bool is_leaf;
    TreeNode* left;
    TreeNode* right;
};

class HuffmanTree{
public:

    HuffmanTree(std::map<char , int>&);

    HuffmanTree(){
        root = new TreeNode(0, 0, false, NULL, NULL);
    };

    ~HuffmanTree();

    TreeNode* getRoot() const{
        return this -> root;
    };

    class NodeComparator {
    public:
        bool operator()(const TreeNode *const lhs, const TreeNode *const rhs) {
            if (lhs->getCount() == rhs->getCount()) {
                return lhs->getChar() > rhs->getChar();
            }

            return lhs->getCount() > rhs->getCount();
        }
    };

    TreeNode* merge(TreeNode* node1, TreeNode* node2);
    void recursiveNodeDelete(TreeNode* node);
   // uint32_t check_count(uint32_t count);

private:
    TreeNode* root;
};


#endif //HUFFMAN_HUFFMAN_H

huffman.cpp

#include "huffman.h"
#include <sstream>
#include <queue>
using namespace std;

HuffmanTree::HuffmanTree(std::map<char, int>& count_map) {
    if (count_map.empty()) {
        std::stringstream ss;
        ss << "Compressor requires a non-empty text.";
        throw std::runtime_error{ss.str()};
    }

    std::priority_queue<TreeNode*, std::vector<TreeNode*>, HuffmanTree::NodeComparator> queue;

    for(auto a : count_map)
        queue.push(new TreeNode(a.first, a.second, true, NULL, NULL));

    while (queue.size() > 1) {
        TreeNode* node1 = queue.top(); queue.pop();
        TreeNode* node2 = queue.top(); queue.pop();
        queue.push(merge(node1, node2));
    }

    root = queue.top(); queue.pop();
}

void HuffmanTree::recursiveNodeDelete(TreeNode* node) {
    if (node == NULL) {
        return;
    }

    recursiveNodeDelete(node->getLeftTree());
    recursiveNodeDelete(node->getRightTree());

    delete node;
}

HuffmanTree::~HuffmanTree() {
    recursiveNodeDelete(root);
}

TreeNode* HuffmanTree::merge(TreeNode* node1, TreeNode* node2) {
    TreeNode* new_node = new TreeNode(0, node1->getCount() + node2->getCount(), false, NULL, NULL);

    if (node1->getCount() < node2->getCount()) {
        new_node->setLeftTree(node1);
        new_node->setRightTree(node2);
    }
    else {
        new_node->setLeftTree(node2);
        new_node->setRightTree(node1);
    }

    new_node->setChar(std::max(node1->getChar(), node2->getChar()));

    return new_node;
}

bitstring.h

#ifndef HUFFMAN_BITSTRING_H
#define HUFFMAN_BITSTRING_H
#include <iostream>
#include <vector>

class BitStringWrite {

private:
    char _byte;
    int _pos;
    std::ostream &_out_f;

public:
    BitStringWrite(std::ostream &_out_f);

    ~BitStringWrite();

    void writeBit(bool bit);

    void writeByte(char b);

    void flush();
};

class BitStringRead {

private:
    char _byte;
    int _pos;
    std::istream &_in_f;

public:

    BitStringRead(std::istream &_in_f);

    char readByte();

    bool readBit();

    char getByte(){
        return _byte;
    }

};
#endif //HUFFMAN_BITSTRING_H

bitstring.cpp

#include "bitstring.h"

BitStringWrite::BitStringWrite(std::ostream &_out_f) : _byte(0), _pos(0), _out_f(_out_f) {}

void BitStringWrite::writeBit(bool bit) {
    if (_pos == 8)
        flush();
    if (bit == 1) {
        _byte |= (1 <<  _pos);
    }
    _pos++;
}

void BitStringWrite::writeByte(char b){
    for(int i = 0; i < 8; i++)
        writeBit((b >> i) & 1);
}

void BitStringWrite::flush() {
    if (_pos != 0) {
        _out_f.write(&_byte, sizeof(char));
        _pos = 0;
        _byte = 0;
    }
}

BitStringRead::BitStringRead(std::istream &_in_f) : _pos(8), _in_f(_in_f) {}

bool BitStringRead::readBit() {
    if (_pos == 8) {
        _in_f.read(&_byte, sizeof(char));
        _pos = 0;
    }
    return (_byte >>  _pos++) & (char)1;
}

char BitStringRead::readByte() {
    char sym = (char)0;
    for (int i = 0; i < 8; i++){
        sym |= ((1 & readBit()) << (i));
    }
    return sym;
}

BitStringWrite::~BitStringWrite() {
    flush();
}