羽球搭子 HarmonyOS 实战(12):双打配对算法与公平性约束
一、公平不是“每轮随机一次”双打排阵看似只要每四个人组成一场实际同时受场地数、参与人数和轮次数约束。完全随机会出现同一名球友连续上场、同一搭档频繁组合、另一组球友反复成为对手。只让每个人上场次数相同也不能保证组合有变化。可落地的公平目标至少包含三项上场次数差尽量小、搭档重复惩罚更高、对手重复惩罚较低。前一项决定谁进入本轮后两项决定入选人员如何分组。把“选人”和“排位”分成两个阶段后算法的行为更容易解释和验证。二、先判断场次能否均分一轮最多使用场地数 × 4名球员参与者不足时则全部上场。若每轮上场人数乘以轮数不能整除总人数就不可能让每个人的上场次数完全一致。页面可以提前提示并给出可均分的推荐轮数。export class PairingAlgorithm { static canDistributeEvenly( playerCount: number, courtCount: number, roundCount: number ): boolean { const playersPerRound Math.min(courtCount * 4, playerCount) return (playersPerRound * roundCount) % playerCount 0 } static recommendedRounds( playerCount: number, courtCount: number, maxRound: number ): number[] { const result: number[] [] for (let round 1; round maxRound; round) { if (this.canDistributeEvenly(playerCount, courtCount, round)) { result.push(round) } } return result } }参与者场地每轮上场4 轮总席位能否均分82832可以每人 4 次102832不可以122832不可以1231248可以每人 4 次这个判断只负责说明“理论上能否完全均分”不能代替后续的搭档与对手优化。三、按上场次数选择本轮人员每轮先把所有索引按已上场次数升序排列再按原始顺序打破同分。这样休息较多的人优先进入下一轮选择过程稳定且可复现。function selectPlayers( playerCount: number, playersPerRound: number, playCounts: number[] ): number[] { const indices: number[] [] for (let index 0; index playerCount; index) { indices.push(index) } indices.sort((left: number, right: number) { if (playCounts[left] ! playCounts[right]) { return playCounts[left] - playCounts[right] } return left - right }) return indices.slice(0, playersPerRound) }使用索引而不是姓名作为历史矩阵下标可以把查询从字符串匹配收敛成数组访问。姓名只在生成最终比赛对象时写回。四、搭档重复需要更高权重四个位置依次组成 A 队两人和 B 队两人。评分时A 队与 B 队内部是搭档关系跨队是对手关系。搭档重复比对手重复更影响体验因此给予三倍惩罚。function overlapScore( arrangement: number[], courtCount: number, partner: number[][], opponent: number[][] ): number { let score 0 for (let court 0; court courtCount; court) { const start court * 4 if (start 3 arrangement.length) break const p0 arrangement[start] const p1 arrangement[start 1] const p2 arrangement[start 2] const p3 arrangement[start 3] score partner[p0][p1] * 3 score partner[p2][p3] * 3 score opponent[p0][p2] score opponent[p0][p3] score opponent[p1][p2] score opponent[p1][p3] } return score }权重不是绝对标准但必须显式。若俱乐部更在意对手多样性可以把对手惩罚提高若水平分组优先还可以加入实力差和性别组合项。五、有限候选搜索替代穷举八名球员的排列已有 40320 种多场地和更多参与者会迅速扩大搜索空间。移动端没有必要穷举全部方案可以对入选人员生成有限数量的确定性洗牌候选保留评分最低的排列。function bestArrangement( selected: number[], roundIndex: number, courtCount: number, partner: number[][], opponent: number[][] ): number[] { let best selected.slice() let bestScore overlapScore(best, courtCount, partner, opponent) const attempts Math.max(60, selected.length * selected.length) for (let turn 1; turn attempts; turn) { const candidate shuffleIdx(selected, roundIndex * 100 turn) const score overlapScore(candidate, courtCount, partner, opponent) if (score bestScore) { best candidate bestScore score } if (bestScore 0) break } return best }相同输入得到相同结果有利于复现问题、编写测试和解释排阵。这里的目标不是数学最优证明而是在固定成本内得到稳定的较优解。六、生成比赛后立刻更新历史候选确定后每个场地取四个位置组成两队并同步更新搭档矩阵、对手矩阵和上场次数。历史必须在进入下一轮前完成否则后续轮次看不到刚生成的组合。function recordCourt( four: number[], playCounts: number[], partner: number[][], opponent: number[][] ): void { const [p0, p1, p2, p3] four partner[p0][p1] partner[p1][p0] partner[p2][p3] partner[p3][p2] const left [p0, p1] const right [p2, p3] left.forEach((a: number) { right.forEach((b: number) { opponent[a][b] opponent[b][a] }) }) four.forEach((index: number) playCounts[index]) }比赛对象还应保留轮次、场地、双方名单和初始比分供计分页面和历史统计继续使用。七、评分模型的取舍要可见策略优点局限适用场景完全随机实现最简单不公平且难复现临时娱乐局只平衡次数休息分配较好搭档可能重复人数较多的短局历史加权搜索组合更丰富、可复现需要维护矩阵多轮固定名单全量最优搜索理论结果更好成本增长极快人数很少的离线计算当前模型先保证上场次数再优化搭档和对手。若名单临时变化应重新生成未开始的轮次已经完成的比赛则作为统计事实保留避免历史被无声改写。候选搜索还需要明确退化行为当所有候选评分都相同保留首个稳定排列当场地数超过实际可组成的比赛数只生成完整的四人场次当临时加入的新球友没有历史记录其矩阵初值为零自然会获得更多低重复候选。规则写清后页面就能把“为什么有人轮休、为什么本轮仍有重复搭档”解释为约束结果而不是让用户误以为随机失效。八、用统计量验收公平性算法验收不应只看页面是否生成卡片还要对结果做统计。至少检查每人上场次数的最大差、搭档重复上限、同一对手出现次数以及相同输入的可复现性。用例输入关键断言标准双场8 人、2 场、4 轮每人 4 次无空场需要轮休10 人、2 场、5 轮上场次数最大差不超过 1单场多轮6 人、1 场、6 轮轮休分布均匀搭档不过度重复边界人数少于 4 人返回空结果并阻止进入对阵页非法参数场地或轮数为 0返回空结果不产生半场比赛可复现性同一名单重复生成每场组合与顺序一致统计时还应验证历史矩阵的对称性甲乙作为搭档一次两个方向的计数都应增加甲对阵丙一次对手矩阵也必须双向一致。若只更新单向关系下一轮评分会因排列方向不同而产生偏差。对每一轮累加全部上场次数并核对总和等于实际比赛数乘以四可以快速发现漏记场地或重复计数。ArkTS 状态驱动页面刷新的规则可参考状态管理概述。算法结果写入状态后列表键应使用稳定比赛 ID避免轮次数组刷新时复用错误卡片。九、总结双打公平排阵可以拆成三个清晰步骤按上场次数选人、用历史矩阵给排列评分、在有限候选中选择最低分方案。显式权重让规则可解释确定性搜索让问题可复现统计验收则避免“看起来随机”冒充公平。这个模型还可以继续叠加水平、混双和连续休息等约束而不必推翻基本结构。