@hidao

【圧縮?】JavaScript で整数を文字列に符号化・復号【パスワード生成?】

英数小文字だけ(36進数相当)

これがよくあるパターン。

console.log(106832262987743896n.toString(36));
// => "t7wvk33m1zs"

// パスワード向けに下8文字だけ採用する
console.log(106832262987743896n.toString(36).slice(-8));
// => "vk33m1zs"

英数大文字と小文字(62進数相当)

大文字を混ぜるので、ちょっと面倒なことをしています。

/**
 * 10進数の文字列を62進数の文字列に符号化
 * @param {string} id 10進数の文字列
 * @returns {string}
 */
function encode(id) {
    let n = BigInt(id);
    let result = '';
    let surplus = 0n;

    while (n > 0) {
        surplus = n % 62n;
        result += surplus <= 36n ? surplus.toString(36) : (surplus - 26n).toString(36).toUpperCase();
        n /= 62n;
    }
    return result;
}

/**
 * 62進数の文字列を10進数の文字列に復号
 * @param {string} id 符号化した文字列
 * @returns {string}
 */
function decode(id) {
    let power = 0;
    let char;
    let result = 0n;
    let num = 0;

    // id 文字列が空になるまでループ
    while (id) {
        char = id.slice(0, 1);  // 先頭から一文字取得
        id = id.slice(1);       // 先頭の一文字を削除
        if (/[A-Z]/.test(char)) {
            // 大文字の場合、parseInt()で計算できる様に数字を整形する
            num = parseInt(char.toLowerCase(), 36) + 26;
        } else {
            // 小文字や数字の場合は普通に parseInt()
            num = parseInt(char, 36);
        }
        result += BigInt(num * Math.pow(62, power));
        power++;  // 一つ上の桁を計算する
    }
    return result;
}

console.log(encode('106832262987743896'));
// => "axyFLT9iT7"
console.log(decode(encode('106832262987743896')));
// => 106832262987743896n
console.log(encode('106832262987743896').slice(-8));
// => "yFLT9iT7"

これだけ頑張っても上の簡単バージョンに比べて10%程度しか短くならないので、あまり活用されることはないかも…

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
    hidao
    岡山アイス珈琲党総帥。開発環境構築が趣味のカイゼンツール作家。 「人の数だけ普通・常識・当たり前が存在する」と信じるマルチスタンダード主義者。 プライベート用アカウントです。
    qiitadon
    Qiitadon(β)から生まれた Qiita ユーザー・コミュニティです。

    コメント

    console.log(decode(encode('98'))); // 3845n
    

    上記、計算に誤りがあるようです。

    for(let i = -2; i < 100; i++){
      console.log(i, encode(i));
    }
    

    上記で確認したところ、以下のようになりました。

    -2 ""
    -1 ""   // マイナス値で空文字を返すのは仕様?
    0 ""    // 0は0を返すべきでは…
    1 "1"
    2 "2"
    
        :
    
    34 "y"
    35 "z"
    36 "10" // 36はAにならないとおかしいのでは
    37 "B"
    38 "C"
    
        :
    
    60 "Y"
    61 "Z"
    62 "01" // 桁の並びが逆では?
    63 "11"
    64 "21"
    
        :
    
    96 "y1"
    97 "z1"
    98 "101"
    99 "B1"
    

    こんな感じで組んでみました。

    const characters =
      '0123456789' +
      'abcdefghijklmnopqrstuvwxyz' +
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    
    const clength = BigInt(characters.length);
    
    function encode(num) {
      let n = BigInt(num);
    
      // 符号処理
      let sign = '';
      if(n < 0) {
        n = -n;
        sign = '-';
      }
    
      let result = '';
      do {
        result = characters[n % clength] + result;
        n /= clength;
      } while(n);
    
      return sign + result;
    }
    
    function decode(str) {
      // 符号処理
      let sign = '';
      if(str[0] === '-') {
        sign = '-';
        str = str.slice(1);
      }
    
      let result = 0n;
      for(let i = 0; i < str.length; i++) {
        result = result * clength + BigInt(characters.indexOf(str[i]));
      }
    
      return sign + result;
    }
    
    
    console.log(encode('106832262987743896')); // 7Ti9TLFyxa
    console.log(decode(encode('106832262987743896'))); // 106832262987743896
    
    console.log(encode('-106832262987743896')); // -7Ti9TLFyxa
    console.log(decode(encode('-106832262987743896'))); // -106832262987743896
    
    console.log(encode('62')); // 10
    console.log(encode('7043229212415')); // 1ZZZZZZZ
    

    文字種テーブルのcharactersはとりあえず62文字にしてありますが、重複さえしなければ何文字でも対応できると思います。
    文字列ではなく配列で持たせてもそのまま対応できるはず。

    1
    あなたもコメントしてみませんか :)
    ユーザー登録
    すでにアカウントを持っている方はログイン
    0