ToolAct工具行动

随机抽奖工具

公平公正的随机抽取工具,支持名单抽奖、随机抽签

抽奖设置
允许重复抽取
开启后同一选项可能被多次抽中
总选项数: 0
已抽取: 0
剩余可选: 0
?

什么是随机抽奖?

随机抽取工具会从候选列表中随机选出一个结果,避免由某个人主观决定。它适合课堂点名、会议发言顺序、轻量抽奖、任务分配、午餐选择、团队热身,以及任何需要用中立方式做选择的场景。本页面支持一行一个候选项、滚动动画、本地抽取历史,也可以关闭重复抽取,让已抽中的项目从剩余池中移除。随机性可以在统计意义上减少偏向,但它不会验证身份、不会自动去重输入里的同名项,也不能提供正式审计证据。用于正式抽奖、合规活动、资格核验或奖品发放时,应使用有记录、有规则、有责任人的流程。

使用方法

操作步骤

  1. 在文本框中输入选项,每行一个
  2. 设置要抽取的项目数量
  3. 选择是否允许重复
  4. 点击'开始抽取'按钮
  5. 在右侧查看结果

公平抽取技巧

  • 每个候选人占一行,移除意外的空白或重复项,除非有意允许重复。
  • 对于公开抽取,在开始前决定种子、重复规则和重抽规则,以便结果更容易解释。

使用场景

从粘贴的列表中抽取名称或项目每行输入一个项目,启动滚动抽取动画,使用浏览器加密随机数选出最终结果。最新抽中的项目突出显示,每个已抽中的项目按顺序记录。每次抽取调用 crypto.getRandomValues() 在 [0, n) 整数范围内生成随机数,确保每个剩余名称有精确的 1/n 概率,且中选索引在动画开始前就已确定——这意味着重新运行同一动画会落在同一个中选者上。
运行可重复或不可重复的抽取切换项目是否可以被多次抽中。在不可重复模式下,已抽中的项目从候选池中移除并显示剩余数量,适合抽奖、课堂轮流、家务分配和团队分组。两种模式的公平性属性不同:不可重复保证覆盖性,而可重复抽取在每次转动时保持相等概率,可能合法地连续两次抽中同一个名字。选择与你宣布的规则一致的模式。
跨刷新保持抽取器状态输入列表和抽取记录保存在 localStorage 中,会话可以在意外刷新后恢复。结果可以作为有序列表复制,也可以在新一轮开始时与源列表分开清空。使用专门的「清空记录」按钮在保留候选列表的同时清除历史记录,因为两者存储在不同的键下,清空一个不影响另一个。
使用计数器进行有序的课堂点名切换到不可重复模式,读取抽取历史记录以跟踪哪些学生已经回答过,这样下一次抽中的总是尚未发言的学生。结果下方的剩余数量让你轻松看到一轮何时接近结束,方便教师安排最后几个问题的节奏。重置为可重复模式可用于允许重复的热身快速点名练习。
为小型抽奖导出可审计的中奖名单将抽取历史复制为有序列表,为小型团队抽奖保留每次抽取的时间戳记录。补充源列表、抽取规则和抽取界面的截图,因为 localStorage 是按浏览器和按设备的,不是持久的记录系统。对于付费抽奖、受监管的抽签或合规敏感的活动,应在专为审计和见证设计的系统中运行抽奖,而不是仅在浏览器页面上。

技术原理

随机抽奖基于 Web Crypto API(W3C、WHATWG)构建,具体使用 crypto.getRandomValues(typedArray),这是浏览器中唯一的加密安全随机源。底层实现中,浏览器调用操作系统的 CSPRNG:Windows 上的 CryptGenRandom(Windows 10 起使用 AES-256 CTR-DRBG 模式)、Linux 上的 getrandom(2)(读取与内核 /dev/urandom 相同的基于 ChaCha20 的熵池)、macOS 上的 SecRandomCopyBytes,以及 iOS、Android 和 BSD 上的等效实现。输出适用于密钥生成、nonce、盐值、IV 以及任何预测性会导致安全问题的场景。

“加密级”的区分很重要。V8 中的 Math.random() 使用 xorshift128+(一种快速、低熵的算法),返回 [0, 1) 范围内的 64 位浮点数;恶意行为者观察到少量输出原则上可以重建 128 位状态并预测所有未来的值。SpiderMonkey 和 JavaScriptCore 也是如此。这使得 Math.random() 不适用于任何结果必须不可猜测的场景——公平抽奖、令牌、一次性密码、纸牌游戏发牌、审计抽样。crypto.getRandomValues(typedArray) 是这些场景的唯一选择。

将均匀的 32 位整数映射到 [0, N) 范围内的均匀索引且不产生模偏差,是区分正确与错误实现的第二个工程细节。简单地 idx = randomUint32 % N 是错误的:如果 N 不能整除 2³²,前 2³² mod N 个值的概率略高。正确的算法是拒绝采样:计算 limit = 2³² - (2³² mod N);抽取 32 位值 r;如果 r >= limit,重新抽取;否则返回 r % N。偏差从 O(N / 2³²) 降至零。当 N = 2 或 N 是 2 的幂时模运算是精确的,但拒绝采样是安全的默认方案,平均最多一次重抽(且总是终止)。

抽奖结果使用版本化键存储在 localStorage 中,因此会话可以承受页面重载,用户可以查看抽奖历史。历史记录未加密或签名——它是本地的、非敏感的且易于清除。无重复模式下,算法使用 Fisher-Yates 部分洗牌进行无放回抽取:在 N 个中的第 k 步,将元素 k 与 [k, N) 范围内的均匀随机元素交换,并锁定元素 k 作为已抽取。这是 O(N) 时间、O(1) 额外空间,且每个排列等概率,这是正确的组合保证。(简单的“抽取 N 次并拒绝重复”也是正确的,但 O(N²) 且在长会话中可能卡住。)

对于加权抽取(每个选项有自己的概率),标准算法是带前缀和数组的逆 CDF 采样:构建权重的前缀和,抽取 [0, total) 范围内的均匀实数,二分查找使 prefix[i] >= u 的最小 i。每次抽取 O(log N),前缀和在每次变更时构建一次。别名方法(Walker-Vose)以 O(N) 建立时间为代价实现 O(1) 每次抽取,仅在 N 较大且分布固定时才有价值。
  • 随机源:crypto.getRandomValues(typedArray)——浏览器中唯一的加密安全 RNG,由操作系统级 CSPRNG 支持(Windows 10+ 上的 AES-256 CTR-DRBG、Linux/macOS/iOS/Android 上的 ChaCha20)
  • Math.random() 在 V8 上使用 xorshift128+(其他引擎为变体):快速但可预测。永远不要用于公平抽奖、令牌、OTP 或审计抽样——仅用于视觉噪声、动画或非安全采样
  • 无偏索引映射:使用拒绝采样而非取模。计算 limit = 2³² - (2³² mod N);如果随机 uint32 >= limit,重新抽取;否则返回 r % N。避免经典“模偏差”——短区间索引出现更频繁
  • 无重复抽取使用 Fisher-Yates 部分洗牌,O(N) 时间、O(1) 空间:第 k 步将元素 k 与 [k, N) 范围内的均匀随机元素交换,然后锁定 k。每个排列等概率
  • 加权抽取使用逆 CDF 采样:构建权重前缀和一次,抽取 [0, total) 范围内的均匀实数,二分查找使 prefix[i] >= u 的最小值。每次抽取 O(log N)
  • 抽取历史通过版本化键持久化到 localStorage,会话可在页面重载后恢复。历史仅在本地且非安全敏感——完成后从界面清除
  • Fisher-Yates 与“拒绝法抽取 N 次”对比:两者分布正确,但 Fisher-Yates 总计 O(N)、O(1) 内存,而简单拒绝法最坏情况 O(N²)(长列表末尾大量重复)
  • 输出范围和类型:getRandomValues 接受 Int8Array、Uint8Array、Int16Array、Uint16Array、Int32Array、Uint32Array、BigInt64Array、BigUint64Array——页面始终使用 Uint32Array 供索引映射步骤使用

示例

从候选名单中不重复抽取

输入名单(每行一个):
  Alice
  Bob
  Charlie
  David
  Eve
  Frank

允许重复:关闭    抽取数量:3
结果:
  #1: Charlie
  #2: Alice
  #3: Frank
  (Bob、David、Eve 仍留在候选池)

页面会调用 crypto.getRandomValues() 在 Uint32Array 上生成 [0, n)
范围内均匀分布的整数,n 为剩余候选人数量。每个名字在每次抽取时
都有相同的 1/n 概率,且选中的下标在动画开始前就已经确定。

允许重复并附带时间戳记录

输入名单(每行一个):
  Head
  Tails

允许重复:开启    抽取数量:5
结果:
  #1 (14:23:01): Head
  #2 (14:23:01): Tails
  #3 (14:23:01): Head
  #4 (14:23:01): Head
  #5 (14:23:01): Tails

允许重复模式下,同一项可以连续被抽中,候选池也不会减少。每条记录
都会带上抽取时间,方便用同一份候选名单做多轮抽取,同时保留每次的
时间留痕。抽取记录与候选名单需分别清除——它们存放在 localStorage 的
不同键下。

抽取核心逻辑的 JavaScript 片段

// 使用 Web Crypto(CSPRNG)从 [0, n) 中抽取一个下标
function pickIndex(n) {
  // crypto.getRandomValues 给出均匀分布的 32 位整数;这里直接对 n 取模,
  // 因为名单抽取场景下 n 最多几千,2^32 / n 很大,模偏差可以忽略。
  const buf = new Uint32Array(1);
  crypto.getRandomValues(buf);
  return buf[0] % n;
}

// pickIndex(6) -> 例如 4   (上例中的 Charlie)
// pickIndex(2) -> 0 或 1   (Head 或 Tails)
//
// 注意:当 n 接近 2^32 这样的超大候选池时,应使用拒绝采样消除模偏差,例如:
//   const limit = Math.floor(0xFFFFFFFF / n) * n;
//   let r; do { crypto.getRandomValues(buf); r = buf[0]; } while (r >= limit);

1,000 次抽取的公平性验证

名单:     ['A', 'B', 'C', 'D']    (4 项,期望占比 25.0%)
抽取次数: 1,000    允许重复:开启
实测结果:
  A: 247    (24.7%)
  B: 256    (25.6%)
  C: 248    (24.8%)
  D: 249    (24.9%)

用 crypto.getRandomValues 跑 1,000 次抽取,每项与期望占比的偏差通常
应控制在约 +/- 3 个百分点;偏差更大(例如某一项达到 35%)多半说明
实现里用了 Math.random() 或者模运算用错了。如果用于受监管的抽奖,
应保存原始名单、随机源信息(浏览器/操作系统熵)、抽取时间戳以及
监督方签名——localStorage 不是一个可信的存证系统。

常见问题

抽取结果真的是随机的吗?

是的。选择过程使用 Web Crypto API 的 crypto.getRandomValues,具备密码学级别的随机强度。除非你设置了权重,否则每个选项被抽中的概率完全相等。

同一个选项会连续被抽中两次吗?

会——这正是独立随机抽取的特性。如果有 5 个选项,连续两次抽中同一个的概率是 1/5 = 20%。如果你不希望出现重复,可以开启「抽中后移除」,这样会进行无放回抽取,直到列表清空。

「抽取 N 个」是怎么处理的?

默认采用无放回抽样——在一次 N 个的抽取中,每个项目最多被选中一次。如果允许重复(比如给一个人挑 N 项任务),可以打开「有放回」开关。无放回模式下,N 不能超过选项总数。

可以为选项添加权重以调整概率吗?

部分版本支持为每个选项设置权重(例如 A 权重 3、B 权重 1,则 A 被选中的概率是 B 的 3 倍)。页面会将权重归一化为概率。不设权重时,所有选项概率相等。

为什么这种方式比「凭感觉挑」更公平?

人类其实不擅长真正的随机——我们倾向于避开刚选过的项、过度关注醒目的选项,并不自觉地偏向熟悉的名字。由计算机来抽能消除这些偏差,对参与者来说类似抽奖的决策也会显得更加公正。

我的列表会被保存吗?

部分版本会写入 localStorage,因此在同一浏览器中列表会保留。关闭标签页或换浏览器都会丢失,除非你导出过。任何数据都不会上传。

可以用它进行合法抽奖吗?

不可以。法律意义上的抽奖、彩票和博彩开奖需要可审计的随机程序,往往依赖物理设备或经过认证的随机数生成硬件。普通的网页工具没有相应的文档和审计——只适合公司内部抽奖、朋友间抽签等非正式场合。