好的,当我写出问题时,我正在考虑解决这个问题。每个学期我都需要找到一种方法,将5-20名学生分成5-20名考试,分为四天,我们可以进行考试。我的工作是尝试将所有相同类型的测试打包到一天,如果可能的话,同时确保没有学生被指派在同一天进行两次测试。
我得到的清单看起来像这样,其中S是学生而T是他们想要的考试:
S1:T1
S1:T2
S2:T1
S2:T3
S2:T4
S3:T1
S3:T2
S4:T3
S5:T2
S5:T3
etc.
我想要做的是将所有T2排序到给定的一天 - A,B,C,D - 并确保学生,如上面的S5 - 不再进行另一项测试那天。
我知道这是一个很常见的问题,现有的解决方案我可以自己建模,但我不知道怎么称呼它...所以我没有搜索功能。
我知道我可以将这些值放入字典中,我知道我可以按键或值排序,但我不知道如何合并一个值然后分配另一个值,例如T1
和第一天给出T4
,第二天给出T2
。
我知道查询应该有示例代码 - 例如,按键或值对字典进行排序 - 但我不知道下一步该做什么。
答案 0 :(得分:4)
您想要确定的是哪些测试具有不相交集的学生。也就是说,给定一组测试,如果每组学生的交集是空的,请考虑设置有效。
首先将您拥有的列表解析为students
和tests
。
students, tests = zip(*map(lambda each: each.split(':'), data.split()))
此处,data
是您发布的内容,换行符或空格分隔的列表。接下来让我们获取一组测试,并创建从这些测试到学生的每个测试的映射。
unique_tests = set(tests)
test_map = {test : set() for test in unique_tests}
for student, test in zip(students, tests):
test_map[test].add(student)
现在,test_map
看起来像这样:
{'T1': {'S1', 'S2', 'S3'},
'T2': {'S1', 'S3', 'S5'},
'T3': {'S2', 'S4', 'S5'},
'T4': {'S2'}}
接下来,让我们列举一下可能的测试组合。
import itertools
test_combos = list(itertools.chain.from_iterable(
(itertools.combinations(unique_tests, i) for i in
range(2, len(unique_tests) + 1))))
test_combos
是:
[('T4', 'T3'),
('T4', 'T1'),
('T4', 'T2'),
('T3', 'T1'),
('T3', 'T2'),
('T1', 'T2'),
('T4', 'T3', 'T1'),
('T4', 'T3', 'T2'),
('T4', 'T1', 'T2'),
('T3', 'T1', 'T2'),
('T4', 'T3', 'T1', 'T2')]
请注意,我省略了长度为1的组合,与4次测试一样,您可以随时将它们放置在4个不同的日子中。我只考虑你可能需要加入测试的情况,即你有比天更多的测试。 (此外,一个测试的单例始终是"有效的"组合。)
现在让我们定义一个函数,该函数将获取测试列表,如果该测试组合有效,则返回True
。同样,有效意味着参加这组测试的所有学生的交集都是空的。
def valid_test_combo(tests):
pairs = itertools.combinations(tests, 2)
return all(map(lambda pair:
test_map[pair[0]].isdisjoint(test_map[pair[1]]), pairs))
然后我们可以通过过滤所有组合来获得有效测试组合的集合:
valid_combos = set(filter(valid_test_combo, test_combos))
{('T4', 'T2')}
根据您提供的限制,您只能将测试'T2'
和'T4'
结合起来。
最后,创建一组可以组合的所有测试,然后计算剩余的测试:
combined_tests = set(itertools.chain.from_iterable(valid_combos))
remaining_tests = unique_tests - combined_tests
days = list(valid_combos) + list(remaining_tests)
现在days
介绍了如何在可用日期内管理测试:
[('T4', 'T2'), 'T3', 'T1']
注意:
如果你很好奇,这似乎与经典的set-covering problem组合学问题几乎相同,这在一般情况下很难解决(NP-hard)。这个解决方案真的很实用,因为你有少量的测试,学生和几天,所以我们可以枚举所有可能的测试组合。
答案 1 :(得分:1)
我不是算法专家,但这似乎有效。如果您发现错误,请建议。 我们的想法是根据当天的测试次数为每个新条目创建排序顺序。如果学生在那天进行了测试,它将尝试在第二天进行测试。唯一的问题是它不是每天平衡测试学生的数量,所以最后一天的学生/考试最少。这可以通过调整排序顺序来解决。
import operator
class NotAssigned(Exception):
pass
students_tests = ['S1:T1','S1:T2','S2:T2','S2:T12','S2:T3','S2:T4','S3:T1','S3:T2','S4:T2','S4:T3','S5:T2','S5:T3','S6:T5','S6:T7']
days = [1,2,3,4]
final_plan = []
st_tup = []#Splitted Students_Tests
for st in students_tests:
st_tup.append(tuple(st.split(":")))
students,tests = zip(*st_tup)
for i in range(len(students)):
if i > 0:
success = False
d,s,t = zip(*final_plan)
t_stats = {}#Get count of specific test on each day
#Generate sorting sequence
if tests[i] not in t:
sort = days
else:
for day in days:
t_stats[day] = 0
for k in range(len(t)):
if t[k] == tests[i]:
t_stats[d[k]] += 1
sort = sorted(t_stats.items(), key=operator.itemgetter(1), reverse=True)
try:
for day in t_stats:
if tuple([day,tests[i]]) in list(zip(d,t)):#Test already has been assigned for this day
if tuple([day,students[i]]) not in list(zip(d,s)):#Student doesn't have test this day
final_plan.append([day,students[i],tests[i]])
success = True
break
if success == False:
for day in days:
if tuple([day,students[i]]) not in list(zip(d,s)):#Student doesn't have test this day
final_plan.append([day,students[i],tests[i]])
success = True
break
if success == False:
raise NotAssigned
except NotAssigned:
print("Couldn't find suitable day for student " + students[i] + " for test " + tests[i])
else:
final_plan.append(list([days[0],students[0],tests[0]]))
print (final_plan)