[英]Scheduling algorithm, finding all non overlapping intervals of set length
我需要为我的管理应用程序实现一个算法,该算法将告诉我何时以及可以为哪个用户分配任务。
我实施了一个强力解决方案,似乎有效,但我想知道是否有更有效的方法来做到这一点。 为简单起见,我重写了算法以对数字列表进行操作(而不是数据库查询等)。 下面我将尝试解释我的思维方式。
假设我们有3个用户可以分配给该任务。
user_a_busy = [[1,2], [2,4], [5,6]]
user_b_busy = [[4,7], [7,8]]
user_c_busy = [[0,1], [1,5]]
列表中的每个元素表示用户在白天不可用的时段。 因此,用户A在凌晨1点到凌晨2点,凌晨2点和凌晨4点之间忙碌,依此类推。 为了能够迭代用户并识别它们,我以字典的形式表示上述列表。
users_to_check = {'A':user_a_busy, 'B':user_b_busy, 'C':user_c_busy}
现在假设我们有一个任务需要1个小时才能完成,我们希望在1个小时的间隔内检查午夜到上午10点之间的时间段(因此任务只能在整个小时内开始)。 以下是以列表形式检查的每个期间的表示。
task_intervals_to_check = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]]
这是一个检查两个间隔是否重叠的函数:
def intervals_overlap(service, busy):
if service[1] > busy[0] and service[0] < busy[1]:
return True
return False
所以现在这里的循环导致可用小时字典和可分配给任务的用户:
result = defaultdict(list)
for interval in task_intervals_to_check:
for user, user_busy in users_to_check.iteritems():
overlaps = False
for busy_period in user_busy:
if intervals_overlap(interval, busy_period):
overlaps = True
break
if not overlaps:
result[interval[0]].append(user)
对于长度为1小时的任务,结果是:
{0:['A','B'],1:['B'],2:['B'],3:['B'],4:['A'],5:['C '],6:['A','C'],7:['A','C'],8:['A','C','B'],9:['A', 'C','B']}
对于长度为2小时的任务,结果是:
{0:['B'],1:['B'],2:['B'],5:['C'],6:['A','C'],7:['A ','C'],8:['A','C','B']}
这是预期的结果。 下面是帮助我找到正确结果的图表:
你可以尝试摆脱最外层的循环。 假设您有周期的开始和结束来检查ps, pe
(示例中为0和10)以及task_duration
的任务持续时间(示例中为1或2)。 假设所有内容都以完整小时为单位,busy_intervals按时间排序。
result = defaultdict(list)
for user, user_busy in users_to_check.iteritems():
for l_busy_per,r_busy_per in zip([[0, ps]] + user_busy, user_busy + [[pe, 0]]):
avail_start = l_busy_per[1]
avail_end = r_busy_per[0]
for hit in range(avail_start, avail_end+1-task_duration):
result[hit].append(user)
我想补充一点问题的表示。 我认为只有开始时间的表示既充足又自然。 如果用户a忙于0-1,2-4和5-6,我会推荐这样的表示:
a_busy = (0, 2, 3, 5)
这意味着用户a在a_busy中每次忙于一个单位时间。 此外,分配的时隙也更自然地表示。
task_times = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
然后我们甚至可以使用基本集理论为每个用户推导出一个解决方案。 设user_busy为开始时间的集合,在给定长度的情况下无法分配用户。 此外,让slot_to_fill成为时隙的开始时间,在给定长度的情况下,期望由用户填充。 然后,slots_to_fill和user_busy的差异是用户的最佳分配。 以下是length = 2的示例,您的用户是:
user_busy = set([0, 1, 2, 3, 4, 5]) # Set where user cannot be assigned
slots_to_fill = set([0, 1, 2, 3, 4, 5, 6, 7, 8]) # Set where users shall be assigned
x = slots_to_fill - user_busy
print(x) # {6, 7, 8}
此解决方案最困难的方面是根据数据构建集合。 在这个问题的自然表示中,解决方案是微不足道的,可以分解为基于每个用户完成:
from itertools import chain
user_busy = [[1,2], [2,4], [5,6]]
task_intervals_to_check = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]]
length = 2
# Convert original data to tuples of starting times
busy_start_time = tuple(chain.from_iterable(range(i, j) for i, j in user_busy))
slots_to_fill = tuple(chain.from_iterable(range(i, j) for i, j in task_intervals_to_check))
def assign(fillslots, not_avail, length):
return filter(lambda x: all(x+i not in not_avail for i in range(length)) and x+length-1 <= max(fillslots), fillslots)
times = assign(slots_to_fill, busy_start_time, length)
print(list(times))
这将返回可以分配用户的开始时间列表,这些列表比列表更方便处理。 可以通过将分配间隔的长度添加到开始时间来计算结束时间。
最后,我不认为在运行时优化方面有很多好处,因为这个问题在计算上相当便宜。 如果要优化解决方案质量,首先必须定义目标。 例如,这可能是这样的:在填充所有时段时最小化分配总数。 尽管如此,这不会增加问题的难度。 与用户相关的约束会使问题变得更加困难,例如,在两小时内不得分配用户A和用户B,并且如果还分配了用户B,则只能分配用户C.
因此,我认为随着您的扩展,您最终将实现更高级的公式,并且现在已经进行了简单的集成。 我怀疑,你最好把时间表作为矩阵来处理。
我的解决方案是用Ruby制作的 - 但这个概念适用于其他语言。
这将允许您找到单独的空闲时间块,但是选择2-4小时您将得到类似这样的内容,以便一目了然地呈现:
[ 1 , 1 , 1 ],
[ 1 , 1 , 1 ],
[ 1 , 1 , 1 ],
对于更高级的搜索和实现算法,这可以在以后派上用场。 对于这个简单的解决方案,我将用以下内容进行演示。
calendar = [ # 0 is free, 1 is busy
[ 1 , 1 , 1 ], #12AM to
[ 1 , 1 , 1 ], #1AM to
[ 1 , 1 , 1 ], #2AM to
[ 1 , 1 , 1 ], #3AM to
[ 1 , 1 , 1 ], #4AM to
[ 1 , 1 , 0 ], #5AM to
[ 1 , 1 , 0 ], #6AM to
[ 1 , 1 , 0 ], #7AM to
[ 1 , 1 , 0 ], #8AM to
[ 0 , 1 , 1 ], #9AM to
[ 0 , 1 , 1 ], #10AM to
[ 1 , 1 , 1 ], #11AM to
[ 1 , 1 , 1 ], #12PM to
[ 1 , 0 , 1 ], #1PM to
[ 1 , 0 , 1 ], #2PM to
[ 1 , 0 , 1 ], #3PM to
[ 1 , 1 , 0 ], #4PM to
[ 1 , 1 , 0 ], #5PM to
[ 1 , 1 , 1 ], #6PM to
[ 1 , 1 , 1 ], #7PM to
[ 1 , 1 , 1 ], #8PM to
[ 1 , 1 , 1 ], #9PM to
[ 1 , 1 , 1 ], #10PM to
[ 1 , 1 , 1 ], #11PM to
["A","B","C"] #Users
]
def find_available_slot(length, calendar)
[].tap do |results|
calendar.transpose.collect do |schedule|
times = schedule[0...-1]
blocks = sort_it(times.each_index.select {|i| times[i] == 0 }).select { |x| x.count >= length }
results << [blocks, schedule.last] unless blocks.empty?
end
results
end
end
def sort_it(arr)
tmp, main = [], []
arr.each_with_index do |x, i|
if arr[i-1]
if arr[i-1] + 1 == x
tmp << x
else
main << tmp unless tmp.empty?
tmp = [x]
end
else
tmp << x
end
end
main << tmp
main
end
find_available_slot(2, calendar)
对于我的示例计划,查找可用2小时的块,它返回以下结果:
=> [[[[9, 10]], "A"], [[[13, 14, 15]], "B"], [[[5, 6, 7, 8], [16, 17]], "C"]]
因此结果返回一个嵌套数组,并且数组的每个元素都是这些用户的块(如果有的话)。 因此result [0]将是第一个用户可用的结果[0] [0]将是块,结果[0] [1]将告诉您哪个用户。
这种调度矩阵是非常强大的长期,我建议您使用2d这样的任何实现。
我做了一个简短的谷歌搜索,你可以在这里阅读更多:
http://www.geeksforgeeks.org/given-n-appointments-find-conflicting-appointments/
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.