> pick | shuffle | pair <
// 貼上清單,挑選得獎者、打亂順序,或將人員分成群組 - 全部在你的瀏覽器中完成
挑選 N 個隨機項目
從你的清單中抽出任意數量的得獎者。不放回以獲得不重複的得獎者,或可放回以進行加權式抽選。
無偏的 Fisher-Yates 洗牌
使用經典的 Knuth 洗牌重新排列整個清單。每種排列出現的機率相等 - 這裡沒有粗糙的排序小聰明。
分成群組
將名單分成兩人一組、三人一組或任意群組大小。非常適合搭檔作業、程式碼審查輪值或祕密聖誕老人配對。
種子可重現性
提供種子字串即可獲得確定性的抽選。相同的種子加上相同的清單永遠產生相同的結果 - 可分享且可驗證。
// 關於隨機抽選工具
運作方式:
此工具使用 Fisher-Yates(又稱 Knuth)洗牌:從最後一個索引走到第一個,每一步將目前的元素與一個均勻隨機選取的較前索引的元素交換。這會在 O(n) 時間內產生無偏的均勻排列。粗糙的技巧 array.sort(() => Math.random() - 0.5) 會有偏差,因為 JavaScript 的排序比較器必須具備傳遞性 - 隨機比較器違反了這一點並扭曲分布。對於未提供種子的抽選,我們從 window.crypto.getRandomValues 取得熵。當你提供種子字串時,我們用 cyrb53 將其雜湊為 53 位元整數,再驅動 mulberry32 PRNG,因此相同的種子會產生相同且可重現的結果。
演算法:
for i = n-1 down to 1: j = randInt(0, i); swap(a[i], a[j]) - unbiased Fisher-Yates
標準與參考:
- >Web Crypto API(W3C)- crypto.getRandomValues 提供加密等級的強隨機性
- >Fisher-Yates 洗牌(1938),由 Donald Knuth 在 The Art of Computer Programming 第 2 卷中發揚光大
- >mulberry32 - Tommy Ettinger 編寫的小巧、快速且分布良好的種子 PRNG
- >cyrb53 - bryc 編寫的 53 位元字串雜湊,用於將種子字串轉換為數值 PRNG 狀態
常見使用情境:
- >贈品活動、抽獎以及從報名者中選出隨機得獎者
- >課堂上的名字抽選與學生隨機點名
- >運動隊伍配對、搭檔健身與賽事對戰表
- >在工程團隊中分配程式碼審查者
- >A/B 測試樣本選取與隨機化的問卷分組
- >祕密聖誕老人配對、禮物交換與團隊午餐夥伴
- >QA 的隨機測試輸入與模糊測試種子
- >群組決策與平手裁決
- >模擬陪審團選任與隨機化的面試小組
- >為社群活動與獎品舉行的彩券式抽選
相關工具:
- >亂數產生器 — 以文字為基礎的搭配工具:以相同的種子可重現性基本元件從某個範圍挑選數字
- >UUID 產生器 — 當你需要的是獨一無二的不透明 ID,而非從清單中選出的得獎者時,使用隨機的 v4 識別碼
- >密碼產生器 — 可控制字元集的加密隨機字串,用於權杖與憑證
- >randompickerwheel.app — 與此文字抽選工具相輔相成的轉盤介面,非常適合現場觀眾抽選、課堂點名與螢幕揭曉
// 抽選範例
贈品活動 — 從 50 筆報名中挑選 3 名得獎者
> input:
alice@x.com
bob@x.com
carlos@x.com
…(共 50 行)
> config:
模式=Pick N, 數量=3, 允許重複=off, 種子=(空白)
> output:
1. carlos@x.com
2. priya@x.com
3. wei@x.com
為簡報順序打亂 10 個名字的清單
> input:
Alice
Bob
Charlie
Dana
Evelyn
Farouk
Gabriela
Hiroshi
Isabella
Jamal
> config:
模式=Shuffle, 種子=(空白)
> output:
1. Hiroshi
2. Alice
3. Farouk
4. Jamal
5. Dana
6. Evelyn
7. Bob
8. Isabella
9. Gabriela
10. Charlie
將 12 名學生的班級分成每組 2 人
> input:
S01
S02
S03
S04
S05
S06
S07
S08
S09
S10
S11
S12
> config:
模式=Pair Up, 群組大小=2, 種子=class-2026-05-02
> output:
群組 1: S07, S02
群組 2: S11, S04
群組 3: S09, S12
群組 4: S01, S06
群組 5: S03, S10
群組 6: S08, S05
可重現的種子抽選 — 以相同種子執行兩次,得到相同的得獎者
> input:
alpha
bravo
charlie
delta
echo
foxtrot
golf
hotel
> config:
模式=Pick N, 數量=2, 種子=launch-day, 允許重複=off
> output:
第 1 次: 1. echo
2. bravo
第 2 次: 1. echo
2. bravo (相同 — 已用種子)
// 為何粗糙的 .SORT() 會有偏差
常見的一行式洗牌是 arr.sort(() => Math.random() - 0.5)。它看起來優雅、只需一行,而且感覺很隨機 — 但所產生的排列分布遠非均勻。ECMAScript 規範要求比較函式必須具備確定性與傳遞性:若 a < b 且 b < c,則 a < c。擲硬幣式的比較器違反了這項契約,而引擎的排序實作會以不均勻的模式拜訪比較配對。
V8(Chrome、Node)使用 Timsort;較舊的引擎使用 quicksort 或 mergesort。每種實作會拜訪不同的比較配對子集,並以不同方式重用快取的比較結果。正如 Mike Bostock 以實證所示(bost.ocks.org/mike/shuffle/compare.html),粗糙的排序會產生強烈偏差的輸出,其中某些排列出現的頻率是其他排列的好幾倍 - 對於贈品活動、A/B 分桶或任何必須公平的抽選而言都無法接受。
正確的演算法是 Fisher-Yates(Knuth 洗牌):從最後一個索引走到第一個,每一步將目前的元素與從索引 0..i 中均勻隨機選取的元素交換。它以 O(n) 執行,使用 O(1) 的額外空間,並可證明以相等的機率產生每一種排列。
// BAD: NOT uniformly random
const shuffled = arr.sort(() => Math.random() - 0.5);
// GOOD: Fisher-Yates (Knuth) shuffle
function shuffle(arr) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
// 自己實作
想建立自己的抽選工具,或稽核我們的?這些正是本頁面使用的基本元件 - 小巧、無相依套件,並且與你按下 PICK 時在瀏覽器中執行的程式碼完全相同。直接複製到 Node 腳本或其他網頁應用中即可。
// Fisher-Yates shuffle (uniform, O(n))
function shuffle(arr, rng = Math.random) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
// Pick N without replacement (shuffle then slice)
function pickN(arr, n, rng = Math.random) {
if (n > arr.length) {
throw new Error('n exceeds list size');
}
return shuffle(arr, rng).slice(0, n);
}
// mulberry32 seeded PRNG + cyrb53 string hash
// cyrb53: turn a seed string into a 53-bit integer
function cyrb53(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed;
let h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
// mulberry32: tiny seeded PRNG -> [0, 1)
function mulberry32(a) {
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = a;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// Build a seeded picker:
const h = cyrb53('my-seed', 1);
const state = (h ^ Math.floor(h / 4294967296)) >>> 0;
const rng = mulberry32(state);
const winners = pickN(['alice', 'bob', 'carlos'], 2, rng);
>> 常見問題
Q:隨機性是如何產生的?
A:當未提供種子時,我們使用 window.crypto.getRandomValues,這是瀏覽器以 Web Crypto API 為後盾、具加密安全性的亂數產生器。它與用於加密金鑰與權杖的熵來源相同,因此對抽獎與洗牌而言綽綽有餘。當你提供種子時,我們透過 cyrb53 字串雜湊確定性地導出 53 位元狀態,並將其餵入 mulberry32 PRNG,使抽選可以重現。
Q:使用種子能兩次得到相同結果嗎?
A:可以。在 SEED 欄位輸入任何字串 - 日期、競賽名稱、雜湊值,什麼都行。相同的種子搭配相同的輸入清單與相同的模式,永遠會產生完全相同的輸出。對於想要事先公開種子、讓參與者驗證抽選的透明贈品活動,或需要在不同機器間重現的工程輪值,這非常寶貴。
Q:Pick N 與 Shuffle 有何不同?
A:Pick N 從你的清單中選出大小為 N 的子集,並可選擇可放回(允許重複)。Shuffle 則回傳整個清單重新排序後的結果 - 不會丟棄或重複任何項目,只是重新排列。當你只想從眾多報名中選出少數得獎者時使用 Pick N;當每個項目仍需參與但要以新的隨機順序排列時(例如簡報佇列或播放清單)使用 Shuffle。
Q:array.sort(() => Math.random() - 0.5) 真的是隨機的嗎?
A:不,它有偏差。ECMAScript 規範要求排序比較器必須具確定性與傳遞性(若 a < b 且 b < c 則 a < c)。隨機比較器違反這項契約,而不同的 JavaScript 引擎執行不同的底層排序演算法(TimSort、mergesort、quicksort),它們與隨機比較器的互動方式各不相同。結果是不均勻的分布,其中某些排列出現的頻率遠高於其他。Fisher-Yates 才是正確的演算法。
Q:可以為奇數筆數的清單配對嗎?
A:可以。Pair Up 模式以 Fisher-Yates 打亂你的清單,再依你所選的群組大小切分。若總數無法整除,最後一組會較小,並在輸出中清楚標示為剩餘群組。對於奇數清單,你可以接受單獨一筆、增加群組大小,或在抽選前加入像「bye」這樣的佔位名稱。
Q:我的清單會被上傳到任何地方嗎?
A:不會。此工具 100% 在用戶端執行 - 你的清單、種子與輸出永遠不會離開你的瀏覽器。沒有伺服器呼叫、沒有記錄,也沒有對輸入進行任何分析。頁面載入後你可以中斷網際網路連線,每個按鈕仍可運作。重新整理頁面,資料就永遠消失了。
Q:這個工具有視覺化的轉盤版本嗎?
A:若想要具備動畫、音效與盛大視覺揭曉的互動式名字轉盤體驗,請參閱 randompickerwheel.app。它是一個互補產品,著重於現場觀眾抽選與螢幕分享。本頁面是以文字為先的抽選工具,著重於大量清單、可重現的種子抽選、CSV 匯出與配對 - 當你需要快速且確定性的答案而非簡報時非常實用。