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 :).