Программирование и научные вычисления на языке Python/§17
Случайные числа находят множество применений в науке и программировании. Цель этого урока в том, чтобы рассмотреть некоторые практические задачи случайных чисел и научиться писать программы, с ними работающие. Для генерации случайных чисел в Python имеется специальный модуль random. Функция random.random() возвращает случайные числа в интервале [0; 1). Попробуем:
>>> import random >>> random.random() 0.33643236965993584 >>> random.random() 0.53110615544542961 >>> random.random() 0.80435595999923271
Получение случайных чисел основано на специальных алгоритмах, то есть они не являются действительно случайными, однако, таковыми в силу их близких свойств, мы можем считать.
Содержание |
Seed - задает случайность [править]
Каждый раз, когда мы используем функцию random.random(), мы на выходе получаем различные числа. Это ясно и естественно, но может быть не очень удобным, когда мы ищем ошибку и тогда удобнее получать один и тот же расклад для каждого запуска программы. Для этого имеется функция random.seed(), вызываемая сразу же перед началом генерации чисел:
>>> random.seed(2) >>> ['%.2f' % random.random() for i in range(7)] ['0.96', '0.95', '0.06', '0.08', '0.84', '0.74', '0.67'] >>> ['%.2f' % random.random() for i in range(7)] ['0.31', '0.61', '0.61', '0.58', '0.16', '0.43', '0.39']
А теперь, если random.seed() снова передать то же число:
>>> random.seed(2) >>> ['%.2f' % random.random() for i in range(7)] ['0.96', '0.95', '0.06', '0.08', '0.84', '0.74', '0.67']
Если мы не устанавливаем seed сами, то модуль устанавливает его по значению текущего времени. Поэтому каждый раз seed будет иметь другое значение, следовательно и наборы случайных чисел будут различны.
Равномерно распределенные случайные числа [править]
Числа, генерируемые функцией random.random() равномерно распределяются между 0 и 1, то есть вероятность обнаружить случайное число в любой точке этого интервала одинакова. Такое распределение называется равномерным (uniform). Функция random.uniform(a, b) более общая — она генерирует равномерно распределенные случайные числа для любого интервала [a, b). Посмотрим, правда ли они распределены равномерно:
import random import matplotlib.pyplot as plt N = 500 x = range(N) y = [random.uniform(-1,1) for i in x] plt.plot(x, y, 'o') plt.show()
Итак, судя по картинке, это действительно равномерное распределение. Но естественно, что имеется и некоторое отклонение, поскольку мы ожидаем настоящей случайности во всем, а значит в разные участки выбранного интервала может попасть разное число точек. Давайте посмотрим, так ли это. Разделим наш интервал на несколько промежутков (пусть это будет 20) и посмотрим гистограммы распределения для разного числа точек:
import random import matplotlib.pyplot as plt y = [random.random() for i in xrange(10**3)] plt.hist(y, 20) plt.show()
Как видно из иллюстраций справа, при стремлении числа точек к бесконечности, равномерность распределения только улучшается.
Random из NumPy [править]
В уже известном нам пакете Numerical Python также есть эффективный модуль построения массивов случайных чисел:
from numpy import random r = random.random() # одно число между 0 и 1 r = random.random(size=10000) # массив с 10000 чисел r = random.uniform(-1, 10) # одно число между -1 и 10 r = random.uniform(-1, 10, size=10000) # массив
Видно, что синтаксис этого модуля от стандартного отличается только наличием параметра size. Но построение массива для оптимизированного numpy-модуля произойдет быстрее.
Среднее значение и среднеквадратическое отклонение [править]
Что такое среднее, вам, конечно известно. Может быть, вы также знакомы из теории вероятностей или статистики с такими понятиями как дисперсия (variance), среднеквадратическое отклонение (standard deviation). Все эти понятия очень важны как параметры, характеризующее распределение случайных величин и потому их вычисление уже встроено в Numerical Python:
N = 10**6 from numpy import random, mean, var, std x = random.uniform(-1, 1, size=N) xm = mean(x) # среднее значение xv = var(x) # дисперсия xs = std(x) # СКО print '''Number: %d mean: %.2e var: %.2e stdev: %.2e''' % (N, xm, xv, xs)
Нормальное распределение [править]
В физических приложениях и гораздо чаще встречается не равномерное, а нормальное (гауссово) распределение вокруг главного значения, вероятность обнаружить которое максимальна, а вероятность обнаружения случайных чисел вокруг него одинаково уменьшается в обе стороны. Например, такое распределение имеет рост человека, ошибки при стрельбе, неточность измерений. Распределение задается этим средним значением (m), называемым математическим ожиданием, и среднеквадратическим отклонением (s).
Отдельные случайные числа мы можем получить либо с помощью стандартного модуля:
import random as random_number r = random_number.normalvariate(m, s)
либо с помощью numpy, где мы можем получить и массив чисел:
from numpy import random r = random.normal(m, s, size=N)
Чтобы представить вид гауссова распределения, напишем немного кода; в качестве параметров возьмем стандартное нормальное распределение — с математическим ожиданием в нуле и СКО, равным 1:
from numpy import random import matplotlib.pyplot as plt N = 1E6 m = 0 s = 1 y = random.normal(m, s, size=N) plt.hist(y, 100) plt.show()
Целые числа [править]
Довольно часто и не требуются распределения, а нужно выбрать из какого-то конкретного набора целых чисел [a, b], например, при броске кубика может выпасть лишь одно из шести чисел [1, 6], при этом с равной вероятностью. Бросить кубик может родной модуль:
import random as random_number r = random_number.randint(a, b)
А может и numpy. Но в отличие от встроенного, интервал здесь полуоткрытый - [a, b) и можно задавать длину возвращаемого массива с помощью N:
from numpy import random r = random.randint(a, b+1, N)
Но, на самом деле, есть функция, позволяющая включать и b, то есть задавать интервал как в первом случае - [a, b]:
from numpy import random r = random.random_integers(a, b, N)
Пример: бросание кубика [править]
Напишем программу, которая кидает кубик N раз и подсчитывает сколько раз выпала шестерка:
import random as random_number N = int(raw_input('How many experiments: ')) M = 0 for i in xrange(N): outcome = random_number.randint(1, 6) if outcome == 6: M += 1 print 'Got six %d times out of %d' % (M, N)
Код очень понятный, разумный, в духе старого доброго программирования. Функция xrange, как мы помним, более быстрый аналог range. Но мы можем написать и элегантную «векторизованную» версию:
from numpy import random, sum N = int(raw_input('How many experiments: ')) eyes = random.random_integers(1, 6, N) success = eyes == 6 # True/False array M = sum(success) # True как 1, False как 0 print 'Got six %d times out of %d' % (M, N)
Конструкция eyes == 6 векторно сравнивает все элементы массива с 6 и возвращает True в случае совпадения. Далее True это то же, что единица, False то же, что 0 и происходит суммирование. При этом важным моментом является использование именно numpy.sum, а не стандартной функции Python, это ускоряет решение для массива до 50 раз.
Случайный элемент списка [править]
Взять случайный элемент списка a очень просто:
number = random_number.choice(a)
Вызов random_number.choice(a) задает случайное число, number — переменная, его запоминающая. Кроме того можно перемешать элементы списка с помощью вызова функции shuffle:
random_number.shuffle(a)
Заметьте, что она ничего не возвращает, а преобразует сам список. Вот несколько примеров:
>>> awards = ['car', 'computer', 'ball', 'pen'] >>> import random as random_number >>> random_number.choice(awards) 'car' >>> awards[random_number.randint(0, len(awards)-1)] # заметьте, это ведь то же самое 'pen' >>> random_number.shuffle(awards) >>> awards[0] 'computer'
Пример: Колода карт [править]
Следующая функция создает и тасует колоду карт, где каждая карта представлена строкой, а колода представляет список таких строк:
def make_deck(): ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] suits = ['C', 'D', 'H', 'S'] deck = [] for s in suits: for r in ranks: deck.append(s + r) random_number.shuffle(deck) return deck
Здесь ’A’ означает туз (ace), ’J’ соответствует валету (jack), ’Q’ — королеве (queen), ’K’ — королю (king); ’C’ это трефы (clubs), ’D’ — бубны (diamonds), ’H’ — черви (hearts) и ’S’ — пики (spades). Достать колоду из карты можно так:
deck = make_deck() card = deck.pop(0) # возвратить и удалить элемент с индексом 0 (="вынуть")
Раздать несколько карт можно так:
def deal_hand(n, deck): hand = [deck[i] for i in range(n)] del deck[:n] return hand, deck
Заметьте, что мы должны передать функции изменяемый список deck. Следующая функция раздает карты нескольким игрокам:
def deal(cards_per_hand, no_of_players): deck = make_deck() hands = [] for i in range(no_of_players): hand, deck = deal_hand(cards_per_hand, deck) hands.append(hand) return hands
players = deal(5, 4) import pprint; pprint.pprint(players)
[['DQ', 'H6', 'H4', 'CJ', 'DA'],
['C2', 'SJ', 'HA', 'C9', 'H3'],
['S6', 'D3', 'CA', 'H7', 'S7'],
['C7', 'S10', 'S2', 'H9', 'C3']]
Теперь смотрим что у нас на руках. Интересным для нас будет наличие пары или тройки карт с одинаковым значением и сколько имеется карт одинаковой масти. Для этого две функции:
def same_rank(hand, n_of_a_kind): ranks = [card[1:] for card in hand] counter = 0 already_counted = [] for rank in ranks: if rank not in already_counted and ranks.count(rank) == n_of_a_kind: counter += 1 already_counted.append(rank) return counter def same_suit(hand): suits = [card[0] for card in hand] counter = {} for suit in suits: count = suits.count(suit) if count > 1: counter[suit] = count return counter
Теперь посмотрим что игроков на руках после первой раздачи:
for hand in players: print '''The hand %s has %d pairs, %s 3-of-a-kind and %s cards of the same suit.''' % \ (', '.join(hand), same_rank(hand, 2), same_rank(hand, 3),\ '+'.join([str(s) for s in same_suit(hand).values()]))
The hand DQ, H6, H4, CJ, DA
has 0 pairs, 0 3-of-a-kind and
2+2 cards of the same suit.
The hand C2, SJ, HA, C9, H3
has 0 pairs, 0 3-of-a-kind and
2+2 cards of the same suit.
The hand S6, D3, CA, H7, S7
has 1 pairs, 0 3-of-a-kind and
2 cards of the same suit.
The hand C7, S10, S2, H9, C3
has 0 pairs, 0 3-of-a-kind and
2+2 cards of the same suit.
Продолжение примера: Класс [править]
На двух предыдущих уроках мы познакомились с классами и их специальными методами. Этот пример отлично годится для того, чтобы применить к нему наши знания. Можно заметить, что deck мы перемещаем из одной функции в другую и эти функции представляются отличными кандидатами на методы класса:
class Deck: def __init__(self): ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] suits = ['C', 'D', 'H', 'S'] self.deck = [s+r for s in suits for r in ranks] random_number.shuffle(self.deck) def hand(self, n=1): """Deal n cards. Return hand as list.""" hand = [self.deck[i] for i in range(n)] # pick cards del self.deck[:n] # remove cards return hand def deal(self, cards_per_hand, no_of_players): """Deal no_of_players hands. Return list of lists.""" return [self.hand(cards_per_hand) \ for i in range(no_of_players)] def putback(self, card): """Put back a card under the rest.""" self.deck.append(card) def __str__(self): return str(self.deck)
Далее мы можем этим пользоваться этим классом, например из файла, куда мы его поместили Deck.py:
from Deck import Deck deck = Deck() print deck players = deck.deal(5, 4)