Сжатие 21 буквенно-цифровых символов в 16 байт

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

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

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

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

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

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

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

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

Любые идеи?

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

EDIT: Извините за путаницу. Поле trade_num_ действительно 18 байт, а не 16. После того, как я опубликовал эту тему, мое интернет-соединение умерло, и я не мог вернуться к этой теме только сейчас.

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

Ответы

Ответ 1

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

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

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

>>> math.log(94**18 * 1000, 256)
15.993547951857446

Это просто вписывается в 16 байтов!


Пример кода

Вот пример кода, написанного на Python (но написанного в очень императивном стиле, так что его легко понять программисты, не являющиеся программистами на 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 обрабатывать очень большие числа. Чтобы преобразовать этот код в С++, вы можете использовать большую целочисленную библиотеку.

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

Ответ 2

Это делает (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, которую мы могли бы использовать для сохранить больше бит.

Ответ 3

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

allowedchars=' !"#$%&\'()*+,-./0123456789:;<=>[email protected][\\]^_`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

Ответ 4

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

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

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

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

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

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

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

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

Ответ 5

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

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

Вы говорите, что числовых символов num находится в диапазоне 0x00-0x7f. Могут ли они действительно быть персонажем в этом диапазоне, включая вкладку, новую строку, 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 или С++? Разве нет какого-то большого класса чисел в С++? К сожалению, я еще не сделал С++. Если нет, мы могли бы легко создать небольшую библиотеку для реализации огромного.

Ответ 6

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

Следовательно, число различных идентификаторов 95 18 & times; 1000 = 3.97 & times; 10 38.

Но эта сжатая структура может удерживать (2 8) 16= 3.40 и times; 10 38 различные значения.

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

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

Ответ 7

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

Ответ 8

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

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

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

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