В парке аттракционов предлагают игру: билет стоит 10 ₽, вы бросаете три кубика, и если сумма 15 или больше — получаете 100 ₽. Стоит ли играть? Ответим двумя способами — моделированием и точным перебором — и сравним.
Шаг 1. Честный ли кубик
Если кубик честный, каждая грань выпадает в среднем в 1/6 бросков. При 60 000 бросков — около 10 000 раз, но не ровно: случайность даёт небольшой разброс.
Создайте генератор с seed 7, сделайте 60 000 бросков кубика и сохраните в faces число выпадений каждой
грани 1–6, а в worst_gap — наибольшее отклонение от ожидаемых 10 000 в большую сторону.
rng.integers(1, 7, size=60000); грани — np.bincount(rolls)[1:].faces - 10000; наибольшее — max.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
import numpy as np
faces = ...
worst_gap = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
import numpy as np
rng = np.random.default_rng(7)
rolls = rng.integers(1, 7, size=60000)
faces = np.bincount(rolls)[1:]
worst_gap = (faces - 10000).max()Отклонение около 150 из 10 000 — полтора процента. Много это или мало? Теория вероятностей говорит, что у честного кубика при 60 000 бросков число выпадений грани обычно отличается от 10 000 примерно на 90 в ту или другую сторону. 153 — в пределах обычного разброса; насторожиться стоило бы, если бы отклонение было в 3–4 раза больше типичного. Строгая проверка честности — критерий хи-квадрат, он за рамками курса.
Шаг 2. Три кубика
Создайте генератор с seed 9, смоделируйте 20 000 бросков трёх кубиков (таблица 20 000 × 3) и сохраните
в sums3 сумму каждого броска, а в common_sum — сумму, которая выпадала чаще всего.
axis=1.np.bincount(sums3): индекс здесь и есть сумма.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
sums3 = ...
common_sum = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
rng = np.random.default_rng(9)
sums3 = rng.integers(1, 7, size=(20000, 3)).sum(axis=1)
common_sum = np.bincount(sums3).argmax()Шаг 3. Точный перебор
У трёх кубиков всего 6 × 6 × 6 = 216 равновероятных исходов — их можно перебрать без случайности. В модуле
о транслировании таблица всех пар получалась из столбца и строки. Для троек нужна третья ось — трёхмерный
массив формы (6, 6, 6). Его удобно представлять стопкой из шести таблиц 6 × 6: all_sums[i] — таблица для
случая, когда на первом кубике i + 1, а all_sums[i, j, k] — сумма (i + 1) + (j + 1) + (k + 1).
Грани первого кубика кладём по оси 0, второго — по оси 1, третьего — по оси 2. np.newaxis можно ставить на любое
место: он добавляет ось длины 1 именно там. А (6,) по первому правилу транслирования дополняется единицами
слева до (1, 1, 6). Сумма трёх массивов растягивается до (6, 6, 6) — все 216 сумм:
face = np.arange(1, 7)
first = face[:, np.newaxis, np.newaxis] # (6, 1, 1): грани первого кубика — по оси 0
second = face[np.newaxis, :, np.newaxis] # (1, 6, 1): второго — по оси 1
third = face # (6,) — это как (1, 1, 6): третьего — по оси 2
print(first.shape, second.shape, third.shape)
all_sums = first + second + third
print(all_sums.shape, all_sums.size)
print(all_sums[0]) # таблица 6 × 6: на первом кубике 1, строки — второй кубик, столбцы — третий
print(all_sums[2, 3, 4]) # 3 + 4 + 5
print(np.bincount(all_sums.ravel())[3:]) # сколько исходов дают сумму 3, 4, …, 18(6, 1, 1) (1, 6, 1) (6,) (6, 6, 6) 216 [[ 3 4 5 6 7 8] [ 4 5 6 7 8 9] [ 5 6 7 8 9 10] [ 6 7 8 9 10 11] [ 7 8 9 10 11 12] [ 8 9 10 11 12 13]] 12 [ 1 3 6 10 15 21 25 27 27 25 21 15 10 6 3 1]
Последняя строка — сколько исходов даёт каждую сумму от 3 до 18. У сумм 10 и 11 — поровну, по 27: они равновероятны. В моделировании из шага 2 десятка обогнала одиннадцать случайно (2550 против 2511); с другим seed победить могла и 11.
Сохраните в p10 точную вероятность выбросить на трёх кубиках ровно 10 — долю исходов в all_sums, где сумма
равна 10. А в sim_p10 — ту же вероятность по моделированию из шага 2.
all_sums == 10 (агрегат без axis считает по всему массиву).sums3 == 10.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
p10 = ...
sim_p10 = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
p10 = (all_sums == 10).mean()
sim_p10 = (sums3 == 10).mean()Шаг 4. Выгодна ли игра
Средний выигрыш за игру — вероятность выигрыша × 100 ₽ минус цена билета 10 ₽. Если он отрицательный, в среднем игра приносит убыток.
Сохраните в win_p точную вероятность получить 15 или больше (по all_sums), в expected — средний результат
одной игры в рублях по точной вероятности, а в sim_expected — то же по моделированию из шага 2.
win_p = (all_sums >= 15).mean().expected = win_p * 100 - 10; для моделирования вместо win_p — доля sums3 >= 15.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
win_p = ...
expected = ...
sim_expected = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
win_p = (all_sums >= 15).mean()
expected = win_p * 100 - 10
sim_expected = (sums3 >= 15).mean() * 100 - 10Выводы
print("выигрышных исходов:", (all_sums >= 15).sum(), "из", all_sums.size)
print("точно:", round(win_p, 4), "→ средний результат", round(expected, 2), "₽")
print("моделирование:", round((sums3 >= 15).mean(), 4), "→", round(sim_expected, 2), "₽")выигрышных исходов: 20 из 216 точно: 0.0926 → средний результат -0.74 ₽ моделирование: 0.0933 → -0.67 ₽
Выигрышных исходов 20 из 216 — чуть больше 9 %. В среднем каждая игра приносит игроку −0.74 ₽: аттракцион в выигрыше. Моделирование дало близкий ответ (−0.67 ₽) — за доли секунды и без единой формулы. Для трёх кубиков можно перебрать всё, а для задач, где исходов миллиарды, моделирование часто оказывается единственным быстрым способом.