根据人员名单以及他们必须见面的人来制定时间表

时间:2018-08-09 03:31:07

标签: python

我有一个人员ID列表,他们应该与谁见面,如下所示:

people = [
    [1, [2,3,4]], 
    [2, [1,3,4]], 
    [3, [1,2,4]],
    [4, [1,2,3]]
]
#    ^ person_id
#        ^ list of person to meet

我想安排列表,以便每个人在不同的时间见面,即我想要的输出是

people = [
    [1, [2,3,4]], 
    [2, [1,4,3]], 
    [3, [4,1,2]],
    [4, [3,2,1]]
]
#        ^ ^ ^  timeslot 1,2,3

这意味着1必须符合23必须符合4。 ...

对于少数人,我可以手工完成。我想知道当我的清单更大时是否有办法解决这个问题。我将CSV文件附加在较大的人ID上,以及他们必须与谁见面的here

注意:不确定是否适合堆栈溢出,我可以在其他地方询问。随时提出建议。

2 个答案:

答案 0 :(得分:3)

已更新,以防万一有人从Google跌落到这里。内容几乎相同。

数据的设置方式,每次会议在某种意义上都属于个人。以第1个人为例,他们需要与第2个人,第3个人和第4个人举行会议。即使第2-4个人的会议列表中未反映这些会议,这也迫使这些人开会。换句话说,存在一个对称性,因此,如果人1和人2之间有会议,则人2和人1之间也有会议。每当问题自然具有感兴趣的对象时(人)以及他们之间的对称关系(会议),可能会成为解决问题的有效工具。就您而言,您具有以下图形。

graph translation of problem

根据此图重新确定您的问题,您想要一种将这些会议分成几组的方法,以便在任何组中,同一个人不会同时出现在两次会议中。这通常称为图形的边缘着色,在该图中,您要用所谓的“颜色”标记这些组中的每个组,并尝试为每个边缘着色,以便没有一个顶点具有两个相邻的边缘相同的颜色。在您的问题中,您给出的解决方案将表示如下。

graph translation of solution

相关信息包括称为Vizing定理的东西。如果一个人最多可以召开N次会议,那么您需要N种或N + 1种不同的颜色(不同的时隙)来解决您的问题。当然,额外的时隙可以正常工作,但是通常您需要一个最小的解决方案。在您的问题中,您给出的解决方案显然很少。通常,某些人可能没有空闲的时间段。

实际上,达到界限是NP完全的。有多种启发式算法通常可以很好地完成工作并接近。 networkx库(顺便说一句我很喜欢)实际上不支持我不相信的边缘着色,但是等效的构造是图形的线图的顶点着色。折线图是将所有边变成顶点并说两个这样的顶点(如果以前是通过顶点连接的边)通过一条边连接的结果。您问题的折线图并不太复杂。

line graph of problem

在查看折线图而非原始折线图时,您想要查找顶点着色,因为现在顶点是会议(您仍要分配),并且边代表由一个不能同时参加两个会议的人联系在一起。对于您的问题,给出的解决方案是折线图的以下顶点着色。

line graph of solution

在图论中进行此练习的全部要点是,对图算法的研究和理解程度很高。以下代码将您的数据结构转换为图形,使用networkx库查找折线图的顶点着色,并将数据重新格式化为所需的结构。

import networkx as nx

def build_line_graph(people):
    G = nx.Graph()
    G.add_edges_from(((p, q) for p, L in people for q in L))
    return nx.line_graph(G)

def color_graph(G):
    return nx.greedy_color(G)

def format_answer(coloring):
    res = {}
    N = max(coloring.values()) + 1
    for meeting in coloring:
        time_slot = coloring[meeting]
        for meeting_member in (0, 1):
            if meeting[meeting_member] not in res:
                res[meeting[meeting_member]] = [None] * N
            res[meeting[meeting_member]][time_slot] = meeting[1-meeting_member]
    return res

def nest_answer(people, formatted):
    return [[p, formatted[p]] for p, v in people]

>>> format_answer(color_graph(build_line_graph(people)))
{1: [2, 3, 4], 2: [1, 4, 3], 3: [4, 1, 2], 4: [3, 2, 1]}

>>> nest_answer(people, format_answer(color_graph(build_line_graph(people))))
[[1, [2, 3, 4]], [2, [1, 4, 3]], [3, [4, 1, 2]], [4, [3, 2, 1]]]

由于某些原因,我更喜欢字典中的答案,但是我们可以对其重新格式化,从而以最小的努力给出您要求的确切答案。

我要指出的一点是,您的问题没有唯一的正确答案。有效答案对时隙的任何排列也是有效答案,并且通常还有许多其他种类的着色可以满足您的必需属性。而且,不能保证贪婪算法会产生最佳解(实际上在病理上不利于反例),但在这种情况下,它实际上会产生您想出的相同解。通常,解决方案会很好。

答案 1 :(得分:1)

这是一个简单的(但可能不是最优的)解决方案:

people = [
    [1, [2,3,4]],
    [2, [1,3,4]],
    [3, [1,2,4]],
    [4, [1,2,3]]
]
meetings = set(tuple(sorted([meeter, meetee]))
               for meeter, meetees in people for meetee in meetees)

current = []
schedule = []
busy = set()
while meetings:
    for meeting in meetings:
        meeter, meetee = meeting
        if meeter in busy or meetee in busy:
            continue
        current.append(meeting)
        meetings.remove(meeting)
        busy.add(meeter)
        busy.add(meetee)
        break
    else:
        schedule.append(current)
        current = []
        busy = set()
if current:
    schedule.append(current)

num_slots = len(schedule)
personal_schedule = {meeter: [None] * num_slots for meeter, meetees in people}
for slot_index, slot in enumerate(schedule):
    for meeting in slot:
        meeter, meetee = meeting
        personal_schedule[meeter][slot_index] = meetee
        personal_schedule[meetee][slot_index] = meeter
personal_schedule = [list(items) for items in personal_schedule.items()]
print(personal_schedule)

首先构建所有会议的列表。然后,我们尝试在当前时间段内填充尽可能多的会议,以使该会议中的所有人员都不在该时间段内处于忙碌状态。当我们再也找不到这样的会议时,我们进入下一个时隙。最后,按照OP中的要求将会议日程表转换为个人日程表格式。