Декодирование файла Хаффмана из канонической формы

Я пишу файл Хаффмана, в котором я сохраняю длину кода канонических кодов в заголовке файла. И во время декодирования я могу восстановить канонические коды и сохранить их в файле std::map<std:uint8_it, std::vector<bool>>. Фактические данные считываются в один файл std::vector<bool>. Прежде чем кто-либо предложит мне использовать std::bitset, позвольте мне пояснить, что коды Хаффмана имеют переменную длину в битах, и, следовательно, я использую std::vector<bool>. Итак, учитывая, что у меня есть символы и соответствующие им канонические коды, как мне декодировать мой файл? Я не знаю, куда идти отсюда. Может кто-нибудь объяснить мне, как я буду декодировать этот файл, так как я не мог найти ничего, связанного с ним при поиске.


person WDRKKS    schedule 11.04.2015    source источник
comment
Generally speaking, the process of decompression is simply a matter of translating the stream of prefix codes to individual byte values, usually by traversing the Huffman tree node by node as each bit is read from the input streamкодирование Хаффмана.   -  person royhowie    schedule 11.04.2015


Ответы (2)


Вам не нужно создавать коды или дерево, чтобы декодировать канонические коды. Все, что вам нужно, это список символов по порядку и количество символов в каждой длине кода. Под «по порядку» я подразумеваю сортировку по длине кода от самой короткой до самой длинной, а в пределах каждой длины кода — по значению символа.

Поскольку канонические коды в пределах длины кода представляют собой последовательные двоичные целые числа, вы можете просто выполнить целочисленное сравнение, чтобы увидеть, попадают ли биты, которые у вас есть, в этот диапазон кода, и, если это так, целочисленное вычитание, чтобы определить, какой это символ.

Ниже приведен код из puff.c (с небольшими изменениями ), чтобы наглядно показать, как это делается. bits(s, 1) возвращает следующий бит из потока. (Это предполагает, что всегда есть следующий бит.) h->count[len] — это количество символов, закодированных кодами длины len, где len находится в 0..MAXBITS. Если вы сложите h->count[1], h->count[2], ..., h->count[MAXBITS], это будет общее количество закодированных символов и длина массива h->symbol[]. Первые h->count[1] символов в h->symbol[] имеют длину 1. Следующие h->count[2] символов в h->symbol[] имеют длину 2. И так далее.

Значения в массиве h->count[], если они правильные, ограничены, чтобы не превысить возможное количество кодов, которые могут быть закодированы в len битах. Он может быть дополнительно ограничен для представления полного кода, т. Е. Нет последовательности битов, которая остается неопределенной, и в этом случае decode() не может возвращать ошибку (-1). Чтобы код был полным и не было превышено количество подписок, сумма h->count[len] << (MAXBITS - len) по всем len должна равняться 1 << MAXBITS.

Простой пример: если мы кодируем e одним битом, t двумя битами, а a и o тремя битами, то h->count[] будет {0, 1, 1, 2} (первое значение, h->count[0] не используется), а h->symbol[] будет {'e','t','a','o'}. Тогда код e будет 0, код t будет 10, a будет 110, а o будет 111.

#define MAXBITS 15              /* maximum bits in a code */

struct huffman {
    short *count;       /* number of symbols of each length */
    short *symbol;      /* canonically ordered symbols */
};

int decode(struct state *s, const struct huffman *h)
{
    int len;            /* current number of bits in code */
    int code;           /* len bits being decoded */
    int first;          /* first code of length len */
    int count;          /* number of codes of length len */
    int index;          /* index of first code of length len in symbol table */

    code = first = index = 0;
    for (len = 1; len <= MAXBITS; len++) {
        code |= bits(s, 1);             /* get next bit */
        count = h->count[len];
        if (code - count < first)       /* if length len, return symbol */
            return h->symbol[index + (code - first)];
        index += count;                 /* else update for next length */
        first += count;
        first <<= 1;
        code <<= 1;
    }
    return -1;                          /* ran out of codes */
}
person Mark Adler    schedule 11.04.2015
comment
Привет, Марк... Я очень ценю, что ты нашел время написать это. Но мне очень жаль, что я не могу понять большую часть этого. Это не ваше объяснение, но, возможно, это мой уровень понимания, который усложняет мне задачу. В любом случае... Спасибо за объяснение, Марк. +1 - person WDRKKS; 11.04.2015
comment
Просто выполните процедуру с примером, и вы получите его. Это не становится намного проще, чем это. Попробуйте расшифровать эти биты слева направо: 10111100 для предоставленного примера кода. - person Mark Adler; 11.04.2015
comment
Хорошо... Спасибо, я так и сделаю. Однако пара вещей. Вы пропустили struct state из своего примера кода. Должен ли я объявить это тоже или нет? Что именно это делает? А также, как бы я расшифровал данные биты, используя эту функцию? Функция принимает аргумент struct huffman, и я читаю биты из файла в std::vector<bool> по причинам, изложенным выше. Извините, если мой вопрос покажется глупым или глупым, но я на самом деле не понял, что делает функция, просто прочитав ее. - person WDRKKS; 12.04.2015
comment
Для этой подпрограммы состояние просто указывает, как и откуда можно получить бит. Вы можете заменить bits(s, 1) на что угодно, чтобы вернуть следующий бит из потока, целое число, равное 0 или 1. Для примеров битов, которые я предоставил в комментарии выше, первый вызов bits(s, 1) вернет 1. Второй вызов вернет 0, третий вызов 1. И так далее. - person Mark Adler; 12.04.2015
comment
Хорошо, Марк... Я попробую. Большое спасибо за ваше объяснение. Если я столкнусь с какими-либо блокпостами, я спрошу здесь снова. Я просто хочу знать одну вещь. В настоящее время я пишу длину битов в заголовке файла. Должен ли я внести какие-либо изменения в отношении этого кода? - person WDRKKS; 12.04.2015
comment
Конечно. Пока вы передадите необходимую информацию для декодирования, все, что вы делаете, будет в порядке. Есть более компактные способы передачи кода. Как только вы получите то, что вы делаете, вы можете посмотреть, как deflate сжимает описание кода. deflate run-length кодирует длину кода, а затем Хаффман кодирует длину кода и серии. - person Mark Adler; 12.04.2015
comment
@MarkAdler Итак, я попробовал ваш код, и он работает с первой попытки, он правильно распаковывает сжатые данные. К сожалению, я не понимаю, как это работает. Мой собственный декомпрессор Хаффмана, который я написал, был намного, намного медленнее, как бы я его ни понимал. В основном он пытался декодировать все символы любой длины (в порядке возрастания), пока не добьется успеха. Я не понимаю, как вы могли так оптимизировать, чтобы был только один цикл. - person Bregalad; 18.08.2017
comment
@Bregalad Думаю, тебе просто нужно изучить код. Это зависит от того, является ли код каноническим, в частности от того, чтобы коды были в целочисленном порядке от самого короткого до самого длинного. - person Mark Adler; 19.08.2017
comment
Вместо того, чтобы потреблять по одному биту за раз, не быстрее ли будет потреблять MAXBITS из потока, находить первый символ меньшей длины, а затем вычитать и индексировать? Может даже округлить битовый блок, прочитанный до ближайшей границы байта, может потребоваться заполнение символов нулями. Любые рекомендации по оптимизации? - person TemplateRex; 21.08.2018
comment
@TemplateRex Для этого биты обратные, поэтому вам нужно будет их перевернуть, что и делает побитовый код. Обратное можно сделать с помощью таблицы, но как только вы доберетесь туда, вы также сможете декодировать с помощью таблицы. Тогда вы получите что-то вроде надувания zlib. - person Mark Adler; 21.08.2018

Ваша карта содержит соответствующую информацию, но она отображает символы в коды. Тем не менее, данные, которые вы пытаетесь декодировать, содержат коды. Таким образом, ваша карта не может использоваться для эффективного чтения символов, соответствующих кодам, поскольку метод поиска ожидает символ. Поиск кодов и получение соответствующего символа будет линейным поиском.

Вместо этого вы должны реконструировать дерево Хаффмана, которое вы построили для шага сжатия. Значения частоты внутренних узлов здесь не имеют значения, но вам понадобятся листовые узлы в правильных позициях. Вы можете создавать дерево на лету, когда читаете заголовок файла. Сначала создайте пустое дерево. Для каждого прочитанного сопоставления символа с кодом создайте соответствующие узлы в дереве. Например. если символ «D» был сопоставлен с кодом 101, то убедитесь, что в корне есть правый дочерний узел, у которого есть левый дочерний узел, у которого есть правый дочерний узел, который содержит символ «D», создавая узлы, если они отсутствовали.

Используя это дерево, вы можете затем декодировать поток следующим образом (псевдокод, предполагая, что получение правильного дочернего элемента соответствует добавлению 1 к коду):

// use a node variable to remember the position in the tree while reading bits
node n = tree.root
while(stream not fully read) {
    read next bit into boolean b
    if (b == true) {
        n = n.rightChild
    } else {
        n = n.leftChild
    }
    // check whether we are in a leaf node now
    if (n.leftChild == null && n.rightChild == null) {
        // n is a leaf node, thus we have read a complete code
        // add the corresponding symbol to the decoded output
        decoded.add(n.getSymbol())
        // reset the search
        n = tree.root
    }
}

Обратите внимание, что инвертирование вашей карты для получения правильного направления все равно приведет к субоптимальной производительности (по сравнению с обходом бинарного дерева), поскольку она не может использовать ограничение на меньшее пространство поиска, как это делает обход.

person muued    schedule 11.04.2015
comment
Спасибо за ответ, мууед. Хотя у меня есть некоторые сомнения. decoded.add(n.getSymbol())... Здесь я сопоставляю сгенерированный код с существующим на карте и добавляю его в файл, верно? Я использую std::back_inserter для добавления. Кроме того, как можно проверить листовой узел? node == NULL, да? Наконец, вы упомянули об инвертировании карты... где бы мне инвертировать карту? Пожалуйста, скажи мне это. Спасибо. - person WDRKKS; 11.04.2015
comment
Я попытался объяснить это дальше через редактирование. decoded.add(n.getSymbol()) — это место, где вы успешно сопоставили прочитанный код с символом и добавили его в выходной файл. Листовая проверка выполняется явно. Вы можете инвертировать карту, чтобы получить отображение кодов в символы. Это направление вам нужно, так как вы читаете коды и хотите соответствующие символы. Тем не менее, обход дерева - лучший способ сделать это. - person muued; 11.04.2015
comment
ОК... Теперь я понял это. Попробую реализовать, а там как пойдет. Однако напоследок. Поскольку я строю свое дерево на этапе сжатия, используя частоты, и поскольку я не записываю частоты в файл, а также, поскольку вы упомянули значения частот, не имеют значения, как мне воссоздать дерево, используя только мою карту? Я пытался найти это, но ничего не нашел. Пожалуйста, просто скажи мне это. Я действительно ценю твою помощь. Спасибо. - person WDRKKS; 11.04.2015
comment
Вы можете использовать свою карту или (что еще проще) просто использовать информацию из файла, когда вы его читаете, полностью избавляя вас от создания карты. Я объяснил, как создать дерево в другом редактировании. Как видите, вы просто создаете узлы и помещаете символы в листья. Никакие частоты не нужны. - person muued; 11.04.2015
comment
Хорошо... Я думаю, что могу работать с этим. Я попытаюсь реализовать это и посмотреть, что произойдет. Если у меня возникнут какие-либо проблемы, я снова отпишусь здесь. Большое спасибо за помощь, мууед. - person WDRKKS; 11.04.2015