Python:将两个值(一个必须重叠;另一个必须不重叠)的项排序为四个bin

时间:2017-11-28 20:27:23

标签: python python-3.x

好的,当我写出问题时,我正在考虑解决这个问题。每个学期我都需要找到一种方法,将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

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

2 个答案:

答案 0 :(得分:4)

您想要确定的是哪些测试具有不相交集的学生。也就是说,给定一组测试,如果每组学生的交集是空的,请考虑设置有效

首先将您拥有的列表解析为studentstests

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)