Python3標准庫:random偽隨機數生成器


1. random偽隨機數生成器

random模塊基於Mersenne Twister算法提供了一個快速偽隨機數生成器。原來開發這個生成器是為了向蒙特卡洛模擬生成輸入,Mersenne Twister算法會生成大周期近均勻分布的數,因此適用於大量不同類型的應用。

1.1 生成隨機數

random()函數從所生成的序列返回下一個隨機的浮點值。返回的所有值都落在0<=n<1.0區間內。

import random

for i in range(5):
    print('%04.3f' % random.random(), end=' ')
print()

重復運行這個程序會產生不同的數字序列。

要生成一個指定數值區間內的數,則要使用uniform()。 

import random

for i in range(5):
    print('{:04.3f}'.format(random.uniform(1, 100)), end=' ')
print()

傳入最小值和最大值,uniform()會使用公式min+(max-min)*random()來調整random()的返回值。

1.2 指定種子

每次調用random()都會生成不同的值,並且在一個非常大的周期之后數字才會重復。這對於生成唯一值或變化的值很有用,不過有些情況下可能需要提供相同的數據集,從而以不同的方式處理。對此,一種技術是使用一個程序生成隨機值,並保存這些隨機值,以便在另一個步驟中再做處理。不過,這對於量很大的數據來說可能並不實用,所以random包含了一個seed()函數,可以用來初始化偽隨機數生成器,使它能生成一個期望的值集。

import random

random.seed(1)

for i in range(5):
    print('{:04.3f}'.format(random.random()), end=' ')
print()

種子(seed)值會控制由公式生成的第一個值,該公式可用來生成偽隨機數。由於公式是確定的,所以改變種子后便設置了將生成的整個序列。seed()的參數可用是任意的可散列對象。默認為使用一個平台特定的隨機源(如果有的話)。但如果沒有這樣一個隨機源,則使用當前時間。

1.3 保存狀態

random()使用的偽隨機算法的內部狀態可以保存,並用於控制后續生成的隨機數。如果在繼續生成隨機數之前恢復前一個狀態,則會減少出現重復的可能性,即避免出現之前輸入中重復的值或值序列。getstate()函數會返回一些數據,以后可以借助setstate()利用這些數據重新初始化偽隨機數生成器。 

import random
import os
import pickle

if os.path.exists('state.dat'):
    # Restore the previously saved state
    print('Found state.dat, initializing random module')
    with open('state.dat', 'rb') as f:
        state = pickle.load(f)
    random.setstate(state)
else:
    # Use a well-known start state
    print('No state.dat, seeding')
    random.seed(1)

# Produce random values
for i in range(3):
    print('{:04.3f}'.format(random.random()), end=' ')
print()

# Save state for next time
with open('state.dat', 'wb') as f:
    pickle.dump(random.getstate(), f)

# Produce more random values
print('\nAfter saving state:')
for i in range(3):
    print('{:04.3f}'.format(random.random()), end=' ')
print()

getstate()返回的數據是一個實現細節,所以這個例子用pickle將數據保存到一個文件;否則,它會把偽隨機數生成器當作一個黑盒。如果程序開始時這個文件存在,則加載原來的狀態並繼續。每次運行時都會在保存狀態之前和之后生成一些數,以展示恢復狀態會使生成器再次生成同樣的值。

第一次:

第二次:

1.4 隨機整數

random()將生成浮點數。可以把結果轉換為整數,不過直接使用randint()生成整數會更方便。

import random

print('[1, 100]:', end=' ')

for i in range(3):
    print(random.randint(1, 100), end=' ')

print('\n[-5, 5]:', end=' ')
for i in range(3):
    print(random.randint(-5, 5), end=' ')
print()

randint()的參數是值的閉區間的兩端。這些數可以是正數或負數,不過第一個值要小於第二個值。

randrange()是從區間選擇值的一種更一般的形式。

import random

for i in range(3):
    print(random.randrange(0, 101, 5), end=' ')
print()

除了開始值(start)和結束值(stop),randrange()還支持一個步長(step)參數,所以它完全等價於從range(start,stop,step)選擇一個隨機值。不過randrange更高效,因為它並沒有真正構造區間。

1.5 選擇隨機元素

隨機數生成器有一種常見用法,即從一個枚舉值序列中選擇元素,即使這些值並不是數字。random包括一個choice()函數,可以從一個序列中隨機選擇。下面這個例子模擬硬幣10000此來統計多少次面朝上,多少次面朝下。

import random
import itertools

outcomes = {
    'heads': 0,
    'tails': 0,
}
sides = list(outcomes.keys())

for i in range(10000):
    outcomes[random.choice(sides)] += 1

print('Heads:', outcomes['heads'])
print('Tails:', outcomes['tails'])

由於只允許兩個結果,所以不必使用數字然后再進行轉換,這里對choice()使用了單詞“heads”(表示面朝上)和“tails”(表示面朝下)。結果以表格形式存儲在一個字典中,使用結果名作為鍵。

1.6 排列

要模擬一個撲克牌游戲,需要把一副牌混起來,然后向玩家發牌,同一張牌不能多次使用。使用choice()可能導致同一張牌被發出兩次,所以,可以用shuffle()來洗牌,然后在發各張牌時刪除所發的牌。

import random
import itertools

FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('H', 'D', 'C', 'S')

def new_deck():
    return [
        # Always use 2 places for the value, so the strings
        # are a consistent width.
        '{:>2}{}'.format(*c)
        for c in itertools.product(
            itertools.chain(range(2, 11), FACE_CARDS),
            SUITS,
        )
    ]

def show_deck(deck):
    p_deck = deck[:]
    while p_deck:
        row = p_deck[:13]
        p_deck = p_deck[13:]
        for j in row:
            print(j, end=' ')
        print()

# Make a new deck, with the cards in order
deck = new_deck()
print('Initial deck:')
show_deck(deck)

# Shuffle the deck to randomize the order
random.shuffle(deck)
print('\nShuffled deck:')
show_deck(deck)

# Deal 4 hands of 5 cards each
hands = [[], [], [], []]

for i in range(5):
    for h in hands:
        h.append(deck.pop())

# Show the hands
print('\nHands:')
for n, h in enumerate(hands):
    print('{}:'.format(n + 1), end=' ')
    for c in h:
        print(c, end=' ')
    print()

# Show the remaining deck
print('\nRemaining deck:')
show_deck(deck)

這些撲克牌被表示為字符串,包括面值和一個表示花色的字母。要創建發出的“一手牌”,可以一次向4個列表分別增加一張牌,然后從這副牌中將發出的牌刪除,使這些牌不會再次發出。

1.7 采樣

很多模擬需要從大量輸入值中得到隨機樣本。sample()函數可以生成無重復值的樣本,並且不會修改輸入序列。下面的例子會打印系統字典中單詞的一個隨機樣本。

words.txt

pear
apricot
grape
pineapple
apple
peach
banana
plum
watermelon
lemon
orange
mango
strawberry

Demo.py

import random

with open('words.txt', 'rt') as f:
    words = f.readlines()
words = [w.rstrip() for w in words]

for w in random.sample(words, 5):
    print(w)

第一次:

第二次:

1.8 多個並發生成器

除了模塊級函數,random還包括一個Random類以管理多個隨機數生成器的內部狀態。之前介紹的所有函數都可以作為Random實例的方法得到,並且每個實例都可以被單獨初始化和使用,而不會干擾其他實例返回的值。

import random
import time

print('Default initializiation:\n')

r1 = random.Random()
r2 = random.Random()

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

print('\nSame seed:\n')

seed = time.time()
r1 = random.Random(seed)
r2 = random.Random(seed)

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

如果系統上設置了很好的原生隨機值種子,那么實例會有獨特的初始狀態。不過,如果沒有一個好的平台隨機值生成器,那么不同實例往往會以當前時間作為種子,並因此生成相同的值。

1.9 SystemRandom

有些操作系統提供了一個隨機數生成器,可以訪問更多能引入生成器的信息源。random通SystemRandom類提供了這個特性,該類與Random的API相同,不過使用os.urandom()生成值,該值會構成所有其他算法的基礎。

import random
import time

print('Default initializiation:\n')

r1 = random.SystemRandom()
r2 = random.SystemRandom()

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

print('\nSame seed:\n')

seed = time.time()
r1 = random.SystemRandom(seed)
r2 = random.SystemRandom(seed)

for i in range(3):
    print('{:04.3f}  {:04.3f}'.format(r1.random(), r2.random()))

SystemRandom產生的序列是不可再生的,因為其隨機性來自系統,而不是來自軟件狀態(實際上,seed()和setstate()根本不起作用)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM