Idź na całość, czyli paradoks Monty'ego Halla

Data publikacji: 2016-11-30 | Tagi:

Paradoks Monty'ego Halla pochodzi od nazwiska prowadzącego teleturniej Let's make a deal. U nas ten program wystąpił pod nazwą Idź na całość i był prowadzony przez Zygmunta Chajzera.

Starsi czytelnicy na pewno wiedzą o czym piszę. Młodszym przypominam materiałem filmowym:

Wróćmy do paradoksu, który intuicyjnie nie ma najmniejszego sensu :).

Wstęp w skrócie. Mamy trzy bramki - jedna z nagrodą, reszta jest pusta. Gracz wybiera jedną z bramek. Następnie prowadzący odsłania jedną bramkę ogłaszając, że jest ona pusta. Gracz ponownie staje przed wyborem: albo zostaje z pierwotnie wybraną bramką, albo zmienia swój wybór. Jeśli gracz dokona zmiany, to w tym momencie zwiększa swoje szanse na wygraną dwukrotnie.

Tak, wiem. To się nie trzyma kupy, o ile ktoś nie jest obcykany w rachunku prawdopodobieństwa (wstyd się przyznać, ale ja nie jestem).

Oto prosta tabelka prezentująca trzy warianty gry i dwa możliwe wybory gracza - ze zmianą decyzji i bez zmiany decyzji. Zakładamy, że pierwszy wybór gracza to zawsze Bramka A. Po czym prowadzący odsłania jedną z bramek, a gracz ma możliwość zmiany Bramki A na inną bramkę.

Warianty Bramka A Bramka B Bramka C Bramka usunięta Zmiana wyboru Bez zmiany wyboru
1 Nagroda Pusta Pusta B lub C Przegrana Wygrana
2 Pusta Nagroda Pusta C Wygrana Przegrana
3 Pusta Pusta Nagroda B Wygrana Przegrana

Z moich wyliczeń wynika, że zostając przy swoim pierwszym wyborze gracz ma 33,3% szans na wygraną (wygrywa jedynie w wariancie 1). Jeśli zmieni zdanie, szanse te rosną do 66,6% (wygrywa w wariancie 2 i wariancie 3).

Jeśli ktoś nadal nie jest przekonany do moich wywodów, to czas go ostatecznie przekonać :).

Napiszmy program, który metodą brute force wykona kilkaset gier ze zmianą zdania i bez zmiany zdania. Niech policzy za nas liczbę wygranych i przegranych. Zobaczymy co z tego wyniknie:

# -*- coding: utf-8 -*-
import random

COUNT = 10000
SWITCH_CHOICE = False

wins = 0

for a in range(COUNT):
    gates = ['a', 'b', 'c', ]
    gates_content = {
        'a': False,
        'b': False,
        'c': False,
    }
    # losujemy wygrywającą bramkę
    winning_gate = random.choice(gates)
    gates_content[winning_gate] = True

    first_choice = random.choice(gates)

    # dla uproszczenia usuwamy z dalszych działań wybór gracza
    gates.remove(first_choice)
    if first_choice == winning_gate:
        # usuwamy którąkolwiek bramkę
        gate_to_remove = random.choice(gates)
    else:
        # usuwamy jedynie przegrywającą bramkę
        gate_to_remove = gates[0] if gates_content[
            gates[0]] is False else gates[1]

    gates.remove(gate_to_remove)

    # przywracamy wybór gracza do dalszych działań
    gates.append(first_choice)

    if not SWITCH_CHOICE:
        choice = first_choice
    else:
        choice = gates[0] if gates[0] is not first_choice else gates[1]

    if gates_content[choice]:
        wins += 1

print("Współczynnik zwycięstw: {ratio}% (ilość gier: {games}, ilość zwycięstw: {wins})".format(
    ratio=wins * 100.0 / COUNT,
    games=COUNT,
    wins=wins,
))

Kod nie wymaga zbyt rozwlekłego opisu. Początkowe warunki ustawiamy na 10000 gier bez zmiany decyzji. Następnie wykonujemy cały algorytm opisany wyżej, czyli wybór gracza, usunięcie pustej bramki z puli i pozostanie przy pierwotnej decyzji. Z moich uruchomień wynika, że gracz wygra w około 33% przypadków.

Aby uruchomić program ze zmianą decyzji, należy ustawić SWITCH_CHOICE na True i uruchomić program ponownie. Znów mamy wybranie bramki przez gracza, usunięcie pustej bramki z puli i (tym razem) zmianę decyzji. I znów - z moich uruchomień jednoznacznie wynika, że gracz w tym wypadku wygrywa w 66% przypadków.

Cały ten post jest niezbitym dowodem na to, że jeśli pozostawimy programistę bez nadzoru i pozwolimy mu błądzić, to pójdzie w mniej więcej właśnie takie krzaki jak ja :).


Oceń ten post:
Podziel się:

comments powered by Disqus

IT w obrazkach: