Лучший алгоритм сжатия для последовательности целых чисел - PullRequest
49 голосов
/ 12 ноября 2008

У меня большой массив с диапазоном целых чисел, которые в основном непрерывны, например, 1-100, 110-160 и т. Д. Все целые числа положительны. Какой будет лучший алгоритм для сжатия этого?

Я попробовал алгоритм дефляции, но это дает мне только 50% сжатия. Обратите внимание, что алгоритм не может быть с потерями.

Все числа уникальны и постепенно увеличиваются.

Также, если вы можете указать мне на реализацию Java такого алгоритма, это было бы здорово.

Ответы [ 15 ]

1 голос
/ 05 мая 2016

Я знаю, что это старая ветка сообщений, но я включаю свой личный тест PHP для идеи SKIP / TAKE, которую я нашел здесь. Я звоню мой STEP (+) / SPAN (-). Возможно, кто-то найдет это полезным.

ПРИМЕЧАНИЕ: я реализовал возможность разрешать дублирование целых чисел, а также отрицательных целых чисел, даже если в первоначальном вопросе были положительные, не дублированные целые числа Не стесняйтесь настраивать его, если хотите попробовать побрить один или два байта.

КОД:

  // $integers_array can contain any integers; no floating point, please. Duplicates okay.
  $integers_array = [118, 68, -9, 82, 67, -36, 15, 27, 26, 138, 45, 121, 72, 63, 73, -35,
                    68, 46, 37, -28, -12, 42, 101, 21, 35, 100, 44, 13, 125, 142, 36, 88,
                    113, -40, 40, -25, 116, -21, 123, -10, 43, 130, 7, 39, 69, 102, 24,
                    75, 64, 127, 109, 38, 41, -23, 21, -21, 101, 138, 51, 4, 93, -29, -13];

  // Order from least to greatest... This routine does NOT save original order of integers.
  sort($integers_array, SORT_NUMERIC); 

  // Start with the least value... NOTE: This removes the first value from the array.
  $start = $current = array_shift($integers_array);    

  // This caps the end of the array, so we can easily get the last step or span value.
  array_push($integers_array, $start - 1);

  // Create the compressed array...
  $compressed_array = [$start];
  foreach ($integers_array as $next_value) {
    // Range of $current to $next_value is our "skip" range. I call it a "step" instead.
    $step = $next_value - $current;
    if ($step == 1) {
        // Took a single step, wait to find the end of a series of seqential numbers.
        $current = $next_value;
    } else {
        // Range of $start to $current is our "take" range. I call it a "span" instead.
        $span = $current - $start;
        // If $span is positive, use "negative" to identify these as sequential numbers. 
        if ($span > 0) array_push($compressed_array, -$span);
        // If $step is positive, move forward. If $step is zero, the number is duplicate.
        if ($step >= 0) array_push($compressed_array, $step);
        // In any case, we are resetting our start of potentialy sequential numbers.
        $start = $current = $next_value;
    }
  }

  // OPTIONAL: The following code attempts to compress things further in a variety of ways.

  // A quick check to see what pack size we can use.
  $largest_integer = max(max($compressed_array),-min($compressed_array));
  if ($largest_integer < pow(2,7)) $pack_size = 'c';
  elseif ($largest_integer < pow(2,15)) $pack_size = 's';
  elseif ($largest_integer < pow(2,31)) $pack_size = 'l';
  elseif ($largest_integer < pow(2,63)) $pack_size = 'q';
  else die('Too freaking large, try something else!');

  // NOTE: I did not implement the MSB feature mentioned by Marc Gravell.
  // I'm just pre-pending the $pack_size as the first byte, so I know how to unpack it.
  $packed_string = $pack_size;

  // Save compressed array to compressed string and binary packed string.
  $compressed_string = '';
  foreach ($compressed_array as $value) {
      $compressed_string .= ($value < 0) ? $value : '+'.$value;
      $packed_string .= pack($pack_size, $value);
  }

  // We can possibly compress it more with gzip if there are lots of similar values.      
  $gz_string = gzcompress($packed_string);

  // These were all just size tests I left in for you.
  $base64_string = base64_encode($packed_string);
  $gz64_string = base64_encode($gz_string);
  $compressed_string = trim($compressed_string,'+');  // Don't need leading '+'.
  echo "<hr>\nOriginal Array has "
    .count($integers_array)
    .' elements: {not showing, since I modified the original array directly}';
  echo "<br>\nCompressed Array has "
    .count($compressed_array).' elements: '
    .implode(', ',$compressed_array);
  echo "<br>\nCompressed String has "
    .strlen($compressed_string).' characters: '
    .$compressed_string;
  echo "<br>\nPacked String has "
    .strlen($packed_string).' (some probably not printable) characters: '
    .$packed_string;
  echo "<br>\nBase64 String has "
    .strlen($base64_string).' (all printable) characters: '
    .$base64_string;
  echo "<br>\nGZipped String has "
    .strlen($gz_string).' (some probably not printable) characters: '
    .$gz_string;
  echo "<br>\nBase64 of GZipped String has "
    .strlen($gz64_string).' (all printable) characters: '
    .$gz64_string;

  // NOTICE: The following code reverses the process, starting form the $compressed_array.

  // The first value is always the starting value.
  $current_value = array_shift($compressed_array);
  $uncompressed_array = [$current_value];
  foreach ($compressed_array as $val) {
    if ($val < -1) {
      // For ranges that span more than two values, we have to fill in the values.
      $range = range($current_value + 1, $current_value - $val - 1);
      $uncompressed_array = array_merge($uncompressed_array, $range);
    }
    // Add the step value to the $current_value
    $current_value += abs($val); 
    // Add the newly-determined $current_value to our list. If $val==0, it is a repeat!
    array_push($uncompressed_array, $current_value);      
  }

  // Display the uncompressed array.
  echo "<hr>Reconstituted Array has "
    .count($uncompressed_array).' elements: '
    .implode(', ',$uncompressed_array).
    '<hr>';

ВЫВОД:

--------------------------------------------------------------------------------
Original Array has 63 elements: {not showing, since I modified the original array directly}
Compressed Array has 53 elements: -40, 4, -1, 6, -1, 3, 2, 2, 0, 8, -1, 2, -1, 13, 3, 6, 2, 6, 0, 3, 2, -1, 8, -11, 5, 12, -1, 3, -1, 0, -1, 3, -1, 2, 7, 6, 5, 7, -1, 0, -1, 7, 4, 3, 2, 3, 2, 2, 2, 3, 8, 0, 4
Compressed String has 110 characters: -40+4-1+6-1+3+2+2+0+8-1+2-1+13+3+6+2+6+0+3+2-1+8-11+5+12-1+3-1+0-1+3-1+2+7+6+5+7-1+0-1+7+4+3+2+3+2+2+2+3+8+0+4
Packed String has 54 (some probably not printable) characters: cØÿÿÿÿ ÿõ ÿÿÿÿÿÿ
Base64 String has 72 (all printable) characters: Y9gE/wb/AwICAAj/Av8NAwYCBgADAv8I9QUM/wP/AP8D/wIHBgUH/wD/BwQDAgMCAgIDCAAE
GZipped String has 53 (some probably not printable) characters: xœ Ê» ÑÈί€)YšE¨MŠ“^qçºR¬m&Òõ‹%Ê&TFʉùÀ6ÿÁÁ Æ
Base64 of GZipped String has 72 (all printable) characters: eJwNyrsNACAMA9HIzq+AKVmaRahNipNecee6UgSsBW0m0gj1iyXKJlRGjcqJ+cA2/8HBDcY=
--------------------------------------------------------------------------------
Reconstituted Array has 63 elements: -40, -36, -35, -29, -28, -25, -23, -21, -21, -13, -12, -10, -9, 4, 7, 13, 15, 21, 21, 24, 26, 27, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 51, 63, 64, 67, 68, 68, 69, 72, 73, 75, 82, 88, 93, 100, 101, 101, 102, 109, 113, 116, 118, 121, 123, 125, 127, 130, 138, 138, 142
--------------------------------------------------------------------------------
1 голос
/ 23 июня 2011

Ваш случай очень похож на сжатие индексов в поисковых системах. Используемый популярный алгоритм сжатия - это алгоритм PForDelta и алгоритм Simple16. Вы можете использовать библиотеку kamikaze для своих нужд сжатия.

1 голос
/ 04 июля 2009

Основная идея, которую вы, вероятно, должны использовать, состоит в том, чтобы для каждого диапазона последовательных целых чисел (я буду называть эти диапазоны) хранить начальный номер и размер диапазона. Например, если у вас есть список из 1000 целых чисел, но имеется только 10 отдельных диапазонов, вы можете сохранить только 20 целых чисел (1 начальное число и 1 размер для каждого диапазона), чтобы представить эти данные, которые будут иметь степень сжатия 98 %. К счастью, вы можете сделать еще несколько оптимизаций, которые помогут в случаях, когда количество диапазонов больше.

  1. Сохраните смещение начального номера относительно предыдущего начального номера, а не сам начальный номер. Преимущество здесь в том, что для хранения номеров обычно требуется меньше битов (это может пригодиться в последующих предложениях по оптимизации). Кроме того, если вы сохраняете только начальные числа, все эти числа будут уникальными, в то время как сохранение смещения дает возможность того, что числа близки или даже повторяются, что может позволить еще большее сжатие с другим методом, применяемым после. 1008 *

  2. Используйте минимальное число возможных бит для обоих типов целых чисел. Вы можете перебирать числа, чтобы получить наибольшее смещение начального целого числа, а также размер наибольшего диапазона. Затем вы можете использовать тип данных, который наиболее эффективно хранит эти целые числа, и просто указать тип данных или число битов в начале сжатых данных. Например, если наибольшее смещение начального целого числа составляет всего 12 000, а наибольший диапазон составляет 9 000, то для всех них можно использовать 2-байтовое целое число без знака. Затем вы можете создать пару 2,2 в начале сжатых данных, чтобы показать, что 2 байта используются для обоих целых чисел. Конечно, вы можете поместить эту информацию в один байт, используя немного битовых манипуляций. Если вам удобно выполнять много тяжелых битовых манипуляций, вы можете хранить каждое число как минимально возможное количество бит, а не соответствовать 1, 2, 4 или 8-байтовым представлениям.

С этими двумя оптимизациями давайте рассмотрим пару примеров (каждый по 4000 байт):

  1. 1000 целых чисел, наибольшее смещение 500, 10 диапазонов
  2. 1000 целых чисел, наибольшее смещение 100, 50 диапазонов
  3. 1000 целых чисел, наибольшее смещение 50, 100 диапазонов

БЕЗ ОПТИМИЗАЦИЙ

  1. 20 целых чисел, 4 байта каждый = 80 байтов. СЖАТИЕ = 98%
  2. 100 целых чисел, 4 байта каждый = 400 байтов. СЖАТИЕ = 90%
  3. 200 целых чисел, 4 байта каждый = 800 байтов. СЖАТИЕ = 80%

С ОПТИМИЗАЦИЯМИ

  1. 1 байт заголовка + 20 цифр, 1 байт каждый = 21 байт. СЖАТИЕ = 99,475%
  2. 1 байтный заголовок + 100 чисел, 1 байт каждый = 101 байт. СЖАТИЕ = 97,475%
  3. 1 байт заголовка + 200 номеров, 1 байт каждый = 201 байт. СЖАТИЕ = 94,975%
1 голос
/ 12 ноября 2008

Я бы посоветовал взглянуть на Кодирование Хаффмана , особый случай Арифметическое кодирование . В обоих случаях вы анализируете начальную последовательность, чтобы определить относительные частоты различных значений. Более часто встречающиеся значения кодируются с меньшим количеством битов, чем менее часто встречающиеся.

0 голосов
/ 12 ноября 2008

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

Вы можете посмотреть на эти и другие алгоритмы без потерь здесь .

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