年会抽奖怕暗箱:写了个随机抽签工具,保证公平

举报
柠檬-夏日清爽 发表于 2026/09/18 09:46:20 2026/09/18
【摘要】 纯前端随机抽签,自己输入名单一键随机抽取,支持滚动动画。技术内核是 Fisher-Yates 洗牌算法——保证每人被抽中的概率完全相等且不重复。

部门抽奖,用 Excel 点名总有人嘀咕"是不是内定"。我自己写个网页抽签:名单自己填,点一下随机出一个,关键是算法得真随机、不重复。简单 Math.random() 取一个其实有隐患,正经该用洗牌算法。

一、为什么不直接 random 取一个

最朴素的写法:

function pick(list) {
  return list[Math.floor(Math.random() * list.length)];
}

抽一个人还行。但要连续抽、且不重复(比如抽三等奖 3 人),这种写法会"同一个人被抽两次"——因为每次都从全集里取,不剔除已中的。

二、Fisher-Yates 洗牌

正确做法先把名单打乱,再按顺序取,这样每个人只会出现一次,且位置完全随机。

function shuffle(arr) {
  const a = [...arr];                       // 复制,不改原名单
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));   // 0~i 随机位
    [a[i], a[j]] = [a[j], a[i]];           // 交换
  }
  return a;
}
function draw(list, n = 1) {
  return shuffle(list).slice(0, n);         // 取前 n 个
}

从后往前,每一步把当前位和前面随机一位交换。数学上能保证每个排列等概率,比"随机取再判断有没有抽过"的写法更干净,也不会陷入重复抽取死循环。

三、滚动动画

image.png

视觉上让名字快速闪动再停,制造"抽奖感":

function roll(list, ms = 1500) {
  const box = document.getElementById('result');
  const t0 = Date.now();
  const id = setInterval(() => {
    box.textContent = list[Math.floor(Math.random() * list.length)];
    if (Date.now() - t0 > ms) {
      clearInterval(id);
      box.textContent = draw(list, 1)[0];   // 停下时给真结果
    }
  }, 60);
}

动画只是表演,最终停下的结果用 draw() 算,跟闪烁无关。

四、几种抽取方式对比

方式 能否不重复 公平性 适用
random 取一个 否 单抽公平 只抽 1 人
标记已抽再 random 能,但可能循环 公平 人多时慢
Fisher-Yates 能 严格等概率 任意场景

小结

抽签别用 Math.random() 反复取一个——连续抽多人会重复命中。正经用 Fisher-Yates 洗牌:从后往前每步与前面随机位交换,数学上保证每个排列等概率、每人只出现一次。复制原数组洗牌避免破坏名单。滚动动画只是表演,最终停在用洗牌结果算出的真值上,跟闪烁无关。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。