Сжатие 21 буквенно-цифровых символов до 16 байтов - PullRequest
14 голосов
/ 06 августа 2010

Я пытаюсь взять 21 байт данных, которые однозначно идентифицируют сделку, и сохранить их в 16-байтовом массиве char.У меня возникают проблемы с правильным алгоритмом для этого.

Идентификатор сделки, который я пытаюсь сжать, состоит из 2 полей:

  1. 18 буквенно-цифровых символов, состоящих изASCII символы от 0x20 до 0x7E, включительно.(32-126)
  2. Трехзначная числовая строка от «000» до «999»

Таким образом, класс C ++, который охватывает эти данные, выглядит следующим образом:

class ID
{
public:
    char trade_num_[18];
    char broker_[3];
};

Эти данные должны храниться в структуре данных 16- char, которая выглядит следующим образом:

class Compressed
{
public:
    char sku_[16];    
};

Я пытался воспользоваться тем фактом, что, поскольку символы в trade_num_ только 0-127 был 1 неиспользованный бит в каждом символе.Аналогично, 999 в двоичном коде - это 1111100111, что составляет всего 10 битов - на 6 битов меньше 2-байтового слова.Но когда я выясняю, насколько я могу сжать это, самое маленькое, что я могу сделать, это 17 байтов;один байт слишком большой.

Есть идеи?

Кстати, trade_num_ - это неправильное название.Он может содержать буквы и другие символы.Вот что говорится в спецификации.

РЕДАКТИРОВАТЬ: Извините за путаницу.Поле trade_num_ действительно 18 байтов, а не 16. После того, как я разместил эту тему, мое интернет-соединение прервалось, и я не мог вернуться к этой теме до сих пор.

EDIT2: Я думаю, что это безопасно сделатьпредположение о наборе данных.Для поля trade_num_ мы можем предположить, что непечатные символы ASCII 0-31 не будут присутствовать.Также не будут коды ASCII 127 или 126 (~).Могут присутствовать все остальные, включая заглавные и строчные буквы, цифры и знаки препинания.Таким образом, в наборе, состоящем из trade_num_, будет всего 94 символа, коды ASCII с 32 по 125 включительно.

Ответы [ 8 ]

33 голосов
/ 06 августа 2010

Если у вас есть 18 символов в диапазоне от 0 до 127 и число в диапазоне от 0 до 999 и максимально сжато, то для этого потребуется 17 байтов.

>>> math.log(128**18 * 1000, 256)
16.995723035582763

Возможно, вы сможетевоспользоваться тем, что некоторые символы, скорее всего, не используются.В частности, маловероятно, что есть какие-либо символы ниже значения 32, и 127 также, вероятно, не используется.Если вы можете найти еще одного неиспользуемого символа, чтобы сначала преобразовать символы в базу 94, а затем упаковать их в байты как можно более точно.байт!


Пример кода

Вот пример кода, написанного на Python (но написанного в очень императивном стиле, так что его легко понятьПрограммисты питона).Я предполагаю, что на входе нет тильд (~).Если они есть, вы должны заменить их другим символом перед кодированием строки.

def encodeChar(c):
    return ord(c) - 32

def encode(s, n):
    t = 0
    for c in s:
        t = t * 94 + encodeChar(c)
    t = t * 1000 + n

    r = []
    for i in range(16):
        r.append(int(t % 256))
        t /= 256

    return r

print encode('                  ', 0)    # smallest possible value
print encode('abcdefghijklmnopqr', 123)
print encode('}}}}}}}}}}}}}}}}}}', 999)  # largest possible value

Вывод:

[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0]
[ 59, 118, 192, 166, 108,  50, 131, 135, 174,  93,  87, 215, 177,  56, 170, 172]
[255, 255, 159, 243, 182, 100,  36, 102, 214, 109, 171,  77, 211, 183,   0, 247]

Этот алгоритм использует способность Python для обработки очень больших чисел.Для преобразования этого кода в C ++ вы можете использовать большую целочисленную библиотеку.

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

5 голосов
/ 06 августа 2010

Это составляет (18 * 7 + 10) = 136 бит или 17 байтов.Вы написали trade_num буквенно-цифровой?Если это означает обычный набор символов [a-zA-Z0-9_], то у вас будет только 6 битов на символ, для чего потребуется (18 * 6 + 10) = 118 бит = 15 байт.

Предполагая, что 8 бит = 1 байт

Или, исходя из другого направления: у вас есть 128 битов для хранения, вам нужно ~ 10 битов для числовой части, поэтому для trade_num осталось 118 битов.18 символов означает 118/18 = 6,555 битов на символы, это означает, что у вас может быть только пространство для кодирования 2 6,555 = 94 различных символов **, если не существует скрытой структуры в trade_num, которую мы могли бы использовать для сохранениябольше битов.

2 голосов
/ 10 августа 2010

Это то, что должно работать, при условии, что вам нужны только символы из allowedchars, и там не более 94 символов.Это Python, но он написан, пытаясь не использовать причудливые ярлыки - так что вы сможете легче перевести его на язык назначения.Однако предполагается, что переменная number может содержать целые числа до 2 ** 128 - в C ++ вам следует использовать некоторый класс больших чисел.

allowedchars=' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}'
alphabase = len(allowedchars)

def compress(code):
    alphanumeric = code[0:18]
    number = int(code[18:21])

    for character in alphanumeric:
        # find returns index of character on the allowedchars list
        number = alphabase*number + allowedchars.find(character)

    compressed = ''
    for i in xrange(16):
        compressed += chr(number % 256)
        number = number/256

    return compressed

def decompress(compressed):
    number = 0

    for byte in reversed(compressed):
        number = 256*number + ord(byte)

    alphanumeric = ''
    for i in xrange(18):
        alphanumeric = allowedchars[number % alphabase] + alphanumeric
        number = number/alphabase

    # make a string padded with zeros
    number = '%03d' % number

    return alphanumeric + number
1 голос
/ 10 августа 2010

Между пробелом (0x20) и тильдой (0x7e) есть 95 символов.(94 в других ответах страдают от ошибки off-1).

Следовательно, число различных идентификаторов составляет 95 18 × 1000 = 3,97 × 10 38 .

Но эта сжатая структура может содержать только (2 8 ) 16 = 3,40 × 10 38 различных значений.

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

  • В ≥15 цифрах trade_num_ или
  • есть 1 неиспользованный символ≥14 неиспользуемых символов в 1 цифре trade_num_, или
  • . Существует только ≤856 брокеров, или
  • . Используется PDP-10 с .9 бит char.
1 голос
/ 06 августа 2010

Ключевые вопросы:

Кажется, в вашем посте есть некоторое противоречие, является ли номер сделки 16 или 18 символами.Вы должны это прояснить.Вы говорите, что всего 21 состоит из 16 + 3.: - (

Вы говорите, что символы торгового номера находятся в диапазоне 0x00-0x7f. Могут ли они быть действительно любым символом в этом диапазоне, включая tab, новую строку, control-C и т. Д.? Или они ограниченыпечатные символы или, может быть, даже буквенно-цифровые символы?

Должны ли выходные 16 байтов быть печатными символами или это в основном двоичное число?

РЕДАКТИРОВАТЬ, после обновления исходного сообщения:

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

Демонстрация математической возможности достаточно проста.94 возможных значения для каждого из 18 символов и 10 возможных значений для каждого из 3. Общее количество возможных комбинаций = 94 ^ 18 * 10 ^ 3 ~ = 3,28E35. Для этого требуется 128 бит. 2 ^ 127 ~ = 1,70e38,что слишком мало, в то время как 2 ^ 128 ~ = 3.40e38, что достаточно велико. 128 бит - это 16 байтов, так что он едва уместится, если мы сможем использовать все возможные битовые комбинации.

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

Концептуально, давайте теперь представим, что у нас был тип данных типа "целое число" длиной 16 байтов.Алгоритм будет выглядеть примерно так:

huge out;
for (int p=0;p<18;++p)
{
  out=out*94+tradenum[p]-32;
}
for (int p=0;p<3;++p)
{
  out=out*10+broker[p]-'0';
}

// Convert output to char[16]
unsigned char[16] out16;
for (int p=15;p>=0;--p)
{
  out16[p]=huge&0xff;
  huge=huge>>8;
}

return out16;

Конечно, у нас нет "огромного" типа данных в C. Используете ли вы чистый C или C ++?Разве в С ++ нет какого-то класса больших чисел?Извините, я давно не делал C ++.Если нет, мы могли бы легко создать небольшую библиотеку для реализации огромной.

1 голос
/ 06 августа 2010

Вы можете сделать это в ~ ~ 15 байтах (14 байтов и 6 бит).

Для каждого символа из trace_num_ вы можете сохранить 1 бит, если хотите сохранить ascii в 7 битах.

  • Тогда у вас есть 2 свободных байта и 2 биты, вы должны иметь 5.

Позвольте получить числовую информацию, каждый символ может быть одним из десяти значений (от 0 до 9). Тогда у вас должно быть 4 бита, чтобы сохранить этот символ, чтобы сохранить число, вы должны иметь 1 байт и 4 бита, тогда вы сохраните половину этого.

  • Теперь у вас есть 3 байта и 6 битов, у вас должно быть 5.

Если вы хотите использовать только qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM1234567890[] Вы можете сохранить каждый символ в 6 битах. Тогда у вас есть следующие 2 байта и 2 бита.

  • Теперь у вас осталось 6 байтов, и ваша строка может быть сохранена в 15 байтах + нулевое окончание = 16 байт.

И если вы сохраните свой номер в целое число на 10 байтов. Вы можете поместить это в 14 байтов и 6 бит.

0 голосов
/ 06 августа 2010

Используйте первые 10 битов для 3-символьной числовой строки (кодируйте биты так, как будто они представляют число, а затем дополняйте нулями в зависимости от ситуации при декодировании).

Хорошо, у вас остается 118 бит и 16 буквенно-цифровых символов для хранения.

0x00 до 0x7F (если вы имеете в виду включительно) содержит 128 возможных символов для представления. Это означает, что каждый символ может быть идентифицирован комбинацией 7 битов. Придумайте индекс, отображающий каждое число, которое эти 7 бит могут представлять фактическому символу. Чтобы представить 16 из ваших «буквенно-цифровых» символов таким образом, вам нужно всего 112 бит.

Теперь у нас есть 122 бита (или 15,25 байта), представляющих наши данные. Добавьте пасхальное яйцо, чтобы заполнить оставшиеся неиспользованные биты, и у вас будет 16-значный массив.

0 голосов
/ 06 августа 2010

Если он может содержать только буквы, то у вас меньше 64 возможностей на символ (26 в верхнем регистре, 26 в нижнем регистре, оставляя вам 12 для пробела, терминатора, подчеркивания и т. Д.).С 6 битами на символ вы должны попасть туда - из 15 символов.Предполагая, что вы не поддерживаете специальные символы.

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