【问题标题】:Creating groups from a set without repeating past groups从集合中创建组而不重复过去的组
【发布时间】:2017-10-16 13:36:36
【问题描述】:

我正在尝试创建一个程序,该程序可以从班级生成学生组,但不会创建以前创建的组。具体来说,我需要每周从同一组学生中创建 2 人的新学生实验组,并且我尽量不要将相同的两个学生配对超过一次。在前几周配对的学生将以某种方式作为输入。

过去的组也需要排除它们的镜像,即如果[1,2]是过去的组,[2,1]也是过去的组。

我的程序在下面。它解决了这个问题,但我想它的效率非常低。如果它是一个更好的解决方案,我会接受完全不同的代码。

import numpy,random
from itertools import combinations
class_list="""a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np
"""
students=class_list.splitlines()
#print len(students),students
combs=[map(int, comb) for comb in combinations(range(len(students)), 2)]
#print combs
done_list=[[0,4],[1,6],[2,13],[3,12],[8,10],[11,14],[15,9],
           [0,13],[1,4],[2,7],[3,12],[5,6],[8,10],[14,15],
           [0,1],[2,3],[4,5],[6,7],[8,9],[10,11],[12,15],[13,14],
           [0,2],[1,3],[4,6],[5,7],[8,14],[10,9],[12,11],[15,13]]
for i_done in done_list:
    if i_done in combs:
        combs.remove(i_done)
f_done=False
while(1):
    if f_done:
        break
    final_list=[]
    final_list_used_students=[]
    for _i in range(len(students)/2):
        rand_i=random.randint(0,len(combs)-1)
        if combs[rand_i][0] not in final_list_used_students and combs[rand_i][1] not in final_list_used_students:
            final_list.append(combs[rand_i])
            final_list_used_students.append(combs[rand_i][0])
            final_list_used_students.append(combs[rand_i][1])
        if len(final_list_used_students)==len(students):
            f_done=True
            break
print final_list

【问题讨论】:

  • 什么组? 2 种组合?
  • 为什么这个人为的代码,以及你为什么使用 Python 2 - 对此有特殊要求吗?
  • 做作,因为这是我的要求,如果可以轻松修改以生成解决方案,请尝试给出我的示例
  • 那我建议你马上切换——Python 3 是新手的首选语言。无论如何,你真的需要随机 - 为什么不订购?

标签: python combinations


【解决方案1】:

首先,我们需要将已经存在的组转换为 tuplesset。每个都需要额外排序,因为这是itertools.combinations 生成它们的顺序。因此。

done_list=[[0,4],[1,6],[2,13],[3,12],[8,10],[11,14],[15,9], #first old set
           [0,13],[1,4],[2,7],[3,12],[5,6],[8,10],[14,15],#2nd old set
           [0,1],[2,3],[4,5],[6,7],[8,9],[10,11],[12,15],[13,14],#3rd old set
           [0,2],[1,3],[4,6],[5,7],[8,14],[10,9],[12,11],[15,13]]#4th old set

done_set = {tuple(sorted(i)) for i in done_list}

然后我们可以创建一个生成器函数,它只产生不是done_set 成员的元素:

from itertools import combinations

def unseen_combinations(items, n):
    for i in combinations(items, n):
        if i not in done_set:
            done_set.add(i)
            yield i


for combination in unseen_combinations(students, 2):
    print(combination)

【讨论】:

  • 这会产生尚未使用的组合。但是我如何搜索这个新集合(不包含折扣组)并创建一组 [total students /2] (这里是 8 个)学生组。这就是我正在努力解决的问题。
  • 我的解决方案只是遍历所有可能的组合,如果该组中的学生尚未添加到集合中,则将它们随机添加到集合中。然后在最终找到匹配项时退出;需要几秒钟
【解决方案2】:

所以基本上你希望每次都覆盖所有项目,其中每个项目只被选择一次并且顺序并不重要。所以我采取了与以前不同的全新方法:

import itertools


def find_path(optional_pairs, num_pairs, result, used_population):
    if num_pairs == 0:
        return result

    while optional_pairs:
        _pair = optional_pairs.pop(0)
        if _pair[0] in used_population or _pair[1] in used_population:
            continue

        # Try omitting this _pair
        pairs = list(optional_pairs)
        result2 = find_path(pairs, num_pairs, list(result), list(used_population))
        if result2:
            return result2

        # Try adding pair to path
        used_pop = list(used_population)
        used_pop.append(_pair[0])
        used_pop.append(_pair[1])
        result2 = list(result)
        result2.append(_pair)

        pairs = list(optional_pairs)
        return find_path(pairs, num_pairs - 1, result2, used_pop)

    return []


def get_duos(population, excluded_duos):
    excluded_duos = excluded_duos + [(x[1], x[0]) for x in excluded_duos]
    all_combinations = itertools.permutations(population, 2)

    optional_pairs = set(all_combinations) - set(excluded_duos)

    return find_path(list(optional_pairs), len(population) / 2, [], [])


print get_duos(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], [('a', 'c'), ('b', 'g'), ('f', 'd'), ('e', 'h'), ('b', 'f'), ('g', 'c'), ('a', 'e'), ('h', 'd')])

我使用了另一个答案中提到的itertools.permutations,从列表中删除了排除的(及其镜像)并进行了处理。现在唯一的技巧是确保我们不会选择一个无法创建解决方案的对 - 因为在覆盖所有项目时,剩余的对无法连接到它。因此,我使用递归,每一步我都尝试使用这对解决方案,直到我们找到解决方案。

享受

【讨论】:

  • 您好,小问题,这不打折互为镜像的组,打折 [1,2] 但不打折 [2,1]。
  • 如果我尝试更改以添加所有镜像组,它会卡在 while 循环中。
【解决方案3】:

如果您不需要随机返回组,那么您可以只记住最后返回的组并继续“递增”下一组。下面的示例代码显示了如何为 50 名学生执行此操作:

student_count = 50
students_nos = range(0, student_count)
current_group = (0, 1)
group_exhausted = False

def get_next_group():
    global current_group
    global group_exhausted
    if group_exhausted:
        return None
    ret = current_group
    if (current_group[0] == students_nos[student_count - 2]) and (current_group[1] == students_nos[student_count - 1]):
        group_exhausted = True
    if current_group[1] == students_nos[student_count - 1]:
        current_group = (current_group[0] + 1, current_group[0] + 2)
    else:
        current_group = (current_group[0], current_group[1] + 1)
    return ret

# Exmpale run.....
while True:
    cur = get_next_group()
    if cur is None:
        break
    print cur

【讨论】:

    【解决方案4】:

    对于 N 选择 2,我想我刚刚听到您的规范归结为:

    itertools.combinations(students, r=2)
    

    文档位于https://docs.python.org/3/library/itertools.html#itertools.permutations

    在运行之前随意排列整个列表。

    只需维护一个描述以前实验室任务的集合,并测试该集合中组合的成员资格以拒绝重复的提案。

    编辑:感谢 Antti Haapala 的组合评论

    我如何搜索这个新集合(不包含折扣组)并创建一个集合...

    我想我不太明白这个问题。假设history 是一个具有历史学生对的集合,其中一对总是按排序顺序出现。那么这只是询问生成器和过滤的问题,是吗?

    shuffled_students = [students[i]
                         for i in numpy.random.permutation(len(students))]
    for pair in itertools.combinations(shuffled_students, r=2):
        pair = sorted(pair)
        if pair in history:
            continue
        history.add(pair)
        schedule_this(pair)
    

    【讨论】:

    • 这会找到组,但不会折扣已使用的组。
    • @azazelspeaks 你会创建一个 set 组已经被看到,并丢弃那些已经是该组成员的组。
    • 是的,我可以用 set.remove(discounted_group) 做到这一点。但是我如何搜索这个新集合(不包含折扣组)并创建一组 [total students /2] (这里是 8 个)学生组。这就是我正在努力解决的问题。
    • 还有排列 -> 组合
    猜你喜欢
    • 1970-01-01
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 2018-02-11
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 2015-11-11
    相关资源
    最近更新 更多