【问题标题】:How to convert base13 string to base64如何将base13字符串转换为base64
【发布时间】:2019-12-18 02:03:01
【问题描述】:

我必须为查询字符串制作一个 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!唯一的 URL,最好只制作一个紧凑的可编码和可解码的查询字符串,并在访问网站时立即对其进行解码。

//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```

【问题讨论】:

    标签: javascript arrays base


    【解决方案1】:

    最好的压缩是你可以把东西放在外面。

    假设您的数据结构是由一个样本给出的Array&lt;Array&lt;int&gt;&gt;,我们可以忽略几乎所有对数据本身没有贡献的内容。

    我不是在压缩字符串,而是数据本身需要 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 太小并且平均数字很大,我们会丢失 1bit/chunk。这是一个权衡;这就是为什么我离开这个可配置的。

    当前格式为 6bits/number。 1 表示结构,5 位作为有效载荷。格式为(1.....)*0.....

    【讨论】:

    • imma 必须检查一下,似乎比原来的帖子更好,虽然我还没有测试过所以不确定。无论哪种方式,非常感谢,我会在短时间内深入研究您的代码:)
    • 是的,这肯定更有效,非常感谢,虽然我喜欢这两个答案,因为你的 URL 会更小,而 Jon Trent 的答案允许我在未来需要时也包含对象,不过现在,我肯定会使用你的代码,因为它会生成更小的 URL,谢谢 :)
    • @Siddarth,如果您希望将接受的答案切换为 Thomas,我不会冒犯。位旋转(即转换为 base2)几乎总能提供更好的字符打包!
    • @JonTrent 实际上我在代码中使用 base32 数字。更好的压缩来自于我不需要对结构进行编码的事实,它在编码器中被硬编码,我可以用单个字符编码 0..31,而不仅仅是 0..9,我可以编码 @ 987654326@ 用一个字符表示数组的长度,我将,“编码”为数字上的一个位;即当字符来自b64字母表的前半部分时,这是一个数字的结尾,如果是来自另一半,则后面还有更多的数字。
    • @SiddharthAgrawal 是的,这是一个非常具体的实现,专门针对假设您的数据结构为Array&lt;Array&lt;int&gt;&gt; 且大部分为小正整数而量身定制。这不是并且从未打算成为通用实现。所以是的,对于这种特定格式,它会产生较小的结果。
    【解决方案2】:

    一个有趣的问题...您需要评估的第一件事是您所寻求的基本转换压缩是否值得。即,需要多少个基数为 64 的字符来表示基数为 13 的n 字符?这涉及到解决...

    13 ** n = 64 ** x
    

    求解 x,我们得到...

     x = n * log(13) / log(64)
    

    即,对于以 13 为底的每 n 位数字,需要多少以 64 为底的数字。几个 n 值的采样返回...

    • 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

    那么如何解释呢?如果您有 10 位以 13 为底的数字,那么您将需要以 64 为底的 7 位数字(6.17 向上舍入)。因此,最佳比率是当 x 等于或略低于整数时。因此,以 13 为底的 8 位需要以 64 为底的 5 位,达到 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%。

    然后,在服务器端,您将需要上面的常量加上以下函数将 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]]"
    

    希望这会有所帮助...

    【讨论】:

    • 非常感谢!最后!所以让我正确理解,你所做的是将base13字符串中的每8个字符转换为十进制数,然后将其转换为base64字符串?对于base64回到base13,你以5为增量?想知道确切的方法,以防我将来必须包含对象。我还发现了stackoverflow.com/questions/23190056/…,它可能对解决这个问题也很有用。
    • 没错。例如,base13 的 8 位数字代表 13**8 个数字,或 815730721 个十进制数字,特别是数字 0 到 815730720 (含)。使用相同的逻辑,base64 的 5 位数字可以表示 0 到 1073741823 (含)的数字。所以该例程将b​​ase13转换为十进制,然后将十进制转换为base64。 (顺便说一句,我想你知道如果你可以执行 POST 而不是 GET,那么数据大小就没有限制,你可以避免所有这些转换的东西。见diffen.com/difference/GET-vs-POST-HTTP-Requests
    • 不太确定 post 和 get 是什么,而且我没有任何服务器端代码,一切都是为我的网站在本地完成的。再次感谢您的解释:)
    【解决方案3】:

    我建议您直接将 Base13 字符串编码为 Base64。 尽管这可能不会导致比您的解决方案更好的压缩,但它消除了您正在执行的大量乘法。另外,如何保证通过arrayToDecimal进行转换时不会发生冲突?

    【讨论】:

    • 我将数组中的每个数字都视为以 15 为底,并使用 a*13^n 公式将其转换为十进制
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-25
    • 2022-11-12
    • 2016-10-21
    相关资源
    最近更新 更多