如何在 python 中轻松洗牌
How to lightly shuffle a list in python
我遇到了这个问题,我想对列表进行随机排列,但只做了一点点。比如说,我只想移动少量元素。有没有简单的方法来完成这个?
现在我能想到的最好的方法是自己构建方法,但是有什么方法可以使用 random
库来为我做这个吗?
对整个列表使用 Fisher-Yates shuffle,但不要 运行。每移动一个条目只需 运行 一步:移动 5 个条目需要 5 步,移动 10 个条目需要 10 步。
使用 Python 的 random
模块的 shuffle
方法。它需要一个 list
和一个 random
作为参数。其中 random
是一个函数,它应该 return float 从 0.0 到 1.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 想要的。大概率偏离对角线表示将数组旋转一个元素
我遇到了这个问题,我想对列表进行随机排列,但只做了一点点。比如说,我只想移动少量元素。有没有简单的方法来完成这个?
现在我能想到的最好的方法是自己构建方法,但是有什么方法可以使用 random
库来为我做这个吗?
对整个列表使用 Fisher-Yates shuffle,但不要 运行。每移动一个条目只需 运行 一步:移动 5 个条目需要 5 步,移动 10 个条目需要 10 步。
使用 Python 的 random
模块的 shuffle
方法。它需要一个 list
和一个 random
作为参数。其中 random
是一个函数,它应该 return float 从 0.0 到 1.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 想要的。大概率偏离对角线表示将数组旋转一个元素