Недавно у нас вышла задача про колоду карт, которую мы решили как математики — с помощью теории вероятности. Теперь проверим, как это бьётся с практикой: напишем код на Python, который проведёт много экспериментов и покажет, какие там решения на самом деле.
Вот исходные задачи:
Первая: У нас есть стандартная колода из 52 игральных карт. Перемешиваем её, кладём рубашкой вверх и начинаем открывать карты по одной. Как только появился первый любой туз — открываем карты дальше. Какую карту мы после этого встретим с большей вероятностью — пикового туза или двойку треф?
Вторая: Берём всё ту же колоду карт, перемешиваем, запоминаем порядок карт и перемешиваем снова. После этого перемешиваем колоду снова и смотрим, на каких позициях оказались карты. Каково математическое ожидание того, что после перемешивания хотя бы одна карта окажется на той же позиции в колоде?
Решим эти задачи по очереди в одном и том же скрипте на Python.
Как установить Python на компьютер и начать на нём писать
Собираем колоду карт
Теоретически мы бы могли создать колоду вручную: сделать список, в котором мы бы перечислили все карты с их мастями и значениями. Но программисты никогда не делают руками то, что можно поручить машине. Поступим так же — соберём колоду из двух списков, масти и значения.
Логика такая:
- Берём список с названиями мастей.
- Берём список со значениями карт.
- В цикле по очереди объединяем по одному значению из этих двух списков и получаем полную колоду из 52 карт.
Сразу подключим нужные модули и переменные: сколько раз мы вытащили первой пикового туза, а сколько — трефовую двойку.
# подключаем модуль случайных значений
import random
# и модуль для копирования элементов
import copy
# списки с названиями мастей и значениями карт
suit = ['т','б', 'ч', 'п']
value = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'В', 'Д', 'К', 'Т', ]
# итоговый список карт в колоде
cards = []
# сколько раз вытащили первой каждую карту
ace = 0
dice = 0
# собираем колоду — для этого берём значение карты и масть
for i in value:
for j in suit:
# соединяем каждое значение с мастью
cards.append(i + ' ' + j)
# выводим исходную колоду
print(cards)
# перемешиваем её
random.shuffle(cards)
# выводим перемешанную колоду
print(len(cards))
Смотрим на результат: первая колода — собранная по очереди из каждой масти и значения, вторая — перемешанные карты. Массивы одной длины, но различаются порядком карт, это как раз то, что нам нужно:
Описываем первую задачу
В первой задаче нам нужно сделать так:
- Перемешиваем карты.
- Открываем их по одной и смотрим, это туз или нет.
- Как только дошли до туза — смотрим по очереди остальные карты.
- Если после этого первой нам встретится двойка треф, то увеличиваем её счётчик, а если первым встретится пиковый туз — то его счётчик.
- Как только мы открыли одну из этих двух карт — останавливаемся.
Теперь запишем это всё на Python. На экране мы пока ничего нового не увидим, потому что нам нужно провести серию экспериментов, а не один.
# решаем первую задачу
def first(nc):
# получаем доступ к глобальным переменным
global ace, dice
# перемешиваем карты
random.shuffle(nc)
# встретили первый туз или пока нет (на старте — нет)
flag = False
# открываем карты по одной
for i in nc:
# убираем только что открытую карту из колоды
nc.pop(0)
# если нам уже встретился первый туз до этого
if flag:
# если очередная карта — двойка треф, то увеличиваем её счётчик и выходим из цикла
if i == '2 т':
dice += 1
break
# если очередная карта — туз пик, то увеличиваем его счётчик и выходим из цикла
if i == 'Т п':
ace += 1
break
# если мы встречаем первого туза — меняем флаг на то, что туз уже был
if 'Т' in i and not flag:
flag = True
Запускаем серию экспериментов для первой задачи
Чтобы приблизить результаты к реальности, мы проведём этот эксперимент 10 000 раз. После этого посмотрим на значения счётчиков двойки и туза, будут ли они различаться и если да — то насколько. Теория говорит нам, что всё будет одинаково, но нужно проверить на практике.
# запускаем серию экспериментов
for s in range(10000):
# делаем копию исходной колоды
newCards = copy.deepcopy(cards)
# запускаем функцию решения первой задачи
first(newCards)
# выводим, сколько раз мы встретили первой двойку, а сколько — туза
print(dice, ace)
Оказывается, серия экспериментов нам показывает, что вероятность встретить первой двойку после туза выше как минимум на 10–15%. Для проверки проведём ещё 10 000 экспериментов:
Получается, что на практике вероятность встретить трефовую двойку выше, чем пикового туза.
Описываем вторую задачу
У нас уже есть код, который создаёт колоду и перемешивает её — используем это для решения второй задачи и просто продолжим всё делать в том же скрипте. Единственное, что нам дополнительно понадобится, — переменная, где мы будем хранить количество совпавших после перемешивания карт:
# сколько раз совпали карты после перемешивания
shuffleCount = 0
Вторая задача решается проще: мы просто перемешиваем колоду и по очереди сравниваем значения карт на одних и тех же местах в двух колодах — перемешанной и исходной. Если находим совпадение на каком-то месте — увеличиваем счётчик на единицу.
# решаем вторую задачу
def second(nc):
# получаем доступ к глобальной переменной
global shuffleCount
# перемешиваем колоду
random.shuffle(nc)
# перебираем порядковые номера карт
for i in range(52):
# если в перемешанной колоде карта на очередной позиции совпадает с исходной — увеличиваем счётчик
if nc[i] == cards[i]:
shuffleCount += 1
Запускаем серию экспериментов для второй задачи
Сделаем то же самое, что и в первой задаче, — серию из 10 000 экспериментов — и посмотрим на результат. Теория говорит, что в среднем как минимум одна карта точно будет на том же месте после перемешивания. Чтобы это узнать, мы разделим количество совпавших мест на количество экспериментов (10 000):
# запускаем серию экспериментов
for s in range(10000):
# делаем копию исходной колоды
newCards = copy.deepcopy(cards)
# запускаем функцию решения второй задачи
second(newCards)
# выводим значение матожидания
print('✅', shuffleCount/10000)
Получается, что тут мы полностью совпали с теоретическим решением: матожидание очень близко к единице. Это значит, что после перемешивания мы почти всегда найдём какую-то карту на прежнем месте.
# подключаем модуль случайных значений
import random
# и модуль для копирования элементов
import copy
# списки с названиями мастей и значениями карт
suit = ['т','б', 'ч', 'п']
value = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'В', 'Д', 'К', 'Т', ]
# итоговый список карт в колоде
cards = []
# сколько раз вытащили первой каждую карту
ace = 0
dice = 0
# сколько раз совпали карты после перемешивания
shuffleCount = 0
# собираем колоду — для этого берём значение карты и масть
for i in value:
for j in suit:
# соединяем каждое значение с мастью
cards.append(i + ' ' + j)
# выводим исходную колоду
print(cards)
# перемешиваем её
random.shuffle(cards)
# выводим перемешанную колоду
print(cards)
# решаем первую задачу
def first(nc):
# получаем доступ к глобальным переменным
global ace, dice
# перемешиваем карты
random.shuffle(nc)
# встретили первый туз или пока нет (на старте — нет)
flag = False
# открываем карты по одной
for i in nc:
# убираем только что открытую карту из колоды
nc.pop(0)
# если нам уже встретился первый туз до этого
if flag:
# если очередная карта — двойка треф, то увеличиваем её счётчик и выходим из цикла
if i == '2 т':
dice += 1
break
# если очередная карта — туз пик, то увеличиваем его счётчик и выходим из цикла
if i == 'Т п':
ace += 1
break
# если мы встречаем первого туза — меняем флаг на то, что туз уже был
if 'Т' in i and not flag:
flag = True
# запускаем серию экспериментов
for s in range(10000):
# делаем копию исходной колоды
newCards = copy.deepcopy(cards)
# запускаем функцию решения первой задачи
first(newCards)
# выводим, сколько раз мы встретили первой двойку, а сколько — туза
print(dice, ace)
# решаем вторую задачу
def second(nc):
# получаем доступ к глобальной переменной
global shuffleCount
# перемешиваем колоду
random.shuffle(nc)
# перебираем порядковые номера карт
for i in range(52):
# если в перемешанной колоде карта на очередной позиции совпадает с исходной — увеличиваем счётчик
if nc[i] == cards[i]:
shuffleCount += 1
# запускаем серию экспериментов
for s in range(10000):
# делаем копию исходной колоды
newCards = copy.deepcopy(cards)
# запускаем функцию решения второй задачи
second(newCards)
# выводим значение матожидания
print('✅', shuffleCount/10000)