You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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会漏掉大量日期区间,导致约束覆盖不全。

再看你修改后代码的问题

修改后的代码有几个明显的语法和逻辑漏洞:

  1. 循环变量错误for e in range(employees):这里employees是列表,range()需要整数参数,应该改成for e in range(len(employees)):
  2. 变量名不一致:你前面定义的是dates列表,但循环里误用了time_slots,会直接触发报错。
  3. 逻辑偏差:你现在的约束是「如果从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

如果员工在dd+consecutive_count-1这几天都工作了,那么接下来的rest天(d+consecutive_countd+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

火山引擎 最新活动