繁体   English   中英

调度算法,找到设定长度的所有非重叠间隔

[英]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这样的任何实现。

我做了一个简短的谷歌搜索,你可以在这里阅读更多:

预约调度算法(N人有N个自由忙位,约束满足)

在O(n)时间内重叠约会?

http://www.geeksforgeeks.org/given-n-appointments-find-conflicting-appointments/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM