You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重构JavaScript格斗赛事排期函数,实现拳手3-4场参赛间隔

拳手赛事排期重构:实现3-4场参赛间隔要求

需求概述

需实现赛事排期逻辑,核心规则:拳手在上一场比赛后,必须间隔3-4场比赛才能再次参赛(即两次参赛的场次编号差需满足3-4,中间包含2-3场其他比赛)。

现有问题

当前实现生成的排期存在两类缺陷:

  • 间隔过小:例如拳手paul在fightNumber1后直接参加fightNumber2,违反连续参赛禁止规则
  • 间隔过大:部分拳手的参赛间隔可达40场,远超需求范围

原始输入数据

const data = [
  {"id":"1","fighter1":"paul","fighter2":"anna","fightNumber":1},
  {"id":"2","fighter1":"jack","fighter2":"paul","fightNumber":2}, // 错误:连续参赛,需间隔3-4场
  {"id":"3","fighter1":"roger","fighter2":"law","fightNumber":3},
  {"id":"4","fighter1":"lee","fighter2":"law","fightNumber":4},
  {"id":"5","fighter1":"law","fighter2":"paul","fightNumber":5},
  {"id":"6","fighter1":"roger","fighter2":"anna","fightNumber":6},
  {"id":"7","fighter1":"lee","fighter2":"jack","fightNumber":7},
  {"id":"8","fighter1":"roger","fighter2":"anna","fightNumber":8},
  {"id":"9","fighter1":"lee","fighter2":"jack","fightNumber":9}
];

目标排期示例

paul的比赛为fightNumber1和4,间隔3场比赛,符合规则:

const new_result= [
  {"id":"1","fighter1":"paul","fighter2":"anna","fightNumber":1},
  {"id":"2","fighter1":"jack","fighter2":"paul","fightNumber":4},
  {"id":"3","fighter1":"roger","fighter2":"law","fightNumber":2},
  {"id":"4","fighter1":"lee","fighter2":"law","fightNumber":6},
  {"id":"5","fighter1":"law","fighter2":"paul","fightNumber":7},
  {"id":"6","fighter1":"roger","fighter2":"anna","fightNumber":5},
  {"id":"7","fighter1":"lee","fighter2":"jack","fightNumber":3},
  {"id":"8","fighter1":"roger","fighter2":"jack","fightNumber":8},
  {"id":"9","fighter1":"lee","fighter2":"anna","fightNumber":9}
];

现有代码缺陷分析

  1. 分组逻辑不全:仅按fighter1分组,忽略fighter2的参赛身份,导致排期未检查双方的间隔要求
  2. 间隔控制缺失:未跟踪拳手最后参赛场次,无法确保符合间隔规则
  3. 场次分配混乱:使用result.length+1直接分配场次,用0.5增量处理冲突,既不符合整数场次要求,也无法控制间隔

重构后的解决方案

function scheduleFights(originalData) {
  // 复制原始数据避免修改原数据
  const data = [...originalData];
  // 记录每个拳手最后一次参赛的场次号
  const lastFight = new Map();
  // 最终排期结果
  const result = [];
  // 已使用的场次号集合
  const usedNumbers = new Set();

  // 按拳手分组,包含fighter1和fighter2所有参赛人员
  const fighterMatches = new Map();
  data.forEach(match => {
    [match.fighter1, match.fighter2].forEach(fighter => {
      if (!fighterMatches.has(fighter)) {
        fighterMatches.set(fighter, []);
      }
      fighterMatches.get(fighter).push(match);
    });
  });

  // 先安排每个拳手的第一场比赛,分散初始排期
  const firstMatches = [];
  fighterMatches.forEach((matches, fighter) => {
    if (matches.length > 0) {
      const match = matches.shift();
      match.processed = true;
      firstMatches.push(match);
    }
  });

  firstMatches.forEach((match, idx) => {
    const fightNumber = idx + 1;
    result.push({...match, fightNumber});
    usedNumbers.add(fightNumber);
    [match.fighter1, match.fighter2].forEach(fighter => {
      lastFight.set(fighter, fightNumber);
    });
  });

  // 处理剩余比赛
  while (data.some(match => !match.processed)) {
    // 筛选符合间隔要求的候选比赛
    const candidates = data.filter(match => {
      if (match.processed) return false;
      const f1Last = lastFight.get(match.fighter1) || 0;
      const f2Last = lastFight.get(match.fighter2) || 0;
      const minAvailable = Math.max(...usedNumbers) + 1;
      // 检查间隔是否符合3-4场要求(场次编号差3-4)
      const f1Valid = minAvailable >= f1Last + 3 && minAvailable <= f1Last + 4;
      const f2Valid = minAvailable >= f2Last + 3 && minAvailable <= f2Last + 4;
      return (f1Last === 0 || f1Valid) && (f2Last === 0 || f2Valid);
    });

    if (candidates.length === 0) {
      // 无符合候选时,安排最早可参赛的比赛
      const earliestMatch = data.filter(match => !match.processed).reduce((prev, curr) => {
        const f1Last = lastFight.get(curr.fighter1) || 0;
        const f2Last = lastFight.get(curr.fighter2) || 0;
        const prevReady = Math.max(lastFight.get(prev.fighter1) || 0, lastFight.get(prev.fighter2) || 0) + 3;
        const currReady = Math.max(f1Last, f2Last) + 3;
        return currReady < prevReady ? curr : prev;
      });
      const fightNumber = Math.max(Math.max(...usedNumbers) + 1, Math.max(lastFight.get(earliestMatch.fighter1) || 0, lastFight.get(earliestMatch.fighter2) || 0) + 3);
      result.push({...earliestMatch, fightNumber});
      usedNumbers.add(fightNumber);
      [earliestMatch.fighter1, earliestMatch.fighter2].forEach(fighter => {
        lastFight.set(fighter, fightNumber);
      });
      earliestMatch.processed = true;
    } else {
      // 随机选择一个候选安排(可改为按原顺序)
      const selected = candidates[Math.floor(Math.random() * candidates.length)];
      const fightNumber = Math.max(...usedNumbers) + 1;
      result.push({...selected, fightNumber});
      usedNumbers.add(fightNumber);
      [selected.fighter1, selected.fighter2].forEach(fighter => {
        lastFight.set(fighter, fightNumber);
      });
      selected.processed = true;
    }
  }

  // 按场次号排序输出
  return result.sort((a, b) => a.fightNumber - b.fightNumber);
}

// 使用示例
const data = [
  {"id":"1","fighter1":"paul","fighter2":"anna","fightNumber":1},
  {"id":"2","fighter1":"jack","fighter2":"paul","fightNumber":2},
  {"id":"3","fighter1":"roger","fighter2":"law","fightNumber":3},
  {"id":"4","fighter1":"lee","fighter2":"law","fightNumber":4},
  {"id":"5","fighter1":"law","fighter2":"paul","fightNumber":5},
  {"id":"6","fighter1":"roger","fighter2":"anna","fightNumber":6},
  {"id":"7","fighter1":"lee","fighter2":"jack","fightNumber":7},
  {"id":"8","fighter1":"roger","fighter2":"anna","fightNumber":8},
  {"id":"9","fighter1":"lee","fighter2":"jack","fightNumber":9}
];

const newResult = scheduleFights(data);
console.log(newResult);

代码说明

  1. 全拳手跟踪:使用lastFight Map记录所有拳手的最后参赛场次,确保双方都符合间隔要求
  2. 初始分散排期:先为每个拳手安排第一场比赛,避免集中参赛
  3. 候选筛选机制:优先选择符合间隔规则的比赛,无候选时自动调整到最早合规场次
  4. 场次号管理:用usedNumbers确保场次唯一,最终按场次号排序输出

规则微调说明

若需严格实现“间隔3-4场比赛”(即两次参赛之间有3-4场其他比赛),可将间隔判断条件改为:

const f1Valid = minAvailable >= f1Last + 4 && minAvailable <= f1Last + 5;
const f2Valid = minAvailable >= f2Last + 4 && minAvailable <= f2Last + 5;

内容的提问来源于stack exchange,提问作者Ulquiorra Schiffer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 06:32:16