我有一个人员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
必须符合2
,3
必须符合4
。 ...
对于少数人,我可以手工完成。我想知道当我的清单更大时是否有办法解决这个问题。我将CSV文件附加在较大的人ID上,以及他们必须与谁见面的here。
注意:不确定是否适合堆栈溢出,我可以在其他地方询问。随时提出建议。
答案 0 :(得分:3)
已更新,以防万一有人从Google跌落到这里。内容几乎相同。
数据的设置方式,每次会议在某种意义上都属于个人。以第1个人为例,他们需要与第2个人,第3个人和第4个人举行会议。即使第2-4个人的会议列表中未反映这些会议,这也迫使这些人开会。换句话说,存在一个对称性,因此,如果人1和人2之间有会议,则人2和人1之间也有会议。每当问题自然具有感兴趣的对象时(人)以及他们之间的对称关系(会议),图可能会成为解决问题的有效工具。就您而言,您具有以下图形。
根据此图重新确定您的问题,您想要一种将这些会议分成几组的方法,以便在任何组中,同一个人不会同时出现在两次会议中。这通常称为图形的边缘着色,在该图中,您要用所谓的“颜色”标记这些组中的每个组,并尝试为每个边缘着色,以便没有一个顶点具有两个相邻的边缘相同的颜色。在您的问题中,您给出的解决方案将表示如下。
相关信息包括称为Vizing定理的东西。如果一个人最多可以召开N次会议,那么您需要N种或N + 1种不同的颜色(不同的时隙)来解决您的问题。当然,额外的时隙可以正常工作,但是通常您需要一个最小的解决方案。在您的问题中,您给出的解决方案显然很少。通常,某些人可能没有空闲的时间段。
实际上,达到界限是NP完全的。有多种启发式算法通常可以很好地完成工作并接近。 networkx
库(顺便说一句我很喜欢)实际上不支持我不相信的边缘着色,但是等效的构造是图形的线图的顶点着色。折线图是将所有边变成顶点并说两个这样的顶点(如果以前是通过顶点连接的边)通过一条边连接的结果。您问题的折线图并不太复杂。
在查看折线图而非原始折线图时,您想要查找顶点着色,因为现在顶点是会议(您仍要分配),并且边代表由一个不能同时参加两个会议的人联系在一起。对于您的问题,给出的解决方案是折线图的以下顶点着色。
在图论中进行此练习的全部要点是,对图算法的研究和理解程度很高。以下代码将您的数据结构转换为图形,使用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中的要求将会议日程表转换为个人日程表格式。