生成列表的随机紊乱

时间:2014-08-08 09:31:25

标签: python random permutation

如何随机混洗列表以使所有元素都不会保留在原始位置?

换句话说,给定一个包含不同元素的列表A,我想生成一个B的排列,以便

  • 这种排列是随机的
  • 以及每个na[n] != b[n]

e.g。

a = [1,2,3,4]
b = [4,1,2,3] # good
b = [4,2,1,3] # good

a = [1,2,3,4]
x = [2,4,3,1] # bad

我不知道这种排列的正确用语(是“总”吗?)因此很难用谷歌搜索。正确的用语似乎是“紊乱”。

7 个答案:

答案 0 :(得分:5)

这种排列称为紊乱。在实践中,您可以尝试随机排列直到达到紊乱,当'n'增长时,它们的比率接近'e'的倒数。

答案 1 :(得分:5)

经过一些研究后,我能够实现所谓的“早期拒绝”算法。在this paper。它是这样的:

import random

def random_derangement(n):
    while True:
        v = range(n)
        for j in range(n - 1, -1, -1):
            p = random.randint(0, j)
            if v[p] == j:
                break
            else:
                v[j], v[p] = v[p], v[j]
        else:
            if v[0] != 0:
                return tuple(v)

我们的想法是:我们一直在洗牌,一旦我们发现我们正在处理的排列无效(v[i]==i),我们就会从头开始破解。

快速测试表明,该算法统一生成所有紊乱:

N = 4

# enumerate all derangements for testing
import itertools
counter = {}
for p in itertools.permutations(range(N)):
    if all(p[i] != i for i in p):
        counter[p] = 0

# make M probes for each derangement
M = 5000
for _ in range(M*len(counter)):
    # generate a random derangement
    p = random_derangement(N)
    # is it really?
    assert p in counter
    # ok, record it
    counter[p] += 1

# the distribution looks uniform
for p, c in sorted(counter.items()):
    print p, c

结果:

(1, 0, 3, 2) 4934
(1, 2, 3, 0) 4952
(1, 3, 0, 2) 4980
(2, 0, 3, 1) 5054
(2, 3, 0, 1) 5032
(2, 3, 1, 0) 5053
(3, 0, 1, 2) 4951
(3, 2, 0, 1) 5048
(3, 2, 1, 0) 4996

为简单起见,我选择此算法,this presentation简要概述了其他想法。

谢谢大家))

答案 2 :(得分:4)

作为一个可能的起点,费雪耶茨洗牌就是这样。

def swap(xs, a, b):
    xs[a], xs[b] = xs[b], xs[a]

def permute(xs):
    for a in xrange(len(xs)):
        b = random.choice(xrange(a, len(xs)))
        swap(xs, a, b)

也许这会解决问题?

def derange(xs):
    for a in xrange(len(xs) - 1):
        b = random.choice(xrange(a + 1, len(xs) - 1))
        swap(xs, a, b)
    swap(len(xs) - 1, random.choice(xrange(n - 1))

这是Vatine所描述的版本:

def derange(xs):
    for a in xrange(1, len(xs)):
        b = random.choice(xrange(0, a))
        swap(xs, a, b)
    return xs

快速统计测试:

from collections import Counter

def test(n):
    derangements = (tuple(derange(range(n))) for _ in xrange(10000))
    for k,v in Counter(derangements).iteritems():
        print('{}   {}').format(k, v)

test(4)

(1, 3, 0, 2)   1665
(2, 0, 3, 1)   1702
(3, 2, 0, 1)   1636
(1, 2, 3, 0)   1632
(3, 0, 1, 2)   1694
(2, 3, 1, 0)   1671

这在它的范围内看起来是统一的,它具有很好的属性,每个元素都有相同的机会出现在每个允许的插槽中。

但不幸的是,它并没有包括所有的紊乱。有4个大小为4的紊乱。(公式和 n = 4 的例子在the Wikipedia article上给出。)

答案 3 :(得分:1)

这应该有效

import random

totalrandom = False
array = [1, 2, 3, 4]
it = 0
while totalrandom == False:
    it += 1
    shuffledArray = sorted(array, key=lambda k: random.random())
    total = 0
    for i in array:
        if array[i-1] != shuffledArray[i-1]: total += 1
    if total == 4:
        totalrandom = True

    if it > 10*len(array):
        print("'Total random' shuffle impossible")
        exit()
print(shuffledArray)

如果调用的迭代次数太多,请注意退出代码的变量it。这解释了诸如[1,1,1]或[3]

之类的数组

修改

事实证明,如果您使用大型阵列(大于15个左右),则会占用大量CPU资源。使用随机生成的100个元素阵列并将其增加到len(array)**3,我的三星Galaxy S4需要很长时间才能解决。

编辑2

在大约1200秒(20分钟)之后,程序结束了说'Total Random shuffle'不可能'。对于大型数组,您需要大量的排列...说len(数组)** 10或其他。

代码:

import random, time

totalrandom = False
array = []
it = 0

for i in range(1, 100):
    array.append(random.randint(1, 6))

start = time.time()

while totalrandom == False:
    it += 1
    shuffledArray = sorted(array, key=lambda k: random.random())
    total = 0
    for i in array:
        if array[i-1] != shuffledArray[i-1]: total += 1
    if total == 4:
        totalrandom = True

    if it > len(array)**3:
        end = time.time()
        print(end-start)
        print("'Total random' shuffle impossible")
        exit()

end = time.time()
print(end-start)
print(shuffledArray)

答案 4 :(得分:1)

这是一个较小的一个,用pythonic语法 -

import random
def derange(s):
 d=s[:]
 while any([a==b for a,b in zip(d,s)]):random.shuffle(d)
 return d

它所做的就是将列表混洗,直到没有元素匹配。另外,请注意,如果传递了一个无法解开的列表,它将永远运行。当存在重复时,它会发生。要删除重复项,只需调用此函数derange(list(set(my_list_to_be_deranged)))

答案 5 :(得分:0)

import random
a=[1,2,3,4]
c=[]
i=0
while i < len(a):
    while 1:
     k=random.choice(a)
     #print k,a[i]
     if k==a[i]:
         pass
     else:
         if k not in c:
             if i==len(a)-2:
                 if a[len(a)-1] not in c:
                     if k==a[len(a)-1]:
                         c.append(k)
                         break
                 else:
                     c.append(k)
                     break
             else:
                 c.append(k)
                 break
    i=i+1
print c

答案 6 :(得分:0)

一种快速的方法是尝试随机播放列表,直到达到该状态为止。您只需要简单地整理列表,直到剩下满足您条件的列表即可。

import random
import copy


def is_derangement(l_original, l_proposal):
    return all([l_original[i] != item for i, item in enumerate(l_proposal)])


l_original = [1, 2, 3, 4, 5]
l_proposal = copy.copy(l_original)

while not is_derangement(l_original, l_proposal):
    random.shuffle(l_proposal)

print(l_proposal)