如何将base13字符串转成base64
How to convert base13 string to base64
我必须为查询字符串创建一个 URL 缩短器。花了几天时间尝试将数组数据压缩为 base64 字符串。认为最好的方法可能是将“[[1,2,9,3],[1,0,2],[39,4]]”之类的东西解释为带有数字 0-9 和 [] 的 base13,符号.
当前算法的工作原理:
将字符串化数组转换为 base13 数组,其中每个元素代表 1 个唯一字符,将此数组转换为 base10 数字,将此数字转换为 base 64 字符串。
但问题是当将 base13 数组转换为 base10 数字时,它会生成像 5.304781188371057e+86 这样的大数字,而这在 js 中是无法保存的。
我当然愿意接受其他解决方案,但请不要建议创建 URL 的数据库之类的东西,因为它不会工作,因为我有多达 51!*51!独特的 URLs,最好只制作一个紧凑的可编码和可解码查询字符串,并在访问网站后立即对其进行解码。
//convert stringified array to array of base13(each element = each digit of base13 number)
function stringToArray(string)
{
let charSet = "[],1234567890";
let array = [];
for(let i = 0; i < string.length; i++)
{
array.push(charSet.indexOf(string[i]));
}
return array;
}
//convert base13 array to one large decimal number
function arrayToDecimal(array, base)
{
var decimal = 0;
for(let i = 0; i < array.length; i++)
{
decimal += array[i] * Math.pow(base, i)
}
return decimal;
}
//convert decimal number back to array
function decimalToArray(decimal, base)
{
var quotient = decimal;
var remainder = [];
while(quotient > base)
{
remainder.push(quotient % base)
quotient = Math.floor(quotient / base);
}
remainder.push(quotient % base)
return remainder;
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// binary to string lookup table
const b2s = alphabet.split('');
// string to binary lookup table
// 123 == 'z'.charCodeAt(0) + 1
const s2b = new Array(123);
for(let i = 0; i < alphabet.length; i++)
{
s2b[alphabet.charCodeAt(i)] = i;
}
// number to base64
const ntob = (number) =>
{
if(number < 0) return `-${ntob(-number)}`;
let lo = number >>> 0;
let hi = (number / 4294967296) >>> 0;
let right = '';
while(hi > 0)
{
right = b2s[0x3f & lo] + right;
lo >>>= 6;
lo |= (0x3f & hi) << 26;
hi >>>= 6;
}
let left = '';
do {
left = b2s[0x3f & lo] + left;
lo >>>= 6;
} while(lo > 0);
return left + right;
};
// base64 to number
const bton = (base64) =>
{
let number = 0;
const sign = base64.charAt(0) === '-' ? 1 : 0;
for(let i = sign; i < base64.length; i++)
{
number = number * 64 + s2b[base64.charCodeAt(i)];
}
return sign ? -number : number;
};
console.log(decimalToArray(bton(ntob(arrayToDecimal([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 13))), 13))
//encoded and decoded, works output:[1,1,1,1,1,1,1,1,1,1,1,1,1]
console.log(arrayToDecimal([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 13))
//encoding doesnt work, array to decimal converts to 5.304781188371057e+86```
我建议您直接将 Base13 字符串编码为 Base64。
尽管这可能不会产生比您的解决方案更好的压缩效果,但它可以消除您正在执行的繁重的乘法运算。此外,如何保证通过 arrayToDecimal 转换时不会发生冲突?
一个有趣的问题...您需要评估的第一件事是您正在寻求的基本转换压缩是否值得。即,需要多少个 base 64 字符来表示 base 13 的 n
个字符?这涉及解决...
13 ** n = 64 ** x
求解 x,我们得到...
x = n * log(13) / log(64)
即每n位13进制,需要多少位64进制。 n returns...
的几个值的采样
- n = 6, x = 3.70
- n = 7, x = 4.31
- n = 8, x = 4.93
- n = 9, x = 5.55
- n = 10, x = 6.17
- n = 11, x = 6.78
- n = 12, x = 7.40
- n = 13, x = 8.01
- n = 14, x = 8.63
- n = 15, x = 9.25
- n = 16, x = 9.86
那么如何解读呢?如果你有 13 位的 10 位数字,你将需要 64 位的 7 位数字(6.17 四舍五入)。所以最好的比率是 x 等于或小于整数。所以,8位13进制需要5位64进制,达到5/8或62.5%压缩率的最佳情况。
假设这足以满足您的要求,那么以下函数会将 "base13" 字符串转换为 base 64。
const base13Chars = "0123456789[],";
const base64Chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_';
// see https://en.wikipedia.org/wiki/Query_string for URL parameter allowable characters.
function base13toBase64(x13) {
base13 = x13.split("").map( c => base13Chars.indexOf(c) );
// Make the array an even multiple of 8
for (i = base13.length; i % 8 !==0; i++) {
base13[i] = 0;
}
x64 = "";
for (i = 0; i < base13.length; i += 8) {
// Calculate base13 value of the next 8 characters.
let n = 0;
for (j = 0; j < 8; j++) {
n = n * 13 + base13[i + j];
}
// Now calculate the base64 of n.
for (j = 0; j < 5; j++) {
x64 = x64 + base64Chars.substr(n % 64,1);
n = Math.floor(n / 64);
}
}
return x64;
}
运行 以上...
base13toBase64( "[[1,2,9,3],[1,0,2],[39,4]]" ) returns "ilYKerYlgEJ4PxAAjaJi"
注意原始值是26个字符的长度,base64值是20个字符,所以压缩率为77%,不太理想的62.5%。这是因为填充使原始数组达到 32 个字符,是 8 的偶数倍。不过,要编码的字符串越长,比率就越接近 62.5%。
然后,在服务器端你需要上面的常量加上下面的函数"uncompress" base64 到 base13 字符串化 URL...
function base64toBase13(x64) {
base64 = x64.split("").map( c => base64Chars.indexOf(c) );
x13 = "";
for (i = 0; i < base64.length; i += 5) {
// Calculate base64 value of the next 5 characters.
let n = 0;
for (j = 5 - 1; 0 <= j; j--) {
n = n * 64 + base64[i + j];
}
// Now calculate the base13 of n.
let x = "";
for (j = 0; j < 8; j++) {
x = base13Chars.substr(n % 13,1) + x;
n = Math.floor(n / 13);
}
x13 = x13 + x;
}
// Removed the trailing 0's as a result of the buffering in
// base13toBase64 to make the array an even multiple of 8.
while (x13.substr(-1,1) === "0") {
x13 = x13.substr(0, x13.length - 1);
}
return x13;
}
运行 以上...
base64toBase13 ( "ilYKerYlgEJ4PxAAjaJi" ) returns "[[1,2,9,3],[1,0,2],[39,4]]"
希望这对您有所帮助...
最好的压缩是当你可以把东西放在外面的时候。
假设您的数据结构是 Array<Array<int>>
由一个示例给出的,我们可以忽略几乎所有对数据本身没有贡献的内容。
我不是在压缩字符串,而是数据本身需要 1 个 b64Character / 5 位来表示一个数字。至于结构,我们只存储子数组的数量和它们各自的长度;所以你的数据中每个数组或多或少有一个额外的字符。
归结为:
function encode(data) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let str = "";
function encode(nr, hasMoreDigits) {
if (nr > 31) {
// I need more bits/characters to encode this number.
//encode the more significant bits with the 0b100000 flag
encode(nr >>> 5, 32);
}
// 0b011111 payload | 0b100000 flag
const index = nr & 31 | hasMoreDigits;
str += alphabet[index];
}
encode(data.length);
data.forEach(arr => {
encode(arr.length);
arr.forEach(v => encode(v >>> 0 /* int32 -> uint32 */));
});
return str;
}
function decode(str) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let i = 0;
function parse() {
let nr = 0,
hasMoreDigits;
do {
const index = alphabet.indexOf(str.charAt(i++));
nr = nr << 5 | index & 31; // 0b011111 payload
hasMoreDigits = index & 32; // 0b100000 flag
} while (hasMoreDigits);
return nr; // int32 due to the bit operations above
}
let data = Array(parse());
for (let j = 0; j < data.length; ++j) {
let arr = data[j] = Array(parse());
for (let k = 0; k < arr.length; ++k) {
arr[k] = parse();
}
}
return data;
}
let data = [
[1, 2, 9, 3],
[1, 0, 2],
[39, 4]
];
let text = encode(data);
let data2 = decode(text);
console.log("input:", data);
console.log("encoded:", text, "length:", text.length);
console.log("output:", data2);
console.log("equal:", JSON.stringify(data) === JSON.stringify(data2));
.as-console-wrapper{top:0;max-height:100%!important}
数字的编码。理想情况下,您会将数字编码为具有静态大小的二进制,但这意味着 32 位/整数,即 6 个字符/数字,所以多字节。
我们将数字分成 'n' 位的块,忽略前导零并对其余部分进行编码。理想情况下,我们可以用很少的字符编码小数字,缺点:如果 n
太小并且平均数字很大,我们会丢失 1 位/块。这是一种权衡;这就是我保留此可配置项的原因。
当前格式为6位/数字。 1 位用于结构,5 位作为有效载荷。格式为 (1.....)*0.....
我必须为查询字符串创建一个 URL 缩短器。花了几天时间尝试将数组数据压缩为 base64 字符串。认为最好的方法可能是将“[[1,2,9,3],[1,0,2],[39,4]]”之类的东西解释为带有数字 0-9 和 [] 的 base13,符号.
当前算法的工作原理: 将字符串化数组转换为 base13 数组,其中每个元素代表 1 个唯一字符,将此数组转换为 base10 数字,将此数字转换为 base 64 字符串。
但问题是当将 base13 数组转换为 base10 数字时,它会生成像 5.304781188371057e+86 这样的大数字,而这在 js 中是无法保存的。
我当然愿意接受其他解决方案,但请不要建议创建 URL 的数据库之类的东西,因为它不会工作,因为我有多达 51!*51!独特的 URLs,最好只制作一个紧凑的可编码和可解码查询字符串,并在访问网站后立即对其进行解码。
//convert stringified array to array of base13(each element = each digit of base13 number)
function stringToArray(string)
{
let charSet = "[],1234567890";
let array = [];
for(let i = 0; i < string.length; i++)
{
array.push(charSet.indexOf(string[i]));
}
return array;
}
//convert base13 array to one large decimal number
function arrayToDecimal(array, base)
{
var decimal = 0;
for(let i = 0; i < array.length; i++)
{
decimal += array[i] * Math.pow(base, i)
}
return decimal;
}
//convert decimal number back to array
function decimalToArray(decimal, base)
{
var quotient = decimal;
var remainder = [];
while(quotient > base)
{
remainder.push(quotient % base)
quotient = Math.floor(quotient / base);
}
remainder.push(quotient % base)
return remainder;
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// binary to string lookup table
const b2s = alphabet.split('');
// string to binary lookup table
// 123 == 'z'.charCodeAt(0) + 1
const s2b = new Array(123);
for(let i = 0; i < alphabet.length; i++)
{
s2b[alphabet.charCodeAt(i)] = i;
}
// number to base64
const ntob = (number) =>
{
if(number < 0) return `-${ntob(-number)}`;
let lo = number >>> 0;
let hi = (number / 4294967296) >>> 0;
let right = '';
while(hi > 0)
{
right = b2s[0x3f & lo] + right;
lo >>>= 6;
lo |= (0x3f & hi) << 26;
hi >>>= 6;
}
let left = '';
do {
left = b2s[0x3f & lo] + left;
lo >>>= 6;
} while(lo > 0);
return left + right;
};
// base64 to number
const bton = (base64) =>
{
let number = 0;
const sign = base64.charAt(0) === '-' ? 1 : 0;
for(let i = sign; i < base64.length; i++)
{
number = number * 64 + s2b[base64.charCodeAt(i)];
}
return sign ? -number : number;
};
console.log(decimalToArray(bton(ntob(arrayToDecimal([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 13))), 13))
//encoded and decoded, works output:[1,1,1,1,1,1,1,1,1,1,1,1,1]
console.log(arrayToDecimal([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 13))
//encoding doesnt work, array to decimal converts to 5.304781188371057e+86```
我建议您直接将 Base13 字符串编码为 Base64。 尽管这可能不会产生比您的解决方案更好的压缩效果,但它可以消除您正在执行的繁重的乘法运算。此外,如何保证通过 arrayToDecimal 转换时不会发生冲突?
一个有趣的问题...您需要评估的第一件事是您正在寻求的基本转换压缩是否值得。即,需要多少个 base 64 字符来表示 base 13 的 n
个字符?这涉及解决...
13 ** n = 64 ** x
求解 x,我们得到...
x = n * log(13) / log(64)
即每n位13进制,需要多少位64进制。 n returns...
的几个值的采样- n = 6, x = 3.70
- n = 7, x = 4.31
- n = 8, x = 4.93
- n = 9, x = 5.55
- n = 10, x = 6.17
- n = 11, x = 6.78
- n = 12, x = 7.40
- n = 13, x = 8.01
- n = 14, x = 8.63
- n = 15, x = 9.25
- n = 16, x = 9.86
那么如何解读呢?如果你有 13 位的 10 位数字,你将需要 64 位的 7 位数字(6.17 四舍五入)。所以最好的比率是 x 等于或小于整数。所以,8位13进制需要5位64进制,达到5/8或62.5%压缩率的最佳情况。
假设这足以满足您的要求,那么以下函数会将 "base13" 字符串转换为 base 64。
const base13Chars = "0123456789[],";
const base64Chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_';
// see https://en.wikipedia.org/wiki/Query_string for URL parameter allowable characters.
function base13toBase64(x13) {
base13 = x13.split("").map( c => base13Chars.indexOf(c) );
// Make the array an even multiple of 8
for (i = base13.length; i % 8 !==0; i++) {
base13[i] = 0;
}
x64 = "";
for (i = 0; i < base13.length; i += 8) {
// Calculate base13 value of the next 8 characters.
let n = 0;
for (j = 0; j < 8; j++) {
n = n * 13 + base13[i + j];
}
// Now calculate the base64 of n.
for (j = 0; j < 5; j++) {
x64 = x64 + base64Chars.substr(n % 64,1);
n = Math.floor(n / 64);
}
}
return x64;
}
运行 以上...
base13toBase64( "[[1,2,9,3],[1,0,2],[39,4]]" ) returns "ilYKerYlgEJ4PxAAjaJi"
注意原始值是26个字符的长度,base64值是20个字符,所以压缩率为77%,不太理想的62.5%。这是因为填充使原始数组达到 32 个字符,是 8 的偶数倍。不过,要编码的字符串越长,比率就越接近 62.5%。
然后,在服务器端你需要上面的常量加上下面的函数"uncompress" base64 到 base13 字符串化 URL...
function base64toBase13(x64) {
base64 = x64.split("").map( c => base64Chars.indexOf(c) );
x13 = "";
for (i = 0; i < base64.length; i += 5) {
// Calculate base64 value of the next 5 characters.
let n = 0;
for (j = 5 - 1; 0 <= j; j--) {
n = n * 64 + base64[i + j];
}
// Now calculate the base13 of n.
let x = "";
for (j = 0; j < 8; j++) {
x = base13Chars.substr(n % 13,1) + x;
n = Math.floor(n / 13);
}
x13 = x13 + x;
}
// Removed the trailing 0's as a result of the buffering in
// base13toBase64 to make the array an even multiple of 8.
while (x13.substr(-1,1) === "0") {
x13 = x13.substr(0, x13.length - 1);
}
return x13;
}
运行 以上...
base64toBase13 ( "ilYKerYlgEJ4PxAAjaJi" ) returns "[[1,2,9,3],[1,0,2],[39,4]]"
希望这对您有所帮助...
最好的压缩是当你可以把东西放在外面的时候。
假设您的数据结构是 Array<Array<int>>
由一个示例给出的,我们可以忽略几乎所有对数据本身没有贡献的内容。
我不是在压缩字符串,而是数据本身需要 1 个 b64Character / 5 位来表示一个数字。至于结构,我们只存储子数组的数量和它们各自的长度;所以你的数据中每个数组或多或少有一个额外的字符。
归结为:
function encode(data) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let str = "";
function encode(nr, hasMoreDigits) {
if (nr > 31) {
// I need more bits/characters to encode this number.
//encode the more significant bits with the 0b100000 flag
encode(nr >>> 5, 32);
}
// 0b011111 payload | 0b100000 flag
const index = nr & 31 | hasMoreDigits;
str += alphabet[index];
}
encode(data.length);
data.forEach(arr => {
encode(arr.length);
arr.forEach(v => encode(v >>> 0 /* int32 -> uint32 */));
});
return str;
}
function decode(str) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let i = 0;
function parse() {
let nr = 0,
hasMoreDigits;
do {
const index = alphabet.indexOf(str.charAt(i++));
nr = nr << 5 | index & 31; // 0b011111 payload
hasMoreDigits = index & 32; // 0b100000 flag
} while (hasMoreDigits);
return nr; // int32 due to the bit operations above
}
let data = Array(parse());
for (let j = 0; j < data.length; ++j) {
let arr = data[j] = Array(parse());
for (let k = 0; k < arr.length; ++k) {
arr[k] = parse();
}
}
return data;
}
let data = [
[1, 2, 9, 3],
[1, 0, 2],
[39, 4]
];
let text = encode(data);
let data2 = decode(text);
console.log("input:", data);
console.log("encoded:", text, "length:", text.length);
console.log("output:", data2);
console.log("equal:", JSON.stringify(data) === JSON.stringify(data2));
.as-console-wrapper{top:0;max-height:100%!important}
数字的编码。理想情况下,您会将数字编码为具有静态大小的二进制,但这意味着 32 位/整数,即 6 个字符/数字,所以多字节。
我们将数字分成 'n' 位的块,忽略前导零并对其余部分进行编码。理想情况下,我们可以用很少的字符编码小数字,缺点:如果 n
太小并且平均数字很大,我们会丢失 1 位/块。这是一种权衡;这就是我保留此可配置项的原因。
当前格式为6位/数字。 1 位用于结构,5 位作为有效载荷。格式为 (1.....)*0.....