如何在 python 中轻松洗牌

How to lightly shuffle a list in python

我遇到了这个问题,我想对列表进行随机排列,但只做了一点点。比如说,我只想移动少量元素。有没有简单的方法来完成这个?

现在我能想到的最好的方法是自己构建方法,但是有什么方法可以使用 random 库来为我做这个吗?

对整个列表使用 Fisher-Yates shuffle,但不要 运行。每移动一个条目只需 运行 一步:移动 5 个条目需要 5 步,移动 10 个条目需要 10 步。

使用 Python 的 random 模块的 shuffle 方法。它需要一个 list 和一个 random 作为参数。其中 random 是一个函数,它应该 return float0.01.0。它有助于 shuffle 以自定义方式随机播放给定的列表。 您可以覆盖该函数。

import random

def rand():
    return random.random() / 5

arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
random.shuffle(arr, random=rand)
# OUTPUT: [9, 3, 4, 5, 6, 7, 8, 1, 2]
from random import randint

def partial_shuffle(l, factor=5):
    for _ in range(factor):
        a, b = randint(0, len(l)), randint(0, len(l)) # pick two random indexes
        l[b], l[a] = l[a], l[b] # swap the values at those indexes
    return l

这是 @rossum 推荐的部分 Fisher-Yates Shuffle。

''.join(partial_shuffle(list('abcdefghijklmnopqrstuvwxyz'), 2))

此示例从一个 运行 生成 "abcdefnhijklmgopqrsyuvwxtz",但将生成另一个 运行 的其他内容。

人们还可以解释 轻微 洗牌,因为在 @rossum 和 @meta4 提到的 Fisher-Yates 算法的每一步都有可能洗牌元素(而不是有固定数量的元素被打乱)。

def conditional_fy(l, p):
    """Shuffle elements of a list with a given probability

    Args:
        l: list
        p: shuffle probability
            (0: elements are never shuffled,
             1: elements are always shuffled)

    """
    assert 0 <= p <= 1

    for i in range(len(l) - 1, 0, -1):
        shuffle = random.random()
        if shuffle < p:
            j = random.randint(0, i - 1)
            l[i], l[j] = l[j], l[i]

一种解释是强烈或弱地保留初始顺序。最弱的保留是完全随机的洗牌,最强的是不偏离初始顺序。

这可以通过创建一个元组来实现,该元组由按常量缩放的原始索引组成,加上一些随机性,然后是值。对元组进行排序,然后遍历以按新顺序恢复原始值。如果索引的比例因子接近于零,则新顺序将是随机的。如果它接近 1,事物将倾向于强烈但不会完全保留其原始顺序。如果它更大,则结果不太可能被洗牌。

import random

orderliness = 0.75

def tuplify(x, y):
    return (orderliness * y + random.gauss(0,1), x)

values = [i+1 for i in range(20)]
print(values)
pairs = list(map(tuplify, values, range(len(values))))
pairs.sort()
partially_ordered_values = [p[1] for p in pairs]
print(partially_ordered_values)

这会产生,例如:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]  # initial ordering
[2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 11, 14, 17, 16, 15, 18, 19, 20]  # weakly shuffled

洗牌的趋势将由 orderliness 的相对大小和 random.gauss() 的标准差决定。

为了展示其中一些解决方案的作用,我发现它有助于多次 运行 蒙特卡洛算法并查看分布

首先是@meta4 解决方案的整理版本,因为它是最充实的:

from random import randrange

def partial_shuffle(l, factor=5):
    n = len(l)
    for _ in range(factor):
        a, b = randrange(n), randrange(n)
        l[b], l[a] = l[a], l[b]

我们可以 运行 多次这样做:

import numpy as np

n = 8
orig = list(range(n))
occur = np.zeros((n, n), int)

for _ in range(100000):
    x = orig[:]
    partial_shuffle(x,1)
    occur[orig,x] += 1

如果我们将出现次数 table 打印为百分比,我们将得到:

[[33.5  9.6  9.5  9.4  9.4  9.6  9.5  9.5]
 [ 9.6 33.2  9.7  9.5  9.6  9.6  9.4  9.4]
 [ 9.5  9.6 33.2  9.5  9.6  9.5  9.6  9.5]
 [ 9.5  9.3  9.6 33.4  9.5  9.5  9.5  9.6]
 [ 9.4  9.6  9.4  9.6 33.3  9.5  9.7  9.5]
 [ 9.6  9.5  9.6  9.6  9.4 33.3  9.5  9.6]
 [ 9.4  9.7  9.5  9.5  9.5  9.6 33.2  9.7]
 [ 9.5  9.5  9.6  9.5  9.7  9.5  9.6 33.2]]

每行代表项目移动到该列的概率。在这种情况下(当 n=8 时)算法将倾向于将元素留在原处,大约 33% 的时间,然后统一选择其余部分

然后我可以 运行(整理)版本的 pjs 代码:

from random import gauss

orderliness = 2

occur = np.zeros((n, n), int)

for _ in range(100000):
    x = sorted(orig, key=lambda i: gauss(i * orderliness, 1))
    occur[orig,x] += 1

这给出了非常不同的输出:

[[91.9  7.9  0.1  0.   0.   0.   0.   0. ]
 [ 7.9 84.1  7.8  0.1  0.   0.   0.   0. ]
 [ 0.1  7.8 84.1  7.9  0.1  0.   0.   0. ]
 [ 0.   0.1  7.9 84.1  7.7  0.1  0.   0. ]
 [ 0.   0.   0.1  7.7 84.2  7.8  0.1  0. ]
 [ 0.   0.   0.   0.1  7.9 84.2  7.7  0.1]
 [ 0.   0.   0.   0.   0.1  7.7 84.2  7.9]
 [ 0.   0.   0.   0.   0.   0.1  7.9 91.9]]

即项目往往保持接近他们开始的地方

这种 table 非常适合检测分布中的偏差,上面似乎没有证据表明。但是,例如,使用 Artyom 的解决方案 (shuffle(x, lambda: random() / 5)) 给出以下内容:

[[  0.   37.4   0.    0.    0.   16.7  23.8  22.1]
 [  0.    0.  100.    0.    0.    0.    0.    0. ]
 [  0.    0.    0.  100.    0.    0.    0.    0. ]
 [  0.    0.    0.    0.  100.    0.    0.    0. ]
 [  1.7   0.    0.    0.    0.   83.3  11.9   3. ]
 [  9.    7.4   0.    0.    0.    0.   64.2  19.4]
 [ 26.7  17.9   0.    0.    0.    0.    0.   55.5]
 [ 62.6  37.4   0.    0.    0.    0.    0.    0. ]]

这可能不是 OP 想要的。大概率偏离对角线表示将数组旋转一个元素