许多程序需要用(看似)随机的值来模拟现实世界中的事件。
常见、熟悉的例子包括:
('Heads', 'Tails')中随机取一个值。让计算机生成真正随机的值是一项出奇困难的技术挑战,所以你会看到这类结果被称为“伪随机”。
实际上,一个设计良好的库,比如 Python 标准库中的 random模块,既快速又灵活,它给出的结果对于建模、模拟和游戏中的大多数应用来说都完全够用。
本页的其余部分会列出 random中几个最常用的函数。
我们鼓励你探索完整的 random文档,因为其中的选项远比这里介绍的要多。
在使用 random模块中的工具之前,必须先导入它:
>>> import random
# Choose random integer from a range
>>> random.randrange(1000)
360
>>> random.randrange(-1, 500)
228
>>> random.randrange(-10, 11, 2)
-8
# Choose random integer between two values (inclusive)
>>> random.randint(5, 25)
22
为了避免反复输入模块名,你可以按名称导入特定的函数:
>>> from random import choice, choices
# Using choice() to pick Heads or Tails 10 times
>>> tosses = []
... for side in range(10):
... tosses.append(choice(['H', 'T']))
>>> print(tosses)
['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
# Using choices() to pick Heads or Tails 8 times
>>> picks = []
... picks.extend(choices(['H', 'T'], k=8))
... print(picks)
['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
randrange()函数有三种形式,用于从 range(start, stop, step)中选取一个随机值:
randrange(stop) 给出一个整数 n,满足 0 <= n < stop
randrange(start, stop) 给出一个整数 n,满足 start <= n < stop
randrange(start, stop, step) 给出一个整数 n,满足 start <= n < stop,且 n 位于序列 start, start + step, start + 2*step... 中对于 step == 1 这一常见情况,randint(a, b)函数可能更方便、更易读。
randint()的可能结果_包含_上界,因此 randint(a, b)等同于使用 randrange(a, b+1):
>>> import random
# Select one number at random from the range 0, 499
>>> random.randrange(500)
219
# Select 10 numbers at random between 0 and 9 two steps apart.
>>> numbers = []
... for integer in range(10):
... numbers.append(random.randrange(0, 10, 2))
>>> print(numbers)
[2, 8, 4, 0, 4, 2, 6, 6, 8, 8]
# roll a die
>>> random.randint(1, 6)
4
本节中的函数假设你从一个序列或其他容器开始。
这通常是一个 list,或者在有一定限制的情况下,是一个 tuple 或 set(tuple 是不可变的,而 set 是无序的)。
choice()和choices()
choice()函数会从给定的序列中随机返回一个元素。
最简单的例子就是抛硬币:
# This will pick one of the two values in the list at random 5 separate times
>>> [random.choice(['H', 'T']) for _ in range(5)]
['T', 'H', 'H', 'T', 'H']
我们也可以用 choices()函数达到基本相同的效果,只需提供一个表示数组长度的关键字参数:
>>> random.choices(['H', 'T'], k=5)
['T', 'H', 'T', 'H', 'H']
在上面的例子中,我们假设硬币是均匀的,正面和反面的概率相同,不过也可以指定权重。 例如,如果袋子里有 10 个红球和 15 个绿球,我们想随机取出一个:
>>> random.choices(['red', 'green'], [10, 15])
['red']
sample()上面的 choices()例子假设的是统计学家所说的“有放回抽样”。
每一次抽取或选择都不会影响后续选择的概率,可供选择的分布在每次抽取之间保持不变。
在红球和绿球的例子中:每次选择后,我们把球_放回_袋中并摇匀,然后才进行下一次抽取。 这与取出一个红球后_它一直留在外面_的情况相反。 不放回球意味着袋中的红球变少了,下一次选择_更不可能_抽到红球。
为了模拟这种“无放回抽样”,random 模块提供了 sample()函数。
sample()的语法与 choices()类似,只是多了一个 counts 关键字参数:
>>> random.sample(['red', 'green'], counts=[10, 15], k=10)
['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green']
样本按被选中的顺序返回。
shuffle()当 k > 1 时,choices()和sample()都会返回新的数组。
相比之下,shuffle()会_原地_打乱数组的顺序,原来的顺序会丢失:
>>> my_list = [1, 2, 3, 4, 5]
>>> random.shuffle(my_list)
>>> my_list
[4, 1, 5, 2, 3]
到目前为止,我们关注的都是所有结果等概率出现的情况。
例如,random.randrange(100)给出 0 到 99 之间任何整数的概率都相同。
许多现实世界的情况远比这复杂。
因此,统计学家创造了各种各样的 distributions,用数学方式描述“现实世界”的结果。
对于整数,当所有概率都相等时,使用 randrange()和randint()。
这被称为 uniform分布。
randrange()和randint()也有对应的浮点数版本。
random() 给出一个 float 值 x,满足 0.0 <= x < 1.0。
uniform(a, b) 给出 x,满足 a <= x <= b。
>>> [round(random.random(), 3) for _ in range(5)]
[0.876, 0.084, 0.483, 0.22, 0.863]
>>> [round(random.uniform(2, 5), 3) for _ in range(5)]
[2.798, 2.539, 3.779, 3.363, 4.33]
它也被称为“正态”分布或“钟形”曲线,是描述测量值不精确性的一种非常常见的方式。
例如,假设你工作的工厂刚买进 10,000 个本应完全相同的螺栓。
你想设置工厂机器人来处理它们,于是称量了 100 个作为样本,发现它们的平均(也就是 mean)重量为 4.731g。
这极不可能意味着它们全都恰好重 4.731g。
也许你发现数值范围从 4.627 到 4.794g,但集中分布在 4.731g 附近。
这就是 Gaussian distribution,它的概率在均值处达到峰值,并在两侧对称地逐渐减小(因此呈“钟形”)。
要在软件中模拟它,我们需要某种方式来指定曲线的宽度(通常,昂贵的螺栓会比便宜的螺栓更紧密地聚集在均值附近!)。
按照惯例,这通过 standard deviation来实现:值小则曲线又尖又窄,值大则曲线又低又宽。
数学家喜欢希腊字母,所以我们用 μ('mu')表示均值,用 σ('sigma')表示标准差。
因此,如果你读到“95% 的值都在 μ 的 2σ 以内”或“希格斯玻色子以 5-sigma 置信度被探测到”,这类说法都与标准差有关。
>>> mu = 4.731
>>> sigma = 0.316
>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)]
[4.72, 4.957, 4.64, 4.556, 4.968]