重构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} ];
现有代码缺陷分析
- 分组逻辑不全:仅按
fighter1分组,忽略fighter2的参赛身份,导致排期未检查双方的间隔要求 - 间隔控制缺失:未跟踪拳手最后参赛场次,无法确保符合间隔规则
- 场次分配混乱:使用
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);
代码说明
- 全拳手跟踪:使用
lastFightMap记录所有拳手的最后参赛场次,确保双方都符合间隔要求 - 初始分散排期:先为每个拳手安排第一场比赛,避免集中参赛
- 候选筛选机制:优先选择符合间隔规则的比赛,无候选时自动调整到最早合规场次
- 场次号管理:用
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
相关产品推荐
相关产品推荐

