# ディールプログラムを作る - Pythonの集合を使う

ご意見ありがとうございます。

``````#!/usr/bin/env python
'''  application of Make a deal statistics
application is using sets {}
for reference:
https://math.stackexchange.com/questions/608957/monty-hall-problem-extended
'''
import random

def Make_a_Deal(doors, doors_to_open):
'''  Generalised function of Make_a_Deal. Logic should be self explanatory
Returns win_1 for the option when no change is made in the choice of
door and win_2 when the option to change is taken.'''

win_1, win_2 = False, False

doors_set = set(range(1, doors+1))
price_set = set(random.sample(doors_set, 1))
choice1_set = set(random.sample(doors_set, 1))
open_set = set(random.sample(doors_set.difference(price_set).
difference(choice1_set), doors_to_open))
choice2_set = set(random.sample(doors_set.difference(open_set).
difference(choice1_set), 1))
win_1 = choice1_set.issubset(price_set)
win_2 = choice2_set.issubset(price_set)

return win_1, win_2

def main():
'''  input:
- throws: number of times to Make_a_Deal (must be > 0)
- doors: number of doors to choose from (must be > 2)
- doors_to_open: number of doors to be opened before giving the option
to change the initial choice (must be > 0 and <= doors-2)'''

try:
throws = int(input('how many throws: '))
doors = int(input('how many doors: '))
doors_to_open = int(input('how many doors to open: '))
if (throws < 1) or (doors < 3) or \
(doors_to_open > doors-2) or (doors_to_open < 1):
print('invalid input')
return

except Exception as e:
print('invalid input: ', e)
return

number_of_wins_1, number_of_wins_2, counter = 0, 0, 0

while counter < throws:
win_1, win_2 = Make_a_Deal(doors, doors_to_open)

if win_1:
number_of_wins_1 += 1
if win_2:
number_of_wins_2 += 1

counter += 1
print('completion is {:.2f}%'.
format(100*counter/throws), end='\r')

print('number of wins option 1 is {:.2f}%: '.
format(100*number_of_wins_1/counter))
print('number of wins option 2 is {:.2f}%: '.
format(100*number_of_wins_2/counter))

if __name__ == '__main__':
main()
``````
10

## 4 答え

これは\ \$ O（1）\ \$空間と時間で行うことができ、そこではそれを\ \$ O（n）\ \$空間と時間で行います。私はあなたのコードをきれいにし、Graipherのコードと大体同じになりました：

``````def make_a_deal(doors, doors_to_open):
doors = range(1, doors+1)
price = set(random.choice(doors))
choice_1 = set(random.choice(doors))
doors = set(doors)
open_doors = set(random.sample(doors - price - choice_1, doors_to_open))
choice_2 = set(random.sample(doors - open_doors - choice_1, 1))
return choice_1 <= price, choice_2 <= price
``````

しかし、それを早くするためにあなたができるいくつかの変更があります。

1. `price``choice_1` を生成したら、ユーザーが獲得したかどうかを確認し、 `True、False` を返します。
2. `price` をランダムに選択する必要はありません。これは、常に価格が最初になるまでドアを回転させることができるためです。そのため、 `price = 0` にすることができます。
3. 上記を拡張すると、0が価格の唯一のドアであることがわかりました。したがって、\ 1からn \ \$はすべて敗者のドアです。これから私達が選んだドアの一つが敗者のドアであったことがわかったので、そのドアを外して他のすべてのドアを下に移動させることができます。これは最後のドアを外すのと同じです。
4. 上記を拡張して、ランダムに選択されているため、\ \$ k \ \$より多くの敗者の扉を削除できます。
5. 2番目の選択肢は\ \$ 0 \からn - k - 1 \ \$の範囲です。

そして、あなたは\ \$ O（1）\ \$スペースと時間を得ることができます：

``````def make_a_deal(doors, doors_to_open):
price = 0
choice_1 = random.randrange(doors)
if choice_1 == price:
return True, False

choice_2 = random.randrange(doors - 1 - doors_to_open)
return False, choice_2 == price
``````
5

@Baldrickk `random.randrange` は範囲からランダムに選択します。私がするのは、OPが行ったように、ドアを別のグループに移動することです。それらから選択しないでください。

どのドアが賞品を隠していても問題は技術的には同じですが、シミュレートされている問題により厳密に一致させるためにランダムな選択をします。

`set` のサンプリングにヘルパー関数を使用した2番目の @ Graipherの回答しかし、戻り値を `random.sample` から `set` に自動的に変換する次の定義を使用することで、解決策を改善できます。

``````def random_choice(population, k=1):
return {*random.sample(population, k)}
``````

あなたのdocstringはコードの読者に意図されているように思われるのであまり意味がありません。代わりに、ユーザーのためにあなたのdocstringを書いてください、どのようにではなく、機能/モジュールがしているかについて文書化してください。

あなたはその名前の中で変数の型を明示する必要はなく、格納されているものを反映する名前を選ぶのであって、方法を選ばない。

`main` とは別に入力の収集を行うと、 `input``argparse` 、またはそれに関するGUI。

ここでは一般的な `Exception` をキャッチしないでください。 `ValueError` だけが気になります。たとえば `KeyboardInterrupt` のような例外に対して ```Invalid input``` が表示されるのを防ぎます。

エラーメッセージを `sys.stderr` に出力し、オプションで `sys.exit（）`

``````#!/usr/bin/env python3

"""Application of Make a deal statistics

Application is using sets {}

For reference:
https://math.stackexchange.com/questions/608957/monty-hall-problem-extended
"""

import sys
import random

def random_choice(population, k=1):
return {*random.sample(population, k)}

def make_a_deal(doors, doors_to_open):
"""Generalised function of Make a Deal.

Returns a pair of boolean indicating:
- wether the original choice was a win for the first element
- wether the changed choice was a win for the second element
"""

doors = set(range(doors))
prize = random_choice(doors)
original_choice = random_choice(doors)

opened = random_choice(doors - prize - original_choice, doors_to_open)
changed_choice = random_choice(doors - opened - original_choice)

return original_choice <= prize, changed_choice <= prize

def main(throws, doors, doors_to_open, progression_callback=None):
"""Gather statistics about repeated simulations of Make a Deal game.

Parameters:
- throws: number of simulations to run
- doors: number of doors to choose from (must be > 2)
- doors_to_open: number of doors to be opened before giving the
option to change the initial choice (must be > 0 and <= doors - 2)
"""
if doors < 3:
raise ValueError('doors must be at least 3')

if not (0 < doors_to_open <= doors - 2):
raise ValueError('doors to open mismatch the number of doors')

original_wins = changed_wins = 0
for throw in range(throws):
original_win, changed_win = make_a_deal(doors, doors_to_open)

if original_win:
original_wins += 1

if changed_win:
changed_wins += 1

if progression_callback is not None:
progression_callback(throw/throws)

return original_wins/throws, changed_wins/throws

def show_completion(ratio):
print('Completion is {:.2%}'.format(ratio), end='\r')

if __name__ == '__main__':
try:
throws = int(input('How many throws: '))
doors = int(input('How many doors: '))
doors_to_open = int(input('How many doors to open: '))
wins1, wins2 = main(throws, doors, doors_to_open, show_completion)
except ValueError as e:
print('Invalid input:', file=sys.stderr, end=' ')
sys.exit(e)

print('Number of wins without changing is {:.2%}'.format(wins1))
print('Number of wins after changing is {:.2%}'.format(wins2))
``````
4

おかげで、たくさんの機能、特にrandom_choiceが直接セットを作り、関数show_completionをmainに渡してくれました。

Pythonには公式のスタイルガイド、 PEP8 があります。変数名と関数名には `lower_case` を使用することをお勧めします。 `docstrings` に関するスタイルガイドもあります。 PEP257 では、三重二重引用符の使用をお勧めします（ ```"" "だから、このように" ""``` ）。

あなたのモジュール `docstring` には、 "n個のドアを持つ拡張モンティ - ホール問題、そのうちのk個はゲームマスターによって開かれています"のような1文章の要約を含めるべきです。

`random.choice``set` に対して機能しないのは残念です。なぜならそれらはインデックス化できないからです。ただし、代わりに `random.sample（x、1）`を使用している場合が多いので、それを関数に含めることをお勧めします。

``````def random_choice(x):
return random.sample(x, 1)[0]
``````

これは、 `set（random_choice（x））`の代わりに `{random_choice（x）}` を使用する必要があることも意味します。これは単一の要素であり、リストではなくなったためです。一つの要素で。

`doors_set.difference(price_set).difference(choice1_set)` is the same as `doors_set - price_set - choice1_set`, which is shorter and at least as readable.

Similarly, `choice1_set.issubset(price_set)` is equivalent to `choice1_set <= price_set` (even though here the more expressive version is probably easier to understand).

Pythonで変数を宣言する必要はありません。特にその値をオーバーライドする場合（ `win_1``win_2` ）は特にです。

あなたが0か1で始まるあなたのドアを列挙しても違いはありません、それで私は簡単のために `set（range（doors））`を使います。

これで、 `make_a_deal` 関数は次のようになります。

``````def make_a_deal(doors, doors_to_open):
'''  Generalised function of Make_a_Deal. Logic should be self explanatory
Returns win_1 for the option when no change is made in the choice of
door and win_2 when the option to change is taken.'''

doors = set(range(doors))
price = {random_choice(doors)}
choice1 = {random_choice(doors)}
open_doors = set(random.sample(doors - price - choice1, doors_to_open))
choice2 = {random_choice(doors - open_doors - choice1)}
no_change = choice1 <= price
change = choice2 <= price

return no_change, change
``````
3

ありがとう、非常に有効な入力を船上に持っていこう。あなたはここでセットを使うことの効率についてコメントすることができますか？

このために、さらに一般化します。複数の賞がある場合があります。しかし、ホストは賞金を背後にしてドアを開くことができないため、開くことができるドアの数が制限されます。

\ \$ n \ \$扉、\ \$ p \ \$賞品があり、ホストが\ \$ d \ \$扉を開いた場合、扉を切り替えずに勝利する確率は単純に\ \$ \ frac {p} {n} \ \$です。ホストがドアを使って行うことは私たちの最初の選択には影響しません。また、ゲームが進行するために、ホストは\ n-p-1 \ \$を超えてドアを開くことができません。

1. 最初の推測では正しかった
2. 最初の推測では賞品を選びませんでした

\ \$（1）\ \$は確率\ \$ \ frac {p} {n} \ \$で発生し、逆に\ \$（2）\ \$は確率\ \$ \ frac {np} {n} \ \$で発生します。 。

ケース\ \$（1）\ \$の場合、最初に選んだドアの後ろに賞があります。そのため、残りの閉じたドアの後ろに\ \$ p-1 \ \$賞があります。ホストが\ \$ d \ \$ドアを開いているので、切り替える\$ \ n-d-1 \ \$ドアがあります。

case \ \$（2）\ \$の場合は、切り替えることができるドアの後ろに\ \$ p \ \$賞品がありますが、選択できるドアはまだあります。

\$\$ P（勝ち\切替え）= \ frac {p} {n} \ frac {p-1} {nd-1} + \ frac {np} {n} \ frac {p} {nd-1} = \ frac {p} {n} \ frac {n-1} {nd-1} \$\$

これを考慮すると、私のpython実装は次のようになります。

``````def get_win_percentage(n, d, p, switching):
if d > n-p-1:
return 0
if not switching:
return p/n
return (p/n)*(n-1)/(n-d-1)
``````

これはあなたの質問に直接答えないことを私は知っていますが、あなたは他のユーザーからたくさんの良いフィードバックを得ているので、私はこの方法で貢献したいと思いました。

3