将数组组压缩成尽可能小的字符串

Compress group of arrays into smallest possible string

可以使用 javascript、下划线或 Jquery 函数回答此问题。

给定 4 个数组:

[17,17,17,17,17,18,18,18,18,18,19,19,19,19,19,20,20,20,20,20]  => x coordinate of unit
[11,12,13,14,15,11,12,13,14,15,11,12,13,14,15,11,12,13,14,15]  => y coordinate of unit
[92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92]  => x (unit moves to this direction x)
[35,36,37,38,39,35,36,37,38,39,35,36,37,38,39,35,36,37,38,39]  => y (unit moves to this direction y)

他们彼此非常相关。 例如所有数组的第一个元素:[17,11,92,35] 是单位 x/y 坐标,也是该单位目标的 x/y 坐标。 所以这里总共有 5*4 = 20 个单位。每个单位的统计数据略有不同。 这4个单位数组x/y坐标在视觉上看起来像一支20个单位的军队"x"(目标"o"):

xxxxx                  o
xxxxx                  o
xxxxx                  o
xxxxx                  o

总会有4个数组。即使是1个单元,也会有4个数组,但是每个数组的大小都是1。这是最简单的情况,也是最常见的。 在实际情况下,每个单元总共有 20 个不同的统计数据(键),并且 14 个键大多与其他单元组完全相同——所有 14 个键。 因此,他们被归为一支具有相同统计数据的军队。不同的只是单位的坐标,还有单位目标的坐标。

我需要将所有这些数据压缩成尽可能小的数据,以后可以解压。

还有更复杂的情况,这14个键一不小心都一样,但是坐标和pattern完全不一样。示例:

[17,17,17,17,17,18,18,18,  215,  18,18,19,19,19,19,19,20,20,20,20,20]  => x coordinate of unit
[11,12,13,14,15,11,12,13,  418,  14,15,11,12,13,14,15,11,12,13,14,15]  => y coordinate of unit
[92,92,92,92,92,92,92,92,  -78,  92,92,92,92,92,92,92,92,92,92,92,92]  => x (unit moves to this direction x)
[35,36,37,38,39,35,36,37,  -887, 38,39,35,36,37,38,39,35,36,37,38,39]  => y (unit moves to this direction y)

在这种情况下,我需要为 2 个不同的军队提取这个数组。
当军队中的单位少于 3 个时,我只是简单地写这些单位而没有 pattern - 如 [215,418,-78,-887],[..] 如果有超过 2 个单位的军队,我需要一个带有 pattern 的压缩字符串,稍后可以解压。在这个例子中有 21 个单位。它只需要分成 1 个单位的军队和 (5x4 = 20) 个单位的军队。

假设每个 n 单位都有一个模式, 用

编码单位
n: sequence units count
ssx: start of source x
dsx: difference of source x
ssy: start of source y
dsy: difference of source y
stx: start of target x
dtx: difference of target x
sty: start of target y
dty: difference of target y

按数组:[n,ssx,dsx,ssy,dsy,stx,dtx,sty,dty]

所以单位:

[17,17,17,17,17],
[11,12,13,14,15],
[92,92,92,92,92],
[35,36,37,38,39]

被编码:

[5,17,0,11,1,92,0,35,1]

当然,如果你事先知道,例如 y 目标对于这样的序列总是相同的,你可以放弃 difference 参数,有: [n,ssx,dsx,ssy,---,stx,dtx,sty,---] => [n,ssx,dsx,ssy,stx,dtx,sty],依此类推。

对于上一个示例中提到的模式的中断,您可以使用其他 'extra' 数组,然后将它们插入序列中,其中:

exsx: extra unit starting x
exsy: extra unit starting y
extx: extra unit target x
exty: extra unit target y
m: insert extra unit at

以便对特殊情况进行编码:

{
patterns:[
   [5,17,0,11,1,92,0,35,1],
   [5,18,0,11,1,92,0,35,1],
   [5,19,0,11,1,92,0,35,1],
   [5,17,0,11,1,92,0,35,1]
],
extras: [
   [215,418,-78,-887,8] // 8 - because you want to insert this unit at index 8
]
}

同样,这是通用编码。模式的任何特定属性都可能进一步减少编码表示。

希望这对您有所帮助。

使用比特流进行高压缩

您可以将值集编码为位流,从而删除未使用的位。您显示的数字不大于 -887(忽略负数),这意味着您可以将所有数字放入 10 位,每个数字节省 54 位(Javascript 使用 64 位数字)。

运行长度压缩

您还有许多重复的数字集,您可以对其使用 运行 长度压缩。您在比特流中设置一个标志,指示后面的一组比特代表一个重复的数字序列,然后您就有了重复的次数和要重复的值。对于随机数序列,您只需保持原样即可。

如果您使用 运行 长度压缩,您会在比特流中创建块类型结构,这使得嵌入进一步压缩成为可能。由于您有许多低于 128 的数字,因此许多数字可以编码为 7 位,甚至更少。对于较小的开销(在这种情况下,每个块 2 位),您可以 select 最小的位大小来打包该块中的所有数字。

可变位深度数字

我创建了一个数字类型值,表示用于在块中存储数字的位数。每个块都有一个数字类型,块中的所有数字都使用该类型。有4种数字类型可以编码成2位。

  • 00 = 4 位数。范围 0-15
  • 01 = 5 位数。范围 0-31
  • 10 = 7 位数。范围 0-127
  • 11 = 10 个位数。范围 0-1023

比特流

为简化此操作,您需要比特流 read/write。它允许您轻松地从位流中写入和读取位。

// Simple unsigned bit stream 
// Read and write to and from a bit stream.
// Numbers are stored as Big endian
// Does not comprehend sign so wordlength should be less than 32 bits
// methods
// eof(); // returns true if read pos > buffer bit size
// read(numberBits); // number of bits to read as one number. No sign so < 32
// write(value,numberBits); // value to write, number of bits to write < 32
// getBuffer(); // return object with buffer and array of numbers, and bitLength the total number of bits
// setBuffer(buffer,bitLength); // the buffers as an array of numbers, and bitLength the total number of bits
// Properties
// wordLength; // read only length of a word.
function BitStream(){
    var buffer = [];
    var pos = 0;
    var numBits = 0;
    const wordLength = 16;
    this.wordLength = wordLength;
    // read a single bit
    var readBit = function(){
        var b = buffer[Math.floor(pos / wordLength)];  // get word
        b = (b >> ((wordLength - 1) - (pos % wordLength))) & 1;
        pos += 1;
        return b;
    }
    // write a single bit. Will fill bits with 0 if wite pos is moved past buffer length
    var writeBit = function(bit){
        var rP = Math.floor(pos / wordLength);
        if(rP >= buffer.length){  // check that the buffer has values at current pos.
            var end = buffer.length;  // fill buffer up to pos with zero

            while(end <= rP){
                buffer[end] = 0;
                end += 1;
            }
        }
        var b = buffer[rP];
        bit &= 1; // mask out any unwanted bits
        bit <<= (wordLength - 1) - (pos % wordLength);
        b |= bit;
        buffer[rP] = b;
        pos += 1;
    }
    // returns true is past eof
    this.eof = function(){
        return pos >= numBits;
    }
    // reads number of bits as a Number
    this.read = function(bits){
        var v = 0;
        while(bits > 0){
            v <<= 1;
            v |= readBit();
            bits -= 1;
        }
        return v;
    }
    // writes value to bit stream 
    this.write = function(value,bits){
        var v;
        while(bits > 0){
            bits -= 1;
            writeBit( (value >> bits) & 1 );
        }
    }
    // returns the buffer and length
    this.getBuffer = function(){
        return {
            buffer : buffer,
            bitLength : pos,
        };                    
    }
    // set the buffer and length and returns read write pos to start
    this.setBuffer = function(_buffer,bitLength){
        buffer = _buffer;
        numBits = bitLength;
        pos = 0;
    }
}    

数字格式

现在开始设计格式。从流中读取的第一位是一个序列标志,如果为 0,那么接下来的块将是一个重复值,如果为 1,那么接下来的块将是一个随机数序列。

块位:描述;

repeat block 保存一个重复的数字

  • 第 0 位:Val 0 = 重复
  • 第 1 位:Val 0 = 4 位重复计数或 1 = 5 位重复计数

然后

  • 第 2、3、4、5 位:4 位重复次数 - 1
  • bits 6,7 : 2 bit 数字类型

  • 第 2、3、4、5、6 位:5 位重复次数 - 1
  • bits 7,8 : 2 bit 数字类型

其次是

然后是一个将根据数字类型重复的值

块结束

序列块保存随机数序列

  • 位 0:Val 1 = 序列
  • 第 1 位:Val 0 = 正序 Val 1 = 负序
  • bits 2,3,4,5 : 4 位数字序列 - 1
  • bits 6,7 : 2 bit 数字类型

然后是数字格式的数字序列

块结束。

继续读取块直到文件结束。

编码器和解码器

以下对象将对平面数字数组进行编码和解码。它只会处理最多 10 位长的数字,因此没有超过 1023 或低于 -1023 的值。

如果您想要更大的数字,则必须更改所使用的数字类型。为此,请更改数组

const numberSize = [0,0,0,0,0,1,2,2,3,3,3]; // the number bit depth          
const numberBits = [4,5,7,10]; // the number bit depth lookup; 

如果你希望最大数字为12位-4095到4095(符号位在块编码中)。我还展示了 7 位数字类型更改为 8。第一个数组用于查找位深度,如果我有一个 3 位数字,你会得到带有 numberSize[bitcount] 的数字类型和用于存储的位数 numberBits[numberSize[bitCount]]

const numberSize = [0,0,0,0,0,1,2,2,2,3,3,3,3]; // the number bit depth          
const numberBits = [4,5,8,12]; // the number bit depth lookup; 



function ArrayZip(){
    var zipBuffer = 0;
    const numberSize = [0,0,0,0,0,1,2,2,3,3,3]; // the number bit depth lookup; 
    const numberBits = [4,5,7,10]; // the number bit depth lookup; 
    this.encode = function(data){ // encodes the data
        var pos = 0;
        function getRepeat(){  // returns the number of repeat values
            var p = pos + 1;
            if(data[pos] < 0){
                return 1;  // ignore negative numbers
            }
            while(p < data.length && data[p] === data[pos]){
                p += 1;
            }
            return p - pos;
        }
        function getNoRepeat(){  // returns the number of non repeat values
                                 // if the sequence has negitive numbers then 
                                 // the length is returned as a negative
            var p = pos + 1;
            if(data[pos] < 0){ // negative numbers
                while(p < data.length && data[p] !== data[p-1] && data[p] < 0){
                    p += 1;
                }
                return -(p - pos);
            }
            while(p < data.length && data[p] !== data[p-1] && data[p] >= 0){
                p += 1;
            }
            return p - pos;
        }
        function getMax(count){
            var max = 0;
            var p = pos;
            while(count > 0){
                max = Math.max(Math.abs(data[p]),max);
                p += 1;
                count -= 1;
            }
            return max;
        }
        var out = new BitStream();
        while(pos < data.length){
            var reps = getRepeat();
            if(reps > 1){
                var bitCount = numberSize[Math.ceil(Math.log(getMax(reps) + 1) / Math.log(2))];
                if(reps < 16){
                    out.write(0,1);  // repeat header
                    out.write(0,1);  // use 4 bit repeat count;
                    out.write(reps-1,4); // write 4 bit number of reps
                    out.write(bitCount,2); // write 2 bit number size 
                    out.write(data[pos],numberBits[bitCount]);
                    pos += reps;
                }else {
                    if(reps > 32){  // if more than can fit in one repeat block split it 
                        reps = 32;
                    }
                    out.write(0,1);  // repeat header
                    out.write(1,1);  // use 5 bit repeat count;
                    out.write(reps-1,5); // write 5 bit number of reps
                    out.write(bitCount,2); // write 2 bit number size 
                    out.write(data[pos],numberBits[bitCount]);
                    pos += reps;
                }
            }else{
                var seq = getNoRepeat(); // get number no repeats
                var neg = seq < 0 ? 1 : 0; // found negative numbers
                seq = Math.min(16,Math.abs(seq));
                // check if last value is the start of a repeating block
                if(seq > 1){
                    var tempPos = pos;
                    pos += seq; 
                    seq -= getRepeat() > 1 ? 1 : 0;
                    pos = tempPos;
                }
                // ge the max bit count to hold numbers
                var bitCount = numberSize[Math.ceil(Math.log(getMax(seq) + 1) / Math.log(2))];
                out.write(1,1);  // sequence header
                out.write(neg,1); // write negative flag
                out.write(seq - 1,4); // write sequence length;
                out.write(bitCount,2); // write 2 bit number size 
                while(seq > 0){
                    out.write(Math.abs(data[pos]),numberBits[bitCount]);
                    pos += 1;
                    seq -= 1;
                }
            }
        }
        // get the bit stream buffer
        var buf = out.getBuffer();
        // start bit stream with number of trailing bits. There are 4 bits used of 16 so plenty
        // of room for aulturnative encoding flages.
        var str = String.fromCharCode(buf.bitLength % out.wordLength);
        // convert bit stream to charcters
        for(var i = 0; i < buf.buffer.length; i ++){
            str += String.fromCharCode(buf.buffer[i]);
        }
        // return encoded string
        return str;
    }

    this.decode = function(zip){
        var count,rSize,header,_in,i,data,endBits,numSize,val,neg;
        data = []; // holds character codes
        decompressed = []; // holds the decompressed array of numbers
        endBits = zip.charCodeAt(0); // get the trailing bits count
        for(i = 1; i < zip.length; i ++){  // convert string to numbers
            data[i-1] = zip.charCodeAt(i);
        }
        _in = new BitStream(); // create a bitstream to read the bits
        // set the buffer data and length 
        _in.setBuffer(data,(data.length - 1) * _in.wordLength + endBits);
        while(!_in.eof()){  // do until eof
            header = _in.read(1);  // read header bit
            if(header === 0){ // is repeat header
                rSize = _in.read(1); // get repeat count size
                if(rSize === 0){
                    count = _in.read(4); // get 4 bit repeat count                    
                }else{
                    count = _in.read(5); // get 5 bit repeat count    
                }
                numSize = _in.read(2); // get 2 bit number size type
                val = _in.read(numberBits[numSize]);  // get the repeated value
                while(count >= 0){  // add to the data count + 1 times
                    decompressed.push(val);
                    count -= 1;
                }                
            }else{
                neg = _in.read(1); // read neg flag
                count = _in.read(4); // get 4 bit seq count                    
                numSize = _in.read(2); // get 2 bit number size type
                while(count >= 0){
                    if(neg){  // if negative numbers convert to neg
                        decompressed.push(-_in.read(numberBits[numSize]));
                    }else{
                        decompressed.push(_in.read(numberBits[numSize]));
                    }
                    count -= 1;
                }
            }
        }
        return decompressed;
    }
}

存储位流的最佳方式是作为字符串。 Javascript 具有 Unicode 字符串,因此我们可以将 16 位打包到每个字符中

结果及使用方法

您需要展平数组。如果您需要添加额外的信息来恢复 multi/dimensional 数组,只需将其添加到数组中,然后让压缩器将其与其余部分一起压缩。

// flatten the array
var data = [17,17,17,17,17,18,18,18,18,18,19,19,19,19,19,20,20,20,20,20,11,12,13,14,15,11,12,13,14,15,11,12,13,14,15,11,12,13,14,15,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,35,36,37,38,39,35,36,37,38,39,35,36,37,38,39,35,36,37,38,39];

var zipper = new ArrayZip();

var encoded = zipper.encode(data);  // packs the 80 numbers in data into 21 characters.
                                    // compression rate of the data array 5120 bits to 336 bits 
                                    // 93% compression.
                                    // or as a flat 7bit ascii string as numbers 239 charcters (including ,)
                                    // 239 * 7 bits = 1673 bits to 336 bits 80% compression.
var decoded = zipper.decode(encoded);

一开始我没有注意到负数,所以压缩对负值效果不佳。

var data = [17,17,17,17,17,18,18,18,  215, 18,18,19,19,19,19,19,20,20,20,20,20, 11,12,13,14,15,11,12,13,  418,  14,15,11,12,13,14,15,11,12,13,14,15, 92,92,92,92,92,92,92,92,  -78,  92,92,92,92,92,92,92,92,92,92,92,92, 35,36,37,38,39,35,36,37,  -887, 38,39,35,36,37,38,39,35,36,37,38,39]

var encoded = zipper.encode(data);  // packs the 84 numbers in data into 33 characters.
                                    // compression rate of the data array 5376 bits to 528 bits
var decoded = zipper.decode(encoded);

总结

如您所见,这会产生非常高的压缩率(几乎是 LZ 压缩的两倍)。该代码远非最佳,您可以轻松地实现具有各种设置的多通道压缩器(编码字符串的开头有 12 个备用位,可用于 select 许多选项来改进压缩。)

我也没有看到负数,直到我回到 post 所以对负数的修复不是很好,所以你可以通过修改 bitStream 来理解负数(即使用>>> 运算符)