如何用PuLP实现PGA梦幻体育阵容的最小唯一球员数约束?
问题描述
我正在使用Python的PuLP库构建PGA每日梦幻体育阵容生成程序,当前基础逻辑(变量定义、目标函数、薪资/人数约束)运行正常,代码如下:
# 每个球员对应一个二进制变量(0/1表示是否入选阵容) # player_dict是存储球员数据的字典,包含Name、Fpts、Salary等字段 lp_variables = { player: plp.LpVariable(player, cat="Binary") for player, _ in self.player_dict.items() } # 目标函数:最大化总得分 self.problem += ( plp.lpSum( self.player_dict[player]["Fpts"] * lp_variables[player] for player in self.player_dict ), "Objective", ) # 薪资约束 max_salary = 50000 if self.site == "dk" else 60000 min_salary = 0 self.problem += ( plp.lpSum( self.player_dict[player]["Salary"] * lp_variables[player] for player in self.player_dict ) <= max_salary ) self.problem += ( plp.lpSum( self.player_dict[player]["Salary"] * lp_variables[player] for player in self.player_dict ) >= self.min_salary ) # 阵容人数约束:固定6名球员 self.problem += ( plp.lpSum(lp_variables[player] for player in self.player_dict) >= 6 ) self.problem += ( plp.lpSum(lp_variables[player] for player in self.player_dict) <= 6 ) # 生成多组阵容 for i in range(self.num_lineups): try: self.problem.solve(plp.PULP_CBC_CMD(msg=0)) except plp.PulpSolverError: self.simDoc.update({'jobProgressLog': ArrayUnion(['求解器出错 - 问题不可行。仅生成{}套阵容,目标为{}套。继续导出。'.format(len(self.lineups), self.num_lineups)])}) score = str(self.problem.objective) for v in self.problem.variables(): score = score.replace(v.name, str(v.varValue)) player_names = [ v.name.replace("_", " ") for v in self.problem.variables() if v.varValue != 0 ] fpts = eval(score) self.lineups[fpts] = player_names # 约束后续阵容得分低于当前最优解,生成次优解 self.problem += plp.lpSum( self.player_dict[player]["Fpts"] * lp_variables[player] for player in self.player_dict ) <= (fpts - 0.001)
现在需要添加一个可选约束:强制每个新生成的阵容与之前所有阵容相比,至少有指定数量的唯一球员。例如要求至少3个唯一球员时,以下两组阵容是无效的(仅存在2个不同球员):
[Player1, Player4, Player29, Player6, Player10] [Player1, Player4, Player29, Player88, Player45]
请问如何用PuLP实现该规则?
解决方案
核心思路是限制新阵容与每一个已生成阵容的重叠球员数量:如果要求至少N个唯一球员,那么新阵容与旧阵容的重叠球员数不能超过总人数 - N(比如总人数6,N=3时,重叠最多3人)。
修改后的完整代码
# 初始化时新增:存储已生成阵容的球员选择状态(二进制变量集合) self.previous_lineups = [] # 可选参数:最小唯一球员数,可根据需求调整 self.min_unique_players = 3 # 阵容固定人数 self.roster_size = 6 # 每个球员对应一个二进制变量(0/1表示是否入选阵容) lp_variables = { player: plp.LpVariable(player, cat="Binary") for player, _ in self.player_dict.items() } # 目标函数:最大化总得分 self.problem += ( plp.lpSum( self.player_dict[player]["Fpts"] * lp_variables[player] for player in self.player_dict ), "Objective", ) # 薪资约束 max_salary = 50000 if self.site == "dk" else 60000 min_salary = 0 self.problem += ( plp.lpSum( self.player_dict[player]["Salary"] * lp_variables[player] for player in self.player_dict ) <= max_salary ) self.problem += ( plp.lpSum( self.player_dict[player]["Salary"] * lp_variables[player] for player in self.player_dict ) >= self.min_salary ) # 阵容人数约束:固定6名球员 self.problem += ( plp.lpSum(lp_variables[player] for player in self.player_dict) >= self.roster_size ) self.problem += ( plp.lpSum(lp_variables[player] for player in self.player_dict) <= self.roster_size ) # 生成多组阵容 for i in range(self.num_lineups): # 对所有已生成的阵容添加重叠限制约束 for idx, prev_lineup in enumerate(self.previous_lineups): # 计算新阵容与旧阵容的重叠球员数:sum(新变量 * 旧变量值) overlap = plp.lpSum( lp_variables[player] * prev_lineup[player] for player in self.player_dict ) # 约束重叠数 ≤ 总人数 - 最小唯一球员数 self.problem += ( overlap <= self.roster_size - self.min_unique_players, f"MaxOverlap_WithLineup_{idx}" ) try: self.problem.solve(plp.PULP_CBC_CMD(msg=0)) except plp.PulpSolverError: self.simDoc.update({'jobProgressLog': ArrayUnion(['求解器出错 - 问题不可行。仅生成{}套阵容,目标为{}套。继续导出。'.format(len(self.lineups), self.num_lineups)])}) break # 无法生成更多符合要求的阵容,终止循环 score = str(self.problem.objective) for v in self.problem.variables(): score = score.replace(v.name, str(v.varValue)) player_names = [ v.name.replace("_", " ") for v in self.problem.variables() if v.varValue != 0 ] fpts = eval(score) self.lineups[fpts] = player_names # 记录当前阵容的球员选择状态,用于后续约束 current_lineup = { player: int(lp_variables[player].varValue) for player in self.player_dict } self.previous_lineups.append(current_lineup) # 约束后续阵容得分低于当前最优解,生成次优解 self.problem += plp.lpSum( self.player_dict[player]["Fpts"] * lp_variables[player] for player in self.player_dict ) <= (fpts - 0.001)
关键说明
- 状态记录:每次生成阵容后,将每个球员的入选状态(0或1)存入
previous_lineups列表,用于后续计算重叠数。 - 重叠约束:在生成新阵容前,遍历所有旧阵容,添加约束
重叠球员数 ≤ 总人数 - 最小唯一球员数。例如总人数6、最小唯一3人时,重叠最多3人,意味着新阵容至少有6-3=3个球员和旧阵容不同。 - 灵活性:
min_unique_players参数可自由调整,设为0则关闭该约束,恢复原有逻辑。
内容的提问来源于stack exchange,提问作者Sean Sailer
相关产品推荐
相关产品推荐

