今日はクリスマスイブ。この時期、忘年会やクリスマス会などで、一年で最も頻繁にビンゴが行われる時期なのではないでしょうか。
ビンゴの典型的なやり方として、リーチになった人がまず名乗り出て、その中から、更にビンゴになった人が名乗り出る、というものがあると思います。リーチになるとビンゴが近いのでは、と、皆、期待して待ちますが、ではリーチになってから、何回ぐらいでビンゴになれるのでしょうか?
ちゃんと確率計算すれば出来ると思いますが、とりあえず、シミュレーションで調べてみました。
Bingoカードの仕様は、Wikipediaで説明されているものを使用しました。以下、Wikipediaの当該ページから引用します。
一般的にビンゴカードはタテ・ヨコ5マスずつ、計25個のマス目が書かれている。
その内、中央を除く24マスには1から75までの番号のうち24個の番号が書かれており、
中央はフリースポットとして最初から有効な番号として扱われる。
1枚のカードの中で同じ番号が重複することはない。
ゲーム参加者全員にビンゴカードを配った後、進行役が無作為に番号を1つ選び
(ビンゴマシーンと呼ばれる専用の道具を使う場合が多い)、
手持ちのカードに同じ番号があればそのマスが有効となる。有効部分には印をつけたり
穴をあけたりして判別できるようにする。
これを繰り返し縦・横・斜めのいずれか1列が揃って有効になった場合に上がりとなり、
「ビンゴ」と叫んで上がったことを宣言する[1]。
カードに書かれる番号は、通常はまったくのランダムで配置されているわけではなく、
一番左の列は1-15の中から5個選ばれている。同様に、左から2列目は16-30、中央列は31-45、
右から2列目は46-60、一番右の列は61-75から5個ずつ(中央列のみフリースポットがあるので4個)
選ばれている[2]。
引用元:https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%B3%E3%82%B4
#!/usr/bin/env python
import numpy as np
import numpy.ma as ma
np.random.seed(7)
class bingocard():
def __init__(self):
self.card = np.asarray([np.random.choice(range(15*i+1, 15*(i+1)+1),5,replace=False) for i in range(5)])
self.card[(2,2)]=0
self.rcnt = np.zeros(5, dtype=int)
self.rcnt[2] = 1
self.ccnt = np.zeros(5, dtype=int)
self.ccnt[2] = 1
self.dia_lr = 1
self.dia_rl = 1
self.isbingo = False
def mask(self,val):
pos = np.where(self.card==val)
if len(pos[0])==0:
return
self.rcnt[pos[0]]+=1
self.ccnt[pos[1]]+=1
if pos[0]==pos[1]:
self.dia_lr+=1
if pos[0]==(4-pos[1]):
self.dia_rl+=1
self.isbingo = ((len(self.rcnt[self.rcnt==5])+len(self.ccnt[self.ccnt==5])+(self.dia_lr==5)+(self.dia_rl==5)))
def numlizhi(self):
return len(self.rcnt[self.rcnt==4]) + len(self.ccnt[self.ccnt==4])+(self.dia_lr==4)+(self.dia_rl ==4)
def do_test(Ncards):
cards = []
numbers = range(1,76)
np.random.shuffle(numbers)
for i in range(Ncards):
cards.append(bingocard())
firstlizhi = [0] * Ncards
firstbingo = [0] * Ncards
bingoids = []
for i,c in enumerate(numbers):
for j,card in enumerate(cards):
if j in bingoids:
continue
card.mask(c)
if firstlizhi[j]==0 and card.numlizhi()>0:
firstlizhi[j] = i
if card.isbingo:
firstbingo[j]=i
bingoids.append(j)
return np.bincount((np.array(firstbingo)-np.array(firstlizhi)))
if __name__=='__main__':
Ntrial = 100
Ncards = 100
results = []
for t in range(Ntrial):
res = do_test(Ncards)
results.append(res)
maxlen = max([len(x) for x in results])
hist = np.zeros((len(results), maxlen))
for i,res in enumerate(results):
hist[i, :len(res)] = res
histall = np.sum(hist, axis=0)
print 'histogram: ', histall
print 'Bingo within 5 times after lizhi', np.sum(histall[:6])
print 'Bingo beyond 5 times after lizhi', np.sum(histall[6:])
100人でのビンゴを100回やった場合を想定し、最初にリーチになってから(firstlizhi)、ビンゴになるまでの回数(firstbingo)をカウントしています。1つの数字の読み上げを1回と数えています。
lizhiはリーチのことです。Wikipediaで初めて知りましたが、「リーチ」と名乗り出るのは日本独自の習慣で、麻雀のリーチ(立直)から来ているそうです。立直の中国語発音はli4zhi2だと思いますので、lizhiと綴りました。
bingocardクラスのインスタンスが1枚のビンゴカードのインスタンスになるように作られています。mask関数で数字をマスクしますが、マスクすると同時に、縦横斜めのカウントを行ってしまい、どこに穴が開けられているかは記憶しない実装になっています。
乱数については、Mersenne Twitsterによる擬似乱数生成器を使わないと実装上まずいとおもいますが、numpyもPythonのrandomも、デフォルトでMersenne Twisterを裏側で使うようになっているので、問題はないと思います。実験が再現できるようにnumpy.random.seed関数で乱数のシードを与えておきましょう。
で、結果は、次のようになりました。100x100なので1万回の試行です。ですから、下二桁の手前に小数点をおけば、%表示になります(例:231→2.31%)。
histogram: [ 0. 231. 248. 301. 282. 317. 325. 392. 347. 356. 393. 445.
410. 398. 417. 383. 418. 367. 346. 347. 296. 272. 306. 252.
266. 211. 209. 166. 180. 159. 138. 115. 110. 97. 94. 65.
64. 46. 47. 37. 31. 19. 22. 25. 11. 13. 8. 4.
6. 3. 3. 0. 0. 2.]
Bingo within 5 times after lizhi 1379.0
Bingo beyond 5 times after lizhi 8621.0
1万回中、リーチになった後、1回でビンゴになった回数が231回、2回でリーチになった回数が248回…というように読みます。
より簡単に、リーチになった後、5回以内にビンゴになる確率と、ビンゴまで5回より多くかかる確率を計算してみたのが、最後の2行です。
リーチになった後、ビンゴまで5回よりも多くかかる確率は86.21%だそうです…
人によって感覚は違うと思いますが、5回よりも多くかかるのは、私の感覚では「結構かかる」方ですので、リーチになっても、ビンゴまで結構待たなければいけないことが大半である、という結果になりました。