Python中的高效排班计划

问题描述

| 我目前正在为一家模型出租车公司做一些排班调度模拟。该公司经营350辆出租车,并且在任何一天都在使用。司机每个工作5班,每班12小时,每天有四个重叠的班次。从3:00-15:00、15:00-3:00、16:00-4:00和4:00-16:00轮班。我最初是用Python开发的,因为需要快速开发它,我认为性能可以接受。原始参数一天只需要轮班两次(3:00-15:00和15:00-3:00),虽然性能不是很好,但对于我的使用来说已经足够了。它可以使用简单的蛮力算法为驾驶员制定每周约8分钟的时间表(评估所有可能的调换以查看情况是否可以得到改善。) 在四个重叠的转变中,性能绝对糟糕。每周计划需要一个多小时。我已经使用cProfile进行了一些性能分析,看来罪魁祸首是两种方法。一种方法是确定将驾驶员换档时是否存在冲突。确保他们不在同一天的轮班中服务,也不在之前或之后的轮班中服务。每天只需两班,这很容易。只需确定驾驶员是否已经安排好直接在上班之前或之后上班。随着四个重叠的转变,这变得更加复杂。第二个罪魁祸首是确定轮班是白天还是夜班的方法。同样,对于原始的两个班次,这就像确定班次号是偶数还是奇数一样简单,班号从0开始。第一个班次(班次0)被指定为夜班,第二个班次被指定为白天,等等等等。现在,前两个是晚上,接下来的两个是,依此类推。这些方法相互调用,因此我将其身体放在下面。
def conflict_exists(shift,driver,shift_data):
    next_type = get_stype((shift+1) % 28)
    my_type = get_stype(shift)

    nudge = abs(next_type - my_type)

    if driver in shift_data[shift-2-nudge] or driver in shift_data[shift-1-nudge] or driver in shift_data[(shift+1-(nudge*2)) % 28] or driver in shift_data[(shift+2-nudge) % 28] or driver in shift_data[(shift+3-nudge) % 28]:
        return True
    else:
        return False
请注意,get_stype返回班次的类型,其中0表示夜班,而1表示日班。 为了确定班次类型,我使用以下方法
def get_stype(k):
    if (k / 4.0) % 1.0 < 0.5:
        return 0
    else:
        return 1
这是cProfile的相关输出
     ncalls  tottime  percall  cumtime  percall
     57662556   19.717    0.000   19.717    0.000 sim8.py:241(get_stype)
     28065503   55.650    0.000   77.591    0.000 sim8.py:247(in_conflict)
是否有人对我如何改进此脚本的性能有任何明智的建议或技巧?任何帮助将不胜感激! 干杯, 提姆 编辑:对不起,我应该澄清每个班次的数据都存储为一个集合,即shift_data [k]是设置的数据类型。 编辑2: 根据下面的请求添加主循环,以及其他调用方法。有点混乱,对此我深表歉意。
def optimize_schedule(shift_data,driver_shifts,recheck):
    skip = set()

    if len(recheck) == 0:
        first_run = True
        recheck = []
        for i in xrange(28):
            recheck.append(set())
    else:
        first_run = False

    for i in xrange(28):

        if (first_run):
            targets = shift_data[i]
        else:
            targets = recheck[i]

        for j in targets:
            o_score = eval_score = opt_eval_at_coord(shift_data,i,j)

            my_type = get_stype(i)
            s_type_fwd = get_stype((i+1) % 28)

            if (my_type == s_type_fwd):
                search_direction = (i + 2) % 28
                end_direction = i
            else:
                search_direction = (i + 1) % 28 
                end_direction = (i - 1) % 28 

            while True:
                if (end_direction == search_direction):
                    break
                for k in shift_data[search_direction]:

                    coord = search_direction * 10000 + k 

                    if coord in skip:
                        continue

                    if k in shift_data[i] or j in shift_data[search_direction]:
                        continue

                    if in_conflict(search_direction,j,shift_data) or in_conflict(i,k,shift_data):
                        continue

                    node_a_prev_score = o_score
                    node_b_prev_score = opt_eval_at_coord(shift_data,search_direction,k)

                    if (node_a_prev_score == 1) and (node_b_prev_score == 1):
                        continue

                    a_type = my_type
                    b_type = get_stype(search_direction)

                    if (node_a_prev_score == 1):
                        if (driver_shifts[j][\'type\'] == \'any\') and (a_type != b_type):
                            test_eval = 2
                        else:
                            continue
                    elif (node_b_prev_score == 1):
                        if (driver_shifts[k][\'type\'] == \'any\') and (a_type != b_type):
                            test_eval = 2
                        else:
                            test_eval = 0
                    else:
                        if (a_type == b_type):
                            test_eval = 0
                        else:
                            test_eval = 2

                    print \'eval_score: %f\' % test_eval

                    if (test_eval > eval_score):

                        cand_coords = [search_direction,k]
                        eval_score = test_eval
                        if (test_eval == 2.0):
                            break
                else:
                    search_direction = (search_direction + 1) % 28
                    continue

                break

            if (eval_score > o_score):
                print \'doing a swap: \',print cand_coords,shift_data[i].remove(j)
                shift_data[i].add(cand_coords[1])

                shift_data[cand_coords[0]].add(j)   
                shift_data[cand_coords[0]].remove(cand_coords[1])

                if j in recheck[i]:
                    recheck[i].remove(j)

                if cand_coords[1] in recheck[cand_coords[0]]:               
                    recheck[cand_coords[0]].remove(cand_coords[1])

                recheck[cand_coords[0]].add(j)
                recheck[i].add(cand_coords[1])

            else:
                coord = i * 10000 + j
                skip.add(coord)

    if first_run:
        shift_data = optimize_schedule(shift_data,recheck)

    return shift_data



def opt_eval_at_coord(shift_data,j):
    node = j
    if in_conflict(i,node,shift_data):
        return float(\'-inf\')
    else:
        s_type = get_stype(i)

        d_pref = driver_shifts[node][\'type\']

        if (s_type == 0 and d_pref == \'night\') or (s_type == 1 and d_pref == \'day\') or (d_pref == \'any\'):
            return 1
        else:
            return 0
    

解决方法

        没有任何东西显然会减慢这些功能的速度,实际上它们并没有减慢速度。他们只是被叫很多。您说您正在使用蛮力算法-您能编写一种不会尝试所有可能组合的算法吗?还是有一种更有效的方法,例如通过驱动程序而不是通过移位存储数据? 当然,如果您需要即时加速,则可以通过在诸如PyPy的解释器中运行或使用Cython将关键部分转换为C来受益。     ,        嗯有趣且有趣的问题。我将不得不多看。现在,我要提供的是:为什么要引入浮子?我会做get_stype()如下:
def get_stype(k):
    if k % 4 < 2:
        return 0
    return 1
这不是一个大的提速,但是它更快(更简单)。另外,您每次喂
get_stype
时都不必执行mod 28,因为
get_stype
中的mod 4已经解决了这个问题。 如果有重大改进,它们将以更好的算法形式出现。 (我并不是说您的算法不好,或者有更好的算法。我还没有花足够的时间在研究它。但是,如果找不到更好的算法,那么它就更重要了。使用PyPy,Cython,Shed Skin或完全以其他(更快)语言重写将大大提高速度。)     ,我认为您的问题不是运行这两个功能所花费的时间。请注意,函数的percall值为
0.000
。这意味着每次调用该函数都需要不到1毫秒的时间。 我认为您的问题是调用函数的次数。 python中的函数调用非常昂贵。例如,调用不执行任何操作的函数57,662,556在我的计算机上花费了7.15秒:
>>> from timeit import Timer
>>> t = Timer(\"t()\",setup=\"def t(): pass\")
>>> t.timeit(57662556)
7.159075975418091
我想知道的一件事是
shift_data
变量。值是列表还是字典?
driver in shift_data[shift-2-nudge]
如果是列表,则
in
将花费
O(N)
时间,而如果是字典,则将花费
O(1)
时间。 编辑:由于设置了shift_data值,应该没问题     ,        在我看来,在两个白班之间或两个黑班之间进行交换永远无济于事。它不会改变驾驶员对变速的满意程度,也不会改变这些变速与其他变速之间的冲突。 因此,我认为您应该只能计划最初的两个班次,白天和黑夜,然后才将分配给这些班次的驾驶员分为两个实际班次。