1

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

Результатом кодирования Хаффмана является массив пар символ-код, где код — целое число произвольной битовой длины. Для достижения сжатия из кодов следует получать битовый поток, который записывается в закодированный файл.

В приведённом ответе makeAndSlice() создаёт битовый поток BitsStream из data[] и отсекает по 8 старших значимых битов в OutputByte по мере накопления их в потоке (while (BitsStreamCount >= ByteBits)). Чтобы получить лаконичный ответ, массивом data[] подменяется последовательность кодовых значений символов исходного файла.

Например, пускай data[] = {110, 1110, 11110, 1110, 110, 11110, 1110, 1}, где все числа записаны в двоичном представлении, тогда makeAndSlice() создаёт битовый поток 11011101111011101101111011101 и разделяет его на следующий список байтов: {11011101, 11101110, 11011110, 11101000}.

  • Слишком общий и плохо сформулированный вопрос, плюс вы не указали что пытались сделать. Если вам нужна реализация алгоритма Хаффмана и LZ77 посмотрите любой исходник, который использует DEFLATE, например http://www.zlib.net/ или http://www.gnu.org/software/gzip/ – igumnov Apr 09 '15 at 20:27
  • Есть исходный файл, есть кодовая таблица. Каждому символу исходного файла переназначается код (целое число) из таблицы. Последовательность закодированных символов надо склеить в битовый поток, разбить по байтам и записать в другой файл. Честно говоря, я уже надцать реализаций посмотрел, но ответ так и не нашёл. Навыки чтения чужого кода у меня практически нулевые. –  Apr 10 '15 at 07:59
  • Вместо заморозки было бы здорово увидеть уточняющие вопросы. Отредактировал формулировку задачи, менее общая мне неизвестна. –  Apr 10 '15 at 17:59
  • не ясно какое отношение "алгоритм сжатия Хаффмана" имеет к "получению битового потока из целых чисел и вывод его в файл". Допустим есть целое число: 3405691582 (десятичная запись) можно его представить ввиде 4 байт: cafebabe (шестнадцатиричная запись) или тоже самое 32 бит: 11001010111111101011101010111110 (двоичная запись). Чтобы в файл число записать, как оно в памяти представлено, можно fwrite() использовать. Пример "входные данные + соответствующий результат" мог бы прояснить вопрос. – jfs Apr 10 '15 at 20:37
  • @jfs, результатом алгоритма сжатия Хаффмана является кодовая таблица. Чтобы записать закодированный файл, надо получить битовый поток соответствующих кодов. Если записывать числа, как они представлены в памяти, никакого сжатия не будет. Ответ на вопрос. –  Apr 10 '15 at 22:17
  • @DAP-DarkneSS: вопрос так и не ясен. Ответ содержит какие-то элеменарные побитовые операции, но из него по крайней мере можно понять, что на входе есть массив int и BitsStream число. getBitsCount() похоже считает сколько бит надо, чтобы число представить. Затем старший байт отсекается. Правильно я понимаю, что вопрос как реализовать некоторый шаг из алгоритма Хаффмана и не имеет отношения к тому как извлечь заданный бит из числа? – jfs Apr 10 '15 at 22:50
  • @jfs, вопрос о шаге даже не самого алгоритма Хаффмана, поскольку в алгоритме нет ничего о вводе/выводе. Ваш вопрос про извлечение бита не понял. –  Apr 11 '15 at 09:41
  • result = data[curr_int] & (1 << curr_bit); из @VladD ответа, извлекает curr_bit-ый бит из data[curr_int] числа. – jfs Apr 11 '15 at 11:58
  • @jfs, тогда да, насколько я понимаю, не имеет. –  Apr 11 '15 at 12:51
  • Если речь не об общем извлечении бит из числа (как заголовок вопроса намекает) и makeAndSlice() функция не является стандартным шагом в алгоритме Хаффмана (на что тело вопроса указывает), то в вопросе не хватает описания (словами) что makeAndSlice() делает (ответ говорит как, но не говорит что), с конкретным примером, например, если int data[] = {3735928559}; (откуда массив обычно берётся?), то какой значение BitsStream на выходе ожидается и почему. – jfs Apr 11 '15 at 13:14
  • @jfs, makeAndSlice() создаёт битовый поток BitsStream из data[] и отсекает по 8 старших значимых битов в OutputByte по мере накопления их в потоке (while (BitsStreamCount >= ByteBits)). data[] — искусственная сущность, после кодирования Хаффмана исходный файл пробегается ещё раз, и каждому символу переназначается код, последовательность этих кодов и должна составлять битовый поток. –  Apr 11 '15 at 14:14
  • @DAP-DarkneSS: нyжно сам вопрос обновить, чтобы он был ясен без комментариев (не нужно дополнительную информацию в комментарии под своим вопросом помещать, отредактируйте сам вопрос). Описание явно не достаточно. Допустим в makeAndSlice() есть баг -- что в описании может помочь найти его (по описанию невозможно понять какие конкретно значения на выходе должны получаться) или доказать, что makeAndSlice() работает корректно. – jfs Apr 11 '15 at 14:26

5 Answers5

1

Ну, а в чём проблема? Всё делается легко, нужно лишь решить, как вы хотите кодировать «поток битов».

Например (для конвенции little endian):

int* data; // данные
size_t num_ints; // сколько их

size_t curr_int; // текущее слово, инициализируется нулём
size_t curr_bit; // текущий бит в слове, инициализируется нулём
#define BITS_IN_CHAR 8

int next_bit()
{
    if (curr_int >= num_ints)
        return -1; // конец потока

    int result = data[curr_int] & (1 << curr_bit);
    curr_bit++;
    if (curr_bit > sizeof(int) * BITS_IN_CHAR)
    {
        curr_bit = 0;
        curr_int++;
    }
    return result ? 1 : 0;
}
VladD
  • 206,799
  • Объясните, пожалуйста, что и зачем происходит в строке int result = data[curr_int] & (1 << curr_bit); ? –  Apr 10 '15 at 08:28
  • @DAP-DarkneSS: смотрите. data[curr_int] выдаёт текущий int из набора данных. 1 << curr_bit — это int с одним битом на позиции curr_bit. Операция & выделяет бит с таким номером из data[curr_int]. (Гляньте, как работает &. Это называется «маскирование».) – VladD Apr 10 '15 at 09:14
  • Будет слишком наглой с моей стороны просьба показать отдельно 1) как склеить два целых числа 2) как отделить от целого 8 старших битов в другую переменную? –  Apr 10 '15 at 12:10
  • @DAP-DarkneSS: Смотрите. Вам придётся точно знать разрядность ваших чисел. Допустим, у вас есть два 8-битный charc1 и c2. И ваш int, например, 32-битный. Тогда вы можете «склеить» их так: ((int)c1 << 8) | (int)c2. – VladD Apr 10 '15 at 12:42
  • Если я правильно помню, каст даже не нужен. – VladD Apr 10 '15 at 12:42
  • @DAP-DarkneSS: если у вас есть 32-битный int n, вы получаете старшие 8 бит просто сдвигая всё на 24 бита влево: n >> 24. Чтобы получить вторые 8 бит, вы сначала сдвигаете влево на 16 бит, а затем выбрасываете старшие 8 бит: (n >> 16) & 0xff. Аналогично третьи 8 бит: (n >> 8) & 0xff. – VladD Apr 10 '15 at 12:45
  • VladD, дело в том, что количество бит в склеиваемых целых разное. Насколько я вижу, это числа от 2^0 до 2^10. То же касается и 8 старших бит: я не могу быть уверенным, что они начинаются с первого разряда. –  Apr 10 '15 at 13:01
  • @DAP-DarkneSS: Тогда вы должны знать, сколько значащих бит в ваших числах. Если в первом числе X бит, второе надо сдвигать влево на X, разумеется. – VladD Apr 10 '15 at 13:19
0

Делаю похожую вещь. К ответам выше добавлю только, что >> это сдвиг вправо, а не влево, а int и long будут иметь разное количество бит взависимости от платформы и лучше использовать uint8_t и подобные типы. Написал класс для таких задач, а для хранения информации о длине битовой последовательности добавил структуру BitRemedy с множеством различных методов: https://github.com/redmms/FineStream/tree/master/module

0

Решение в лоб видится таким:

int getBitsCount (unsigned long int NumberToCount)
{
  for (int BitsCount = 0; NumberToCount != 0; BitsCount++)
  {
    NumberToCount >>= 1;
  }
  return (BitsCount);
}

int makeAndSlice ()
{
  int *data;
  unsigned long int BitsStream = 0;
  int OutputByte = 0;
  int BitsStreamCount = 0;
  () // цикл прохождения массива целых
  {
    if ((BitsStreamCount = getBitsCount (BitsStream)) >= 8)
    {
      OutputByte = BitsStream >> (BitsStreamCount - 8);
      BitsStream -= ((unsigned long int) OutputByte) << (BitsStreamCount - 8);
      // делаем с нашим байтом, что хотим
    }

    BitsStream = BitsStream << getBitsCount (data [i]) + data [i]
  }
}

Очевидный минус — упование на то, что старший бит всегда будет единицей, так что @VladD абсолютно прав в том, что надо знать битовую длину каждого целого и битового потока, т.о. проще считать и хранить не только коды Хаффмана, но и их битовые длины, и рассчитывать исходя из них битовую длину потока.

  • Старший бит будет единицей в около 50% случаев, если считать все значения битов равноправными :-( – VladD Apr 10 '15 at 15:03
  • Так точно, как минимум надо считать BitsStreamCount на основании битовых длин целых и манипуляций с потоком. –  Apr 10 '15 at 15:31
0

Подкину-ка я сюда на всякий случай известный битхак для определения числа установленных бит в 32 битном слове. Может ТСу пригодится:

uint32_t v; //v - входное слово.
uint32_t c; //c- число бит.

v = v - ((v >> 1) & 0x55555555);                    
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);     
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; 
igumnov
  • 7,806
  • Пока важно только число бит в char, но лишней информация не будет. –  Apr 10 '15 at 15:55
  • Но ведь ничто не мешает преобразовать char в int и посчитать. Сейчас еще подумаю можно ли как-то модифицировать это на 8 битный регистр. – igumnov Apr 10 '15 at 15:59
0

Работа над ошибками:

int getBitsCount (unsigned long int NumberToCount)
{
  for (int BitsCount = 0; NumberToCount != 0; BitsCount++)
  {
    NumberToCount >>= 1;
  }
  return (BitsCount);
}

int makeAndSlice ()
{
  const int ByteBits = 8 * sizeof (char) // мало ли…
  int *data;
  int BitsDataCount = 0;
  unsigned long int BitsStream = 0;
  int OutputByte = 0;
  int BitsStreamCount = 0;
  () // цикл прохождения массива целых
  {
    BitsDataCount = getBitsCount (data [i]);
    BitsStream = (BitsStream << BitsDataCount) + data [i];
    BitsStreamCount += BitsDataCount;
    while (BitsStreamCount >= ByteBits) // укорачиваем поток, пока он не меньше байта
    {
      OutputByte = BitsStream >> (BitsStreamCount - ByteBits);
      BitsStream -= ((unsigned long int) OutputByte) << (BitsStreamCount - ByteBits);
      BitsStreamCount -= ByteBits; // для битового потока нельзя применять getBitsCount
      // делаем с нашим байтом, что хотим
    }
  }
  OutputByte = BitsStream << (ByteBits - BitsStreamCount) // последний байт добиваем нулями справа
}

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

  • sizeof(char) всегда 1. Кол-во бит при этом может отличаться от 8 (CHAR_BIT). – jfs Apr 10 '15 at 22:46
  • CHAR_BIT — именно то, что нужно вместо ByteBits. –  Apr 11 '15 at 09:38