Communityコーディング&開発github.com

Chibuchibs/free-claude-skills

シャッフル、ランダム選択、偏りのある確率を検出し、分布を適切にテストします。コードが配列をシャッフルする、クイズの回答やオプションをランダム化する、サブセットをランダム選択する、確率を割り当てる、戦利品や報酬を生成する、あるいは結果が「不正操作された」「いつも同じものを選ぶ」と言われた場合や、シャッフルやランダム化の方法を尋ねられたときに使用します。

free-claude-skills とは?

これは、JavaScriptコードにおける最も一般的なランダム化バグである、`Array.sort()` と `Math.random()` の不適切な組み合わせ(`items.sort(() => Math.random() - 0.5)`)を検出・修正するスキルです。この誤ったシャッフルはソートの一貫性を破壊し、偏った順列を生成します。具体的には、クイズの選択肢をシャッフルする際に、最初のオプションが選ばれる確率が約28.3%と、本来の25%より高くなるなど、結果に統計的な偏りが生じます。スキルは、正しいFisher–Yates(フィッシャー–イェーツ)シャッフルアルゴリズムの実装を提供し、さらに分布テストの自動実行や、表示される確率とコードの実際のロール結果が一致しているかの検証を支援します。Claude Code、Coder、CursorなどのAIエージェントがコードレビューや生成時にこの問題を自動的に指摘・修正できるように設計されています。また、正当なノイズ(例:物理エンジンのジッター)と誤ったシャッフルを区別するためのパターンマッチングの注意点も含んでいます。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Chibuchibs/free-claude-skills/tree/HEAD/skills/randomness-correctness

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

シャッフルではないもの

JavaScriptにおける最も一般的なランダム化バグ:

const shuffled = items.sort(() => Math.random() - 0.5);   // 間違い

これは一見問題なく見え、あらゆる場所で使われています。しかし、これはシャッフルではありません。ソートの比較関数は一貫性が求められますが、これはそうではないため、エンジンのソートは偏った順列を生成します。

4つのクイズ選択肢で測定した結果:

位置正解がそこに配置される確率
028.3%
128.2%
224.7%
318.8%

一様ならそれぞれ25%のはずです。実際には、常に最初の選択肢を選ぶ方がランダムに選ぶよりも良い結果が出ました。これに気づいたプレイヤーは有利になり、クイズは静かに不公平になっていました。

修正方法

Fisher–Yatesシャッフルを、ヘルパー関数として一度だけ実装します:

function shuffle(a) {
  const out = [...a];
  for (let i = out.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [out[i], out[j]] = [out[j], out[i]];
  }
  return out;
}

そして、すべての該当箇所を置き換えます。このバグは通常、複数回出現します。あるコードベースでは11箇所で見つかりました。クイズの選択肢のシャッフル、問題の選出、提供されるアイテムの選択、メニューの順序付けなど、それぞれが少しずつ偏りを生んでいました。

パターンマッチで一律に置き換えないでください。 (Math.random() - 0.5) は物理演算のノイズやジッターとして正当に使用されることもあります:

velocity += (Math.random() - 0.5) * 0.012;   // これはそのままに

各該当箇所が実際にソートの比較関数であることを確認してください。

分布をテストする

ランダム性を目視で確認しないでください。カウントしましょう:

const pos = [0,0,0,0], N = 200000;
for (let i = 0; i < N; i++) {
  const order = shuffle(['a','b','c','d'].map((t,i) => [t,i]));
  pos[order.findIndex(o => o[1] === 0)]++;
}
pos.forEach((c,i) => console.log(i, (c/N*100).toFixed(1) + '%'));

各位置の割合が約3パーセントポイント以上広がっている場合、何かが偏っていることを意味します。

テストには適切な生成器を使用してください。 手作りの線形合同法生成器では1~2ポイントの「誤差」が生じ、実際のバグのように見えましたが、それはテスト自身の問題でした。Math.random() は分布の確認には十分に一様です。カスタム生成器は再現可能なシードが必要な場合に温存し、別途検証してください。

表示する確率を確認する

どこかに確率を表示する場合(パーセンテージ、プログレスバー、成功確率など)、それがコードが実際にロールする値と一致していることを確認してください。実際のロールを数十万回シミュレートして比較します:

cred   0  shown 5.0%/40.0%/55.0%   actual 5.0%/40.0%/55.0%
cred 100  shown 40.0%/40.0%/20.0%  actual 40.0%/40.1%/20.0%

最大誤差が0.5パーセントポイント未満であれば、表示は嘘をついていません。結果が反映されない表示確率は、何も表示しないより悪いです。なぜなら、人々はそれに基づいて意思決定を行うからです。

ついでに確認すること

表示された目標が実際に達成可能かどうか確認してください。カウンターが「3/5」と表示している場合、プレイヤーには5が目標であることを示しています。もし5が発生する確率が0.17%しかない場合、インターフェースは実質的に決して起こらないことを約束していることになります。表示するすべてのマイルストーンの実際の確率を計算してください。

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

関連スキル