如何在Python+puan网球队选队模型中融入排名实现最优选队?
网球队选队优化问题解决方案
问题背景
每年需为网球队组队参赛,已用Python结合puan工具包实现基础选队规则:
- 5名候选球员,需选出2名单打、2名双打球员
- 球员不能同时参加单打和双打
- 已实现基础规则的代码,但需:
- 融入球员单打/双打排名(数值越低实力越强),自动选出最强阵容
- 支持后续添加外籍球员数量限制、主客场适配等可扩展规则
一、融入排名实现最优阵容
核心思路是最小化总实力评分:因为排名越低代表实力越强,所以将2名单打球员的单打排名之和 + 2名双打球员的双打排名之和作为目标函数,求解最小值即可得到最强阵容。
修改后的代码实现
import puan import puan.logic.plog as pg import puan.modules.configurator as cc import puan_solvers as ps # 球员排名数据(数值越低实力越强) players = { "p1": {"s_rank": 1.9225, "d_rank": 1.8000}, "p2": {"s_rank": 3.8156, "d_rank": 3.7765}, "p3": {"s_rank": 4.0611, "d_rank": 2.3133}, "p4": {"s_rank": 3.0974, "d_rank": 3.9666}, "p5": {"s_rank": 2.3790, "d_rank": 2.5672} } # 定义布尔变量:球员是否参加单打/双打 p1s = puan.variable(id="p1s") p2s = puan.variable(id="p2s") p3s = puan.variable(id="p3s") p4s = puan.variable(id="p4s") p5s = puan.variable(id="p5s") p1d = puan.variable(id="p1d") p2d = puan.variable(id="p2d") p3d = puan.variable(id="p3d") p4d = puan.variable(id="p4d") p5d = puan.variable(id="p5d") # 基础约束规则 # 恰好2名单打球员 singles_exact = pg.Exactly(propositions=[p1s,p2s,p3s,p4s,p5s], value=2) # 恰好2名双打球员 doubles_exact = pg.Exactly(propositions=[p1d,p2d,p3d,p4d,p5d], value=2) # 球员不能兼项 no_double_duty = [pg.Not(pg.All(p_s, p_d)) for p_s, p_d in zip([p1s,p2s,p3s,p4s,p5s], [p1d,p2d,p3d,p4d,p5d])] # 构建选队模型 team_model = cc.StingyConfigurator( singles_exact, doubles_exact, *no_double_duty ) # 定义目标函数:最小化总排名(单打球员s_rank之和 + 双打球员d_rank之和) objective = ( p1s * players["p1"]["s_rank"] + p2s * players["p2"]["s_rank"] + p3s * players["p3"]["s_rank"] + p4s * players["p4"]["s_rank"] + p5s * players["p5"]["s_rank"] + p1d * players["p1"]["d_rank"] + p2d * players["p2"]["d_rank"] + p3d * players["p3"]["d_rank"] + p4d * players["p4"]["d_rank"] + p5d * players["p5"]["d_rank"] ) # 求解最优解(最小化目标函数) optimal_solution = next( team_model.select( solver=ps.glpk_solver, only_leafs=True, minimize=objective # 指定最小化目标 ) ) print("最优阵容:") for player, status in optimal_solution.items(): if status == 1.0: role = "单打" if player.endswith("s") else "双打" print(f"{player[:2]} 参加 {role}") print("\n原始解数据:", optimal_solution)
代码说明
- 用
pg.Exactly替代原有的AtMost+AtLeast,简化"恰好N人参赛"的约束定义 - 目标函数将球员参赛状态(0/1)与对应排名相乘后求和,通过最小化该值确保选出实力最强的组合
- 通过
minimize=objective参数将目标函数传入求解器,自动计算最优解
二、扩展规则支持外籍球员、主客场适配
所有扩展规则都通过向cc.StingyConfigurator添加新约束或调整目标函数实现,保持模型的可扩展性。
1. 外籍球员数量限制
假设每个球员新增is_foreign属性,要求参赛球员中外籍人数不超过1人:
# 新增球员属性:是否为外籍 players = { "p1": {"s_rank": 1.9225, "d_rank": 1.8000, "is_foreign": False}, "p2": {"s_rank": 3.8156, "d_rank": 3.7765, "is_foreign": True}, "p3": {"s_rank": 4.0611, "d_rank": 2.3133, "is_foreign": False}, "p4": {"s_rank": 3.0974, "d_rank": 3.9666, "is_foreign": True}, "p5": {"s_rank": 2.3790, "d_rank": 2.5672, "is_foreign": False} } # 定义外籍球员参赛总人数约束:不超过1人 foreign_players = [ p1s * players["p1"]["is_foreign"] + p1d * players["p1"]["is_foreign"], p2s * players["p2"]["is_foreign"] + p2d * players["p2"]["is_foreign"], p3s * players["p3"]["is_foreign"] + p3d * players["p3"]["is_foreign"], p4s * players["p4"]["is_foreign"] + p4d * players["p4"]["is_foreign"], p5s * players["p5"]["is_foreign"] + p5d * players["p5"]["is_foreign"] ] foreign_limit = pg.AtMost(propositions=foreign_players, value=1) # 将新约束加入模型 team_model = cc.StingyConfigurator( singles_exact, doubles_exact, *no_double_duty, foreign_limit )
2. 主客场适配规则
方式1:优先选择主场表现好的球员(调整目标函数)
给主场适配的球员排名加权重,比如主场时本地球员的排名乘以0.8(相当于实力提升):
# 假设当前为主场,本地球员排名权重0.8 home_weight = 0.8 objective = ( p1s * players["p1"]["s_rank"] * (home_weight if not players["p1"]["is_foreign"] else 1) + p2s * players["p2"]["s_rank"] * (home_weight if not players["p2"]["is_foreign"] else 1) + p3s * players["p3"]["s_rank"] * (home_weight if not players["p3"]["is_foreign"] else 1) + p4s * players["p4"]["s_rank"] * (home_weight if not players["p4"]["is_foreign"] else 1) + p5s * players["p5"]["s_rank"] * (home_weight if not players["p5"]["is_foreign"] else 1) + p1d * players["p1"]["d_rank"] * (home_weight if not players["p1"]["is_foreign"] else 1) + p2d * players["p2"]["d_rank"] * (home_weight if not players["p2"]["is_foreign"] else 1) + p3d * players["p3"]["d_rank"] * (home_weight if not players["p3"]["is_foreign"] else 1) + p4d * players["p4"]["d_rank"] * (home_weight if not players["p4"]["is_foreign"] else 1) + p5d * players["p5"]["d_rank"] * (home_weight if not players["p5"]["is_foreign"] else 1) )
方式2:强制规则(如主场必须至少1名本地球员)
# 定义主场约束:至少1名本地球员参赛 local_players_in_match = [ p1s * (not players["p1"]["is_foreign"]) + p1d * (not players["p1"]["is_foreign"]), p2s * (not players["p2"]["is_foreign"]) + p2d * (not players["p2"]["is_foreign"]), p3s * (not players["p3"]["is_foreign"]) + p3d * (not players["p3"]["is_foreign"]), p4s * (not players["p4"]["is_foreign"]) + p4d * (not players["p4"]["is_foreign"]), p5s * (not players["p5"]["is_foreign"]) + p5d * (not players["p5"]["is_foreign"]) ] home_local_requirement = pg.AtLeast(propositions=local_players_in_match, value=1) # 将约束加入模型 team_model = cc.StingyConfigurator( singles_exact, doubles_exact, *no_double_duty, home_local_requirement )
内容的提问来源于stack exchange,提问作者manana_banana
相关产品推荐
相关产品推荐

