5

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

谢谢!

4

7 回答 7

2

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

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

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

您可以使用 base 64 编码将其减少到 42 个字符,但请注意,对于 base 32 和 base 64 版本,您最终可能会在最终结果中出现可能令人反感的单词(请参阅上面的小提琴以获取例子)。十六进制版本也可能包含令人反感的内容,但更少(一张坏脸让爸爸成为 cad?)

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

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

作为参考,这里是 bits-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 位,这可能会生成最多两个虚假的额外字符。我包括它们是因为如果速度很重要,它们可能(或可能不会)更快,因为它们从输入中获取更大的块。

于 2012-11-16T00:32:32.430 回答
2

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

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

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

幸运的是,ECMAScript 实现具有内置方法,允许您将数字从一个基数转换为另一个基数,从基数 2 转换为 36(含)。使用parseInt(string, radix)它可以将写入 base的String数值转换为类型的值,使用它可以将值转换为写入 base的数字。stringradixNumbernumber.toString(radix)NumbernumberStringradix

但是,因为ECMAScriptNumber类型是 IEEE-754 双精度浮点数的实现,所以整数精度有几个限制。AIUI 之一是,对于一个满是 1 的位数组,除非您的数组不包含超过 53 个位元素(或者您的字符串不包含超过 53 个“1”),否则您无法将整个位字符串向前转换并且在不损失精度的情况下返回。(IEEE-754 双精度数的有效位精度为 53 位。

但是您可以将大(二进制)数字视为较小(二进制)数字字符串的串联,将原始比特流分成足够小的块并将每个块转换为更大的基数。0在任何情况下,每个块都会丢失有关连续高位的信息。所以在从转换结果中恢复比特流时,需要在左边的每个chunk上补零,这样每个解码后的chunk都和原来的chunk一样长。块大小需要与编码流所需的步数以及解码时需要填充的零的数量相权衡。

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 位随机位的数组 –</p>

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);
于 2012-11-17T20:24:55.557 回答
0

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

// 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);

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

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/

于 2012-11-16T07:33:31.887 回答
0

我刚刚制作了这个非常幼稚的实现。

它将在"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;
};
于 2012-11-16T00:30:35.567 回答
0

为什么不使用base64?我前段时间写过这样的东西,但它使用了类型化数组:

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

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

于 2012-11-16T01:22:46.777 回答
0

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

论文中提到的技术:

  • base64
  • 拉丁语1
  • UTF-16
  • PNG
于 2012-11-16T05:19:41.637 回答
0

这是一个将 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。

于 2012-11-16T01:16:03.207 回答