алгоритм сжатия отсортированных целых чисел

У меня есть большая последовательность случайных целых чисел, отсортированных от самого низкого до самого высокого. Цифры от 1 бита и заканчиваются около 45 бит. В начале списка у меня цифры очень близко друг к другу: 4, 20, 23, 40, 66. Но когда числа начинают расти, расстояние между ними тоже немного выше (на самом деле расстояние между ними является алеаторным). Дублированных номеров нет.

Я использую немного упаковка чтобы сэкономить место, но в любом случае это файл может стать действительно огромным.

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

спасибо.

5 ответов


вы можете сжимать оптимально, если вы знаете истинное распределение данных. Если вы можете предоставить распределение вероятности для каждого целого числа, вы можете использовать арифметическое кодирование или другое кодирование методы сжатия до теоретического минимального размера.

фокус в точном предсказании.

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

Далее, вы можете попробовать построить очень простой модели для прогнозирования на следующее расстояние. Держите гистограмму всех ранее виденных расстояний и вычисляйте вероятности по частотам.

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

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


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

Это тип Дельта-кодирования (т. е. каждое число представлено расстоянием от предыдущего), состоящим из вектора кодов, которые являются либо

  • один 1 бит, представляющий Дельта 2k, который добавляется к Дельте в следующем коде, или

  • 0-бит, за которым следует K-битная Дельта, указывающая, что следующее число является указанной дельтой от предыдущего.

например, если k равно 4, последовательность:

0011 1 1 0000 1 0001

коды трех чисел. Первая четырехразрядная кодировка (3) - это первая Дельта, взятая из начального значения 0, поэтому первое число равно 3. Этот следующие два одиночных 1 накапливаются в дельте 2 & centerdot;*24, или 32, который добавляется к следующей дельте 0000, в общей сложности 32. Итак, второе число-3+32=35. Наконец, последняя Дельта-это один 24 плюс 1, всего 17, а третье число-35+17=52.

1-бит указывает, что следующая Дельта должна быть увеличена на 2k (или, в более общем плане, каждая Дельта увеличивается на 2k умножьте количество немедленно предшествующий 1-бит.)

другой, возможно, лучший способ думать об этом заключается в том, что каждая Дельта кодируется как битовая последовательность переменной длины: 1Я0(1/0)k, представляющий дельту i & centerdot; 2k+[K-битный суффикс]. Но первая презентация лучше согласуется с доказательством оптимальности.

Так как каждый код " 1 " представляет собой приращение 2k, там не может быть больше, чем m/2k из них, где m - наибольшее число в наборе для сжатия. Остальные коды соответствуют числам и имеют общую длину n*(2k + 1), где N-размер набора. Оптимальное значение k примерно log2 m / n, который в вашем случае был бы 7 или 8.

Я сделал быстрое доказательство концепции алгоритма, не беспокоясь об оптимизации. Это все еще достаточно быстро; сортировка случайной выборки занимает намного больше времени, чем сжатие/распаковка. Я пробовал с несколькими различными семенами и векторными размерами от 16,400,000 до 31,000,000 с диапазоном значений [0, 4,000,000,000). Биты, используемые для каждого значения данных, варьировались от 8,59 (n=31000000) до 9,45 (n=16400000). Все тесты были выполнены с 7-битными суффиксами; log2 m / n варьируется от 7,01 (n=31000000) до 7,93 (n=16400000). Я пробовал с 6-битными и 8-битными суффиксами; за исключением случая n=31000000, где 6-битные суффиксы были немного меньше, 7-битный суффикс всегда был лучшим. Так что я думаю, что оптимальный k не совсем пол (log2 m / n), но это не далеко.

код сжатия:

void Compress(std::ostream& os,
              const std::vector<unsigned long>& v,
              unsigned long k = 0) {
  BitOut out(os);
  out.put(v.size(), 64);
  if (v.size()) {
    unsigned long twok;
    if (k == 0) {
      unsigned long ratio = v.back() / v.size();
      for (twok = 1; twok <= ratio / 2; ++k, twok *= 2) { }
    } else {
      twok = 1 << k;
    }
    out.put(k, 32);

    unsigned long prev = 0;
    for (unsigned long val : v) {
      while (val - prev >= twok) { out.put(1); prev += twok; }
      out.put(0);
      out.put(val - prev, k);
      prev = val;
    }
  }
  out.flush(1);
}

декомпрессия:

std::vector<unsigned long> Decompress(std::istream& is) {
  BitIn in(is);
  unsigned long size = in.get(64);
  if (size) {
    unsigned long k = in.get(32);
    unsigned long twok = 1 << k;

    std::vector<unsigned long> v;
    v.reserve(size);
    unsigned long prev = 0;
    for (; size; --size) {
      while (in.get()) prev += twok;
      prev += in.get(k);
      v.push_back(prev);
    }
  }
  return v;
}

может быть немного неудобно использовать кодировки переменной длины; альтернативой является сохранение Первого БИТа каждого кода (1 или 0) в битовом векторе, а k-битных суффиксов в отдельном векторе. Это было бы особенно удобно, если k равно 8.

вариант, который приводит к небольшим более длинным файлам, но немного проще построить индексы для того, чтобы использовать только 1-бит, Дельта. Тогда дельты всегда a * 2k для некоторого a, возможно 0, где a-количество последовательных 1 бит, предшествующих коду суффикса. Затем индекс состоит из местоположений каждого Nth 1-бит в битовом векторе и соответствующий индекс в вектор суффикса (т. е. индекс суффикса, соответствующий следующему 0 в битовом векторе).



Я хочу добавить еще один ответ с простым решением:

  1. преобразовать числа в дельты, как обсуждалось ранее
  2. запустите его через алгоритм 7-zip LZMA2. Это даже многоядерный готов

Я думаю, что это даст почти идеальные результаты в вашем случае, потому что расстояния имеют простое распределение. 7-zip сможет забрать его.


0x12345678
0x12349785
0x13111111
0x13444444

сохраненные данные будут (в hex):

12,12,13,13
34,34,11,44
56,97,11,44
78,85,11,44

затем я пропустил это через компрессор дефляции.

Я не помню, какие коэффициенты сжатия я смог достичь с этим, но это было значительно лучше, чем сжатие самих чисел.


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

Если ваша последовательность состоит из действительно случайных чисел, сгенерированных каким-то недетерминированным способом, то другие ответы, уже опубликованные, предлагают множество хороший Совет.