簡體   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