在js中压缩一串0和1
compressing a string of 0's and 1's in js
简介
我目前正在用 js 编写 John Conway 的 Game of Life。我的游戏正在运行 (view here),并且我正在开发额外的功能,例如与您的朋友分享您的“网格/游戏”。为此,我将网格的值(如果单元格是活的还是死的)提取到一长串 0 和 1 中。
此字符串的长度可变,因为网格的大小并不总是相同。例如:
grid 1 has a length and width of 30 => so the string's length is 900
grid 2 has a length and width of 50 => so the string's length is 2500
问题
如您所见,这些由 0 和 1 组成的字符串太长,无法复制和共享。
无论我多么努力,我似乎都无法想出一个代码来将这么长的字符串压缩成易于处理的字符串。
关于如何压缩(和解压)这个有什么想法吗?
我考虑过简单地写下从 1x1 到 100x100 的网格大小的每个可能的网格选项,并给它们一个 key/reference 以用作可共享代码。手动执行此操作会很疯狂,但也许你们中的任何人都知道如何创建可以执行此操作的算法?
如果我们假设网格包含的 0 比 1 多得多,您可能想试试这个简单的压缩方案:
- 将二进制字符串转换为十六进制字符串
- 将 '00' 子字符串转换为 'z' 符号
- 将 'zz' 个子字符串转换为 'Z' 个符号
- 我们可以走得更远,但让我们在这里停下来进行演示
下面是一个 16x16 网格的例子:
var bin =
'0000000000000000' +
'0000001000000000' +
'0000011100000000' +
'0000001000000000' +
'0000000000000000' +
'0000000000111000' +
'0000100000111000' +
'0000000000111000' +
'0000000000000000' +
'0000000000000000' +
'0000000010000000' +
'0000000101000000' +
'0000000010000000' +
'0000000000000000' +
'0000100000000000' +
'0000000000000000';
var packed = bin
.match(/(.{4})/g)
.map(function(x) {
return parseInt(x, 2).toString(16);
})
.join('')
.replace(/00/g, 'z')
.replace(/zz/g, 'Z');
这将生成字符串 "Z02z07z02ZZ380838z38ZZz8z14z08Zz8Zz"。
解包过程正好相反:
var bin = packed
.replace(/Z/g, 'zz')
.replace(/z/g, '00')
.split('')
.map(function(x) {
return ('000' + parseInt(x, 16).toString(2)).substr(-4, 4);
})
.join('');
请注意,只有当输入字符串的长度是 4 的倍数时,此代码才能正常工作。如果不是这种情况,您将不得不填充输入并裁剪输出。
编辑:第二种方法
如果输入是完全随机的——0 和 1 的数量大致相同,并且没有特定的重复模式——你能做的最好的事情可能是将二进制字符串转换为 BASE64 字符串。它将明显更短(这次固定压缩率约为 17%),并且用户仍然可以copied/pasted。
包装:
var bin =
'1110101110100011' +
'0000101111100001' +
'1010010101011010' +
'0000110111011111' +
'1111111001010101' +
'0111000011100001' +
'1011010100110001' +
'0111111110010100' +
'0111110110100101' +
'0000111101100111' +
'1100001111011100' +
'0101011100001111' +
'0110011011001101' +
'1000110010001001' +
'1010100010000011' +
'0011110000000000';
var packed =
btoa(
bin
.match(/(.{8})/g)
.map(function(x) {
return String.fromCharCode(parseInt(x, 2));
})
.join('')
);
将生成字符串“66ML4aVaDd/+VXDhtTF/lH2lD2fD3FcPZs2MiaiDPAA=”。
开箱:
var bin =
atob(packed)
.split('')
.map(function(x) {
return ('0000000' + x.charCodeAt(0).toString(2)).substr(-8, 8);
})
.join('');
或者,如果您想更进一步,可以考虑使用 base91 之类的东西,以减少编码开销。
LZ-字符串
使用 LZ-string 我能够将 "code" 压缩很多。
通过像这样简单地将其压缩为 base64:
var compressed = LZString.compressToBase64(string)
解压也是这么简单:
var decompressed = LZString.decompressFromBase64(compressed)
但是这个压缩字符串的长度仍然很长,因为你有大约 0 和 1 一样多(示例中没有给出)
但压缩确实有效。
如果不是很明显,您尝试存储的字符串看起来像二进制字符串。
计数系统
二进制是一种数字base-2. This essentially means that there are two characters being used to keep count. Normally we are used to count with base-10 (decimal characters). In computer science the hexadecimal system (base-16)也被广泛使用
由于您不是将位存储为位而是字节(如果您希望将它们存储为位,请使用var a = 0b1100001;
)'binary' 您希望存储的内容与具有相同长度的任何其他随机字符串一样多 space。
由于您使用的是二进制系统,因此每个位置只有 2 个可能的值。使用十六进制值时,单个位置最多可以容纳 16 个可能的值。在紧凑地存储数据方面,这已经是一个很大的改进。例如 0b11111111
和 0xff
都表示十进制数 255.
在您的情况下,您必须存储每 8 个字节中的 6 个字节。最后你会被一个只有原始字符串长度的 1/4 的字符串卡住。
Javascript实施
本质上,我们要做的是将您存储的字符串解释为二进制并检索十六进制值。幸运的是 JavaScript 内置了实现这样的功能:
var bin =
'1110101110100011' +
'0000101111100001' +
'1010010101011010' +
'0000110111011111' +
'1111111001010101' +
'0111000011100001' +
'1011010100110001' +
'0111111110010100' +
'0111110110100101' +
'0000111101100111' +
'1100001111011100' +
'0101011100001111' +
'0110011011001101' +
'1000110010001001' +
'1010100010000011' +
'0011110000000000';
var returnValue = '';
for (var i = 0; i < parseInt(bin.length / 8); i++) {
returnValue += parseInt(bin.substr(i*8, 8), 2).toString(16);
}
console.log(bin.length); // Will return 265
console.log(returnValue.length); // Will return 64
我们说 "parse this string and interpret it like a base-2 number and store it as a hexadecimal string"。
解码实际上是一样的。将上面示例中出现的所有数字 8 替换为 2,反之亦然。
请注意
此代码正确运行的先决条件是二进制长度可被 8 整除。请参见以下示例:
parseInt('00011110', 2).toString(16); // returns '1e'
parseInt('1e', 16).toString(2); // returns '11110'
// Technically both representations still have the same decimal value
解码时你应该添加前导零直到你有一个完整的字节(8 位)。
如果您必须存储的位置不能被 8 整除,您可以添加填充,并在输出字符串的前面添加一个数字,以确定要去除多少位置。
等等,还有更多
要获得更短的字符串,您可以构建一个包含 265 个字符的查找 table,您可以在其中搜索与特定位置关联的字符。 (这是可行的,因为您仍然将十六进制值存储为字符串。)遗憾的是,ASCII nor the UTF-8 编码都不适合这种情况,因为有些块的值没有字符定义。
它可能看起来像:
// Go fill this array until you have 265 values within it.
var lookup = ['A', 'B', 'C', 'D'];
var smallerValue = lookup[0x00];
这样你可以在一个位置有 265 个可能的值,并且你已经充分利用了你的字节。
请注意这里没有真正的压缩。我们宁愿利用数据类型更有效地用于您当前的用例。
回答
对于任何想知道我到底是怎么做到的,这里是方法:
首先,我确保传入的每个字符串都用前导 0 填充,直到它可以除以 8。(节省用于填充的 0 的数量,因为在解压缩时需要它们)
我使用 Corstian 的答案和函数将我的字符串(解释为二进制)压缩为十六进制字符串。虽然我不得不做一点小改动。
并非每个长度为 8 的二进制子字符串都会 return 正好是 2 个十六进制字符。所以对于那些情况,我最终只是在子字符串前面添加了一个 0。十六进制子字符串将具有相同的值 但 它的长度现在为 2.
接下来我使用了 Arnaulds 回答中的功能。获取每个双字符并将其替换为单个字符(十六进制字母表中未使用的字符以避免冲突)。我为每个十六进制字符做了两次。
For example:
the hex string 11
will become h
and hh
will become H
01101111
will become 0h0H
由于大多数网格的死细胞比活细胞多,我确保 0 能够进一步压缩,再次使用 Arnaulds 方法,但更进一步。
00
-> g
| gg
-> G
| GG
-> w
| ww
-> W
| WW
-> x
| xx
-> X
| XX
-> y
| yy
-> Y
| YY
-> z
| zz
-> Z
这导致 Z
代表 4096(二进制)0
压缩的最后一步是在压缩字符串前面添加前导 0 的数量,因此我们可以在解压结束时将它们剃掉。
这就是 returned 字符串最后的样子。
amount of leading 0s
-compressed string
so a 64*64 empty grid, will result in 0-Z
解压缩实际上就是以相反的方式做所有事情。
首先拆分表示我们使用压缩字符串填充的前导 0 的数量。
然后使用 Arnaulds 功能,将进一步“压缩”的字符转回十六进制代码。
获取这个十六进制字符串并将其转回二进制代码。确保,正如 Corstian 指出的那样,每个二进制子串的长度都是 8。(如果不是,我们用前导 0 填充子串,直到 do,恰好,长度为 8)
然后最后一步是去掉我们用作填充的前导 0,使开始字符串可除以 8。
函数
我用来压缩的函数:
/**
* Compresses the a binary string into a compressed string.
* Returns the compressed string.
*/
Codes.compress = function(bin) {
bin = bin.toString(); // To make sure the binary is a string;
var returnValue = ''; // Empty string to add our data to later on.
// If the lenght of the binary string is not devidable by 8 the compression
// won't work correctly. So we add leading 0s to the string and store the amount
// of leading 0s in a variable.
// Determining the amount of 'padding' needed.
var padding = ((Math.ceil(bin.length/8))*8)-bin.length;
// Adding the leading 0s to the binary string.
for (var i = 0; i < padding; i++) {
bin = '0'+bin;
}
for (var i = 0; i < parseInt(bin.length / 8); i++) {
// Determining the substring.
var substring = bin.substr(i*8, 8)
// Determining the hexValue of this binary substring.
var hexValue = parseInt(substring, 2).toString(16);
// Not all binary values produce two hex numbers. For example:
// '00000011' gives just a '3' while what we wand would be '03'. So we add a 0 in front.
if(hexValue.length == 1) hexValue = '0'+hexValue;
// Adding this hexValue to the end string which we will return.
returnValue += hexValue;
}
// Compressing the hex string even further.
// If there's any double hex chars in the string it will take those and compress those into 1 char.
// Then if we have multiple of those chars these are compressed into 1 char again.
// For example: the hex string "ff will result in a "v" and "ffff" will result in a "V".
// Also: "11" will result in a "h" and "1111" will result in a "H"
// For the 0s this process is repeated a few times.
// (string with 4096 0s) (this would represent a 64*64 EMPTY grid)
// will result in a "Z".
var returnValue = returnValue.replace(/00/g, 'g')
.replace(/gg/g, 'G')
// Since 0s are probably more likely to exist in our binary and hex, we go a step further compressing them like this:
.replace(/GG/g, 'w')
.replace(/ww/g, 'W')
.replace(/WW/g, 'x')
.replace(/xx/g, 'X')
.replace(/XX/g, 'y')
.replace(/yy/g, 'Y')
.replace(/YY/g, 'z')
.replace(/zz/g, 'Z')
//Rest of the chars...
.replace(/11/g, 'h')
.replace(/hh/g, 'H')
.replace(/22/g, 'i')
.replace(/ii/g, 'I')
.replace(/33/g, 'j')
.replace(/jj/g, 'J')
.replace(/44/g, 'k')
.replace(/kk/g, 'K')
.replace(/55/g, 'l')
.replace(/ll/g, 'L')
.replace(/66/g, 'm')
.replace(/mm/g, 'M')
.replace(/77/g, 'n')
.replace(/nn/g, 'N')
.replace(/88/g, 'o')
.replace(/oo/g, 'O')
.replace(/99/g, 'p')
.replace(/pp/g, 'P')
.replace(/aa/g, 'q')
.replace(/qq/g, 'Q')
.replace(/bb/g, 'r')
.replace(/rr/g, 'R')
.replace(/cc/g, 's')
.replace(/ss/g, 'S')
.replace(/dd/g, 't')
.replace(/tt/g, 'T')
.replace(/ee/g, 'u')
.replace(/uu/g, 'U')
.replace(/ff/g, 'v')
.replace(/vv/g, 'V');
// Adding the number of leading 0s that need to be ignored when decompressing to the string.
returnValue = padding+'-'+returnValue;
// Returning the compressed string.
return returnValue;
}
我用来解压的函数:
/**
* Decompresses the compressed string back into a binary string.
* Returns the decompressed string.
*/
Codes.decompress = function(compressed) {
var returnValue = ''; // Empty string to add our data to later on.
// Splitting the input on '-' to seperate the number of paddin 0s and the actual hex code.
var compressedArr = compressed.split('-');
var paddingAmount = compressedArr[0]; // Setting a variable equal to the amount of leading 0s used while compressing.
compressed = compressedArr[1]; // Setting the compressed variable to the actual hex code.
// Decompressing further compressed characters.
compressed = compressed// Decompressing the further compressed 0s. (even further then the rest of the chars.)
.replace(/Z/g, 'zz')
.replace(/z/g, 'YY')
.replace(/Y/g, 'yy')
.replace(/y/g, 'XX')
.replace(/X/g, 'xx')
.replace(/x/g, 'WW')
.replace(/W/g, 'ww')
.replace(/w/g, 'GG')
.replace(/G/g, 'gg')
.replace(/g/g, '00')
// Rest of chars...
.replace(/H/g, 'hh')
.replace(/h/g, '11')
.replace(/I/g, 'ii')
.replace(/i/g, '22')
.replace(/J/g, 'jj')
.replace(/j/g, '33')
.replace(/K/g, 'kk')
.replace(/k/g, '44')
.replace(/L/g, 'll')
.replace(/l/g, '55')
.replace(/M/g, 'mm')
.replace(/m/g, '66')
.replace(/N/g, 'nn')
.replace(/n/g, '77')
.replace(/O/g, 'oo')
.replace(/o/g, '88')
.replace(/P/g, 'pp')
.replace(/p/g, '99')
.replace(/Q/g, 'qq')
.replace(/q/g, 'aa')
.replace(/R/g, 'rr')
.replace(/r/g, 'bb')
.replace(/S/g, 'ss')
.replace(/s/g, 'cc')
.replace(/T/g, 'tt')
.replace(/t/g, 'dd')
.replace(/U/g, 'uu')
.replace(/u/g, 'ee')
.replace(/V/g, 'vv')
.replace(/v/g, 'ff');
for (var i = 0; i < parseInt(compressed.length / 2); i++) {
// Determining the substring.
var substring = compressed.substr(i*2, 2);
// Determining the binValue of this hex substring.
var binValue = parseInt(substring, 16).toString(2);
// If the length of the binary value is not equal to 8 we add leading 0s (js deletes the leading 0s)
// For instance the binary number 00011110 is equal to the hex number 1e,
// but simply running the code above will return 11110. So we have to add the leading 0s back.
if (binValue.length != 8) {
// Determining how many 0s to add:
var diffrence = 8 - binValue.length;
// Adding the 0s:
for (var j = 0; j < diffrence; j++) {
binValue = '0'+binValue;
}
}
// Adding the binValue to the end string which we will return.
returnValue += binValue
}
var decompressedArr = returnValue.split('');
returnValue = ''; // Emptying the return variable.
// Deleting the not needed leading 0s used as padding.
for (var i = paddingAmount; i < decompressedArr.length; i++) {
returnValue += decompressedArr[i];
}
// Returning the decompressed string.
return returnValue;
}
URL 缩短器
我仍然发现“压缩”字符串对于共享/粘贴来说有点长。所以我使用了一个简单的 URL 缩短器 (view here) 来使这个过程对用户来说更容易一些。
现在您可能会问,那为什么还要压缩这个字符串呢?
原因如下:
首先,我的项目托管在 github 页面 (gh-pages) 上。 gh-pages 的信息页面告诉我们 url 不能 超过 2000 个字符。这意味着最大网格大小将是 2000 的平方根 - 基数 url 的长度,这并没有那么大。通过使用这种“压缩”,我们能够共享更大的网格。
第二个原因是,这是一个挑战。我发现处理这些问题很有趣,也很有帮助,因为你学到了很多东西。
直播
您可以查看我项目的实时版本here. and/or find the github repository here。
谢谢
我要感谢所有帮助我解决这个问题的人。特别是 Corstian 和 Arnauld,因为我最终使用他们的答案来实现我的最终功能。
太棒了.... 谢谢你们!给它点赞!
在生命游戏中有一个由 1 和 0 组成的棋盘。我想备份到上一代 - 大小 4800 - 将每 16 个单元格保存为十六进制 = 大小的 1/4。 http://innerbeing.epizy.com/cwebgl/gameoflife.html [g = 开始] [b = 备份]
function drawGen(n) {
stop(); var i = clamp(n,0,brw*brh-1), hex = gensave[i].toString();
echo(":",i, n,nGEN); nGEN = i; var str = '';
for (var i = 0; i < parseInt(hex.length / 4); i++)
str = str + pad(parseInt(hex.substr(i*4,4), 16).toString(2),16,'0');
for (var j=0;j<Board.length;j++) Board[j] = intr(str.substr(j,1));
drawBoard();
}
function Bin2Hex(n) {
var i = n.indexOf("1"); /// leading Zeros = NAN
if (i == -1) return "0000";
i = right(n,i*-1);
return pad(parseInt(i,2).toString(16),4,'0');
}
function saveGen(n) {
var b = Board.join(''), str = ''; /// concat array to string 10101
for (var i = 0; i < parseInt(b.length / 16); i++)
str = str + Bin2Hex(b.substr(i*16,16));
gensave[n] = str;
}
function right(st,n) {
var s = st.toString();
if (!n) return s;
if (n < 0) return s.substr(n * -1,s.length + n);
return s.substr(s.length - n,n);
}
function pad(str, l, padwith) {
var s = str;
while (s.length < l) s = padwith + s;
return s;
}
简介
我目前正在用 js 编写 John Conway 的 Game of Life。我的游戏正在运行 (view here),并且我正在开发额外的功能,例如与您的朋友分享您的“网格/游戏”。为此,我将网格的值(如果单元格是活的还是死的)提取到一长串 0 和 1 中。
此字符串的长度可变,因为网格的大小并不总是相同。例如:
grid 1 has a length and width of 30 => so the string's length is 900
grid 2 has a length and width of 50 => so the string's length is 2500
问题
如您所见,这些由 0 和 1 组成的字符串太长,无法复制和共享。
无论我多么努力,我似乎都无法想出一个代码来将这么长的字符串压缩成易于处理的字符串。
关于如何压缩(和解压)这个有什么想法吗?
我考虑过简单地写下从 1x1 到 100x100 的网格大小的每个可能的网格选项,并给它们一个 key/reference 以用作可共享代码。手动执行此操作会很疯狂,但也许你们中的任何人都知道如何创建可以执行此操作的算法?
如果我们假设网格包含的 0 比 1 多得多,您可能想试试这个简单的压缩方案:
- 将二进制字符串转换为十六进制字符串
- 将 '00' 子字符串转换为 'z' 符号
- 将 'zz' 个子字符串转换为 'Z' 个符号
- 我们可以走得更远,但让我们在这里停下来进行演示
下面是一个 16x16 网格的例子:
var bin =
'0000000000000000' +
'0000001000000000' +
'0000011100000000' +
'0000001000000000' +
'0000000000000000' +
'0000000000111000' +
'0000100000111000' +
'0000000000111000' +
'0000000000000000' +
'0000000000000000' +
'0000000010000000' +
'0000000101000000' +
'0000000010000000' +
'0000000000000000' +
'0000100000000000' +
'0000000000000000';
var packed = bin
.match(/(.{4})/g)
.map(function(x) {
return parseInt(x, 2).toString(16);
})
.join('')
.replace(/00/g, 'z')
.replace(/zz/g, 'Z');
这将生成字符串 "Z02z07z02ZZ380838z38ZZz8z14z08Zz8Zz"。
解包过程正好相反:
var bin = packed
.replace(/Z/g, 'zz')
.replace(/z/g, '00')
.split('')
.map(function(x) {
return ('000' + parseInt(x, 16).toString(2)).substr(-4, 4);
})
.join('');
请注意,只有当输入字符串的长度是 4 的倍数时,此代码才能正常工作。如果不是这种情况,您将不得不填充输入并裁剪输出。
编辑:第二种方法
如果输入是完全随机的——0 和 1 的数量大致相同,并且没有特定的重复模式——你能做的最好的事情可能是将二进制字符串转换为 BASE64 字符串。它将明显更短(这次固定压缩率约为 17%),并且用户仍然可以copied/pasted。
包装:
var bin =
'1110101110100011' +
'0000101111100001' +
'1010010101011010' +
'0000110111011111' +
'1111111001010101' +
'0111000011100001' +
'1011010100110001' +
'0111111110010100' +
'0111110110100101' +
'0000111101100111' +
'1100001111011100' +
'0101011100001111' +
'0110011011001101' +
'1000110010001001' +
'1010100010000011' +
'0011110000000000';
var packed =
btoa(
bin
.match(/(.{8})/g)
.map(function(x) {
return String.fromCharCode(parseInt(x, 2));
})
.join('')
);
将生成字符串“66ML4aVaDd/+VXDhtTF/lH2lD2fD3FcPZs2MiaiDPAA=”。
开箱:
var bin =
atob(packed)
.split('')
.map(function(x) {
return ('0000000' + x.charCodeAt(0).toString(2)).substr(-8, 8);
})
.join('');
或者,如果您想更进一步,可以考虑使用 base91 之类的东西,以减少编码开销。
LZ-字符串
使用 LZ-string 我能够将 "code" 压缩很多。
通过像这样简单地将其压缩为 base64:
var compressed = LZString.compressToBase64(string)
解压也是这么简单:
var decompressed = LZString.decompressFromBase64(compressed)
但是这个压缩字符串的长度仍然很长,因为你有大约 0 和 1 一样多(示例中没有给出)
但压缩确实有效。
如果不是很明显,您尝试存储的字符串看起来像二进制字符串。
计数系统
二进制是一种数字base-2. This essentially means that there are two characters being used to keep count. Normally we are used to count with base-10 (decimal characters). In computer science the hexadecimal system (base-16)也被广泛使用
由于您不是将位存储为位而是字节(如果您希望将它们存储为位,请使用var a = 0b1100001;
)'binary' 您希望存储的内容与具有相同长度的任何其他随机字符串一样多 space。
由于您使用的是二进制系统,因此每个位置只有 2 个可能的值。使用十六进制值时,单个位置最多可以容纳 16 个可能的值。在紧凑地存储数据方面,这已经是一个很大的改进。例如 0b11111111
和 0xff
都表示十进制数 255.
在您的情况下,您必须存储每 8 个字节中的 6 个字节。最后你会被一个只有原始字符串长度的 1/4 的字符串卡住。
Javascript实施
本质上,我们要做的是将您存储的字符串解释为二进制并检索十六进制值。幸运的是 JavaScript 内置了实现这样的功能:
var bin =
'1110101110100011' +
'0000101111100001' +
'1010010101011010' +
'0000110111011111' +
'1111111001010101' +
'0111000011100001' +
'1011010100110001' +
'0111111110010100' +
'0111110110100101' +
'0000111101100111' +
'1100001111011100' +
'0101011100001111' +
'0110011011001101' +
'1000110010001001' +
'1010100010000011' +
'0011110000000000';
var returnValue = '';
for (var i = 0; i < parseInt(bin.length / 8); i++) {
returnValue += parseInt(bin.substr(i*8, 8), 2).toString(16);
}
console.log(bin.length); // Will return 265
console.log(returnValue.length); // Will return 64
我们说 "parse this string and interpret it like a base-2 number and store it as a hexadecimal string"。
解码实际上是一样的。将上面示例中出现的所有数字 8 替换为 2,反之亦然。
请注意
此代码正确运行的先决条件是二进制长度可被 8 整除。请参见以下示例:
parseInt('00011110', 2).toString(16); // returns '1e'
parseInt('1e', 16).toString(2); // returns '11110'
// Technically both representations still have the same decimal value
解码时你应该添加前导零直到你有一个完整的字节(8 位)。
如果您必须存储的位置不能被 8 整除,您可以添加填充,并在输出字符串的前面添加一个数字,以确定要去除多少位置。
等等,还有更多
要获得更短的字符串,您可以构建一个包含 265 个字符的查找 table,您可以在其中搜索与特定位置关联的字符。 (这是可行的,因为您仍然将十六进制值存储为字符串。)遗憾的是,ASCII nor the UTF-8 编码都不适合这种情况,因为有些块的值没有字符定义。
它可能看起来像:
// Go fill this array until you have 265 values within it.
var lookup = ['A', 'B', 'C', 'D'];
var smallerValue = lookup[0x00];
这样你可以在一个位置有 265 个可能的值,并且你已经充分利用了你的字节。
请注意这里没有真正的压缩。我们宁愿利用数据类型更有效地用于您当前的用例。
回答
对于任何想知道我到底是怎么做到的,这里是方法:
首先,我确保传入的每个字符串都用前导 0 填充,直到它可以除以 8。(节省用于填充的 0 的数量,因为在解压缩时需要它们)
我使用 Corstian 的答案和函数将我的字符串(解释为二进制)压缩为十六进制字符串。虽然我不得不做一点小改动。
并非每个长度为 8 的二进制子字符串都会 return 正好是 2 个十六进制字符。所以对于那些情况,我最终只是在子字符串前面添加了一个 0。十六进制子字符串将具有相同的值 但 它的长度现在为 2.
接下来我使用了 Arnaulds 回答中的功能。获取每个双字符并将其替换为单个字符(十六进制字母表中未使用的字符以避免冲突)。我为每个十六进制字符做了两次。
For example:
the hex string11
will becomeh
andhh
will becomeH
01101111
will become0h0H
由于大多数网格的死细胞比活细胞多,我确保 0 能够进一步压缩,再次使用 Arnaulds 方法,但更进一步。
00
->g
|gg
->G
|GG
->w
|ww
->W
|WW
->x
|xx
->X
|XX
->y
|yy
->Y
|YY
->z
|zz
->Z
这导致 Z
代表 4096(二进制)0
压缩的最后一步是在压缩字符串前面添加前导 0 的数量,因此我们可以在解压结束时将它们剃掉。 这就是 returned 字符串最后的样子。
amount of leading 0s
-compressed string
so a 64*64 empty grid, will result in0-Z
解压缩实际上就是以相反的方式做所有事情。
首先拆分表示我们使用压缩字符串填充的前导 0 的数量。
然后使用 Arnaulds 功能,将进一步“压缩”的字符转回十六进制代码。
获取这个十六进制字符串并将其转回二进制代码。确保,正如 Corstian 指出的那样,每个二进制子串的长度都是 8。(如果不是,我们用前导 0 填充子串,直到 do,恰好,长度为 8)
然后最后一步是去掉我们用作填充的前导 0,使开始字符串可除以 8。
函数
我用来压缩的函数:
/**
* Compresses the a binary string into a compressed string.
* Returns the compressed string.
*/
Codes.compress = function(bin) {
bin = bin.toString(); // To make sure the binary is a string;
var returnValue = ''; // Empty string to add our data to later on.
// If the lenght of the binary string is not devidable by 8 the compression
// won't work correctly. So we add leading 0s to the string and store the amount
// of leading 0s in a variable.
// Determining the amount of 'padding' needed.
var padding = ((Math.ceil(bin.length/8))*8)-bin.length;
// Adding the leading 0s to the binary string.
for (var i = 0; i < padding; i++) {
bin = '0'+bin;
}
for (var i = 0; i < parseInt(bin.length / 8); i++) {
// Determining the substring.
var substring = bin.substr(i*8, 8)
// Determining the hexValue of this binary substring.
var hexValue = parseInt(substring, 2).toString(16);
// Not all binary values produce two hex numbers. For example:
// '00000011' gives just a '3' while what we wand would be '03'. So we add a 0 in front.
if(hexValue.length == 1) hexValue = '0'+hexValue;
// Adding this hexValue to the end string which we will return.
returnValue += hexValue;
}
// Compressing the hex string even further.
// If there's any double hex chars in the string it will take those and compress those into 1 char.
// Then if we have multiple of those chars these are compressed into 1 char again.
// For example: the hex string "ff will result in a "v" and "ffff" will result in a "V".
// Also: "11" will result in a "h" and "1111" will result in a "H"
// For the 0s this process is repeated a few times.
// (string with 4096 0s) (this would represent a 64*64 EMPTY grid)
// will result in a "Z".
var returnValue = returnValue.replace(/00/g, 'g')
.replace(/gg/g, 'G')
// Since 0s are probably more likely to exist in our binary and hex, we go a step further compressing them like this:
.replace(/GG/g, 'w')
.replace(/ww/g, 'W')
.replace(/WW/g, 'x')
.replace(/xx/g, 'X')
.replace(/XX/g, 'y')
.replace(/yy/g, 'Y')
.replace(/YY/g, 'z')
.replace(/zz/g, 'Z')
//Rest of the chars...
.replace(/11/g, 'h')
.replace(/hh/g, 'H')
.replace(/22/g, 'i')
.replace(/ii/g, 'I')
.replace(/33/g, 'j')
.replace(/jj/g, 'J')
.replace(/44/g, 'k')
.replace(/kk/g, 'K')
.replace(/55/g, 'l')
.replace(/ll/g, 'L')
.replace(/66/g, 'm')
.replace(/mm/g, 'M')
.replace(/77/g, 'n')
.replace(/nn/g, 'N')
.replace(/88/g, 'o')
.replace(/oo/g, 'O')
.replace(/99/g, 'p')
.replace(/pp/g, 'P')
.replace(/aa/g, 'q')
.replace(/qq/g, 'Q')
.replace(/bb/g, 'r')
.replace(/rr/g, 'R')
.replace(/cc/g, 's')
.replace(/ss/g, 'S')
.replace(/dd/g, 't')
.replace(/tt/g, 'T')
.replace(/ee/g, 'u')
.replace(/uu/g, 'U')
.replace(/ff/g, 'v')
.replace(/vv/g, 'V');
// Adding the number of leading 0s that need to be ignored when decompressing to the string.
returnValue = padding+'-'+returnValue;
// Returning the compressed string.
return returnValue;
}
我用来解压的函数:
/**
* Decompresses the compressed string back into a binary string.
* Returns the decompressed string.
*/
Codes.decompress = function(compressed) {
var returnValue = ''; // Empty string to add our data to later on.
// Splitting the input on '-' to seperate the number of paddin 0s and the actual hex code.
var compressedArr = compressed.split('-');
var paddingAmount = compressedArr[0]; // Setting a variable equal to the amount of leading 0s used while compressing.
compressed = compressedArr[1]; // Setting the compressed variable to the actual hex code.
// Decompressing further compressed characters.
compressed = compressed// Decompressing the further compressed 0s. (even further then the rest of the chars.)
.replace(/Z/g, 'zz')
.replace(/z/g, 'YY')
.replace(/Y/g, 'yy')
.replace(/y/g, 'XX')
.replace(/X/g, 'xx')
.replace(/x/g, 'WW')
.replace(/W/g, 'ww')
.replace(/w/g, 'GG')
.replace(/G/g, 'gg')
.replace(/g/g, '00')
// Rest of chars...
.replace(/H/g, 'hh')
.replace(/h/g, '11')
.replace(/I/g, 'ii')
.replace(/i/g, '22')
.replace(/J/g, 'jj')
.replace(/j/g, '33')
.replace(/K/g, 'kk')
.replace(/k/g, '44')
.replace(/L/g, 'll')
.replace(/l/g, '55')
.replace(/M/g, 'mm')
.replace(/m/g, '66')
.replace(/N/g, 'nn')
.replace(/n/g, '77')
.replace(/O/g, 'oo')
.replace(/o/g, '88')
.replace(/P/g, 'pp')
.replace(/p/g, '99')
.replace(/Q/g, 'qq')
.replace(/q/g, 'aa')
.replace(/R/g, 'rr')
.replace(/r/g, 'bb')
.replace(/S/g, 'ss')
.replace(/s/g, 'cc')
.replace(/T/g, 'tt')
.replace(/t/g, 'dd')
.replace(/U/g, 'uu')
.replace(/u/g, 'ee')
.replace(/V/g, 'vv')
.replace(/v/g, 'ff');
for (var i = 0; i < parseInt(compressed.length / 2); i++) {
// Determining the substring.
var substring = compressed.substr(i*2, 2);
// Determining the binValue of this hex substring.
var binValue = parseInt(substring, 16).toString(2);
// If the length of the binary value is not equal to 8 we add leading 0s (js deletes the leading 0s)
// For instance the binary number 00011110 is equal to the hex number 1e,
// but simply running the code above will return 11110. So we have to add the leading 0s back.
if (binValue.length != 8) {
// Determining how many 0s to add:
var diffrence = 8 - binValue.length;
// Adding the 0s:
for (var j = 0; j < diffrence; j++) {
binValue = '0'+binValue;
}
}
// Adding the binValue to the end string which we will return.
returnValue += binValue
}
var decompressedArr = returnValue.split('');
returnValue = ''; // Emptying the return variable.
// Deleting the not needed leading 0s used as padding.
for (var i = paddingAmount; i < decompressedArr.length; i++) {
returnValue += decompressedArr[i];
}
// Returning the decompressed string.
return returnValue;
}
URL 缩短器
我仍然发现“压缩”字符串对于共享/粘贴来说有点长。所以我使用了一个简单的 URL 缩短器 (view here) 来使这个过程对用户来说更容易一些。
现在您可能会问,那为什么还要压缩这个字符串呢?
原因如下:
首先,我的项目托管在 github 页面 (gh-pages) 上。 gh-pages 的信息页面告诉我们 url 不能 超过 2000 个字符。这意味着最大网格大小将是 2000 的平方根 - 基数 url 的长度,这并没有那么大。通过使用这种“压缩”,我们能够共享更大的网格。
第二个原因是,这是一个挑战。我发现处理这些问题很有趣,也很有帮助,因为你学到了很多东西。
直播
您可以查看我项目的实时版本here. and/or find the github repository here。
谢谢
我要感谢所有帮助我解决这个问题的人。特别是 Corstian 和 Arnauld,因为我最终使用他们的答案来实现我的最终功能。
太棒了.... 谢谢你们!给它点赞!
在生命游戏中有一个由 1 和 0 组成的棋盘。我想备份到上一代 - 大小 4800 - 将每 16 个单元格保存为十六进制 = 大小的 1/4。 http://innerbeing.epizy.com/cwebgl/gameoflife.html [g = 开始] [b = 备份]
function drawGen(n) {
stop(); var i = clamp(n,0,brw*brh-1), hex = gensave[i].toString();
echo(":",i, n,nGEN); nGEN = i; var str = '';
for (var i = 0; i < parseInt(hex.length / 4); i++)
str = str + pad(parseInt(hex.substr(i*4,4), 16).toString(2),16,'0');
for (var j=0;j<Board.length;j++) Board[j] = intr(str.substr(j,1));
drawBoard();
}
function Bin2Hex(n) {
var i = n.indexOf("1"); /// leading Zeros = NAN
if (i == -1) return "0000";
i = right(n,i*-1);
return pad(parseInt(i,2).toString(16),4,'0');
}
function saveGen(n) {
var b = Board.join(''), str = ''; /// concat array to string 10101
for (var i = 0; i < parseInt(b.length / 16); i++)
str = str + Bin2Hex(b.substr(i*16,16));
gensave[n] = str;
}
function right(st,n) {
var s = st.toString();
if (!n) return s;
if (n < 0) return s.substr(n * -1,s.length + n);
return s.substr(s.length - n,n);
}
function pad(str, l, padwith) {
var s = str;
while (s.length < l) s = padwith + s;
return s;
}