Python中的高效班次调度

时间:2011-06-21 19:59:06

标签: 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, driver_shifts, 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, driver_shifts, 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, driver_shifts, recheck)

    return shift_data



def opt_eval_at_coord(shift_data, driver_shifts, i, 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

4 个答案:

答案 0 :(得分:4)

没有什么能明显减缓这些功能,实际上它们并不慢。他们只是被召唤了很多。你说你正在使用强力算法 - 你能编写一个不尝试每种可能组合的算法吗?或者是否有更有效的方法,例如通过驱动程序而不是按班次存储数据?

当然,如果你需要即时加速,它可能会受益于运行像PyPy这样的解释器,或者使用Cython将关键部分转换为C语言。

答案 1 :(得分:3)

嗯。有趣和有趣的问题。我将不得不更多地看待它。现在,我有这个提供:你为什么要推出花车?我会按如下方式执行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或完全用不同(更快)的语言重写。)

答案 2 :(得分:2)

我认为您的问题不是运行这两个功能所需的时间。请注意,函数的 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)时间,如果它是一个词典,则会{{1}}时间。

编辑:由于shift_data值是设置,这应该没问题

答案 3 :(得分:2)

在我看来,在两个班次之间或两个夜班之间交换将永远不会有帮助。它不会改变司机喜欢班次的程度,也不会改变这些班次与其他班次的冲突。

所以我认为你应该能够最初计划两个班次,白天和黑夜,然后才将分配到班次的司机分成两个实际班次。