【问题标题】:Filter generated permutations in python在python中过滤生成的排列
【发布时间】:2020-05-20 16:10:07
【问题描述】:

我想生成列表中元素的排列,但只保留一个集合,其中每个元素在每个位置上只出现一次。

例如 [1, 2, 3, 4, 5, 6] 可能是一个用户列表,我想要 3 个排列。一个好的集合是:

[1,2,3,5,4,6]
[2,1,4,6,5,3]
[3,4,5,1,6,2]

但是,例如,不能将 [1,3,2,6,5,4] 添加到上面,因为有两个排列,其中 1 出现在第一个位置两次,5 也会出现在第一个位置第五个位置两次,但是其他元素只出现在这些位置上一次。

到目前为止我的代码是:

# this simply generates a number of permutations specified by number_of_samples

def generate_perms(player_list, number_of_samples):
    myset = set()
    while len(myset) < number_of_samples:
         random.shuffle(player_list)
         myset.add(tuple(player_list))
    return [list(x) for x in myset]

# And this is my function that takes the stratified samples for permutations.
def generate_stratified_perms(player_list, number_of_samples):
    user_idx_dict = {}
    i = 0

    while(i < number_of_samples):
        perm = generate_perms(player_list, 1)
        for elem in perm:
            if not user_idx_dict[elem]:
                user_idx_dict[elem] = [perm.index(elem)]
            else:
                user_idx_dict[elem] += [perm.index(elem)]
        [...]
    return total_perms

但我不知道如何完成第二个功能。

所以简而言之,我想给我的函数一些要生成的排列,并且该函数应该给我那个排列数量,其中没有一个元素出现在同一位置上比其他元素多(一次,如果全部出现有一次,两次,如果都出现两次,等等)。

【问题讨论】:

  • 到底是什么问题?你在哪方面苦苦挣扎?
  • @AMC 我不知道如何完成该功能,我正在考虑将每个看到用户的索引(来自所有以前的排列)放入字典中,然后检查是否有任何新排列用户是否已在该位置看到过,但这将非常昂贵,因此我的代码中的 [...] 部分。

标签: python permutation permute


【解决方案1】:

让我们首先解决生成n 或更少行的情况。在这种情况下,您的输出必须是 Latin rectangleLatin square。这些很容易生成:首先构建一个拉丁方格,打乱行,打乱列,然后只保留前r 行。以下始终适用于构建拉丁方格:

1 2 3 ... n
2 3 4 ... 1
3 4 5 ... 2
... ... ...
n 1 2 3 ...

打乱行比打乱列容易得多,所以我们将打乱行,然后取transpose,然后再次打乱行。这是 Python 中的一个实现:

from random import shuffle

def latin_rectangle(n, r):
    square = [
        [1 + (i + j) % n for i in range(n)]
        for j in range(n)
    ]
    shuffle(square)
    square = list(zip(*square)) # transpose
    shuffle(square)
    return square[:r]

例子:

>>> latin_rectangle(5, 4)
[(2, 4, 3, 5, 1),
 (5, 2, 1, 3, 4),
 (1, 3, 2, 4, 5),
 (3, 5, 4, 1, 2)]

请注意,此算法无法生成所有可能的拉丁方格;通过构造,这些行是彼此的循环排列,因此您不会在其他equivalence classes 中得到拉丁方格。我假设这没关系,因为在所有可能的输出上生成统一的概率分布并不是问题要求之一。

好处是这可以保证在O(n^2) 时间内始终有效,因为它不使用rejection samplingbacktracking


现在让我们解决r &gt; n 的情况,即我们需要更多行。除非r % n == 0,否则每列的每个数字都不能有相同的频率,但它很简单,可以保证每列中的频率最多相差 1。生成足够多的拉丁方格,将它们放在彼此的顶部,然后从中切出r 行。为了获得额外的随机性,可以安全地打乱那些 r 行,但只能在切片之后。

def generate_permutations(n, r):
    rows = []
    while len(rows) < r:
        rows.extend(latin_rectangle(n, n))
    rows = rows[:r]
    shuffle(rows)
    return rows

例子:

>>> generate_permutations(5, 12)
[(4, 3, 5, 2, 1),
 (3, 4, 1, 5, 2),
 (3, 1, 2, 4, 5),
 (5, 3, 4, 1, 2),
 (5, 1, 3, 2, 4),
 (2, 5, 1, 3, 4),
 (1, 5, 2, 4, 3),
 (5, 4, 1, 3, 2),
 (3, 2, 4, 1, 5),
 (2, 1, 3, 5, 4),
 (4, 2, 3, 5, 1),
 (1, 4, 5, 2, 3)]

这使用数字1n,因为第一个列表理解中的公式1 + (i + j) % n。如果你想使用除数字1n 以外的东西,你可以把它当作一个列表(例如players)并将列表理解的这一部分更改为players[(i + j) % n],其中n = len(players)

【讨论】:

    【解决方案2】:

    如果运行时不是那么重要,我会采用惰性方式并生成所有可能的排列(itertools 可以为您完成),然后过滤掉所有不符合您要求的排列。

    这是一种方法。

    import itertools
    
    def permuts (l, n):
        all_permuts = list(itertools.permutations(l))
        picked = []
    
        for a in all_permuts:
            valid = True
            for p in picked:
                for i in range(len(a)):
                    if a[i] == p[i]:
                        valid = False
                        break
    
            if valid:
                picked.append (a)
    
            if len(picked) >= n:
                break
    
        print (picked)
    
    
    permuts ([1,2,3,4,5,6], 3)
    

    【讨论】:

    • 过滤能不能早点完成?因为我必须生成 10000 - 100 000 个排列。
    • 当然,你只需要一个额外的 if 条件,如果达到你的排列数量,它会打破循环。我编辑了这个例子。但无论如何,生成 100000 个排列是一项艰巨的任务。即使没有过滤,这在运行时也会非常昂贵。复杂度是 O(n!)
    猜你喜欢
    • 2019-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    • 2011-01-08
    • 1970-01-01
    • 2023-04-01
    • 2016-10-12
    相关资源
    最近更新 更多