「必看」如何开发一个有趣的分组抽签小程序!

1,151 阅读4分钟

cover.png

前言

今天我要和大家分享一个非常有用的js算法:分组抽签。在团队合作开发中,经常需要将团队成员分组,来完成各自的任务。而抽签的方式自然是最公平、最简单的方法之一。

需求分析

在介绍具体的抽签算法之前,我们先来分析一下这个问题的需求。我们需要从团队成员列表中随机抽取若干人,并将这些人分配到不同的组中。那么,如何才能确保分组的公平性呢?

公平性的要求

首先,我们需要确保每个人只会被抽中一次,避免出现某个人被抽到多个组的情况。其次,我们需要尽可能地让每个组的人数相近,以确保各组的工作量基本相同。

算法设计思路

在满足上述需求的前提下,我们可以通过以下算法来进行分组抽签:

  1. 将团队成员列表随机排序。
  2. 根据分组的数量,计算每个组应该包含的人数,记为baseNum。
  3. 依次遍历团队成员列表,将每个人放入对应的组中。具体操作步骤如下:
    • 如果该人已经被分配了组,直接跳过。
    • 如果该人所在的组未满员,将其加入该组,并标记为已分组。
    • 如果该人所在的组已满员,寻找下一个未满员的组,将其加入该组,并标记为已分组。如果没有未满员的组,则随机选择一个非自己所在组的未满员组,将其加入该组,并标记为已分组。

通过以上阐述可知,核心的算法思路是要随机排序、尽量平均分组并避免同一人多次参与。现在我们开始着手编写这个算法。

开始编码

随机排序

为了实现随机排序,我们可以先将团队成员数组进行浅拷贝,然后使用Array.sort()方法来进行排序,代码如下:

function shuffle(array) {
  const arr = [...array]; // 进行浅拷贝
  for (let i = arr.length - 1; i > 0; i--) { // 遍历数组元素
    const j = Math.floor(Math.random() * (i + 1)); // 生成随机下标
    [arr[i], arr[j]] = [arr[j], arr[i]]; // 交换位置
  }
  return arr; // 返回排序后的数组
}

该方法接受一个数组作为参数,返回乱序排列之后的新数组。

尽可能平均分组

为了实现尽量平均分组,我们可以使用Math.ceil()方法来向上取整,保证每个组的人数不少于baseNum。代码如下:

const team = ['张三', '李四', '王五', '赵六', '钱七', '孙八']; // 假设有6名成员
const groupNum = 3; // 定义需要抽几组

const baseNum = Math.ceil(team.length / groupNum); // 计算每组包含的人数

console.log(baseNum); // 输出2

避免同一人多次参与

为了避免同一个人多次参与分组,我们需要给已经分配了组的成员打上已分组的标记,这样在后续循环中就可以跳过已经分配了组的成员。代码如下:

const team = ['张三', '李四', '王五', '赵六', '钱七', '孙八']; // 团队成员数组
const groupNum = 3; // 分组数

const baseNum = Math.ceil(team.length / groupNum); // 计算每组需要包含多少人

const shuffledTeam = shuffle(team); // 乱序排列团队成员 

let assignedCount = 0; // 计数器,记录已分配的人数

let groups = []; // 定义一个空数组,用来存放每个分组的成员

// 遍历随机排序后的团队成员列表,进行分组
shuffledTeam.forEach(member => {
  if (member.assigned) return; // 如果该成员已被分配,则跳过
  for (let i = 0; i < groupNum; i++) {
    const group = groups[i] || []; // 如果当前分组未定义,则将其初始值设为空数组
    if (group.length < baseNum && !group.includes(member)) { // 如果该分组未满员,并且该成员没有被分配到其他组内
      group.push(member); // 将该成员加入当前分组
      member.assigned = true; // 给该成员打上已分组标记
      assignedCount++; // 记录已分配人数
      groups[i] = group; // 将该分组重新赋值给原数组
      break; // 遍历下一个成员
    }
  }
});

在上述代码中,我们使用了一个名为assigned的属性作为已分组标记。每位成员在初始化时都未被分组,因此默认为undefined。当某位成员被分配到某个组内后,我们就会将其assigned属性设置为true,以避免重复分组。

上手练习

接下来让我们来进行一个简单的实现过程和实验,使大家更加深入理解如何实现一个前端抽签小程序。

HTML结构

首先,我们需要先创建一些基本的HTML结构,来让用户输入团队成员信息,并设定分组数量和按钮控制器:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>分组抽签小程序</title>
  <script src="main.js"></script>
</head>
<body>
  <h1>分组抽签小程序</h1>
  <form id="form">
    <div>
      <label for="members">团队成员:</label>
      <textarea id="members" name="members" placeholder="请用英文半角逗号分隔不同的成员"></textarea>
    </div>
    <div>
      <label for="groups">分组数量:</label>
      <input type="number" id="groups" name="groups" value="2" min="2">
    </div>
    <button type="submit">开始抽签</button>
  </form>
  <div id="result"></div>
</body>
</html>

JS代码

在JavaScript中,我们需要编写3个函数来实现这个小程序:一是获取关键DOM元素(getInputs),二是执行抽签操作并输出结果(drawLots),三是事件监听逻辑(handleSubmit)。代码如下:

// 获取form表单输入值的函数
function getInputs(form) {
  const members = form.members.value.trim().split(/[,\n]/).map(m => m.trim()).filter(m => m); // 转换成数组格式,并清理空格和空白行
  const groups = +form.groups.value; // 输入分组数量
  return { members, groups };
}

// 主要操作函数
function drawLots(members, groups) {
  const baseNum = Math.ceil(members.length / groups); // 计算每组包含多少成员
   const shuffledMembers = shuffle(members); // 将成员数组随机排序
  
  let assignedCount = 0; // 计数器,记录已分配的人数
  let result = ''; // 定义一个空字符串,用来存放抽签结果

  let groupsRes = []; // 定义一个空数组,用来存放每个分组的成员

  // 遍历随机排序后的成员列表,进行分组
  shuffledMembers.forEach(member => {
    if (member.assigned) return; // 如果该成员已被分配,则跳过
    for (let i = 0; i < groups; i++) {
      const group = groupsRes[i] || []; // 如果当前分组未定义,则将其初始值设为空数组
      if (group.length < baseNum && !group.includes(member)) { // 如果该分组未满员,并且该成员没有被分配到其他组内
        group.push(member); // 将该成员加入当前分组
        member.assigned = true; // 给该成员打上已分组标记
        assignedCount++; // 记录已分配人数
        groupsRes[i] = group; // 将该分组重新赋值给原数组
        break; // 遍历下一个成员
      }
    }
  });

  // 输出结果
  groupsRes.forEach((group, index) => {
    result += `<h3>第${index+1}组:</h3><ul>`;
    group.forEach(member => result += `<li>${member}</li>`);
    result += `</ul>`;
  });

  document.getElementById('result').innerHTML = result; // 修改DOM节点内容,将抽签结果展示给用户
}

// 监听表单提交事件
function handleSubmit(event) {
  event.preventDefault();

  const form = event.currentTarget;
  const { members, groups } = getInputs(form);

  if (members.length === 0) {
    alert('请输入团队成员!');
    return;
  }

  drawLots(members, groups);
}

document.getElementById('form').addEventListener('submit', handleSubmit); // 挂载监听函数

代码中包括了浅层的代码实现方法,只要牢记三个阶段内容,便可轻松完成这个小程序。

结语

有兴趣的童鞋可以自己动手实验一番,相信会有意想不到的收获!

感谢大家的阅读!