将21个字母数字字符压缩为16个字节

时间:2010-08-05 22:09:45

标签: c++ algorithm

我正在尝试获取21个字节的数据,这些数据唯一地标识交易并将其存储在16字节char数组中。我无法为此提出正确的算法。

我正在尝试压缩的交易ID包含2个字段:

  1. 18个字母数字字符 由ASCII字符组成 0x20至0x7E,包含。 (32-126)
  2. 3个字符的数字字符串“000”到“999”
  3. 所以包含这些数据的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位 - 比2字节字短6位。但是当我弄清楚我能把它压缩多少时,我能做到的最小值是17个字节;一个字节太大了。

    有什么想法吗?

    顺便说一下,trade_num_是用词不当。它可以包含字母和其他字符。这就是规范所说的。

    编辑:对不起,我很抱歉。 trade_num_字段确实是18个字节,而不是16个。在我发布这个帖子后,我的网络连接已经死了,直到现在我才回到这个线程。

    EDIT2:我认为对数据集做出假设是安全的。对于trade_num_字段,我们可以假设不存在不可打印的ASCII字符0-31。 ASCII码也不是127或126(〜)。所有其他人可能都在场,包括大写和小写字母,数字和标点符号。这将在trade_num_组成的集合中共有94个字符,包括32到125的ASCII代码。

8 个答案:

答案 0 :(得分:33)

如果您在0 - 127范围内有18个字符,并且在0 - 999范围内有一个数字并且尽可能地压缩它,那么它将需要17个字节。

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

您可以利用一些角色最有可能不被使用的事实。特别是,不可能存在低于值32的任何字符,并且也可能不使用127。如果你可以找到一个未使用的字符,那么你可以先将字符转换为base 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处理非常大数字的能力。要将此代码转换为C ++,您可以使用大整数库。

您当然需要等效的解码功能,原理相同 - 操作以相反的顺序执行。

答案 1 :(得分:5)

这使得(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 :(得分:2)

这是应该有效的,假设您只需要来自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

答案 3 :(得分:1)

您可以在~~ 15字节(14字节和6位)中执行此操作。

对于trace_num_中的每个字符,如果要将ascii保存为7位,则可以保存1位。

  • 然后你有2个字节空闲和2 比特,你必须有5个。

让我们获取数字信息,每个char可以是十个值(0到9)中的一个。 然后你必须有4位来保存这个字符,为了保存你必须有1字节和4位的数字,然后你节省了一半。

  • 现在你有3个字节空闲和6位, 你必须有5个。

如果您只想使用qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM1234567890[] 您可以将每个char保存为6位。然后你有接下来的2个字节和2个比特。

  • 现在剩下6个字节,你的字符串可以保存15个字节+ nulltermination = 16bytes。

如果您将数字保存为10个字节的整数。您可以将其设置为14个字节和6个位。

答案 4 :(得分:1)

关键问题是:

您的帖子中似乎存在一些矛盾,无论交易号码是16还是18个字符。你需要清除它。你说总数是21,包括16 + 3。 : - (

您说交易num字符在0x00-0x7f范围内。他们真的可以成为该范围内的任何角色,包括制表符,新行,control-C等吗?或者它们仅限于可打印字符,甚至可能是字母数字?

输出16个字节是否必须是可打印字符,还是基本上是二进制数?

编辑,更新原始帖子后:

在这种情况下,如果输出可以是字符集中的任何字符,则可以。如果它只能是可打印的字符,那就不是。

证明数学可能性很简单。 18个字符中的每一个都有94个可能的值,每个可能的值为10个。可能的组合总数= 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 ++中是不是有某种大数字类?对不起,我有一段时间没有做过C ++。如果没有,我们可以轻松创建一个小库来实现巨大的。

答案 5 :(得分:1)

空格(0x20)和波浪号(0x7e)之间有 95 个字符。 (其他答案中的94遇到了1分之一的错误)。

因此,不同ID的数量为95 18 ×1000 = 3.97×10 38

但该压缩结构只能保持(2 8 16 = 3.40×10 38 不同的值。

因此,除非:

,否则无法用该结构表示所有ID
  • trade_num_
  • 的≥15位数中有1个未使用的字符
  • trade_num_
  • 的1位数中有≥14个未使用的字符
  • 只有≤856个经纪人,或
  • 您使用的是具有9-bit char
  • 的PDP-10

答案 6 :(得分:0)

如果它只能包含字母,那么每个字符的可能性少于64个(26个大写,26个小写,留下12个空格,终结符,下划线等)。每个字符6位,你应该到达那里 - 15个字符。假设你不支持特殊字符。

答案 7 :(得分:0)

对于3个字符的数字字符串使用前10位(对它们表示一个数字进行编码,然后在解码时使用零填充)。

好的,这会留下118位和16个字母数字字符来存储。

0x00到0x7F(如果你的意思是包含)包含128个可能的字符来表示。这意味着每个字符可以通过7位的组合来识别。想出一个索引,将7位可以表示的每个数字映射到实际字符。要以这种方式表示16个“字母数字”字符,您需要总共112位。

我们现在有122位(或15.25字节)代表我们的数据。添加一个复活节彩蛋以填充剩余的未使用的位,并且您有16个字符的数组。