Python 生成列表的随机混乱

Python 生成列表的随机混乱,python,random,permutation,Python,Random,Permutation,如何随机洗牌列表,使所有元素都不保留在其原始位置 换句话说,给定一个具有不同元素的列表a,我想生成它的排列B,这样 这种排列是随机的 对于每个n,a[n]!=b[n] e、 g 我不知道这种排列的正确术语(它是“total”吗?),因此很难用谷歌搜索。正确的术语似乎是“精神错乱”。作为一个可能的起点,费舍尔·耶茨的洗牌是这样的 def swap(xs, a, b): xs[a], xs[b] = xs[b], xs[a] def permute(xs): for a in

如何随机洗牌列表,使所有元素都不保留在其原始位置

换句话说,给定一个具有不同元素的列表
a
,我想生成它的排列
B
,这样

  • 这种排列是随机的
  • 对于每个
    n
    a[n]!=b[n]
e、 g


我不知道这种排列的正确术语(它是“total”吗?),因此很难用谷歌搜索。正确的术语似乎是“精神错乱”。

作为一个可能的起点,费舍尔·耶茨的洗牌是这样的

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))

以下是梵蒂娜描述的版本:

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)
测试(4)

这在其范围内看起来是一致的,并且它具有一个很好的特性,即每个元素在每个允许的插槽中出现的机会均等

但不幸的是,它并没有包括所有的精神错乱。共有9个大小为4的精神错乱。(上给出了n=4的公式和示例)。

这应该行得通

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分钟)后,节目结束时说“完全随机洗牌不可能”。对于大型数组,需要大量的排列。。。说len(数组)**10或其他什么

代码:

随机导入
a=[1,2,3,4]
c=[]
i=0
而我
这种排列被称为错乱。在实践中,你可以尝试随机排列,直到碰到一个错乱,它们的比率随着“n”的增长接近“e”的倒数。

经过一些研究,我能够实现如中所述的“提前拒绝”算法。事情是这样的:

import random

def random_derangement(n):
    while True:
        v = [i for i in 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

我选择这个算法是为了简单,简单地概述了其他想法。

这里是一个较小的算法,使用python语法-

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))

一种快速的方法是尝试无序移动列表,直到达到该状态。您只需尝试洗牌您的列表,直到您得到满足您的条件的列表

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)

然后我会注意到,这不是一个“完全”随机洗牌。在stackoverflow->上有一个类似的问题,但回答者说:
我的算法实际上很糟糕:你仍然有机会以最后一点未被洗牌结束。
希望这能帮助你指向正确的方向。@J0HN:我编辑了这个示例来说明这一点清楚。那
[1,1,2,3]
呢?是只需要“完全”洗牌索引,还是算法也应该查看值?@OllieFord:我的意思是,算法应该随机生成一个“好”排列。我认为这有可能导致“索引溢出”或“保留最后一个元素不变”。嗯,你说得对。修复了索引问题,但最后一个元素可能保持不变。再次编辑-这一个有机会吗?虽然我现在太累了,不能做数学题。Quick说“可能”。显然,一个经典的Fisher-Yates(您实现的是“统一洗牌算法”,虽然在列表的“已通过”部分进行交换),从索引1开始,选择“小于当前索引”作为交换位置,保证所有元素最终位于不同的位置,而您的第二个算法似乎就是这样做的,但是有了数组的“尾端”。再次更新。我的版本不工作(和以前一样,索引溢出,只使用倒数第二个元素,而不是最后一个)。而你的(假设我实现正确)不会产生所有可能的混乱。谢谢!“Derange”是我一直在寻找的词。我认为这里的任何其他答案(包括我的)都不能解决这个问题-这应该是公认的答案。我尝试了这个方法(比如
,而1:shuffle(a);if is_Derange(a)返回a
),不幸的是结果分布不均匀。@georg你能扩展吗?如果
shuffle()。在任何情况下,“早期故障”算法都是对这种方法的合理优化,对我来说效果很好。换句话说,尝试生成置换,直到找到正确的置换。这可能行得通,但我有点担心性能(我的列表有100多个元素)。@georg是的,我自己刚刚测试过。。。我认为克里斯·马丁的回答是错的
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
import random
def derange(s):
 d=s[:]
 while any([a==b for a,b in zip(d,s)]):random.shuffle(d)
 return d
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)