【问题标题】:Python Shift Scheduling OptimizationPython 班次调度优化
【发布时间】:2014-09-21 08:46:01
【问题描述】:

我目前正在研究一种工作轮班调度算法。我们的轮班时间表完全包括 4-3(4 天上,3 天休息)和 4-3 轮班(例如:周日、周一、周二、下一周和下周周日、周五、周六休息) ) - 星期从星期日到星期六。

“直线”4-3 移位或旋转 4-3 有 49 种可能的变体。由于它是 24/7 操作,因此需要考虑一周中的所有 7 天。截至目前,我将其用于一个较小的工作组,其中有 11 人在早班,12 人在晚班,但还有其他多达 200 人的工作组,我想将此算法扩展到其他工作组。

基本前提是管理组每天都有所需的人力,算法将只返回一组早班和晚班,为他们提供所需的人力。

很快就变得非常痛苦,为 11 个人安排 49 次可能的班次(有重复)并整理所有这些可能的组合需要数千年的时间。结果,我能够从 49 个班次列表中挑选出最常用的 16-20 个班次。

这使它易于管理,但仅适用于 11 或 12 人。显然,每增加一个人,可能的组合数量就会呈指数增长。截至目前,我的算法执行以下操作:

  1. 作为变量,我有一堆班次,其中 1 和 0 代表他们在那里的一周中的每一天的“班次”和“班次” - 超过两周的时间。我现在只使用一个示例......例如:

    h = [0,1,1,1,1,0,0,0,1,1,1,1,0,0]
    e = [0,0,1,1,1,1,0,0,0,1,1,1,1,0]
    d = [0,0,0,1,1,1,1,0,0,0,1,1,1,1]
    c = [1,1,1,1,0,0,0,1,1,1,1,0,0,0]
    m = [0,1,1,0,1,1,0,0,1,1,0,1,1,0]
    p = [1,1,0,0,0,1,1,1,1,0,0,0,1,1]
    q1 = [1,1,1,0,0,0,1,1,1,1,0,0,0,1]
    a = [1,0,0,0,1,1,1,1,0,0,0,1,1,1]
    

然后我有一个列表,其中包含大约 16 到 20 个(在此示例中我将仅使用 8 个)班次,这些班次是最常用的(如果不是专门使用的话)加上一个用于计算人数的变量和经理要求的变量(early_count):

shifts = [h,e,c,d,m,p,q1, a]
early_bid_personnel = 11
early_count = [5,6,7,7,6,8,5]

然后我有一个生成器表达式为早班创建所有可能的班次组合,并查看星期日加起来是否达到所需的数字 (5)。那些在星期日加起来的然后被 mon 生成器表达式引用,所有这些星期一被制成表格,然后被 Tue 列表引用。我在 14 天的时间里这样做 - 因为一些轮班在第二周扭曲了人员平衡:

sun = (combs for combs in combinations_with_replacement(shifts,early_bid_personnel) if (sum(combs[i][0] for i in range(0,early_bid_personnel)) == early_count[0]))
mon = (mon for mon in sun if (sum(mon[i][1] for i in range(0,early_bid_personnel)) == early_count[1]))
tue = (tue for tue in mon if (sum(tue[i][2] for i in range(0,early_bid_personnel)) == early_count[2]))
wed = (wed for wed in tue if (sum(wed[i][3] for i in range(0,early_bid_personnel)) == early_count[3]))
thu = (thu for thu in wed if (sum(thu[i][4] for i in range(0,early_bid_personnel)) == early_count[4]))
fri = (fri for fri in thu if (sum(fri[i][5] for i in range(0,early_bid_personnel)) == early_count[5]))
sat = (sat for sat in fri if (sum(sat[i][6] for i in range(0,early_bid_personnel)) == early_count[6]))
sec_sun = (sec_sun for sec_sun in sat if (sum(sec_sun[i][7] for i in range(0,early_bid_personnel)) == early_count[0]))
sec_mon = (sec_mon for sec_mon in sec_sun if (sum(sec_mon[i][8] for i in range(0,early_bid_personnel)) == early_count[1]))
sec_tue = (sec_tue for sec_tue in sec_mon if (sum(sec_tue[i][9] for i in range(0,early_bid_personnel)) == early_count[2]))
sec_wed = (sec_wed for sec_wed in sec_tue if (sum(sec_wed[i][10] for i in range(0,early_bid_personnel)) == early_count[3]))
sec_thu = (sec_thu for sec_thu in sec_wed if (sum(sec_thu[i][11] for i in range(0,early_bid_personnel)) == early_count[4]))
sec_fri = (sec_fri for sec_fri in sec_thu if (sum(sec_fri[i][12] for i in range(0,early_bid_personnel)) == early_count[5]))
sec_sat = (sec_sat for sec_sat in sec_fri if (sum(sec_sat[i][13] for i in range(0,early_bid_personnel)) == early_count[6]))

我遍历的 sec_sat 表达式,在自定义字典中查找 1 和 0 的字符串,并将其转换为班次分配的实际字母。然后我基本上为晚班再次做同样的事情。两者结合起来为经理提供了他们正在寻找的确切数字。这很好用,例如,11 个人只有 8 个班次可供选择,而 12 个人可以选择 8 个班次延迟。但是,如果工作组的规模扩大到 20 人,我想用 12、14、16 或 gasp 所有 49 个班次来确定班次,这仍然是相当不合理的.

我知道第一个生成器表达式仍在使用替换检查每个组合,并且只返回周日加起来的组合,这是核心问题。我可以认真地使用一些帮助来找出一种比 O(n^2) 更好或更糟的方法。

我周围有什么方法可以生成所有可能的组合并检查每个组合吗?另外,如果我在初始生成器表达式中加入一些约束,例如最多只有 5 个 'a' 移位:

sun = (combs for combs in combinations_with_replacement(shifts,early_bid_personnel) if (sum(combs[i][0] for i in range(0,early_bid_personnel)) == early_count[0]) and combs.count(a) <= 5)

生成器表达式仍然必须生成一些东西,检查它是否有 5 个或更少的 'a' 班次,如果有更多则跳过它,对吗?

【问题讨论】:

    标签: python algorithm optimization


    【解决方案1】:

    您可以使用蒙特卡罗模拟来解决这个问题。你不需要经历所有可能的组合。你只需要找到一个符合条件的,而且有很多。让我重新定义你的问题。让 [sun, mon, tue, ..., sec_sat] 你的经理的要求。 [q1, q2, ..., q49] 将是 49 个不同班次中每个班次的人数。和矩阵:

    s1[0]  s1[1] ... s1[13]
    s2[0]  s2[1] ... s2[13]
    ....................
    s49[0] s49[1] ... s49[13]
    

    是一周中每个班次和每一天的开/关表。例如:如果周日是班次 1 的上班日,则 s1[0] 将为 1,否则为零。如果第二个星期日是班次 3 的上班日,则 s3[7] 为 1,否则为 0。等等等等。有了这个定义,您的问题可以这样重写:

    sun       <=  q1*s1[0] + ... +  q49*s49[0]
    mon       <=  q1*s1[1] + ... + q49*s49[1]
    tue       <=  q1*s1[2] + ... + q49*s49[2]
    wed       <=  q1*s1[3] + ... + q49*s49[3]
    thu       <=  q1*s1[4] + ... + q49*s49[4]
    fri       <=  q1*s1[5] + ... + q49*s49[5]
    sat       <=  q1*s1[6] + ... + q49*s49[6]
    sec_sun   <=  q1*s1[7] + ... + q49*s49[7]
    sec_mon   <=  q1*s1[8] + ... + q49*s49[8]
    sec_tue   <=  q1*s1[9] + ... + q49*s49[9]
    sec_wed   <=  q1*s1[10] + ... + q49*s49[10]
    sec_thu   <=  q1*s1[11] + ... + q49*s49[11]
    sec_fri   <=  q1*s1[12] + ... + q49*s49[12]
    sec_sat   <=  q1*s1[13] + ... + q49*s49[13]
    

    您的未知数是 [q1, q2, ..., q49]。其余的都是已知的。如何使用模拟来找到解决方案?您将生成随机 [q1,...,q49] 向量并检查是否满足您的条件。如果是,则破坏算法并返回结果。例如(某种伪代码):

    import random
    # Define your restrictions
    mon = 
    tue = 
    .........
    sec_sat = 
    # Define your shifts
    s1 = [1,1,1,1,0,0,0,1,1,1,1,0,0,0]
    s2 = [0,1,1,1,1,0,0,0,1,1,1,1,0,0]
    ...................................
    s49 = [...]
    while True:
        q = [0]*49
        # We place workers in random shifts
        for i in range(number_of_workers):
            q[random.randint(0,len(q)-1)] += 1
    
        if (mon <= q[0]*s1[0] + q[1]*s2[0] + ... + q[48]*s49[0]) and
           (tue <= q[0]*s1[1] + q[1]*s2[1] + ... + q[48]*s49[1]) and         
           .........................................................
           (sec_sat <= q[0]*s1[13] + q[1]*s2[13] + ... + q[48]*s49[13]):
           # Condition met, return result
           return q
    

    我为您的上述示例实施了一个解决方案,但班次数量有限:

    import random
    # Restrictions
    r = [5, 6, 7, 7, 6, 8, 5, 5, 6, 7, 7, 6, 8, 5]
    # Shifts
    s = []
    s.append([0,1,1,1,1,0,0,0,1,1,1,1,0,0])
    s.append([0,0,1,1,1,1,0,0,0,1,1,1,1,0])
    s.append([0,0,0,1,1,1,1,0,0,0,1,1,1,1])
    s.append([1,1,1,1,0,0,0,1,1,1,1,0,0,0])
    s.append([0,1,1,0,1,1,0,0,1,1,0,1,1,0])
    s.append([1,1,0,0,0,1,1,1,1,0,0,0,1,1])
    s.append([1,1,1,0,0,0,1,1,1,1,0,0,0,1])
    s.append([1,0,0,0,1,1,1,1,0,0,0,1,1,1])
    
    number_of_shifts = len(s)
    number_of_workers = 11
    number_of_days = len(s[0])
    
    while True:
        q = [0]*number_of_shifts
        for i in range(number_of_workers):
            q[random.randint(0,len(q)-1)] += 1
        t = [sum([q[j]*s[j][i] for j in range(number_of_shifts)]) for i in range(number_of_days)]
        if sum([r[i] <= t[i] for i in range(number_of_days)]) == number_of_days:
            print q
            break
    

    执行起来并不需要太多。这是它找到的解决方案之一:[0, 3, 2, 2, 1, 2, 1, 0]

    【讨论】:

      猜你喜欢
      • 2013-05-20
      • 1970-01-01
      • 1970-01-01
      • 2018-09-09
      • 1970-01-01
      • 2020-05-23
      • 1970-01-01
      • 1970-01-01
      • 2016-01-15
      相关资源
      最近更新 更多