Python中没有重复项的随机组合



假设您有一个大小为n的可迭代t。您想从t中绘制r元素的随机组合l。您要求l组合不同。到目前为止,我的看法如下(灵感来自 iter 工具配方(:

def random_combinations(iterable,r,size):
n=len(tuple(iterable))
combinations=[None]*size

f=mt.factorial                                            # Factorial function
nCr=f(n)//(f(r)*f(n-r))                                   # nCr
iteration_limit=10*nCr                      # Limit of iterations 10 times nCr

repeated_combinations=0                     # Counter of repeated combinations
i=0                                         # Storage index

combinations[i]=tuple(sorted(rn.sample(xrange(n),r)))      # First combination
i+=1                                                    # Advance the counting

while i < size:                                  # Loop for subsequent samples
indices=tuple(sorted(rn.sample(xrange(n),r)))
test=[ combinations[j] for j in range(i) ]
test.append(indices)
test=len(list(set(test)))
if test == i+1:                                           # Test of duplicity
repeated_combinations=0
combinations[i]=indices
i+=1
else:
repeated_combinations+=1
if repeated_combinations == iteration_limit:       # Test for iteration limit
break
return combinations

有没有其他更有效的方法可以做到这一点?我问这个是因为我将从巨大的可迭代对象(超过 100 个元素(中绘制几种组合。


选择最有用的答案后,我确认该解决方案的问题在于过滤未选择的组合的迭代。然而,这激发了我寻找一种更快的方法来过滤它们。我最终通过以下方式使用集合

import itertools as it
import math as mt
import random as rn
def random_combinations(iterable,r,l):
"""
Calculates random combinations from an iterable and returns a light-weight
iterator.

Parameters
----------

iterable : sequence, list, iterator or ndarray
Iterable from which draw the combinations.
r : int
Size of the combinations.
l : int
Number of drawn combinations.

Returns
-------

combinations : iterator or tuples
Random combinations of the elements of the iterable. Iterator object.

"""
pool=tuple(iterable)
n=len(pool)

n_combinations=nCr(n,r)                                                  # nCr
if l > n_combinations:              # Constrain l to be lesser or equal to nCr
l=n_combinations

combinations=set()           # Set storage that discards repeated combinations
while len(combinations) < l:
combinations.add(tuple(sorted(rn.sample(zrange(n),r))))

def filtro(combi):       # Index combinations to actual values of the iterable
return tuple(pool[index] for index in combi)

combinations=it.imap(filtro,combinations)              # Light-weight iterator

return combinations

该套装会自动处理重复的组合。

您可以在C(n,r(序列中选择l个随机索引,并返回与这些选定的随机索引对应的组合。

import itertools
import random
import math
def random_combinations(iterable, r, l):
copy1, copy2 = itertools.tee(iterable)
num_combos = math.comb(sum(1 for _ in copy1), r)
rand_indices = set(random.sample(range(num_combos), l))
combos = itertools.combinations(copy2, r)
selected_combos = (x[1] for x in  enumerate(combos) if x[0] in rand_indices)
return list(itertools.islice(selected_combos, l))

为了避免迭代组合,我们需要一种跳过组合的机制。 我不确定Python的标准库中是否存在这样的机制。

与其生成所有组合,然后选择其中一个(增长速度比n快得多(,不如执行以下操作:

  • 创建一个空的哈希表或集合。
  • 按顺序选择r项的示例(下面是伪代码中的实现(。
  • 检查样本是否已经存在(例如,作为哈希表中的键或集合中的值(。 如果没有,请存储该样本(例如,作为哈希表中的键或集合中的值(。
  • 继续l直到以这种方式存储样品。

下面引用的伪代码。 另见L. Devroye的《非均匀随机变量生成》,第620页。

METHOD RandomRItemsInOrder(t, r)
n = size(t)
// Special case if r is 1
if r==1: return [t[RNDINTEXC(n)]]
i = 0
kk = r
ret = NewList()
while i < n and size(ret) < r
u = RNDINTEXC(n - i)
if u <= kk
AddItem(ret, t[i])
kk = kk - 1
end
i = i + 1
end
return ret
END METHOD

除了上面的伪代码,您还可以通过储层采样生成随机样本,但保持样本的规范顺序并非易事。

最新更新