繁体   English   中英

Javascript中的位压缩

[英]Bit compression in Javascript

有没有办法将250 + 1和0的JavaScript数组压缩成更易于管理的东西(比如更短的字符串)然后可以自由地解压缩? 有点像谷歌做图像编码的方式......

谢谢!

我可以通过编码作为基数32给你几乎1:5的压缩。我选择包含一个简单的长度值,使其允许可变长度。 请看这个小提琴演示这个技术有两个功能,允许你往返该值。 (或者你可以看到我在@slebetman提醒我javascript中存在的本机数字基本转换之前创建的更早,更天真的十六进制版本 。)

这是一组250个1和0的样本输出。 字符数不计入前导“250 |”:

base 32, 50 chars: 250|qgl6alf1q2lbl1aclau3k5ana2kpals78alek59ilboeglajgu
base 16, 63 chars: 250|D42A6555E1D0AABA854CAABC3A155750A995578742AAEA1532AAF0E85553878

您可以使用base 64编码将其缩小到42个字符,但请注意,对于基本32和base 64版本,最终结果中的单词可能会令人反感(请参阅上面的小提示例)。 十六进制版本也可能有令人反感的内容,但更不如此(一个糟糕的面孔让爸爸成为一个cad?)

如果您需要再保存8个字符,请告诉我,我会为您编写额外的脚本。 避免元音可能是处理令人反感的单词问题的一种方法。 如果您需要这样做,请告诉我。

如果你的位串总是 250个字符,那么函数可以简化一点,但我不想做这个假设。

这里参考的是bit-to-base-32功能。

function bitstringEncode(bitstring) {
    var i, l = bitstring.length,
        retval = l.toString() + '|';
    for (i = 0; i < l; i += 5) {
        retval += parseInt((bitstring.substr(i, 5) + '0000').substr(0, 5), 2).toString(32);
    }
    return retval;
}

此函数将填充到最接近的5位,并可能在您提供的长度末尾生成一个虚假的额外字符。 我包含了每个转换函数的第二个版本,它填充到最接近的10位,这可能会产生最多两个虚假的额外字符。 我包括它们是因为如果速度很重要,它们可能 (或可能不)更快,因为它们从输入中获取更大的块。

(在其他答案中没有太多解释,所以除了介绍我的方法之外,我想在我的答案中讨论到目前为止提出的方法。请耐心等待。)

正如其他答案所示,可以将比特数组视为比特流,这基本上是用基数2写入的相当大的数。相同的数字可以写在另一个数字基数中。 因为十进制数字以外的单个字符可用于更大数字基数的更高值数字(例如十六进制中15的“F”或“f”),数字基数越大,显示所需的数字(字符)越少它。

正如那些答案所建议的那样,你可以使用base64编码甚至更大的基础(Unicode基本多语言平面有65536个代码点,并且符合ECMAScript的实现支持这一点 ,所以基础65536是一个明显的可能性,尽管你需要再次进行百分比编码对于URI ),但在ECMAScript中需要用户定义的函数,也许是包含它的库; 至少它需要转换算法的非本机实现,这必然比本机转换算法慢。

幸运的是,ECMAScript实现具有内置方法,允许您将数字从一个基数转换为另一个基数,从2到36(包括2和36)。 parseInt(string, radix)使用它可以转换成一个数字Stringstring写在基地radix的值Number类型,且有number.toString(radix) ,使用它可以将转换Numbernumber为numeric以基数为radix编写的String

但是,因为ECMAScript Number类型是IEEE-754双精度浮点数的实现 ,所以整数精度有几个限制。 AIUI的一个是,对于一个完整的数组,除非你的数组不包含超过53个位元素(或你的字符串不包含超过53“1”),你不能转换整个位字符串和回来不失精度。 IEEE-754双精度的有效位数具有53位的精度。

但是您可以将较大(二进制)数字视为较小(二进制)数字字符串的串联,将原始比特流分成足够小的块并将每个块转换为更大的基数。 在任何情况下,对于每个块,丢失关于0连续高位的信息。 因此,当从转换结果恢复比特流时,您需要用零填充左侧的每个块,以便每个解码的块与原始块一样长。 块大小需要与编码流所需的步数和解码时需要填充的零的数量进行权衡。

AIUI,如果你从左到右处理比特流,每个块编码的数字可能会更大,因此编码的字符串可能会更长,即使有更大的基数,因为可能设置了块中的高位(例如,将右边界11|001|001 - 3 | 1 | 1 - 与左边界110|010|01 - 6|2|1 - 进行比较,两者都有块大小3)。 首先对数据进行编码的原因是一个简短的 URI。 因此,在编码之前完成流,您应该从右到左处理流。 (如果该数字是块大小的倍数,这种方法也消除了在编码字符串中包含原始位数的必要性。)

这些考虑导致以下一般 (为了可读性,未完全优化)功能:

/*
 * @param bitArray : Array[Number|String]
 * @param chunkSize : optional Number = 53
 * @param chunkBase: optional Number = 36
 * @param delim : optional String = ","
 *   Delimiter to use.
 * @return string
 */
function bitEncode (bitArray, chunkSize, chunkBase, delim)
{
  var chunkArray = [];
  if (!chunkSize || chunkSize < 2 || chunkSize > 53)
  {
    chunkSize = 53;
  }

  if (!chunkBase)
  {
    chunkBase = 36;
  }

  for (var i = bitArray.length; i > 0; i -= chunkSize)
  {
    var index = i - chunkSize;
    if (index < 0)
    {
      index = 0;
    }

    var slice = bitArray.slice(index, i);
    var chunk = parseInt(slice.join(""), 2).toString(chunkBase);
    chunkArray.unshift(chunk);
  }

  return chunkArray.join(delim);
}

/*
 * @param input : String
 * @param length : Number > 1
 *   Target length of input after left-padded with zeros
 * @return string
 */
function leadingZero (input, length)
{
  input = String(input);

  var inputLength = input.length;
  if (inputLength >= length)
  {
    return input;
  }

  var padding = [];
  padding.length = length + 1 - inputLength;

  return padding.join("0") + input;
}

/*
 * @param s : String
 * @param chunkSize : optional Number = 53
 * @param chunkBase : optional Number = 36
 * @param delim : optional String = ","
 * @return Array[string]
 */
function bitDecode (s, chunkSize, chunkBase, delim)
{
  var chunkArray = s.split(delim || ",");
  var bitArray = [];
  if (!chunkSize || chunkSize > 53)
  {
    chunkSize = 53;
  }

  if (!chunkBase)
  {
    chunkBase = 36;
  }

  for (var i = 0, len = chunkArray.length; i < len; ++i)
  {
    bitArray = bitArray.concat(
      leadingZero(
        parseInt(chunkArray[i], chunkBase).toString(2),
        chunkSize)
      .split(""));
  }

  return bitArray;
}

如您所见,此处的默认块大小为53位,默认基数为36.因此,250个随机位的数组 -

var a = [];
for (var i = 250; i--;)
{
  a[i] = +(Math.random() < 0.5);
}

- 可能是(53位右边的块)

/*
              "11111110110011110011000011001010101010\
11010011111010010010100110100100010011001011001010111\
00100100010000101110011010000011100010010101011100011\
11100010110110111001101110000100011101101111101111100\
10001110110100010101110010011100110110100101110010011"
*/
a.join("")

将默认编码为

/* "3hou1lt6,21ewvahkfvb,ck8t6olnmr,26lbvliu2rg,1dh74lghy8j" (55 characters) */
var s = bitEncode(a)

并且可以像这样解码:

var a = bitDecode(s);

这些常规函数应允许您改变块大小和基数,以便为您的用例优化编码字符串。 (由于分隔符,任何可能令人反感的词都可能被分成两部分。)

但是,请注意,如果原始数组长度不是块大小的倍数,则解码后的数组将包含额外的前导零。 如果存在这种可能性并且存在问题,您可以通过传递ErikE建议的原始长度来解决该问题,然后使用该值:

var originalLength = …;

a = a.slice(a.length - originalLength);

或(除了版本1.6之前的JavaScript和版本9.52之前的Opera ECMAScript之外的所有主要实现)

a = a.slice(-originalLength);

我刚刚制作了这个非常天真的实现。

它将在"111000111"[['1',3],['0',3], ['1',3]] (反之亦然)。

希望它应该适用于大二进制字符串,它应该有很多重复字符。 在最坏的情况下( 01010101... ),您将使用1+7*n字符( n是输入字符串的大小)。

希望有人能提供更有效的解决方案吗?

var compress = function (input){
    var output = [], current = null;
    for (var t = 0; t < input.length; ++t ) {
        if (current === null || current[0] !== input[t]) {
            current = [input[t], 0];
            output.push(current);
        }

        ++ current[1];
    }

    return output;
};

var decompress = function (input) {
    var output = '';

    for (var t = 0; t < input.length; ++t) {
        for (var u = 0; u < input[t][1]; ++u) {
            output += input[t][0];
        }
    }

    return output;
};

这是一个将1和0转换为十六进制的实现。 在服务器上将它转换回1和0应该相当简单。 转换为十六进制基本上每个字符存储4位,因此它将250位的序列转换为63个字符。

但要注意,这会以4位块的形式转换数据,因此您需要将序列填充为252位(4位对齐)或256位(8位对齐)。 下面的实现不处理填充,因为我不知道你想从哪一端填充数据:

function binArray2HexArray (binArray) {
    var hexArray = [];
    while (binArray.length) {
        hexArray.push(parseInt(binArray.splice(0,4),2).toString(16));
    }
    return hexArray;
}

显然,您可以加入返回的数组,将其转换为十六进制字符串。

如果将数据填充为8位对齐,则可以通过将拼接参数更改为:将每个循环8位操作来加速函数。

binArray.splice(0,8)

同样,如果将数据填充到16位对齐,则可以通过一次拼接16位来再次加速。 由于浮点表示,javascript开始舍入数字之前,我认为限制是32位。 因为我不确定各种javascript引擎如何处理32位整数的签名,所以我会更满意16。

为什么不使用base64? 我刚才写了这样的东西,但是它使用了类型化的数组:

https://github.com/beatgammit/base64-js/blob/master/lib/b64.js

基本上只需将1和0转换为字节,base64对其进行编码。 Base64可以在URL中传递,因此它适用于您的情况。

呃! 我终于找到了一篇我几个月前读过的文章。 它描述了有效压缩字符串的多种方法,你应该试一试: 就是这样

论文中提到的技术:

  • BASE64
  • LATIN1
  • UTF-16
  • PNG

这两个函数都需要一个字符串输入:

// input size must be less then 256 characters
// first byte in returned output is length of original string
// this is used during decoding for correct padding of last 8 bits
function encodeBits(input) {
    var output = String.fromCharCode(input.length);
    while(1) {
        output += String.fromCharCode(parseInt(input.substr(0,8),2));
        input = input.substr(8);
        if(input.length == 0) {
            break;
        }
    }

    return output;
}

function decodeBits(input) {
    var output = "";    
    var bits;
    var finalLength = input.charCodeAt(0);
    input = input.substr(1);

    while(1) {
        bits = input.charCodeAt(0).toString(2);

        // string must be left padded with 0's
        while(bits.length < 8) {
            if((bits.length+output.length) == finalLength) {
                break;
            }
            bits = "0"+bits;
        }

        output += bits;

        input = input.substr(1);
        if(input.length == 0) {
            break;
        }
    }

    return output;
}

编码

var instr = "101001110010100110010000111011111010110110001001111010110110";
var encStr = encodeBits(instr);

您可以使用escape对输出进行编码

var escapedStr = escape(encStr); // returns '%3C%A7%29%90%EF%AD%89%EB%06'

解码

使用unescape解码

var unescapedStr = unescape("%3C%A7%29%90%EF%AD%89%EB%06");
var bitStr = decodeBits(unescaped);

// bitStr now contains original input
"101001110010100110010000111011111010110110001001111010110110"

作为escape / unescape的替代方案,您还可以使用btoaatob ,这将为您提供更短的编码。

这个函数及其用法在这个工作示例中进行了演示: http//jsfiddle.net/EU4nL/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM