从python中的列表生成一个随机的,同样可能的组合

时间:2017-11-11 06:09:40

标签: python

我们说我有一个这样的列表:['a','b','c']。我需要从这个列表中随机组合,例如['a','c']。但是我需要所有组合具有相同的概率,因此获得['a']的机会应该与获得['b','c']的机会完全相同。我的真实列表是22个元素长,所以枚举每个单独的组合是不可能的。我的第一个想法是使用random.sample但是需要你指定元素的数量,这些元素必须随机选择,但概率必须是(这个组合中的元素数量)/(所有组合中的元素数量) )这是巨大的数字。有没有更好的方法?这将运行数千次,因此非常有效的解决方案。

2 个答案:

答案 0 :(得分:4)

我将使用combination为n选择i创建一个iterable,然后使用chain将所有这些组合组合为i等于1到n。组合的总数将为2 ** n - 1,因此我将从0到2 ** n - 2中选择一个随机整数。最后,使用islice从迭代中选择一个。

from itertools import islice, combinations, chain
from string import ascii_uppercase

def pickcomb(i):
    n = len(i)
    allcomb = chain(*(combinations(i, j) for j in range(1, n + 1)))
    k = random.randint(0, 2 ** n - 2)
    return list(islice(allcomb, k, k + 1))[0]

pickcomb(ascii_uppercase[:22])

('A', 'E', 'F', 'H', 'I', 'K', 'L', 'M', 'O', 'Q', 'S', 'T')

让我们测试一下

我怀疑大数字,我们应该看到相当均匀的分布。我将使用pandas.value_counts。您可以看到我们拥有正确数量的观察类型和相当均匀的分布。

import pandas as pd

s = pd.value_counts([pickcomb(ascii_uppercase[:5]) for _ in range(100000)])
print(len(s), 2 ** 5 - 1, s, sep='\n\n')

31

31

(A, B, C, D, E)    3329
(A, D)             3320
(C, D)             3301
(A, D, E)          3277
(D, E)             3276
(B, C, D)          3270
(A, E)             3268
(A, B)             3258
(C, E)             3251
(A, B, C)          3250
(A, B, C, E)       3248
(C, D, E)          3245
(A, C)             3245
(D,)               3241
(C,)               3234
(A, B, D)          3227
(A, C, E)          3220
(B, D, E)          3215
(A, B, E)          3213
(B, C, E)          3213
(B, C, D, E)       3213
(A, C, D)          3211
(B, E)             3194
(B, C)             3193
(A, B, D, E)       3185
(A, B, C, D)       3174
(A, C, D, E)       3158
(E,)               3151
(B,)               3150
(B, D)             3148
(A,)               3122
dtype: int64

答案 1 :(得分:4)

这是一种非常有效的方法。给定集合的所有组合的集合称为power set,即给定集合的所有子集的集合。如果集合S包含m个项目,那么总共有2**m个可能的组合,包括空集和S本身。

因此,要从S的幂集中随机选择一个组合,我们只需要从range(2**m)中选择一个随机数n作为幂集的索引,然后生成对应于n的组合。

我们可以通过查看n的二进制扩展将索引号n转换为组合。 n中有m位。我们将这些位与S中的项配对。如果给定位为1,则为我们的组合选择该项,如果它为0,则拒绝该项。

这是一个简短的演示。

from random import seed, randrange

seed(42)

def indexed_combination(seq, n):
    result = []
    for u in seq:
        if n & 1:
            result.append(u)
        n >>= 1
        if not n:
            break
    return result

print('Testing indexed_combination')
seq = 'abc'
for i in range(1 << len(seq)):
    print(i, ''.join(indexed_combination(seq, i)))
print()

def random_combination(seq):
    n = randrange(1 << len(seq))
    return indexed_combination(seq, n)

print('Testing random_combination')
seq = 'abcdefghij'
for i in range(20):
    print(i, random_combination(seq))

<强>输出

Testing indexed_combination
0 
1 a
2 b
3 ab
4 c
5 ac
6 bc
7 abc

Testing random_combination
0 ['c', 'f', 'g', 'h']
1 ['a', 'b', 'e', 'f']
2 ['a', 'b', 'e', 'f', 'j']
3 ['a', 'c', 'e', 'f', 'g', 'h', 'i']
4 ['a', 'd', 'g', 'h', 'i']
5 ['a', 'c', 'd', 'e', 'i']
6 ['a', 'e', 'g', 'h']
7 ['b', 'e', 'f', 'h']
8 ['f', 'g', 'i', 'j']
9 ['a', 'g']
10 ['a', 'c', 'd', 'e', 'f']
11 ['a', 'b', 'c', 'd', 'e', 'f', 'h']
12 ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i']
13 ['c', 'd', 'e', 'g', 'h', 'i']
14 ['b', 'c', 'e', 'f']
15 ['a', 'b', 'c', 'e', 'h', 'i']
16 ['a', 'b', 'd', 'e', 'g', 'i', 'j']
17 ['a', 'b', 'g', 'h', 'i']
18 ['a', 'b', 'c', 'e', 'h', 'i', 'j']
19 ['a', 'd', 'e', 'f', 'j']

我使用固定的种子编号在脚本开头调用随机seed函数。在开发使用伪随机数的代码时,我发现这样做很方便,因为当随机数可重现时,它可以更容易地测试和调试代码。在实际应用中,您应该使用系统熵源为Radomizer播种。您可以通过取消seed电话或执行seed(None)轻松完成此操作。如果您想要比标准Mersenee Twister生成器提供的更多随机性,您可以通过random.SystemRandom类连接到系统的随机源。