OR-Tools CP-SAT员工排班:最大连续工作日及强制休息日约束设置问题
OR-Tools CP-SAT员工排班:最大连续工作日及强制休息日约束设置问题
嘿,我来帮你梳理下这个排班约束的问题~你的需求很明确:员工连续工作4天后必须休息2天,同时不能出现连续工作超过4天,或者连续4天工作后没休够2天的情况。咱们一步步拆解你代码里的问题,再给出能准确实现需求的约束写法。
先看你最初版本代码的问题
你最初写的约束逻辑是这样的:
model.Add(sum(assignments[(e, d+i )] for i in range(consecutive_count+rest*2)) <= consecutive_count)
这个约束的意思是「在4+2*2=8天的窗口内,员工最多只能工作4天」,完全偏离了你的需求——它会过度限制排班(比如连「连续工作3天+休息1天+工作3天」这种合理安排都不允许),而且窗口的范围计算len(dates) - consecutive_count-rest*2 +1会漏掉大量日期区间,导致约束覆盖不全。
再看你修改后代码的问题
修改后的代码有几个明显的语法和逻辑漏洞:
- 循环变量错误:
for e in range(employees):这里employees是列表,range()需要整数参数,应该改成for e in range(len(employees)): - 变量名不一致:你前面定义的是
dates列表,但循环里误用了time_slots,会直接触发报错。 - 逻辑偏差:你现在的约束是「如果从t开始连续4天都工作,那么t之前的2天和之后的2天都必须休息」——但你的需求只要求连续工作4天后必须休息2天,不需要限制工作前的日期(比如员工可以在连续工作4天前的一天上班,只要这4天是连续的、之后休够2天就行)。另外,你还漏掉了「禁止连续工作超过4天」的约束(也就是第三个FAIL例子里的连续5天工作场景)。
正确的约束写法
我们需要分两个核心约束来实现你的需求:
约束1:禁止连续工作超过consecutive_count天
确保任何连续consecutive_count+1天里,员工最多工作consecutive_count天,这样就不会出现连续5天工作的违规情况:
consecutive_count = 4 rest = 2 for e in range(len(employees)): # 遍历所有可能的连续consecutive_count+1天的起始日期 for d in range(len(dates) - consecutive_count): # 连续consecutive_count+1天中,工作天数不能超过consecutive_count model.Add(sum(assignments[(e, d+i)] for i in range(consecutive_count+1)) <= consecutive_count)
约束2:连续工作consecutive_count天后,必须休息rest天
如果员工在d到d+consecutive_count-1这几天都工作了,那么接下来的rest天(d+consecutive_count到d+consecutive_count+rest-1)必须全部休息:
for e in range(len(employees)): # 遍历所有有足够后续休息日的起始日期 for d in range(len(dates) - consecutive_count - rest + 1): # 创建布尔变量,标记员工是否在d到d+consecutive_count-1天都工作了 worked_consecutively = model.NewBoolVar(f"worked_consec_{e}_{d}") # 当worked_consecutively为True时,说明这consecutive_count天都在工作 model.Add(sum(assignments[(e, d+i)] for i in range(consecutive_count)) == consecutive_count).OnlyEnforceIf(worked_consecutively) # 强制后续rest天必须休息(即排班为0) for i in range(rest): model.Add(assignments[(e, d+consecutive_count+i)] == 0).OnlyEnforceIf(worked_consecutively)
处理边界情况
如果月底前的连续工作无法满足后续休息要求(比如12月29-31日+12月28日凑够4天,但之后没有2天休息日),需要单独禁止这种情况:
for e in range(len(employees)): # 计算无法满足后续休息要求的起始日期范围 max_valid_start = len(dates) - consecutive_count for d in range(max_valid_start + 1, len(dates) - consecutive_count + 1): if d >= len(dates) - consecutive_count + 1: break # 禁止员工从d开始连续工作consecutive_count天 model.Add(sum(assignments[(e, d+i)] for i in range(consecutive_count)) <= consecutive_count - 1)
完整修正后的代码
把这些约束整合,再修正排班结果生成的小错误:
from ortools.sat.python import cp_model model = cp_model.CpModel() employees = ["daniel","karla","sergio"] dates = [f"12/{d+1}" for d in range(31)] # 生成12/1到12/31的日期列表 assignments = {} for e in range(len(employees)): for d in range(len(dates)): assignments[(e, d)] = model.NewBoolVar(f"employee_{e}_dates_{d}") consecutive_count = 4 rest = 2 # 约束1:禁止连续工作超过consecutive_count天 for e in range(len(employees)): for d in range(len(dates) - consecutive_count): model.Add(sum(assignments[(e, d+i)] for i in range(consecutive_count+1)) <= consecutive_count) # 约束2:连续工作consecutive_count天后,必须休息rest天 for e in range(len(employees)): for d in range(len(dates) - consecutive_count - rest + 1): worked_consecutively = model.NewBoolVar(f"worked_consec_{e}_{d}") model.Add(sum(assignments[(e, d+i)] for i in range(consecutive_count)) == consecutive_count).OnlyEnforceIf(worked_consecutively) for i in range(rest): model.Add(assignments[(e, d+consecutive_count+i)] == 0).OnlyEnforceIf(worked_consecutively) # 处理月底边界情况 for e in range(len(employees)): max_valid_start = len(dates) - consecutive_count for d in range(max_valid_start + 1, len(dates) - consecutive_count + 1): if d >= len(dates) - consecutive_count + 1: break model.Add(sum(assignments[(e, d+i)] for i in range(consecutive_count)) <= consecutive_count - 1) solver = cp_model.CpSolver() status = solver.Solve(model) schedule = [] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: for e in range(len(employees)): for d in range(len(dates)): if solver.Value(assignments[(e, d)]) == 1: schedule.append([dates[d], employees[e]]) # 打印示例结果 for entry in schedule: print(f"{entry[0]}: {entry[1]}") else: print("找不到可行的排班方案。")
逻辑验证
这个代码完全匹配你的示例要求:
- OK例子:
[0,1,1,1,1,0,0,...]符合约束,连续4天工作后休息2天,会被允许。 - 第一个FAIL例子:
[0,0,1,1,1,1,0,1,...]连续4天工作后只休息1天就复工,会被约束2禁止。 - 第二个FAIL例子:
[0,0,1,1,1,1,1,...]连续5天工作,会被约束1禁止。
备注:内容来源于stack exchange,提问作者nakamura johnielo




