Python:将两个值(一个值必须重叠,另一个值不能重叠)的项排序到四个容器中

Python:将两个值(一个值必须重叠,另一个值不能重叠)的项排序到四个容器中,python,python-3.x,Python,Python 3.x,好的,我在写问题的时候正在思考这个问题。每学期我都需要找到一种方法,将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——并确保一个学生,如上面

好的,我在写问题的时候正在思考这个问题。每学期我都需要找到一种方法,将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
在a天给出,而
T2
在B天给出


我知道查询应该有示例代码——例如,按键或值对字典排序——但我不知道下一步该怎么做

你要确定的是,哪些考试有不相交的学生参加。也就是说,给定一组测试,如果每组学生之间的交集是空的,则认为该集合是有效的。< /P>
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))
首先,将您的列表解析为
学生
测试

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\u 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))))
测试组合是:

[('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
。同样,valid意味着参加这组测试的所有学生的交叉点都是空的

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']
注:


如果你好奇的话,这似乎与经典的组合数学问题几乎相同,一般来说很难解决(NP难)。这个解决方案只有在你有少量的测试、学生和天数的情况下才真正实用,所以我们可以列举所有可能的测试组合。

我不是算法专家,但这似乎有效。如果您发现bug,请给出建议。
import itertools
test_combos = list(itertools.chain.from_iterable(
        (itertools.combinations(unique_tests, i) for i in
        range(2, len(unique_tests) + 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)

我打算明天把这个放到我的剧本里,然后我会检查一下。我对他的回答真正感兴趣的是你对每件事解释得多么仔细。特别感谢你在结尾的留言。@JohnLaudun不客气。在看到你的评论后,我会再看一遍,这里有几个问题。首先,
valid\u test\u combo
函数不正确,因为它只验证每个集合是否与第一个集合成对不相交,而不是它们是否都彼此不相交。第二个是计算
剩余的\u测试
。如果一个测试与多个其他测试进行有效组合,此方法将对其进行“双重计数”。我会想办法解决这些问题,但希望这能让你开始。这是一个很棒的答案,在帮助我学习如何思考问题方面非常有帮助,但它并没有提供非常有用的输出:这是一个长串的测试组合,没有显示天数或学生。(见下面对亚历克斯回答的评论。)@JohnLaudun很高兴这至少有点帮助。解析方法输出的方法只是每天交付的测试列表。既然你知道哪些学生在参加每次考试,那就是你所需要的。但正如我在之前的评论中提到的,最后一点并不完全正确。它不考虑
valid\u test\u组合
返回两个有效组合,每个组合中的测试相同的情况。你需要把它们分开过滤掉,这并不难。如果你还打算使用这些代码,我可以编辑我的帖子。我很难理解这两个答案是如何导入数据的,所以我最终创建了一个元组列表
[(“S1”,“4”),(“S1”,“6”),(“S2”,“11sf”),(“S3”,“6”),(“S3”,“7”),(“S4”,“11cws”),(“S5”,“7”),(“S6”,“4”),(“S6”,“2”),(“S6”,“11nf”)(“S7”、“7”)、(“S8”、“6”)、(“S8”、“10”)、(“S9”、“5”)、(“S10”、“3”)、(“S11”、“11sl”)、(“S12”、“7”)、(“S13”、“11sl”)、(“S14”、“6”)
,然后我作为
学生导入,测试=zip(*l_of u t)
。我对每个答案的结果的注释如下。我想感谢bnaecker和Alex,所以只允许一个。”@“所以我把这两个问题都抛在脑后——因为我的答案让我在一个我根本不知道的问题上朝着正确的方向