Last active
July 6, 2017 08:33
-
-
Save JobLeonard/4a2ca3e9e3e9ee544f5b76acc7699ef8 to your computer and use it in GitHub Desktop.
LZString old vs new compression short repetitive string #jsbench #jsperf #jsbench #jsperf (http://jsbench.github.io/#4a2ca3e9e3e9ee544f5b76acc7699ef8) #jsbench #jsperf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<title>LZString old vs new compression short repetitive string #jsbench #jsperf #jsbench #jsperf</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/1.0.0/benchmark.min.js"></script> | |
<script src="./suite.js"></script> | |
</head> | |
<body> | |
<h1>Open the console to view the results</h1> | |
<h2><code>cmd + alt + j</code> or <code>ctrl + alt + j</code></h2> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
(function (factory) { | |
if (typeof Benchmark !== "undefined") { | |
factory(Benchmark); | |
} else { | |
factory(require("benchmark")); | |
} | |
})(function (Benchmark) { | |
var suite = new Benchmark.Suite; | |
Benchmark.prototype.setup = function () { | |
var LZString = (function() { | |
// private property | |
var f = String.fromCharCode; | |
var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | |
var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; | |
var baseReverseDic = {}; | |
function getBaseValue(alphabet, character) { | |
if (!baseReverseDic[alphabet]) { | |
baseReverseDic[alphabet] = {}; | |
for (var i=0 ; i<alphabet.length ; i++) { | |
baseReverseDic[alphabet][alphabet.charAt(i)] = i; | |
} | |
} | |
return baseReverseDic[alphabet][character]; | |
} | |
var LZString = { | |
compressToBase64 : function (input) { | |
if (input == null) return ""; | |
var res = LZString._compress(input, 6, function(a){return keyStrBase64.charAt(a);}); | |
switch (res.length % 4) { // To produce valid Base64 | |
default: // When could this happen ? | |
case 0 : return res; | |
case 1 : return res+"==="; | |
case 2 : return res+"=="; | |
case 3 : return res+"="; | |
} | |
}, | |
decompressFromBase64 : function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrBase64, input.charAt(index)); }); | |
}, | |
compressToUTF16 : function (input) { | |
if (input == null) return ""; | |
return LZString._compress(input, 15, function(a){return f(a+32);}) + " "; | |
}, | |
decompressFromUTF16: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 16384, function(index) { return compressed.charCodeAt(index) - 32; }); | |
}, | |
//compress into uint8array (UCS-2 big endian format) | |
compressToUint8Array: function (uncompressed) { | |
var compressed = LZString.compress(uncompressed); | |
var buf=new Uint8Array(compressed.length*2); // 2 bytes per character | |
for (var i=0, TotalLen=compressed.length; i<TotalLen; i++) { | |
var current_value = compressed.charCodeAt(i); | |
buf[i*2] = current_value >>> 8; | |
buf[i*2+1] = current_value % 256; | |
} | |
return buf; | |
}, | |
//decompress from uint8array (UCS-2 big endian format) | |
decompressFromUint8Array:function (compressed) { | |
if (compressed===null || compressed===undefined){ | |
return LZString.decompress(compressed); | |
} else { | |
var buf=new Array(compressed.length/2); // 2 bytes per character | |
for (var i=0, TotalLen=buf.length; i<TotalLen; i++) { | |
buf[i]=compressed[i*2]*256+compressed[i*2+1]; | |
} | |
var result = []; | |
buf.forEach(function (c) { | |
result.push(f(c)); | |
}); | |
return LZString.decompress(result.join('')); | |
} | |
}, | |
//compress into a string that is already URI encoded | |
compressToEncodedURIComponent: function (input) { | |
if (input == null) return ""; | |
return LZString._compress(input, 6, function(a){return keyStrUriSafe.charAt(a);}); | |
}, | |
//decompress from an output of compressToEncodedURIComponent | |
decompressFromEncodedURIComponent:function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
input = input.replace(/ /g, "+"); | |
return LZString._decompress(input.length, 32, function(index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); }); | |
}, | |
compress: function (uncompressed) { | |
return LZString._compress(uncompressed, 16, function(a){return f(a);}); | |
}, | |
_compress: function (uncompressed, bitsPerChar, getCharFromInt) { | |
if (uncompressed == null) return ""; | |
var i, value, | |
context_dictionary= {}, | |
context_dictionaryToCreate= {}, | |
context_c="", | |
context_wc="", | |
context_w="", | |
context_enlargeIn= 2, // Compensate for the first entry which should not count | |
context_dictSize= 3, | |
context_numBits= 2, | |
context_data=[], | |
context_data_val=0, | |
context_data_position=0, | |
ii; | |
for (ii = 0; ii < uncompressed.length; ii += 1) { | |
context_c = uncompressed.charAt(ii); | |
if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) { | |
context_dictionary[context_c] = context_dictSize++; | |
context_dictionaryToCreate[context_c] = true; | |
} | |
context_wc = context_w + context_c; | |
if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) { | |
context_w = context_wc; | |
} else { | |
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { | |
if (context_w.charCodeAt(0)<256) { | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<8 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} else { | |
value = 1; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | value; | |
if (context_data_position ==bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = 0; | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<16 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
delete context_dictionaryToCreate[context_w]; | |
} else { | |
value = context_dictionary[context_w]; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
// Add wc to the dictionary. | |
context_dictionary[context_wc] = context_dictSize++; | |
context_w = String(context_c); | |
} | |
} | |
// Output the code for w. | |
if (context_w !== "") { | |
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { | |
if (context_w.charCodeAt(0)<256) { | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<8 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} else { | |
value = 1; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | value; | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = 0; | |
} | |
value = context_w.charCodeAt(0); | |
for (i=0 ; i<16 ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
delete context_dictionaryToCreate[context_w]; | |
} else { | |
value = context_dictionary[context_w]; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
} | |
context_enlargeIn--; | |
if (context_enlargeIn == 0) { | |
context_enlargeIn = Math.pow(2, context_numBits); | |
context_numBits++; | |
} | |
} | |
// Mark the end of the stream | |
value = 2; | |
for (i=0 ; i<context_numBits ; i++) { | |
context_data_val = (context_data_val << 1) | (value&1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data_position = 0; | |
context_data.push(getCharFromInt(context_data_val)); | |
context_data_val = 0; | |
} else { | |
context_data_position++; | |
} | |
value = value >> 1; | |
} | |
// Flush the last char | |
while (true) { | |
context_data_val = (context_data_val << 1); | |
if (context_data_position == bitsPerChar-1) { | |
context_data.push(getCharFromInt(context_data_val)); | |
break; | |
} | |
else context_data_position++; | |
} | |
return context_data.join(''); | |
}, | |
decompress: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); }); | |
}, | |
_decompress: function (length, resetValue, getNextValue) { | |
var dictionary = [], | |
next, | |
enlargeIn = 4, | |
dictSize = 4, | |
numBits = 3, | |
entry = "", | |
result = [], | |
i, | |
w, | |
bits, resb, maxpower, power, | |
c, | |
data = {val:getNextValue(0), position:resetValue, index:1}; | |
for (i = 0; i < 3; i += 1) { | |
dictionary[i] = i; | |
} | |
bits = 0; | |
maxpower = Math.pow(2,2); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
switch (next = bits) { | |
case 0: | |
bits = 0; | |
maxpower = Math.pow(2,8); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
c = f(bits); | |
break; | |
case 1: | |
bits = 0; | |
maxpower = Math.pow(2,16); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
c = f(bits); | |
break; | |
case 2: | |
return ""; | |
} | |
dictionary[3] = c; | |
w = c; | |
result.push(c); | |
while (true) { | |
if (data.index > length) { | |
return ""; | |
} | |
bits = 0; | |
maxpower = Math.pow(2,numBits); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
switch (c = bits) { | |
case 0: | |
bits = 0; | |
maxpower = Math.pow(2,8); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
dictionary[dictSize++] = f(bits); | |
c = dictSize-1; | |
enlargeIn--; | |
break; | |
case 1: | |
bits = 0; | |
maxpower = Math.pow(2,16); | |
power=1; | |
while (power!=maxpower) { | |
resb = data.val & data.position; | |
data.position >>= 1; | |
if (data.position == 0) { | |
data.position = resetValue; | |
data.val = getNextValue(data.index++); | |
} | |
bits |= (resb>0 ? 1 : 0) * power; | |
power <<= 1; | |
} | |
dictionary[dictSize++] = f(bits); | |
c = dictSize-1; | |
enlargeIn--; | |
break; | |
case 2: | |
return result.join(''); | |
} | |
if (enlargeIn == 0) { | |
enlargeIn = Math.pow(2, numBits); | |
numBits++; | |
} | |
if (dictionary[c]) { | |
entry = dictionary[c]; | |
} else { | |
if (c === dictSize) { | |
entry = w + w.charAt(0); | |
} else { | |
return null; | |
} | |
} | |
result.push(entry); | |
// Add w+entry[0] to the dictionary. | |
dictionary[dictSize++] = w + entry.charAt(0); | |
enlargeIn--; | |
w = entry; | |
if (enlargeIn == 0) { | |
enlargeIn = Math.pow(2, numBits); | |
numBits++; | |
} | |
} | |
} | |
}; | |
return LZString; | |
})(); | |
var LZStringNew = ( | |
function () { | |
// private property | |
var f = String.fromCharCode, | |
Base64CharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(''), | |
UriSafeCharArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".split(''), | |
Base64ReverseDic = {}, | |
UriSafeReverseDic = {}, | |
i = 65; | |
while (i--) { | |
Base64ReverseDic[Base64CharArray[i].charCodeAt(0)] = i; | |
UriSafeReverseDic[UriSafeCharArray[i].charCodeAt(0)] = i; | |
} | |
var getChar16Bits = function (a) { return f(a); }, | |
getCharFromBase64 = function (a) { return Base64CharArray[a]; }, | |
getCharFromURISafe = function (a) { return UriSafeCharArray[a]; }, | |
getCharFromUTF16 = function (a) { return f(a + 32); }; | |
var LZString = { | |
compressToBase64: function (input) { | |
if (input == null) return ""; | |
var res = LZString._compressToArray(input, 6, getCharFromBase64); | |
// To produce valid Base64 | |
var i = res.length % 4; | |
while (i--) { | |
res.push("="); | |
} | |
return res.join(''); | |
}, | |
decompressFromBase64: function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
return LZString._decompress(input.length, 32, function (index) { return Base64ReverseDic[input.charCodeAt(index)]; }); | |
}, | |
compressToUTF16: function (input) { | |
if (input == null) return ""; | |
var compressed = LZString._compressToArray(input, 15, getCharFromUTF16); | |
compressed.push(" "); | |
return compressed.join(''); | |
}, | |
decompressFromUTF16: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 16384, function (index) { return compressed.charCodeAt(index) - 32; }); | |
}, | |
//compress into uint8array (UCS-2 big endian format) | |
compressToUint8Array: function (uncompressed) { | |
var compressed = LZString.compressToArray(uncompressed); | |
var buf = new Uint8Array(compressed.length * 2); // 2 bytes per character | |
for (var i = 0, TotalLen = compressed.length; i < TotalLen; i++) { | |
var current_value = compressed[i].charCodeAt(0); | |
buf[i * 2] = current_value >>> 8; | |
buf[i * 2 + 1] = current_value & 0xFF; | |
} | |
return buf; | |
}, | |
//decompress from uint8array (UCS-2 big endian format) | |
decompressFromUint8Array: function (compressed) { | |
if (compressed === null || compressed === undefined) { | |
return LZString.decompressFromArray(compressed); | |
} else if (compressed.length == 0) { | |
return null; | |
} | |
return LZString._decompress(compressed.length, 128, function (index) { return compressed[index]; }); | |
}, | |
//compress into a string that is already URI encoded | |
compressToEncodedURIComponent: function (input) { | |
if (input == null) return ""; | |
return LZString._compressToArray(input, 6, getCharFromURISafe).join(''); | |
}, | |
//decompress from an output of compressToEncodedURIComponent | |
decompressFromEncodedURIComponent: function (input) { | |
if (input == null) return ""; | |
if (input == "") return null; | |
input = input.replace(/ /g, "+"); | |
return LZString._decompress(input.length, 32, function (index) { return UriSafeReverseDic[input.charCodeAt(index)]; }); | |
}, | |
compress: function (uncompressed) { | |
return LZString.compressToArray(uncompressed).join(''); | |
}, | |
compressToArray: function (uncompressed) { | |
return LZString._compressToArray(uncompressed, 16, getChar16Bits); | |
}, | |
_compressToArray: function (uncompressed, bitsPerChar, getCharFromInt) { | |
if (uncompressed == null) return []; | |
var i = 0, j = 0, value = 0, | |
dictionary = {}, | |
freshNode = true, | |
c = 0, | |
c0 = 1, | |
new_node = { 0: 3 }, // first node will always be | |
node = new_node, // initialised like this. | |
enlargeIn = 1, | |
dictSize = 4, | |
numBits = 2, | |
data = [], | |
data_val = 0, | |
data_position = 0; | |
if (uncompressed.length) { | |
// If there is a string, the first charCode is guaranteed to | |
// be new, so we write it to output stream, and add it to the | |
// dictionary. For the same reason we can initialize freshNode | |
// as true, and new_node, node and dictSize as if | |
// it was already added to the dictionary (see above). | |
c = uncompressed.charCodeAt(0); | |
c0 = c + 1; | |
// == Write first charCode token to output == | |
// 8 or 16 bit? | |
value = c < 256 ? 0 : 1 | |
// insert "new 8/16 bit charCode" token | |
// into bitstream (value 1) | |
for (i = 0; i < numBits; i++) { | |
// Value is 0 (8 bit) or 1 (16 bit). | |
// We shift it into the bitstream in reverse | |
// (shifting has precedence over bitmasking) | |
data_val = value >> i | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
// insert charCode bits into bitstream | |
// Nasty but effective hack: | |
// loop 8 or 16 times based on token value | |
value = 8 + 8 * value; | |
for (i = 0; i < value; i++) { | |
// shifting has precedence over bitmasking | |
data_val = c >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
// Add charCode to the dictionary. | |
dictionary[c0] = new_node; | |
for (j = 1; j < uncompressed.length; j++) { | |
c = uncompressed.charCodeAt(j); | |
c0 = c + 1; | |
// does the new charCode match an existing prefix? | |
new_node = node[c0]; | |
if (new_node) { | |
// continue with next prefix | |
node = new_node; | |
} else { | |
// Prefix+charCode does not exist in trie yet. | |
// We write the prefix to the bitstream, and add | |
// the new charCode to the dictionary if it's new | |
// Then we set `node` to the root node matching | |
// the charCode. | |
if (freshNode) { | |
// Prefix is a freshly added character token, | |
// which was already written to the bitstream | |
freshNode = false; | |
} else { | |
// write out the current prefix token | |
value = node[0]; | |
for (i = 0; i < numBits; i++) { | |
// shifting has precedence over bitmasking | |
data_val = value >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
} | |
// Is the new charCode a new character | |
// that needs to be stored at the root? | |
new_node = dictionary[c0]; | |
if (new_node == undefined) { | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
// insert "new 8/16 bit charCode" token, | |
// see comments above for explanation | |
value = c < 256 ? 0 : 1 | |
for (i = 0; i < numBits; i++) { | |
data_val = value >> i | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
value = 8 + 8 * value; | |
for (i = 0; i < value; i++) { | |
data_val = c >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
new_node = { 0: dictSize++ }; | |
dictionary[c0] = new_node; | |
// Note of that we already wrote | |
// the charCode token to the bitstream | |
freshNode = true; | |
} | |
// add node representing prefix + new charCode to trie | |
new_node = { 0: dictSize++ }; | |
node[c0] = new_node; | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
// set node to first charCode of new prefix | |
node = dictionary[c0]; | |
} | |
} | |
// === Write last prefix to output === | |
if (freshNode) { | |
// character token already written to output | |
freshNode = false; | |
} else { | |
// write out the prefix token | |
value = node[0]; | |
for (i = 0; i < numBits; i++) { | |
// shifting has precedence over bitmasking | |
data_val = value >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
} | |
// Is c a new character? | |
new_node = dictionary[c0]; | |
if (new_node == undefined) { | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
// insert "new 8/16 bit charCode" token, | |
// see comments above for explanation | |
value = c < 256 ? 0 : 1 | |
for (i = 0; i < numBits; i++) { | |
data_val = value >> i | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
value = 8 + 8 * value; | |
for (i = 0; i < value; i++) { | |
data_val = c >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
} | |
// increase token bitlength if necessary | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
} | |
// Mark the end of the stream | |
for (i = 0; i < numBits; i++) { | |
// shifting has precedence over bitmasking | |
data_val = 2 >> i & 1 | data_val << 1; | |
if (++data_position == bitsPerChar) { | |
data_position = 0; | |
data.push(getCharFromInt(data_val)); | |
data_val = 0; | |
} | |
} | |
// Flush the last char | |
data_val <<= bitsPerChar - data_position; | |
data.push(getCharFromInt(data_val)); | |
return data; | |
}, | |
decompress: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed == "") return null; | |
return LZString._decompress(compressed.length, 32768, function (index) { return compressed.charCodeAt(index); }); | |
}, | |
decompressFromArray: function (compressed) { | |
if (compressed == null) return ""; | |
if (compressed.length == 0) return null; | |
return LZString._decompress(compressed.length, 32768, function (index) { return compressed[index].charCodeAt(0); }); | |
}, | |
_decompress: function (length, resetValue, getNextValue) { | |
// "Math.log2(resetValue)" is ES6, so we use | |
// this while loop instead for backwards compatibility | |
var _resetValue = 0; | |
while (resetValue >> ++_resetValue) { } | |
var dictionary = [0, 1, 2], | |
enlargeIn = 4, | |
dictSize = 4, | |
numBits = 3, | |
entry = "", | |
result = [], | |
w = "", | |
bits = 0, | |
maxpower = 2, | |
power = 0, | |
c = "", | |
data_val = getNextValue(0), | |
data_position = _resetValue, | |
data_index = 1; | |
// Get first token, guaranteed to be either | |
// a new character token (8 or 16 bits) | |
// or end of stream token. | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
// if end of stream token, return empty string | |
if (bits == 2) { | |
return ""; | |
} | |
// else, get character | |
maxpower = bits * 8 + 8; | |
bits = power = 0; | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
c = f(bits); | |
dictionary[3] = c; | |
w = c; | |
result.push(c); | |
// read rest of string | |
while (data_index <= length) { | |
// read out next token | |
maxpower = numBits; | |
bits = power = 0; | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
// 0 or 1 implies new character token | |
if (bits < 2) { | |
maxpower = (8 + 8 * bits); | |
bits = power = 0; | |
while (power != maxpower) { | |
// shifting has precedence over bitmasking | |
bits += (data_val >> --data_position & 1) << power++; | |
if (data_position == 0) { | |
data_position = _resetValue; | |
data_val = getNextValue(data_index++); | |
} | |
} | |
dictionary[dictSize] = f(bits); | |
bits = dictSize++; | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
} else if (bits == 2) { | |
// end of stream token | |
return result.join(''); | |
} | |
if (bits > dictionary.length) { | |
return null; | |
} | |
entry = bits < dictionary.length ? dictionary[bits] : w + w.charAt(0); | |
result.push(entry); | |
// Add w+entry[0] to the dictionary. | |
dictionary[dictSize++] = w + entry.charAt(0); | |
w = entry; | |
if (--enlargeIn == 0) { | |
enlargeIn = 1 << numBits++; | |
} | |
} | |
return ""; | |
} | |
}; | |
return LZString; | |
} | |
)(); | |
var testStringTattoo = 'During tattooing, ink is injected into the skin, initiating an immune response, and cells called "macrophages" move into the area and "eat up" the ink. The macrophages carry some of the ink to the body\'s lymph nodes, but some that are filled with ink stay put, embedded in the skin. That\'s what makes the tattoo visible under the skin. Dalhousie Uiversity\'s Alec Falkenham is developing a topical cream that works by targeting the macrophages that have remained at the site of the tattoo. New macrophages move in to consume the previously pigment-filled macrophages and then migrate to the lymph nodes, eventually taking all the dye with them. "When comparing it to laser-based tattoo removal, in which you see the burns, the scarring, the blisters, in this case, we\'ve designed a drug that doesn\'t really have much off-target effect," he said. "We\'re not targeting any of the normal skin cells, so you won\'t see a lot of inflammation. In fact, based on the process that we\'re actually using, we don\'t think there will be any inflammation at all and it would actually be anti-inflammatory.'; | |
var testStringRepeat = 'aaaaabaaaaacaaaaadaaaaaeaaaaa'; | |
}; | |
suite.add("OLD 'During tattooing, ...'", function () { | |
// OLD 'During tattooing, ...' | |
var t = LZString.compress(testStringTattoo); | |
}); | |
suite.add("NEW 'During tattooing, ...'", function () { | |
// NEW 'During tattooing, ...' | |
var t = LZStringNew.compress(testStringTattoo); | |
}); | |
suite.add("OLD 'aaaaabaaaaacaaaaadaaaaaeaaaaa'", function () { | |
// OLD 'aaaaabaaaaacaaaaadaaaaaeaaaaa' | |
var t = LZString.compress(testStringRepeat); | |
}); | |
suite.add("NEW 'aaaaabaaaaacaaaaadaaaaaeaaaaa'", function () { | |
// NEW 'aaaaabaaaaacaaaaadaaaaaeaaaaa' | |
var t = LZStringNew.compress(testStringRepeat); | |
}); | |
suite.on("cycle", function (evt) { | |
console.log(" - " + evt.target); | |
}); | |
suite.on("complete", function (evt) { | |
console.log(new Array(30).join("-")); | |
var results = evt.currentTarget.sort(function (a, b) { | |
return b.hz - a.hz; | |
}); | |
results.forEach(function (item) { | |
console.log((idx + 1) + ". " + item); | |
}); | |
}); | |
console.log("LZString old vs new compression short repetitive string #jsbench #jsperf #jsbench #jsperf"); | |
console.log(new Array(30).join("-")); | |
suite.run(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment