Python
確率
ビンゴ
Bingo

ビンゴ:リーチ後何回でビンゴになる?

今日はクリスマスイブ。この時期、忘年会やクリスマス会などで、一年で最も頻繁にビンゴが行われる時期なのではないでしょうか。

ビンゴの典型的なやり方として、リーチになった人がまず名乗り出て、その中から、更にビンゴになった人が名乗り出る、というものがあると思います。リーチになるとビンゴが近いのでは、と、皆、期待して待ちますが、ではリーチになってから、何回ぐらいでビンゴになれるのでしょうか?
ちゃんと確率計算すれば出来ると思いますが、とりあえず、シミュレーションで調べてみました。

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回よりも多くかかるのは、私の感覚では「結構かかる」方ですので、リーチになっても、ビンゴまで結構待たなければいけないことが大半である、という結果になりました。