random - Generatori Pseudocasuali di Numeri

Scopo: Implementa parecchi tipi di generatori pseudocasuali di numeri.

Il modulo random fornisce un veloce generatore pseudocasuale di numeri basato sull'algoritmo Mersenne Twister. Originariamente sviluppato per produrre input per le simulazioni Monte Carlo, tramite Mersenne Twister vengono generati numeri con distribuzione pressochè uniformi rendendoli adatti a un grande spettro di applicazioni.

Generare Numeri Casuali

La funzione random() ritorna il prossimo valore a virgola mobile più vicino dalla sequenza generata. Tutti i valori di ritorno sono racchiusi tra 0 <= 1.0.

# random_random.py

import random

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

Eseguendo il programma ripetutamente, si ottengono sequenze di numeri differenti.

$ python3 random_random.py

0.038 0.115 0.705 0.902 0.907

$ python3 random_random.py

0.919 0.640 0.473 0.384 0.223

Per generare numeri compresi in un intervallo numerico definito si utilizza uniform().

# random_uniform.py

import random

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

Si passano i valori minimi e massimi ed uniform() adatta i valori restituiti da random() usando la formula min + (max - min) * random().

$ python3 random_uniform.py

79.664 89.742 89.874 1.878 34.278

Utilizzare un valore seme (Seeding)

random() produce valori diversi ogni volta che viene invocato ed ha un periodo di tempo molto largo prima che un qualsiasi numero venga ripetuto. Questo è utile per produrre valori univoci o variazioni, ma ci sono volte nelle quali è utile avere lo stesso insieme di dati a disposizione per essere elaborato in diversi modi. Una tecnica è quella di utilizzare un programma per generare valori casuali e salvarli per essere elaborati come passo separato; il che potrebbe non essere praticabile per una vasta mole di dati, quindi random comprende la funzione seed() per inizializzare un generatore pseudocasuale in modo da produrre un insieme di valori atteso.

# random_seed.py

import random

random.seed(1)

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

Il valore seme controlla il primo valore prodotto dalla formula utilizzata per produrre i numeri pseudocasuali, e, visto che la formula è deterministica, imposta anche l'intera sequenza prodotta dopo che il seme è cambiato. L'argomento di seed() può essere una qualsiasi oggetto hashable. La modalità predefinita è utilizzare una sorgente di casualità specifica alla piattaforma, se disponibile. Altrimenti viene utilizzato l'orario corrente.

$ python3 random_seed.py

0.134 0.847 0.764 0.255 0.495

$ python3 random_seed.py

0.134 0.847 0.764 0.255 0.495

Salvare lo Stato

Lo stato interno dell'algoritmo di pseudocasualità utilizzato da random() può essere salvato e utilizzato per controllare i numeri prodotti in esecuzioni successive. Ristabilire lo stato precedente prima di continuare riduce la possibilità di valori o sequenze di valori ripetute dall'input precedente. La funzione getstate() ritorna dati che possono essere utilizzati per reinizializzare il generatore di numeri casuali successivamente con setstate().

# random_state.py

import random
import os
import pickle

if os.path.exists('state.dat'):
    # Ripristino dello stato precedentemente salvato
    print('Trovato state.dat, inizializzazione del modulo random')
    with open('state.dat', 'rb') as f:
        state = pickle.load(f)
    random.setstate(state)
else:
    # Si usa uno stato di partenza noto
    print('state.dat non trovato, utilizzo un valore seme')
    random.seed(1)

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

# Salva lo state per la prossima volta
with open('state.dat', 'wb') as f:
    pickle.dump(random.getstate(), f)

# Produce ulteriori valori casuali
print('\nDopo il salvataggio dello stato:')
for i in range(3):
    print('{:04.3f}'.format(random.random()), end=' ')
print()

I dati restituiti da getstate() sono un dettaglio di implementazione, quindi questo esempio salva i dati in un file con pickle e li tratta come una scatola nera. Se il file esiste quando il programma parte, carica il vecchio stato e continua. Ogni esecuzione produce alcuni numeri prima e dopo il salvataggio dello stato, per mostrare che il ripristino dello stato fa sì che il generatore produca nuovamente gli stessi valori.

$ python3 random_state.py

state.dat non trovato, utilizzo un valore seme
0.134 0.847 0.764

Dopo il salvataggio dello stato:
0.255 0.495 0.449

$ python3 random_state.py

Trovato state.dat, inizializzazione del modulo random
0.255 0.495 0.449

Dopo il salvataggio dello stato:
0.652 0.789 0.094

Interi Casuali

random() genera numeri a virgola mobile. E' possibile convertirli in interi, ma è più conveniente utilizzare randint() per generare direttamente gli interi.

# random_randint.py

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()

Gli argomenti per randint() sono i valori dell'intervallo compresi gli estremi. I numeri possono essere negativi o positivi, ma il primo valore dovrebbe essere inferiore al secondo.

$ python3 random_randint.py

[1, 100]: 22 65 41
[-5, 5]: 2 1 -2

randrange() è una forma più generica per selezionare valori da un intervallo.

# random_randrange.py

import random

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

randrange() supporta l'argomento step, oltre ai valori di inizio e fine, il che lo rende completamente equivalente alla selezione di un valore da range(start, stop, step). E' tuttavia più efficiente, in quanto l'intervallo non viene in realtà costruito.

$ python3 random_randrange.py

0 50 75

Scegliere Elementi Casuali

Un uso comune per i generatori di numeri casuali è per selezionare un elemento casuale da una sequenza di valori enumerati, anche se detti valori non sono numeri. random comprende la funzione choice() per effettuare una scelta casuale da una sequenza. Questo esempio simula il lancio di una moneta per 10.000 volte per conteggiare il numero di testa e croce ottenuti.

# random_choice.py

import random
import itertools

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

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

print('Testa:', outcomes['heads'])
print('Croce:', outcomes['tails'])

Ci sono solo due risultati possibili, quindi invece che usare numeri e convertirli, vengono usati con choice() le parole "testa" e "croce". I risultati sono disposti in un dizionario che utilizza i nomi dei risultati come chiave.

$ python3 random_choice.py

Testa: 4965
Croce: 5035

Permutazioni

Per la simulazione di un gioco di carte è necessario mescolare il mazzo, quindi distribuire le carte ai giocatori, senza usare la stessa carta più di una volta. Se si utilizzasse choice() si potrebbe viceversa distribuire la stessa carta due volte; al contrario il mazzo potrebbe essere "mescolato" con shuffle() e le singole carte verranno rimosse non appena distribuite.

# random_shuffle.py

import random
import itertools

FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('\u2665', '\u2666', '\u2663', '\u2660')


def new_deck():
    return [
        # Si utilizzano due caratteri per il valore in modo che le stringhe
        # abbiano una lunghezza consistente.
        '{:>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()

# Si crea un nuovo mazzo, con le carte ordinate
deck = new_deck()
print('Mazzo Iniziale :')
show_deck(deck)

# Si mescola il mazzo per rendere l'ordine casuale
random.shuffle(deck)
print('\nMazzo Mescolato:')
show_deck(deck)

# Si distribuiscono 4 mani di 5 carte ciascuna
hands = [[], [], [], []]

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

# Si mostrano le mani
print('\nMani:')
for n, h in enumerate(hands):
    print('{}:'.format(n + 1), end=' ')
    for c in h:
        print(c, end=' ')
    print()

# Si mostra la rimanenza del mazzo
print('\nRimaste nel mazzo:')
show_deck(deck)

Le carte sono rappresentate come stringhe con il valore e il simbolo unicode che indica il seme. Le mani distribuite sono create aggiungendo una carta alla volta a ognuna delle quattro liste e rimosse dal mazzo in modo che non possano essere distribuite nuovamente.

$ python3 random_shuffle.py

Mazzo Iniziale
 2♥  2♦  2♣  2♠  3♥  3♦  3♣  3♠  4♥  4♦  4♣  4♠  5♥
 5♦  5♣  5♠  6♥  6♦  6♣  6♠  7♥  7♦  7♣  7♠  8♥  8♦
 8♣  8♠  9♥  9♦  9♣  9♠ 10♥ 10♦ 10♣ 10♠  J♥  J♦  J♣
 J♠  Q♥  Q♦  Q♣  Q♠  K♥  K♦  K♣  K♠  A♥  A♦  A♣  A♠

Mazzo Mescolato:
 K♦  5♦  6♦  9♦ 10♦  4♦  4♣  K♠  A♦  Q♠ 10♥  K♥ 10♣
 A♥  7♥  8♦  9♥  7♦  A♠  6♥  K♣  3♥  2♣  4♠  5♣  J♥
 J♦  3♠  4♥  7♣  5♥  Q♦  J♠  6♠  2♠  3♣  8♣  7♠  8♥
 5♠  3♦ 10♠  A♣  Q♣  2♦  Q♥  J♣  8♠  9♠  6♣  9♣  2♥

Mani:
1:  2♥  8♠  Q♣  5♠  3♣
2:  9♣  J♣  A♣  8♥  2♠
3:  6♣  Q♥ 10♠  7♠  6♠
4:  9♠  2♦  3♦  8♣  J♠

Rimaste nel mazzo:
 K♦  5♦  6♦  9♦ 10♦  4♦  4♣  K♠  A♦  Q♠ 10♥  K♥ 10♣
 A♥  7♥  8♦  9♥  7♦  A♠  6♥  K♣  3♥  2♣  4♠  5♣  J♥
 J♦  3♠  4♥  7♣  5♥  Q♦

Campionamento

Molte simulazioni richiedono campioni casuali da una popolazione di valori in input. La funzione sample() genera campioni senza ripetere i valori e senza modificare la sequenza in input. Questo esempio stampa un campione casuale di parole dal dizionario di sistema (solo sistemi Mac e Unix - N.d.T.).

# random_sample.py

import random

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

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

L'algoritmo per produrre l'insieme dei risultati prende in considerazione la dimensione dell'input e del campione richiesto per produrre il risultato nel modo più efficiente possibile.

$ python3 random_sample.py

lure's
Wendi's
better's
formalism's
breaking

$ python3 random_sample.py

sissier
forgo
sanely
chicaneries
Shelia's

Generatori Multipli Simultanei

Oltre alle funzioni a livello di modulo, random comprende anche la classe Random per gestire lo stato interno di parecchi generatori di numeri casuali. Tutte le funzioni descritte in precedenza sono disponibili come metodi delle istanze di Random, e ciascuna istanza può essere inizializzata e utilizzata separatamente, senza interferire con i valori ritornati da altre istanze.

# random_random_class.py

import random
import time

print('Inizializzazione predefinita:\n')

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

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

print('\nStesso seme:\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()))

Su di un sistema con un buon sistema di semi di valori casuali, le istanze partono in uno stato univoco. Tuttavia, se questa condizione non si manifesta, è probabile che il seme utilizzato per le istanze sia l'orario corrente, di conseguenza verranno prodotti gli stessi valori.

$ python3 random_random_class.py

Inizializzazione predefinita:

0.377  0.857
0.286  0.152
0.758  0.057

Stesso seme:

0.933  0.933
0.958  0.958
0.225  0.225

SystemRandom

Alcuni sistemi operativi forniscono un generatore di numeri casuali che ha accesso a ulteriori fonti di entropia che possono essere inserite nel generatore. random espone questa caratteristica tramite la classe SystemRandom, che ha la stessa API di Random ma utilizza os.urandom() per generare i valori che formano le basi di tutti gli altri algoritmi.

# random_system_random.py

import random
import time

print('Inizializzazione predefinita:\n')

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

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

print('\nStesso seme:\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()))

Le sequenze prodotte da SystemRandom non sono riproducibili dato che la casualità proviene dal sistema, invece che da uno stato software (in effetti seed() e setstate() non hanno alcun effetto).

$ python3 random_system_random.py

Inizializzazione predefinita:

0.249  0.057
0.914  0.401
0.682  0.903

Stesso seme:

0.823  0.142
0.456  0.970
0.027  0.730

Distribuzioni Non Uniformi

Mentre la distribuzione uniforme dei valori prodotta da random() è utile per molti scopi, ci sono altre distribuzioni che modellano più accuratamente specifiche situazioni. Il modulo random include funzioni per produrre valori anche per quelle distribuzioni. Sono elencati di seguito, ma non trattati dettagliatamente in quanto il loro uso tende a essere specialistico e richiede esempi più complessi.

Normale

La distribuzione normale viene comunemente utilizzata per valori ininterrotti non uniformi tipo voti, altezze, larghezze ecc. La curva prodotta dalla distribuzione ha una forma particolare che viene denominata "curva a campana". random include due funzioni per generare valori con una distribuzione normale, normalvariate() e gauss() (leggermente più veloce) - la distribuzione normale viene anche chiamata distribuzione Gaussiana.

La funzione collegata lognormvariate() produce valori pseudocasuali dove il logaritmo dei valori è normalmente distribuito. Queste distribuzioni sono utili per valori che sono il prodotto di diverse variabili casuali che non interagiscono.

Approssimazione

La distribuzione triangolare viene usata come distribuzione approssimata per campioni di piccole dimensioni. La curva di una distribuzione triangolare ha i due punti bassi ai valore minimo e massimo noti, il punto alto è quello (la moda) che si stima sia basato sul risultato più probabile (rispecchiato dall'argomento mode per triangular()).

Esponenziale

expovariate() produce una distribuzione esponenziale utile per simulare valori di arrivo o intervalli temporali per processi omogenei di Poisson tipo l'intervallo del decadimento radioattivo oppure le richieste in arrivo su di un server web.

La distribuzione paretiana o legge di potenza, corrisponde a molti fenomeni osservabili e fu resa popolare dal libro La coda lunga di Chris Anderson. La funzione paretovariate() è utile per simulare l'allocazione di risorse agli individui (beni alle persone, richieste ai musicisti, attenzione ai blog ecc.).

Angolare

La distribuzione di Von Mises, o circolare normale (prodotta da vonmisesvariate()) viene utilizzata per calcolare le probabilità di valori ciclici tipo angoli, giorni di calendario, orari.

Dimensioni

betavariate() genera valori con la distribuzione Beta, comunemente utilizzata in statistiche Bayesiane e applicazioni tipo la progettazione della durata di compiti.

La distribuzione Gamma prodotta da gammavariate() viene utilizzata per la progettazione delle dimensioni di cose tipo i tempi di attesa, precipitazioni ed errori computazionali.

La distribuzione di Weibull calcolata da weibullvariate() viene utilizzata in analisi di guasti, progettazione industriale e previsioni del tempo. Descrive la distribuzione di dimensioni di particelle o altri oggetti discreti.

Vedere anche:

random
La documentazione della libreria standard per questo modulo.
Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom number generator
Articolo di M. Matsumoto e T. Nishimura