かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript よくあるランダムな文字列を生成するやつのメモ

ハンズオンとかチュートリアルでよく出てくる欄ラムな文字列を作る処理をちゃんとみてみようと思った

export const getRandomString = (n: number): string => {
  const S = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

  return Array.from(crypto.getRandomValues(new Uint32Array(n)))
    .map((v) => S[v % S.length])
    .join('');
};

こういうやつ。

長さ n のランダムな値を作成する

Array.from(crypto.getRandomValues(new Uint32Array(n)));

Array.from は Array like なものを配列に変換するので、crypto.getRandomValues(new Uint32Array(n)) が Array Like なオブジェクトを作成している

crypto.getRandomValues()

Crypto.getRandomValues() メソッドは、暗号強度の強い乱数値を取得します。引数に与えた配列は、すべて乱数 (暗号的な意味でランダムに) で埋められます。
window.crypto.getRandomValues(typedArray);
cf. Crypto.getRandomValues() - Web API | MDN

  • 配列をランダムな値で埋める
  • 引数は TypedArray オブジェクトである必要がある

new Uint32Array(n)

Uint32Array は型付き配列で、プラットフォームのバイト順による 32 ビット符号なし整数値の配列を表します。バイト順の制御が必要な場合は、代わりに DataView を使用してください。中身は 0 で初期化されます。
cf.
Uint32Array - JavaScript | MDN
TypedArray - JavaScript | MDN

  • 0 ~ 4294967295 (32 ビット長、符号なし整数値) の値を入れることができる長さ n の配列を作る
  • 配列の初期値は 0 になっている

👇

const n = 4;
// 値が n 個の TypedArray オブジェクトを作成 (値の範囲は Uint32)
const typedArray = new Uint32Array(n);
// => Uint32Array(4) [0, 0, 0, 0, buffer: ArrayBuffer(16), byteLength: 16, byteOffset: 0, length: 4, Symbol(Symbol.toStringTag): 'Uint32Array']

// typedArray の値を Uint32Array の範囲 0 〜 4294967295 のランダムな値で埋める
const cryptoArray = crypto.getRandomValues(typedArray);
// => Uint32Array(4) [2918305009, 3973388824, 2771620870, 1227775043, buffer: ArrayBuffer(16), byteLength: 16, byteOffset: 0, length: 4, Symbol(Symbol.toStringTag): 'Uint32Array']

// TypedArray オブジェクトを配列に変換する
const randomArray = Array.from(cryptoArray);
// => [2918305009, 3973388824, 2771620870, 1227775043]

ランダムな数字の配列からランダムな文字列を生成する

x % b は 0 〜 (b-1) になる

除算のあまりは必ず 0 〜 (割る数 - 1) の範囲になるので、割る数を配列の長さにすれば配列の index の範囲に収まる値になる a = x % array.length => a = 0 - (array.length - 1) なので array[x % array.length] で配列のいずれかの値が取得できる

JavaScript の文字列は配列のようにアクセスが可能

const str = 'Hoshimiya Ichigo';
str.length; // => 16
str[0]; // => 'H'
str[10]; // => 'I'
str[(str.length - 1)]; // => 'o'

なので、String[n % String.length] は文字列のいずれかの文字が返される ( 𩸽 とか emoji のサロゲートペアが含まれない文字列の前提)

.map((v) => S[v % S.length]) の部分は 0 ~ 4294967295 のランダムな数が入った配列の値 1つ1つを文字列 S の中にあるいずれかの文字に置き換えている。最後にその配列を .join('') で文字列に結合するので文字列 S に含まれる文字を使った文字列長 n のランダムな文字列が作成できる。
👇

const S = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

const n = 4;
const typedArray = new Uint32Array(n);
const cryptoArray = crypto.getRandomValues(typedArray);
const randomArray = Array.from(cryptoArray);
// => [2918305009, 3973388824, 2771620870, 1227775043]

// 0 ~ 4294967295 の値を一つづつ S に含まれる文字に変換
const randamStrArray = randomArray.map((v) => {
  console.log(v, '>>', v % S.length);
  return S[v % S.length];
});
// 2918305009 >> 39
// 3973388824 >> 32
// 2771620870 >> 26
// 1227775043 >> 17

randamStrArray;
// => ['N', 'G', 'A', 'r']

// 文字列結合
randamStrArray.join('');
// => NGAr

👇 関数化

export const getRandomString = (n: number): string => {
  const S = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

  return Array.from(crypto.getRandomValues(new Uint32Array(n)))
    .map((v) => S[v % S.length])
    .join('');
};


getRandomString(16);
// => '5pEM2KSpOIZ0f4tj'
getRandomString(16);
// => 'kqSW7uHTvFf4q84u' (実行の度に異なる値が返される)
getRandomString(32);
// => 'YHmAwSm3V3ey6iHqsUgGRAmAz2lkLQU9'
所感

普段自分でやってると npm にある uuid とか使っちゃっうから、ハンズオンとかで出てきたらコピペで済ませてたけど、プログラミングスクールのお手伝いで説明する側になったの何をしてるのかちゃんと見ておこうという気分になったのでした。

処理を分解して、一つ一つ見ていけば何をしているのか理解しやすいですね。
crypto.getRandomValues とか new Uint32Array とか完全に理解したわけじゃないけど、雰囲気はつかめました。

₍ ᐢ. ̫ .ᐢ ₎ おわり。


[参考]

マグメル深海水族館おもしろいです