如何解决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值,应该没问题
, 在我看来,在两个白班之间或两个黑班之间进行交换永远无济于事。它不会改变驾驶员对变速的满意程度,也不会改变这些变速与其他变速之间的冲突。
因此,我认为您应该只能计划最初的两个班次,白天和黑夜,然后才将分配给这些班次的驾驶员分为两个实际班次。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。