summer_tree_home

Check iOでPython3をマスターするぜっ

Pearls in the box (DropBox) - 白い真珠の確率は?

どんな問題?

Pearls in the box
http://www.checkio.org/mission/box-probability/

箱の中に白と黒の真珠が入っている。交互に真珠を一つ箱から取り出し、取り出したものと反対の色の真珠を箱に入れる。このとき、N回後に白の真珠を取り出すことができる確率を求めよ。

引数は、最初の真珠の並び(黒は'b'、白は'w')と、ステップ数(N)が渡される。
戻り値は、確率を小数点2桁で返す。

ステップ数は20以下、真珠の数も20以下となる。

例題:

checkio('bbw', 3) == 0.48
checkio('wwb', 3) == 0.52
checkio('www', 3) == 0.56
checkio('bbbb', 1) == 0
checkio('wwbb', 4) == 0.5
checkio('bwbwbwb', 5) == 0.48

どうやって解く?

初期値が 'wbb' であれば、次のステップでは 'bbb'、'wwb'、'wbw' の3パターンとなる。その次のステップでも同様にそれぞれ3パターンずつ変化していく。
Nステップ後に白を取り出す確率を求めるのだから、N-1ステップ後に白が何個あるかを調べればいい。

itertools.product()で、x番目の真珠を選ぶ操作を、N-1回繰り返し、その結果の真珠の並びを文字列で取得する。
すべての文字列から'w'の数を調べて確率を求める。

from itertools import product

def checkio(marbles, step):
    def swap_marbles(choices):
        l = list(marbles)
        for i in choices:
            l[i] = 'b' if l[i] == 'w' else 'w'
        return ''.join(l)

    s = ''.join([swap_marbles(p) for p in product(range(len(marbles)), repeat=step - 1)])
    return round(s.count('w') / len(s), 2)

セルフチェックは無事にクリア。
Run&Checkすると・・・あれ?固まってる?

しばらく待っても反応がない。FAILになるわけでもなく、途中で止まってしまったようだ。
GitHubで公開されているテスト問題を確認してみると、どうやら、

"input": ('wwwwwwwwwwwwwwwwwwww', 20)

で止まっているようだ。

ローカルで試してみると、メモリーエラーになってしまった。
真珠20個で20ステップという問題だから、20^19パターン。ちょっと多すぎたか。



もうちょっと計算回数を減らす方法を考えることにした。
毎回、白と黒のどちらかを選択するわけだから、ステップ毎に2パターンと考えることができる。これなら、2^19パターンで済むので、大丈夫だろう。先ほどと違って、パターンごとに確率が変わってくる。

初期値が 'wbb'(白1+黒2)であれば、

  • 白を選択(確率は1/3)→ 結果は、白0+黒3
  • 黒を選択(確率は2/3)→ 結果は、白2+黒1

となる。
この白黒のどちらかを選択する操作を、N-1回繰り返す。

まとめ

from itertools import product
 
def checkio(marbles, step):
    def calc_probability(choices):
        white, length = marbles.count('w'), len(marbles)
        ratio = 1
        for c in choices:
            if c == 'w':
                ratio *= white / length
                white -= 1
            else:
                ratio *= 1 - white / length
                white += 1
            if white < 0 or white > length:
                return 0
        return ratio * (white / length)
 
    return round(sum(calc_probability(p) for p in product(['w', 'b'], repeat=step - 1)), 2)

http://www.checkio.org/mission/box-probability/publications/natsuki/python-3/first/

calc_probabilyty()では、ステップ毎の確率をratioとして累積していき、最終的に白が出てくる確率を求めている。すべてのパターンを合計すれば答えが求められる。

他の人の答え

再帰を使っている人が多かった。

数学的に解く人も。

def checkio(seq, n):
    r = len(seq)
    w = seq.count('w')
    return round(1/2 + (1-2/r)**(n-1) * (w/r-1/2), 2)

http://www.checkio.org/mission/box-probability/publications/Juge_Ti/python-3/maths-with-proof/

コメントで詳しい説明があるけど、、、よくわからない。数学つらい orz