我有一台有1mb内存的电脑,没有其他本地存储。我必须使用它通过TCP连接接受100万个8位十进制数字,对它们进行排序,然后通过另一个TCP连接发送排序的列表。
数字列表可能包含重复的,我不能丢弃。代码将放在ROM中,所以我不需要从1 MB中减去我的代码的大小。我已经有了驱动以太网端口和处理TCP/IP连接的代码,它需要2 KB的状态数据,包括1 KB的缓冲区,代码将通过它读取和写入数据。这个问题有解决办法吗?
问答来源:
slashdot.org
cleaton.net
我有一台有1mb内存的电脑,没有其他本地存储。我必须使用它通过TCP连接接受100万个8位十进制数字,对它们进行排序,然后通过另一个TCP连接发送排序的列表。
数字列表可能包含重复的,我不能丢弃。代码将放在ROM中,所以我不需要从1 MB中减去我的代码的大小。我已经有了驱动以太网端口和处理TCP/IP连接的代码,它需要2 KB的状态数据,包括1 KB的缓冲区,代码将通过它读取和写入数据。这个问题有解决办法吗?
问答来源:
slashdot.org
cleaton.net
当前回答
基数树表示可以接近于处理这个问题,因为基数树利用了“前缀压缩”的优势。但是很难想象一个基树表表法可以在一个字节中表示单个节点——两个可能是极限。
但是,不管数据是如何表示的,一旦它被排序,它就可以以前缀压缩的形式存储,其中数字10、11和12将由001b、001b、001b表示,表示从前一个数字增加1。那么,也许10101b表示增量5,1101001b表示增量9,以此类推。
其他回答
请参阅第一个正确答案或后面带有算术编码的答案。下面你可能会发现一些有趣的,但不是100%防弹的解决方案。
这是一个非常有趣的任务,这里有另一个解决方案。我希望有人会觉得这个结果有用(或者至少有趣)。
阶段1:初始数据结构,粗略压缩方法,基本结果
Let's do some simple math: we have 1M (1048576 bytes) of RAM initially available to store 10^6 8 digit decimal numbers. [0;99999999]. So to store one number 27 bits are needed (taking the assumption that unsigned numbers will be used). Thus, to store a raw stream ~3.5M of RAM will be needed. Somebody already said it doesn't seem to be feasible, but I would say the task can be solved if the input is "good enough". Basically, the idea is to compress the input data with compression factor 0.29 or higher and do sorting in a proper manner.
让我们先解决压缩问题。有一些相关的测试已经可用:
http://www.theeggeadventure.com/wikimedia/index.php/Java_Data_Compression
“我运行了一个测试,压缩100万个连续整数使用 各种形式的压缩。结果如下:
None 4000027
Deflate 2006803
Filtered 1391833
BZip2 427067
Lzma 255040
看起来LZMA (Lempel-Ziv-Markov链算法)是一个很好的选择。我准备了一个简单的PoC,但仍有一些细节需要强调:
Memory is limited so the idea is to presort numbers and use compressed buckets (dynamic size) as temporary storage It is easier to achieve a better compression factor with presorted data, so there is a static buffer for each bucket (numbers from the buffer are to be sorted before LZMA) Each bucket holds a specific range, so the final sort can be done for each bucket separately Bucket's size can be properly set, so there will be enough memory to decompress stored data and do the final sort for each bucket separately
请注意,所附的代码是一个POC,它不能用作最终解决方案,它只是演示了使用几个较小的缓冲区以某种最佳方式(可能是压缩)存储预排序数字的想法。LZMA并不是最终的解决方案。它被用作向这个PoC引入压缩的最快方法。
请看下面的PoC代码(请注意它只是一个演示,要编译它将需要LZMA-Java):
public class MemorySortDemo {
static final int NUM_COUNT = 1000000;
static final int NUM_MAX = 100000000;
static final int BUCKETS = 5;
static final int DICT_SIZE = 16 * 1024; // LZMA dictionary size
static final int BUCKET_SIZE = 1024;
static final int BUFFER_SIZE = 10 * 1024;
static final int BUCKET_RANGE = NUM_MAX / BUCKETS;
static class Producer {
private Random random = new Random();
public int produce() { return random.nextInt(NUM_MAX); }
}
static class Bucket {
public int size, pointer;
public int[] buffer = new int[BUFFER_SIZE];
public ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
public DataOutputStream tempDataOut = new DataOutputStream(tempOut);
public ByteArrayOutputStream compressedOut = new ByteArrayOutputStream();
public void submitBuffer() throws IOException {
Arrays.sort(buffer, 0, pointer);
for (int j = 0; j < pointer; j++) {
tempDataOut.writeInt(buffer[j]);
size++;
}
pointer = 0;
}
public void write(int value) throws IOException {
if (isBufferFull()) {
submitBuffer();
}
buffer[pointer++] = value;
}
public boolean isBufferFull() {
return pointer == BUFFER_SIZE;
}
public byte[] compressData() throws IOException {
tempDataOut.close();
return compress(tempOut.toByteArray());
}
private byte[] compress(byte[] input) throws IOException {
final BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(input));
final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(compressedOut));
final Encoder encoder = new Encoder();
encoder.setEndMarkerMode(true);
encoder.setNumFastBytes(0x20);
encoder.setDictionarySize(DICT_SIZE);
encoder.setMatchFinder(Encoder.EMatchFinderTypeBT4);
ByteArrayOutputStream encoderPrperties = new ByteArrayOutputStream();
encoder.writeCoderProperties(encoderPrperties);
encoderPrperties.flush();
encoderPrperties.close();
encoder.code(in, out, -1, -1, null);
out.flush();
out.close();
in.close();
return encoderPrperties.toByteArray();
}
public int[] decompress(byte[] properties) throws IOException {
InputStream in = new ByteArrayInputStream(compressedOut.toByteArray());
ByteArrayOutputStream data = new ByteArrayOutputStream(10 * 1024);
BufferedOutputStream out = new BufferedOutputStream(data);
Decoder decoder = new Decoder();
decoder.setDecoderProperties(properties);
decoder.code(in, out, 4 * size);
out.flush();
out.close();
in.close();
DataInputStream input = new DataInputStream(new ByteArrayInputStream(data.toByteArray()));
int[] array = new int[size];
for (int k = 0; k < size; k++) {
array[k] = input.readInt();
}
return array;
}
}
static class Sorter {
private Bucket[] bucket = new Bucket[BUCKETS];
public void doSort(Producer p, Consumer c) throws IOException {
for (int i = 0; i < bucket.length; i++) { // allocate buckets
bucket[i] = new Bucket();
}
for(int i=0; i< NUM_COUNT; i++) { // produce some data
int value = p.produce();
int bucketId = value/BUCKET_RANGE;
bucket[bucketId].write(value);
c.register(value);
}
for (int i = 0; i < bucket.length; i++) { // submit non-empty buffers
bucket[i].submitBuffer();
}
byte[] compressProperties = null;
for (int i = 0; i < bucket.length; i++) { // compress the data
compressProperties = bucket[i].compressData();
}
printStatistics();
for (int i = 0; i < bucket.length; i++) { // decode & sort buckets one by one
int[] array = bucket[i].decompress(compressProperties);
Arrays.sort(array);
for(int v : array) {
c.consume(v);
}
}
c.finalCheck();
}
public void printStatistics() {
int size = 0;
int sizeCompressed = 0;
for (int i = 0; i < BUCKETS; i++) {
int bucketSize = 4*bucket[i].size;
size += bucketSize;
sizeCompressed += bucket[i].compressedOut.size();
System.out.println(" bucket[" + i
+ "] contains: " + bucket[i].size
+ " numbers, compressed size: " + bucket[i].compressedOut.size()
+ String.format(" compression factor: %.2f", ((double)bucket[i].compressedOut.size())/bucketSize));
}
System.out.println(String.format("Data size: %.2fM",(double)size/(1014*1024))
+ String.format(" compressed %.2fM",(double)sizeCompressed/(1014*1024))
+ String.format(" compression factor %.2f",(double)sizeCompressed/size));
}
}
static class Consumer {
private Set<Integer> values = new HashSet<>();
int v = -1;
public void consume(int value) {
if(v < 0) v = value;
if(v > value) {
throw new IllegalArgumentException("Current value is greater than previous: " + v + " > " + value);
}else{
v = value;
values.remove(value);
}
}
public void register(int value) {
values.add(value);
}
public void finalCheck() {
System.out.println(values.size() > 0 ? "NOT OK: " + values.size() : "OK!");
}
}
public static void main(String[] args) throws IOException {
Producer p = new Producer();
Consumer c = new Consumer();
Sorter sorter = new Sorter();
sorter.doSort(p, c);
}
}
对于随机数,它产生如下结果:
bucket[0] contains: 200357 numbers, compressed size: 353679 compression factor: 0.44
bucket[1] contains: 199465 numbers, compressed size: 352127 compression factor: 0.44
bucket[2] contains: 199682 numbers, compressed size: 352464 compression factor: 0.44
bucket[3] contains: 199949 numbers, compressed size: 352947 compression factor: 0.44
bucket[4] contains: 200547 numbers, compressed size: 353914 compression factor: 0.44
Data size: 3.85M compressed 1.70M compression factor 0.44
对于一个简单的升序序列(使用一个桶),它产生:
bucket[0] contains: 1000000 numbers, compressed size: 256700 compression factor: 0.06
Data size: 3.85M compressed 0.25M compression factor 0.06
EDIT
结论:
不要试图欺骗大自然 使用更简单的压缩和更低的内存占用 确实需要一些额外的线索。普通的防弹方案似乎并不可行。
第二阶段:强化压缩,最终结论
正如在前一节中已经提到的,任何合适的压缩技术都可以使用。因此,让我们摒弃LZMA,转而采用更简单、更好(如果可能的话)的方法。有很多好的解决方案,包括算术编码,基树等。
无论如何,简单但有用的编码方案将比另一个外部库更能说明问题,它提供了一些漂亮的算法。实际的解决方案非常简单:因为存在部分排序的数据桶,所以可以使用增量而不是数字。
随机输入测试结果稍好:
bucket[0] contains: 10103 numbers, compressed size: 13683 compression factor: 0.34
bucket[1] contains: 9885 numbers, compressed size: 13479 compression factor: 0.34
...
bucket[98] contains: 10026 numbers, compressed size: 13612 compression factor: 0.34
bucket[99] contains: 10058 numbers, compressed size: 13701 compression factor: 0.34
Data size: 3.85M compressed 1.31M compression factor 0.34
示例代码
public static void encode(int[] buffer, int length, BinaryOut output) {
short size = (short)(length & 0x7FFF);
output.write(size);
output.write(buffer[0]);
for(int i=1; i< size; i++) {
int next = buffer[i] - buffer[i-1];
int bits = getBinarySize(next);
int len = bits;
if(bits > 24) {
output.write(3, 2);
len = bits - 24;
}else if(bits > 16) {
output.write(2, 2);
len = bits-16;
}else if(bits > 8) {
output.write(1, 2);
len = bits - 8;
}else{
output.write(0, 2);
}
if (len > 0) {
if ((len % 2) > 0) {
len = len / 2;
output.write(len, 2);
output.write(false);
} else {
len = len / 2 - 1;
output.write(len, 2);
}
output.write(next, bits);
}
}
}
public static short decode(BinaryIn input, int[] buffer, int offset) {
short length = input.readShort();
int value = input.readInt();
buffer[offset] = value;
for (int i = 1; i < length; i++) {
int flag = input.readInt(2);
int bits;
int next = 0;
switch (flag) {
case 0:
bits = 2 * input.readInt(2) + 2;
next = input.readInt(bits);
break;
case 1:
bits = 8 + 2 * input.readInt(2) +2;
next = input.readInt(bits);
break;
case 2:
bits = 16 + 2 * input.readInt(2) +2;
next = input.readInt(bits);
break;
case 3:
bits = 24 + 2 * input.readInt(2) +2;
next = input.readInt(bits);
break;
}
buffer[offset + i] = buffer[offset + i - 1] + next;
}
return length;
}
请注意,这种方法:
不消耗大量内存 使用流 提供了不那么坏的结果
完整的代码可以在这里找到,BinaryInput和BinaryOutput实现可以在这里找到
最终结论
没有最终结论:)有时候,从元级别的角度来回顾一下任务,这确实是个好主意。
花点时间完成这个任务很有趣。顺便说一下,下面有很多有趣的答案。感谢您的关注和愉快的编码。
解决方案可能只是因为1兆字节和100万字节之间的差异。大约有2的8093729.5次方种不同的方法来选择100万个允许重复的8位数,顺序不重要,所以一台只有100万字节RAM的机器没有足够的状态来表示所有的可能性。但是1M (TCP/IP少2k)是1022*1024*8 = 8372224位,所以解决方案是可能的。
第一部分,初始解
这个方法需要1M多一点,我稍后会改进它以适应1M。
我将把0到99999999范围内的数字的紧凑排序列表存储为7位数字的子列表序列。第一个子列表包含从0到127的数字,第二个子列表包含从128到255的数字,等等。100000000/128正好是781250,因此需要781250个这样的子列表。
每个子列表由一个2位的子列表头和一个子列表体组成。子列表主体为每个子列表条目占用7位。所有子列表都连接在一起,并且这种格式可以确定一个子列表的结束位置和下一个子列表的开始位置。一个完全填充的列表所需的总存储空间是2*781250 + 7*1000000 = 8562500位,大约是1.021 m -字节。
4个可能的子列表头值是:
00空子列表,后面什么都没有。
01单例,在子列表中只有一个条目,并且接下来的7位保存它。
子列表至少包含两个不同的数字。除了最后一个条目小于或等于第一个条目外,条目以非递减顺序存储。这允许识别子列表的结尾。例如,数字2,4,6将被存储为(4,6,2)。数字2,2,3,4,4将被存储为(2,3,4,2)。
子列表包含单个数字的2个或更多重复。接下来的7位给出数字。然后是0个或多个值为1的7位条目,后面是一个值为0的7位条目。子列表体的长度决定了重复的次数。例如,数字12,12将存储为(12,0),数字12,12,12将存储为(12,1,0),数字12,12,12,12将存储为(12,1,1,0),以此类推。
我从一个空列表开始,读入一堆数字并将它们存储为32位整数,对新数字进行排序(可能使用heapsort),然后将它们合并到一个新的紧凑排序列表中。重复该操作,直到不再需要读取数字为止,然后再次遍历紧凑列表以生成输出。
下面的行表示列表合并操作开始前的内存。“O”是存放已排序的32位整数的区域。“X”是存放旧紧凑列表的区域。“=”符号是紧凑列表的扩展空间,“O”中的每个整数对应7位。“Z”是其他随机的开销。
ZZZOOOOOOOOOOOOOOOOOOOOOOOOOO==========XXXXXXXXXXXXXXXXXXXXXXXXXX
合并例程从最左边的“O”和最左边的“X”开始读取,并从最左边的“=”开始写入。直到所有的新整数被合并,写指针才会捕获紧凑列表的读指针,因为这两个指针为每个子列表前进2位,为旧紧凑列表中的每个条目前进7位,并且有足够的额外空间容纳新数字的7位条目。
第二部分,把它塞进1M
为了将上面的解决方案压缩到1M,我需要使紧凑列表的格式更紧凑一点。我将去掉其中一个子列表类型,这样就只有3个不同的子列表头值。然后我可以使用“00”,“01”和“1”作为子列表头值,并节省一些比特。子列表类型为:
空子列表,后面什么都没有。
B单例,在子列表中只有一个条目,接下来的7位保存它。
子列表至少包含2个不同的数字。除了最后一个条目小于或等于第一个条目外,条目以非递减顺序存储。这允许识别子列表的结尾。例如,数字2,4,6将被存储为(4,6,2)。数字2,2,3,4,4将被存储为(2,3,4,2)。
子列表由单个数字的2个或2个以上的重复组成。
我的3个子列表头值将是“A”,“B”和“C”,所以我需要一种方法来表示d类型的子列表。
Suppose I have the C-type sublist header followed by 3 entries, such as "C[17][101][58]". This can't be part of a valid C-type sublist as described above, since the third entry is less than the second but more than the first. I can use this type of construct to represent a D-type sublist. In bit terms, anywhere I have "C{00?????}{1??????}{01?????}" is an impossible C-type sublist. I'll use this to represent a sublist consisting of 3 or more repetitions of a single number. The first two 7-bit words encode the number (the "N" bits below) and are followed by zero or more {0100001} words followed by a {0100000} word.
For example, 3 repetitions: "C{00NNNNN}{1NN0000}{0100000}", 4 repetitions: "C{00NNNNN}{1NN0000}{0100001}{0100000}", and so on.
That just leaves lists that hold exactly 2 repetitions of a single number. I'll represent those with another impossible C-type sublist pattern: "C{0??????}{11?????}{10?????}". There's plenty of room for the 7 bits of the number in the first 2 words, but this pattern is longer than the sublist that it represents, which makes things a bit more complex. The five question-marks at the end can be considered not part of the pattern, so I have: "C{0NNNNNN}{11N????}10" as my pattern, with the number to be repeated stored in the "N"s. That's 2 bits too long.
我将不得不借2位,然后从这个模式中4位未使用的位中还钱。读取时,遇到“C{0NNNNNN}{11N00AB}10”时,输出“N”中数字的2个实例,用A位和B位覆盖最后的“10”,并将读指针倒回2位。对于这个算法,破坏性读取是可以的,因为每个紧凑列表只遍历一次。
当写入一个重复2次的单个数字的子列表时,写入“C{0NNNNNN}11N00”并将借来的比特计数器设置为2。在每次写入借位计数器非零的时候,它会为写入的每一位减数,当计数器为零时写入“10”。因此,接下来写入的2位将进入槽A和槽B,然后“10”将被放到最后。
用“00”、“01”和“1”表示3个子列表头值,我可以将“1”分配给最流行的子列表类型。我需要一个小表来将子列表标题值映射到子列表类型,并且我需要每个子列表类型的出现计数器,以便我知道最好的子列表标题映射是什么。
当所有子列表类型都同样流行时,就会出现完全填充的紧凑列表的最坏情况最小表示。在这种情况下,我为每3个子列表头保存1位,因此列表大小为2*781250 + 7*1000000 - 781250/3 = 8302083.3位。四舍五入到32位的字边界,即8302112位,或1037764字节。
1M减去TCP/IP状态和缓冲区的2k是1022*1024 = 1046528字节,剩下8764字节可供使用。
但是改变子列表头映射的过程如何呢?在下面的内存映射中,“Z”是随机开销,“=”是空闲空间,“X”是紧凑列表。
ZZZ=====XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
从最左边的“X”开始读,从最左边的“=”开始写,然后往右写。当它完成时,压缩列表将会变得更短,它将会在内存的错误一端:
ZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=======
所以我需要把它向右分流
ZZZ=======XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
在头映射变化过程中,多达1/3的子列表头将从1位变为2位。在最坏的情况下,这些都将位于列表的头部,因此在开始之前,我至少需要781250/3位的空闲存储空间,这使我回到了紧凑列表的前一个版本的内存要求:(
为了解决这个问题,我将781250子列表分成10个子列表组,每个子列表组78125子列表。每个组都有自己独立的子列表头映射。用字母A到J表示组:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
在子列表头映射变化期间,每个子列表组缩小或保持不变:
ZZZ=====AAAAAABBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAA=====BBCCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABB=====CCCCDDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCC======DDDDDEEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDD======EEEFFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEE======FFFGGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFF======GGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGG=======HHIJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHH=======IJJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHI=======JJJJJJJJJJJJJJJJJJJJ
ZZZAAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ=======
ZZZ=======AAAAAABBCCCDDDDDEEEFFFGGGGGGGGGGHHIJJJJJJJJJJJJJJJJJJJJ
映射更改期间子列表组临时扩展的最坏情况是78125/3 = 26042位,小于4k。如果我允许4k加上1037764字节用于完全填充的紧凑列表,那么内存映射中的“Z”就剩下8764 - 4096 = 4668字节。
对于10个子列表头映射表、30个子列表头出现计数和我需要的其他几个计数器、指针和小缓冲区,以及我已经不注意使用的空间,比如函数调用返回地址和局部变量的堆栈空间,这些应该足够了。
第三部分,运行需要多长时间?
对于空的紧凑列表,1位的列表头将用于空的子列表,列表的起始大小将是781250位。在最坏的情况下,每增加一个数字,列表就增长8位,因此32 + 8 = 40位的空闲空间需要将每个32位数字放在列表缓冲区的顶部,然后排序和合并。在最坏的情况下,更改子列表报头映射将导致占用2*781250 + 7*entries - 781250/3位的空间。
如果策略是在列表中至少有800000个数字的情况下,每5次合并后更改子列表头映射,那么最坏的情况下运行将涉及大约30M的紧凑列表读写活动。
来源:
http://nick.cleaton.net/ramsortsol.html
下面是这类问题的一般解决方案:
一般程序
所采取的方法如下。该算法在一个32位字的缓冲区上操作。它在循环中执行以下过程:
We start with a buffer filled with compressed data from the last iteration. The buffer looks like this |compressed sorted|empty| Calculate the maximum amount of numbers that can be stored in this buffer, both compressed and uncompressed. Split the buffer into these two sections, beginning with the space for compressed data, ending with the uncompressed data. The buffer looks like |compressed sorted|empty|empty| Fill the uncompressed section with numbers to be sorted. The buffer looks like |compressed sorted|empty|uncompressed unsorted| Sort the new numbers with an in-place sort. The buffer looks like |compressed sorted|empty|uncompressed sorted| Right-align any already compressed data from the previous iteration in the compressed section. At this point the buffer is partitioned |empty|compressed sorted|uncompressed sorted| Perform a streaming decompression-recompression on the compressed section, merging in the sorted data in the uncompressed section. The old compressed section is consumed as the new compressed section grows. The buffer looks like |compressed sorted|empty|
执行此过程,直到所有数字都已排序。
压缩
当然,这种算法只有在知道实际要压缩什么之前,才有可能计算出新排序缓冲区的最终压缩大小。其次,压缩算法需要足够好来解决实际问题。
所使用的方法使用三个步骤。首先,算法将始终存储排序序列,因此我们可以只存储连续条目之间的差异。每个差值都在[0,99999999]的范围内。
这些差异随后被编码为一元比特流。这个流中的1表示“向累加器添加1,0表示“将累加器作为一个条目发出,并重置”。所以差N由N个1和1个0表示。
所有差异的和将接近算法支持的最大值,所有差异的计数将接近算法中插入的值的数量。这意味着我们期望流在最后包含最大值1和计数0。这允许我们计算流中0和1的期望概率。即,0的概率为count/(count+maxval), 1的概率为maxval/(count+maxval)。
我们使用这些概率来定义这个比特流上的算术编码模型。这个算术代码将在最佳空间中精确地编码1和0的数量。我们可以计算该模型对于任何中间位流所使用的空间:bits = encoded * log2(1 + amount / maxval) + maxval * log2(1 + maxval / amount)。若要计算算法所需的总空间,请将encoded设置为amount。
为了不需要大量的迭代,可以向缓冲区添加少量开销。这将确保算法将至少对适合这个开销的数量进行操作,因为到目前为止,算法最大的时间成本是每个周期的算术编码压缩和解压缩。
除此之外,在算术编码算法的定点近似中,存储簿记数据和处理轻微的不准确性是需要一些开销的,但总的来说,即使使用可以包含8000个数字的额外缓冲区,该算法也能够容纳1MiB的空间,总共1043916字节的空间。
最优
除了减少算法的开销外,理论上不可能得到更小的结果。为了仅仅包含最终结果的熵,1011717个字节是必要的。如果我们减去为提高效率而增加的额外缓冲区,该算法使用1011916字节来存储最终结果+开销。
基数树表示可以接近于处理这个问题,因为基数树利用了“前缀压缩”的优势。但是很难想象一个基树表表法可以在一个字节中表示单个节点——两个可能是极限。
但是,不管数据是如何表示的,一旦它被排序,它就可以以前缀压缩的形式存储,其中数字10、11和12将由001b、001b、001b表示,表示从前一个数字增加1。那么,也许10101b表示增量5,1101001b表示增量9,以此类推。
如果输入流可以接收几次,这将是很大的 更简单(没有关于这方面的信息,想法和时间-性能问题)。
然后,我们可以数小数。如果是计数值的话 容易使输出流。通过计算值来压缩。它 这取决于输入流中的内容。