🉑

TypeScriptの簡易ランダム英数文字列生成

2024/09/10に公開


TypeScript(JavaScript)でHTMLタグのid属性に使用できる簡易的なユニークな英数ランダム文字列生成機が欲しくなり実装した備忘メモです。

前提

  • 英数文字列で構成される任意長の文字列を生成する
    • ただし最初の文字は数字にしない
  • 大文字と小文字は区別する
  • 実装の簡単化のためz|Zは含まない
  • 文字列長は指定可能にする
  • 必要に応じてユニーク保証回数の制限を設定可能にする

実装

code
class HtmlIds {
  #limit = Number.POSITIVE_INFINITY;
  #length;
  #store = new Set<string>();
  constructor(len: number, limit: number = 0) {
    if (limit > 0) { this.#limit = limit; }
    this.#length = len;
  }
  get(): string {
    if (this.#store.size > this.#limit) { this.#store.clear(); }
    let id = HtmlIds.#gen(this.#length);
    while (this.#store.has(id)) { id = HtmlIds.#gen(this.#length); }
    this.#store.add(id);
    return id;
  }

  static #gen(len: number): string {
    function getRandChar(nodigit: boolean) {
      const ALPHABETIC = [[65, 25],[97, 25]];  // [A-Y],[a-y]
      const ALPHANUMERIC = [...ALPHABETIC, [48, 10]];  // [A-Y],[a-y],[0-9]
      const baseAry = nodigit ? ALPHABETIC : ALPHANUMERIC;
      const [base, rate] = baseAry[Math.trunc(Math.random() * baseAry.length)];
      return Math.trunc(base + (Math.random() * rate));
    }
    return String.fromCharCode(...Array(len).fill(null).map((_,i) => getRandChar(!i)));
  }
}

使い方

  1. 生成長と必要に応じて回数制限を指定しインスタンス化する
const genId = new HtmlIds(4);
const genIdWithLimit = new HtmlIds(5, 2000);
  1. 必要な時に取得する
console.log(genId.get());
// "jKub"
console.log(genIdWithLimit.get());
// "CB87n"

簡単な解説

get(): string {
  if (this.#store.size > this.#limit) { this.#store.clear(); }
  let id = HtmlIds.#gen(this.#length);
  while (this.#store.has(id)) { id = HtmlIds.#gen(this.#length); }
  this.#store.add(id);
  return id;
}
  • this.#store(Set)を用いてユニークを担保
  • 結果重複時は再生成する
  • 生成数が制限を超えた場合Setをリセット

static #gen(len: number): string {
  function getRandChar(nodigit: boolean) {
    const ALPHABETIC = [[65, 25],[97, 25]];  // [A-Y],[a-y]
    const ALPHANUMERIC = [...ALPHABETIC, [48, 10]];  // [A-Y],[a-y],[0-9]
    const baseAry = nodigit ? ALPHABETIC : ALPHANUMERIC;
    const [base, rate] = baseAry[Math.trunc(Math.random() * baseAry.length)];
    return Math.trunc(base + (Math.random() * rate));
  }
  return String.fromCharCode(...Array(len).fill(null).map((_,i) => getRandChar(!i)));
}
  • baseAryMath.randomを用いて文字2種or3種の中のどれにするか選択
  • 文字種の最初のasciiコードからMath.randomでオフセットを計算
  • String.fromCharCodeでasciiコードを文字列化
    • 1文字目(インデックス0)はnodigittrueになる

  • なぜzを含まないか
    • 26文字にするとこの方法では確率に偏りが出るため
    • 手動設定時でもzを含めると確実にユニークになるため
  • 組み合わせ数参考
文字数 組み合わせ数
3 180,000通り (50*60*60)
4 10,800,000通り (50*60*60*60)
5 648,000,000通り (50*60*60*60*60)
6 38,880,000,000通り (50*60*60*60*60*60)

おまけ

UUID生成

function genUUIDv4(): string {
  return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16));
}
console.log(genUUIDv4());
// "606c8fc6-a04e-46df-9935-8ebb1d991bca"
console.log(crypto.randomUUID());
// "c9d6fd0f-ffbe-49d9-8c60-554b5e9e575e"

参考文献

Discussion