許多程式需要用(看似)隨機的值來模擬真實世界的事件。
常見且熟悉的例子包括:
('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]