我有一个整数数组,我们假设它们是int64_t
类型。现在,我知道每个整数的每个前n
位都是有意义的(也就是说,我知道它们受到某些边界的限制)。
以删除所有不必要空间的方式转换数组的最有效方法是什么(即我在a[0]
处有第一个整数,在a[0] + n bits
处有第二个整数,依此类推)?
我希望它尽可能通用,因为n
会不时发生变化,但我想可能会对特定n
类似2或者2的幂进行智能优化。
当然我知道我可以只重复超过价值,我只想问你StackOverflowers你是否能想到更聪明的方式。
编辑:
这个问题不是关于压缩数组以尽可能减少空间。我只需要从每个整数“切割”n bits
并给出数组我知道我可以安全切割的确切n
位。
答案 0 :(得分:6)
我同意keraba你需要使用像霍夫曼编码或者Lempel-Ziv-Welch算法这样的东西。你所说的方式包装的问题在于你有两个选择:
第一个选项相对容易实现,但实际上会浪费很多空间,除非所有整数都很小。
第二个选项的主要缺点是你必须在输出比特流中以某种方式传达n的变化。例如,每个值必须具有与之关联的长度。这意味着您为每个输入值存储两个整数(尽管是较小的整数)。使用这种方法你很有可能增加文件大小。
Huffman或LZW的优点在于它们以这样的方式创建码本:可以从输出比特流导出码的长度而不实际存储长度。这些技术可以让你非常接近香农极限。
我决定给你最初的想法(常数n,删除未使用的位和包装)尝试一下,这是我提出的天真实现:
#include <sys/types.h>
#include <stdio.h>
int pack(int64_t* input, int nin, void* output, int n)
{
int64_t inmask = 0;
unsigned char* pout = (unsigned char*)output;
int obit = 0;
int nout = 0;
*pout = 0;
for(int i=0; i<nin; i++)
{
inmask = (int64_t)1 << (n-1);
for(int k=0; k<n; k++)
{
if(obit>7)
{
obit = 0;
pout++;
*pout = 0;
}
*pout |= (((input[i] & inmask) >> (n-k-1)) << (7-obit));
inmask >>= 1;
obit++;
nout++;
}
}
return nout;
}
int unpack(void* input, int nbitsin, int64_t* output, int n)
{
unsigned char* pin = (unsigned char*)input;
int64_t* pout = output;
int nbits = nbitsin;
unsigned char inmask = 0x80;
int inbit = 0;
int nout = 0;
while(nbits > 0)
{
*pout = 0;
for(int i=0; i<n; i++)
{
if(inbit > 7)
{
pin++;
inbit = 0;
}
*pout |= ((int64_t)((*pin & (inmask >> inbit)) >> (7-inbit))) << (n-i-1);
inbit++;
}
pout++;
nbits -= n;
nout++;
}
return nout;
}
int main()
{
int64_t input[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
int64_t output[21];
unsigned char compressed[21*8];
int n = 5;
int nbits = pack(input, 21, compressed, n);
int nout = unpack(compressed, nbits, output, n);
for(int i=0; i<=20; i++)
printf("input: %lld output: %lld\n", input[i], output[i]);
}
这是非常低效的,因为它是一次一步,但这是在不处理endianess问题的情况下实现它的最简单方法。我没有用很多值来测试它,只测试了测试中的值。此外,没有边界检查,并假设输出缓冲区足够长。所以我所说的是,这段代码可能只是为了帮助你开始教育目的。
答案 1 :(得分:5)
大多数压缩算法都会接近编码整数所需的最小熵,例如,霍夫曼编码,但像数组一样访问它将是非常重要的。
答案 2 :(得分:5)
今天我发布了:PackedArray: Packing Unsigned Integers Tightly(github project)。
它实现了一个随机访问容器,其中项目在位级别打包。换句话说,它就像你能够操纵一个例如uint9_t
或uint17_t
数组:
PackedArray principle:
. compact storage of <= 32 bits items
. items are tightly packed into a buffer of uint32_t integers
PackedArray requirements:
. you must know in advance how many bits are needed to hold a single item
. you must know in advance how many items you want to store
. when packing, behavior is undefined if items have more than bitsPerItem bits
PackedArray general in memory representation:
|-------------------------------------------------- - - -
| b0 | b1 | b2 |
|-------------------------------------------------- - - -
| i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 |
|-------------------------------------------------- - - -
. items are tightly packed together
. several items end up inside the same buffer cell, e.g. i0, i1, i2
. some items span two buffer cells, e.g. i3, i6
答案 3 :(得分:2)
我知道这似乎是显而易见的,因为我确信实际上有一个解决方案,但为什么不使用较小的类型,如uint8_t
(最多255)?或uint16_t
(最多65535)?我确信你可以使用定义的值和/或操作等对int64_t
进行位操作,但除了学术练习之外,为什么呢?
在学术练习的注释中,Bit Twiddling Hacks是一本很好的读物。
答案 4 :(得分:1)
如果您有固定尺寸,例如你知道你的数字是38位而不是64位,你可以使用位规范来构建结构。有趣的是你也有更小的元素适合剩下的空间。
struct example {
/* 64bit number cut into 3 different sized sections */
uint64_t big_num:38;
uint64_t small_num:16;
uint64_t itty_num:10;
/* 8 bit number cut in two */
uint8_t nibble_A:4;
uint8_t nibble_B:4;
};
如果没有一些跳跃,这不是大/小端安全,所以只能在程序中使用而不是在导出的数据格式中使用。它通常用于在单个位中存储布尔值,而不定义移位和掩码。
答案 5 :(得分:1)
从Jason B的实现开始,我最终编写了自己的版本来处理位块而不是单位。一个区别是它是lsb:它从最低输出位开始到最高。这只会使得使用二进制转储(如Linux xxd -b
)更难阅读。作为一个细节,int*
可以简单地更改为int64_t*
,最好是unsigned
。我已经用几百万个阵列测试了这个版本,它看起来很稳固,所以我将分享其余部分:
int pack2(int *input, int nin, unsigned char* output, int n)
{
int obit = 0;
int ibit = 0;
int ibite = 0;
int nout = 0;
if(nin>0) output[0] = 0;
for(int i=0; i<nin; i++)
{
ibit = 0;
while(ibit < n) {
ibite = std::min(n, ibit + 8 - obit);
output[nout] |= (input[i] & (((1 << ibite)-1) ^ ((1 << ibit)-1))) >> ibit << obit;
obit += ibite - ibit;
nout += obit >> 3;
if(obit & 8) output[nout] = 0;
obit &= 7;
ibit = ibite;
}
}
return nout;
}
int unpack2(int *oinput, int nin, unsigned char* ioutput, int n)
{
int obit = 0;
int ibit = 0;
int ibite = 0;
int nout = 0;
for(int i=0; i<nin; i++)
{
oinput[i] = 0;
ibit = 0;
while(ibit < n) {
ibite = std::min(n, ibit + 8 - obit);
oinput[i] |= (ioutput[nout] & (((1 << (ibite-ibit+obit))-1) ^ ((1 << obit)-1))) >> obit << ibit;
obit += ibite - ibit;
nout += obit >> 3;
obit &= 7;
ibit = ibite;
}
}
return nout;
}
答案 6 :(得分:0)
我认为你不能避免迭代元素。 AFAIK霍夫曼编码需要“符号”的频率,除非您知道生成整数的“过程”的统计信息,否则您必须计算(通过迭代每个元素)。