我的解决方案:最佳情况7.025位/数字,最坏情况14.193位/数字,粗略平均值8.551位/数字。流编码,无随机访问。
即使在阅读ruslik的答案之前,我也立即想到对每个数字之间的差异进行编码,因为它很小并且应该相对一致,但是解决方案还必须能够适应最坏的情况。我们有一个100000个数字的空间,其中仅包含1000个数字。在完全统一的电话簿中,每个数字都比前一个数字大100:
55555-12 3 45
55555-12 4 45
55555-12 5 45
如果真是这样,由于它是一个已知的常数,因此需要零存储来对数字之间的差进行编码。不幸的是,数字可能与理想步长100不同。我将对与理想增量100的差进行编码,因此,如果两个相邻的数字相差103,则我将对数字3进行编码;如果两个相邻的数字相差92,则I将编码为-8。我称理想增量为100的增量为“ 方差 ”。
变化范围可以从-99(即两个连续的数字)到99000(整个电话簿由数字00000…00999和最远的数字99999组成),范围为99100个可能值。
我的目标是,如果我遇到更大的差异(像来分配最小的存储进行编码的最常见的差异,扩大存储的Protobuf的varint
)。我将使用七个位的块,其中六个用于存储,最后使用一个额外的标志位来指示此方差与当前块之后的另一个块一起存储,最多三个块(最多提供三个) 3 * 6 = 18位存储空间,即262144个可能值,比可能的变化数(99100)多。在加高标志之后的每个附加块均具有较高的重要性位,因此第一个块始终具有位0-在图5中,可选的第二块具有位6-11,并且可选的第三块具有位12-17。
单个块提供六位存储空间,可以容纳64个值。我想映射64个最小方差以适合该单个块(即-32至+31的方差),因此我将使用ProtoBuf ZigZag编码,最多使用-99至+98的方差(因为不需要(超出-99的负方差),这时我将切换为常规编码,偏移98:
差异| 编码值
----------- + ----------------
0 | 0
-1 | 1个
1 | 2
-2 | 3
2 | 4
-3 | 5
3 | 6
... | ...
-31 | 61
31 | 62
-32 | 63
----------- | ---------------- 6位
32 | 64
-33 | 65
33 | 66
... | ...
-98 | 195
98 | 196
-99 | 197
----------- | ---------------之字形结尾
100 | 198
101 | 199
... | ...
3996 | 4094
3997 | 4095
----------- | --------------- 12位
3998 | 4096
3999 | 4097
... | ...
262045 | 262143
----------- | ---------------- 18位
关于如何将方差编码为位的一些示例,包括指示额外块的标志:
差异| 编码位
----------- + ----------------
0 | 000000 0
5 | 001010 0
-8 | 001111 0
-32 | 111111 0
32 | 000000 1 000001 0
-99 | 000101 1 000011 0
177 | 010011 1 000100 0
14444 | 001110 1 100011 1 000011 0
因此,样本电话簿的前三个数字将被编码为如下的位流:
BIN 000101001011001000100110010000011001 000110 1 010110 1 00001 0
PH#55555-12345 55555-12448 55555-12491
POS 1 2 3
最好的情况是,电话簿在某种程度上是均匀分布的,并且没有两个电话号码的方差大于32,因此它将使用每个号码7位加上32位作为起始号码,总计32 + 7 * 999 = 7025位。
混合方案,其中800个电话号码的方差适合一个块(800 * 7 = 5600),180个电话号码适合两个块(180 * 2 * 7 = 2520),而19个电话号码适合三个块(20 * 3) * 7 = 399),加上开头的 32位,总计8551位。
最坏的情况是,25个数字适合三个块(25 * 3 * 7 = 525位),其余974个数字适合两个块(974 * 2 * 7 = 13636位),外加第一个数字的32位,用于盛大总数是14193位。
编码数量|
1块| 2块| 3块| 总位数
--------- + ---------- + ---------- + ------------
999 | 0 | 0 | 7025
800 | 180 | 19 | 8551
0 | 974 | 25 | 14193
我可以看到可以执行四个其他优化来进一步减少所需的空间:
- 第三个块不需要完整的七个位,它可以仅仅是五个位而没有标志位。
- 可以通过数字的初始传递来计算每个块的最佳大小。也许对于某些电话簿,最好让第一个块具有5 + 1位,第二个7 + 1和第三个5 + 1。这将进一步将大小减小到最小6 * 999 + 32 = 6026位,再加上两组三位来存储块1和2的大小(块3的大小是所需的16位的剩余部分),总计6032位!
- 相同的初始遍历可以计算出比默认值100更好的预期增量。也许有一本电话簿从55555-50000开始,所以它的号码范围是一半,因此预期增量应该是50。或者也许是非线性的分布(也许是标准偏差)和其他一些最佳预期增量。这将减少典型的方差,并可能允许使用更小的第一块。
- 可以在第一遍中进行进一步的分析,以对电话簿进行分区,每个分区都有自己的预期增量和组块大小优化。对于电话簿的某些高度均匀的部分,这将允许较小的第一块大小(减少消耗的位数),对于不均匀的部分将允许较大的块大小(减少在连续标志上浪费的位数)。