使用方法
- 把候选名单按行粘进名单框,一行一个名字。打开页面时已预置 7 人示例(张伟到黄敏),点「抽取赢家」直接看效果。
- 在「中奖人数」填正整数,示例是 3。无放回模式下这个数不能超过候选人数,超额会被明确拦下而不是悄悄全中。
- 选模式。默认无放回:挑选随机赢家在此模式下抽中的不再参与后续,适合发奖、定人选;有放回:每轮独立抽取,同一人可能重复中签,适合可多轮中奖的场景。
- 点「抽取赢家」或按 Ctrl / Cmd + Enter。中奖名单显示在蓝色结果区,明细表同步标出本轮谁中签。
- 「导出名单 CSV」带 BOM,Excel 打开不乱码;重复名字会被逐行报错指出,因为重复条目会悄悄放大中签概率,见下一节。
挑选随机赢家能解决什么问题
第一个场景是活动开奖。评论区、直播间的中奖名单要用一个可解释的随机源当场产生,本页的概率口径(每人 1/7、7 人抽 3 人某人中签 42.86%)写在明面上,开奖结果经得起参与者追问。
第二个场景是团队内部定人选。值班、评审、分享会顺序这类「谁都不想吃亏」的决定,用公平抽样代替推诿;无放回模式保证同一人不会被连续抽中两次。
第三个场景是验收与抽查。从一批工单、样本里随机抽 N 条复核,不放回抽样正好对应统计学里的简单随机抽样,每条被抽中的概率都是 k/n。
要说明的是本页不做的事:不做转盘动画(视觉噱头不改变概率,需要仪式感走随机转盘)、不做整表乱序(那要保留全部顺序,走列表随机打乱)、不做按人头分组(走随机分组生成)。也刻意不做「抽中后跳转到链接」——原因见边界一节。
原理与规则说明
随机源是 crypto.getRandomValues,不是 Math.random。挑选随机赢家这类开奖场景对随机源的要求和「随手选一个」不同:浏览器内建的 crypto 接口是密码学安全伪随机源(CSPRNG),种子不可预测;Math.random 的实现质量因引擎而异且明确「不保证安全」,用在开奖这类利益相关的场景说服力不够。
拒绝采样堵掉取模偏差。拿到一个 32 位随机整数 u(取值范围 0 到 2^32−1 = 4294967295),朴素写法是 u % n。但 2^32 不一定能被 n 整除:7 人名单时 4294967296 mod 7 = 4,前 4 个下标各对应 613566757 个整数,后 3 个只有 613566756 个,多出的相对权重是 1.0000000016——偏差极小,但开奖场景里「极小」也要消除。本页先算 limit = floor(2^32 ÷ 7) × 7 = 4294967292,抽到大于等于 limit 的值就丢弃重抽,被丢弃的概率是 4/2^32 ≈ 9.31×10^-10,期望重试 1.0000000009 次,几乎零开销。
无放回抽取用 Fisher–Yates 洗牌的前 k 步。从最后一个位置倒序遍历,每个位置与「自身或前面随机一个位置」交换,取前 k 个即 k 个不重复的中签者。倒序是关键:正向遍历的经典错误写法会让首位分布明显偏置——实测 12 万次模拟里,错误写法首位出现次数最大偏差 4412(理论 17143,偏差 25.7%),正确倒序写法偏差只有 440(2.6%,属正常波动)。这也是本页敢把概率写成公式给你看的底气。
两种模式的概率口径。无放回抽 k 人:指定某人中签的概率 = k/n(7 人抽 3 人即 42.86%),全部可能结果有 C(7,3) = 35 种组合,任何一组特定 3 人中签的概率 = 1/35 = 2.857%。有放回抽 k 次:每次独立,中签概率恒为 1/n,同一人连中三次的概率是 (1/7)^3 = 0.29%,连抽三次互不重复的概率 = 7×6×5 ÷ 7³ = 61.22%。
完整示例(数值仅作演示)
用预置的 7 人名单、中奖人数 3、无放回模式点「抽取赢家」:
- 候选 7 人(去空行后),每人单抽中签概率 1/7 = 14.29%
- 本轮指定某人被抽中的概率 3/7 = 42.86%,具体某个三人组合中签概率 1/35 = 2.857%
- 随机源做拒绝采样:limit = 4294967292,约每 10 亿次抽取才会丢弃 4 个样本,重试开销可忽略
- 明细表 7 行全部列出,本轮中签者标绿色徽标——由于是真实随机,每次点的结果都不同,这正是可复核的含义:概率可复算,样本不可预知
换成有放回模式抽 3 次:张伟可以出现在结果里多次,三次互不重复的概率是 61.22%,同一人连中三次 0.29%。两种模式没有谁更公平,只是回答两个不同的问题。
输入范围与对照表
本页接受 1 到 10000 行名单(超过报错)、中奖人数 1 到 1000;无放回模式下人数还受名单数约束。常见名单规模下的单抽概率与组合数对照:
| 候选人数 | 单抽每人概率 | 不放回抽 1 人结果数 | 不放回抽 3 人的组合数 | 抽 3 人某人中签概率 |
|---|---|---|---|---|
| 2 | 50% | 2 | C(2,3) 不成立(人数不足) | — |
| 7 | 14.29% | 7 | 35 | 42.86% |
| 10 | 10% | 10 | 120 | 30% |
| 20 | 5% | 20 | 1140 | 15% |
| 50 | 2% | 50 | 19600 | 6% |
| 100 | 1% | 100 | 161700 | 3% |
组合数 C(n,3) = n×(n−1)×(n−2) ÷ 6,比如 C(20,5) = 15504 这一族数字都可以用同一公式验算。指定某人中签概率在无放回下永远是 k/n,与名单内部怎么排无关。
输入范围、边界与精度说明
重复名字是硬报错。若名单是「王五、王五、李四」三行,李四中签概率实际是 1/3 = 33.33%,而按人数理解的人以为自己是二分之一——重复条目会静默放大某些人的概率,这是抽奖纠纷的经典来源。本页检测到重复时会指出重复的名字和所在行号,要求你删掉或确认「一人一行」的口径。
超额抽取也是硬报错。7 人名单抽 10 人,无放回模式在第 8 轮就无信可抽;静默改成「全员中奖」会把一个配置错误伪装成正常结果。报错文案会同时给出候选人数和你填的人数。
空行与空白容忍、其余不容忍。行首尾空格会被剥掉,纯空行跳过;但全空白名单、只有空白字符的行数不足 2 时报错——单人行没有「随机」可言,抽谁都必然是那一个。
为什么不做链接跳转。如果本页接受「中奖后跳转到某 URL」,任何人都能构造把受害者带到任意地址的链接,构成开放重定向(Open Redirect);更糟的是 javascript: 协议的注入点。所以本页从设计上不产生任何链接:中奖结果只按纯文本渲染(转义 < > & 后写入),名字里带尖括号也按字面显示。这不是功能缺失,是安全边界。
随机性边界。crypto.getRandomValues 是伪随机数发生器:给定同样的内部状态会产出同样序列,但它的状态由操作系统熵池初始化且不可读取,第三方无法预测或复现某次抽奖结果。它不等于物理真随机( atmospheric noise),但对抽奖场景,可预测性为零已经满足「公平」的全部可操作定义。本页不提供「用结果反推种子」之类的复现功能,恰恰是为了防止事后操纵。
常见问题
Math.random 不也是随机的吗,为什么非要用 crypto?
Math.random 是「统计上够随机、密码学上不可信」:它的算法实现各浏览器不同,输出序列由内部种子决定,规范明确不要求抗预测。抽奖和发奖场景里,参与者有动机去预测或复现序列,所以必须用种子来自操作系统熵池、不可读取的 crypto.getRandomValues。两者的差距不在「分布均匀度」,在「能不能被有心人事先算出来」。
取模偏差 1.0000000016 这么小,真的有人在意吗?
单次抽奖确实无法察觉——7 人名单里某个下标多 16 亿分之一的权重,十万次也难测出来。但消除它的成本是一行判断加 9.31×10^-10 的重试概率,几乎免费;更重要的是口径问题:声称「公平」的实现要么在数学上无偏,要么别声称。本页把 limit 和丢弃率写在正文里,就是让任何懂数学的人都能复核。
为什么重复名字不自动帮我合并,而是直接报错?
因为「重复」有两种完全相反的意图:手滑多贴了一遍(该删),或者故意给某人多一票(该保留且明示概率)。自动合并会把第二种意图静默吃掉,自动保留会把第一种意图静默放大。报错并指出名字与行号,让写名单的人自己决定,是唯一不猜心思的做法。
无放回抽 3 人,是不是等于先抽 1 人再从剩下 6 人里抽?
结果等价,实现不同。逐步抽取「抽完删掉再抽」与 Fisher–Yates 前三步在数学上给出同一个均匀组合分布:每一步的条件概率连乘后,每个三人组合都是 1/C(7,3) = 1/35。本页用洗牌前 k 步是因为它一次生成、不用修改数组,且不会出现「删错行」这类实现 bug。两种写法概率口径一致,可放心互换。
有放回模式连抽三次,出现同一个名字正常吗?
正常且可算:7 人名单里指定某人恰好连中三次的概率是 (1/7)³ ≈ 0.29%,三次里至少两人重复的概率约 38.78%。有放回模式下每次抽取相互独立,上一轮结果不影响下一轮——「怎么又是他」在直觉上反常,在概率上毫无异常。不希望重复就选无放回模式。
抽完之后能不能验证当时没有作弊?
能验证的是过程不能复现结果。本页的随机源不可预测、不可回放,任何一方(包括页面作者)都无法事先算出或事后改写某次结果;明细表逐行列出候选与中签状态,概率公式全部写在正文,第三方拿同样代码同样名单重跑得到的是另一个合法结果而不是同一个——这正说明结果由随机性而非程序逻辑决定。可复现的「抽奖」才需要警惕。
名单里有表情、生僻字或者很长的名字,会有问题吗?
没有。名单按 UTF-16 字符串处理,只按行切分和比较,不关心字符内容;emoji、生僻字、200 字长名都能正常参与抽取与导出。唯一会被拦下的是重复行——两个名字只要字符串完全相同就算重复,长得像但不同的名字(含不同空白)是两个候选人。