FC2ブログ

名もなき黒猫の散歩道

MENU

じゃんけんを判定する [Python3]

こんにちは。黒猫です。今回はちゃんとPythonを解説したいと思います!

前回の記事で40人でじゃんけんをしてはいけないことがよくわかりました。今回の記事はちょっとプログラミング側の話で、じゃんけんの勝敗をどうやってPythonで判定するのが一番良いのかについて述べたいと思います。

え、何でそんなこと考えないといけないかって?
実は前回の記事のデータ、2〜40人すべての計算が終わるまでになんと1日以上かかっているのです!つまり、いい加減な方法で勝負判定していると、もっと時間がかかってしまって、大変なことになります。こういう地道な努力と気配りがプログラミングでは大事だったりするのです。

では考えていきましょう。
2人程度のじゃんけんをプログラムで認識させるのであれば、じゃんけんの優劣(グーはパーに負けてチョキに勝つ、など)を全て記載して、勝敗を判定させれば良いと思います。

しかしこの方法では、人数が増えてくるとたちまち計算量がハンパなくなってきます。(3人なら3C2 = 3回の勝負判定くらいですが、10人なら10C2 = 45回の勝負判定、20人なら20C2 = 190回の勝負判定、というようにだんだんと増えてくる)
しかも3人以上の時には2人では起こりえない「あいこ」、つまり全員違う手を出す、という可能性も考えねばなりません。

ですので、方法を少し考えないといけません。

じゃんけんで勝負が着くとは、簡単に言えば「あいこ」にならないことです。
「あいこ」とは、

  1.  全員同じ手を出している
  2.  「グー」「チョキ」「パー」が出揃っている

の2通りが考えられます。逆に考えれば、「グー」「チョキ」「パー」のうち、どれか2種類だけが出ていれば勝負が着いている、ということです。

なので、例えばグー、チョキ、パーをそれぞれa, b, cと略記して、例えば10人が出した手をaabbbccabcと表現してやれば、あとはこの文字列を分析して何種類の文字があるのかを調べればよいことになります。

というわけで、以下の3つの方法を考えました。

  1. 集合の要素数で判断
  2. if で場合分け
  3. stripで削る

1. 集合の要素数で判断

Pythonには、“集合 (set)”という概念があります。これは“お互いに異なるデータの集まり”という意味です。例えば、

{あ, い, う}、{10, 21, 32, 43, 54}、{A, 65, え, 猫}

みたいなものはすべて集合ですが、

{あ, い, う, あ}

は、"あ"が2つあるので集合ではありません。この場合は強制的に"あ"が1つ削除されて、{あ, い, う}に変換されます。これを使ってやれば、

{a, a, b, b, b, c, c, a, b, c} --> {a, b, c}
{a, a, b, b, b, a, a, a, b, a} --> {a, b}
{a, a, a, a, a, a, a, a, a, a} --> {a}

というように変換可能ですので、あとは集合のデータの種類数(集合の長さ)を調べてやれば、勝敗が決まったかどうかが分かりそうです(上の例だと2番目だけ勝負が着いている)。

というわけでPythonでこれを書くと以下のようになります。
s = "aabbbccabc"       # じゃんけんの手を示した文字列をsと命名
s_set = set(s)         # 文字列sを集合に変換
if len(s_set) == 2:    # 集合の長さ(LENgth)が2のとき
    print("勝負あり") 
else:                  # 集合の長さが2以外のとき
    print("あいこ")

2. if で場合分け

Pythonには、文字列に目的の文字があるかどうかを検索する方法があります。それを使えば、

if aがある -- yes --> if bがある -- yes --> if cがない -- yes --> 勝負あり
if aがある -- yes --> if bがある -- yes --> if cがない -- no --> あいこ
if aがある -- yes --> if bがある -- no --> if cがない -- no --> 勝負あり
if aがある -- yes --> if bがある -- no --> if cがない -- yes --> あいこ

のように場合分けすることで勝負が着いたかどうかを判断できそうです。というわけでPythonでこれを書くと以下のようになります。
s = "aabbbccabc"             # じゃんけんの手を示した文字列をsと命名
if "a" in s:                 # 文字列sの中に"a"はあるか?
    if "b" in s:             # 文字列sの中に"b"はあるか?
        if "c" not in s:     # 文字列sの中に"c"は無いか?
            print("勝負あり")
        else:                # 文字列sの中に"c"がある時
            print("あいこ") 
    elif "c" in s:           # 文字列sの中に"b"は無いが"c"はあるか?
        print("勝負あり")
    else:                    # 文字列sの中に"b"も"c"も無い
        print("あいこ")
elif "b" in s:               # 文字列sの中に"a"は無いが"b"はあるか?
    if "c" in s:             # 文字列sの中に"c"はあるか?
        print("勝負あり") 
    else:                    # 文字列sの中に"c"が無い時
        print("あいこ")
else:                        # 文字列sの中に"a"も"b"も無い
    print("あいこ") 

3. stripで区切る

Pythonには、ある文字を使って文字列の両端からその文字を引き剥がす(strip)方法があります。例えば以下のような感じです。

"aabaaaacabaaa" -- aでstrip --> "baaaacab" 
"aabaababbbbb" -- bでstrip --> "aabaaba" 
"aaaaaaaaaaaaa" -- aでstrip --> "" 

複数文字を使ってstripすることもできます。

"aabaaaacabaaa" -- aとbでstrip --> "c"
"aabaababbbbb" -- aとbでstrip --> ""
"aabaababbbbb" -- aとcでstrip --> "baababbbbb" 
"aaaaaaaaaaaaa" -- aとbでstrip --> ""
"aaaaaaaaaaaaa" -- aとcでstrip --> ""

この例からわかるように、もし文字列に2種類の文字しか含まれない場合、その2種類の文字でstripしてやった時にのみ、文字列から文字がなくなってしまう、ということがわかります。
これを使えば、stripした後の文字列の長さを数えることで、勝負が着いたかどうかを判定できそうです。というわけでPythonでこれを書くと以下のようになります。
s = "aabbbccabc"                # じゃんけんの手を示した文字列をsと命名
method = ["ab", "bc", "ca"]     # 異なる手のペア(全3組)
ans = []                        # 解析結果の一時保存場所
for i in method:                # methodから1組ずつ取り出して解析(iにab, bc, caのいずれかが代入される)
    p = s                       # コピー作成(元の文字列を変更しないよう)
    ans.append(len(p.strip(i))) # iをpからstripした後のpの文字数(LENgth)を一時保存
if ans.count(0) == 1:           # strip後に文字列pが消えた(文字数 = 0)ときが1回だけあるとき
    print("勝負あり") 
else:                           # 上記以外
    print("あいこ") 


検証
という感じでじゃんけん勝負判定メソッドを3つ用意しました。次は、この実行速度を測ります。これ以上説明を書くと長ったらしいので、測定に使用した本丸のコードは追記に載せます(勝負判定メソッド自体に変化はありません)。どうやってPython3でグラフを書くのかなど、気になる方は見て下さい。

今回は1000人でのじゃんけんを10000回(勝負が着くとは限らない)行って、1回あたりの処理に必要な時間を調べています。結果は以下のような感じになりました。
janken_speedtest.png  
というわけで2つ目の「if で場合分け」法が一番速い、ということが分かりました。他に比べて5倍以上速いですね。すばらしい!

この結果を基に、前回の記事でのシミュレーションはすべて、この「ifで場合分け」法を使って行っています。これでも1日かかるとは。。やはり40人でじゃんけんしてはいけませんね。。笑

実は黒猫は、一番はじめに3.のstripの方法を思いついて計算を開始したのですが、2日経っても計算が終わらなかったので、諦めて改善方法を探ることにしたのが、今回の発端でした。いやはや、プログラムの効率って本当に大事です。。痛感。。


そんな感じで、終わりたいと思います!
私の自己満足にお付き合い頂きありがとうございました!笑

追記。
以下、今回の実行速度シミュレーションに使ったコードの全体像です。
実行にはもちろんpython3が必要ですが、さらにpython3のライブラリであるmatplotlibとnumpyも必要です。
もしanacondaのpythonで実行される場合は、pythonではなくpythonwで実行して下さい。
(anacondaのpythonはメインディスプレイへのアクセス権がない)
また、これを実行して得られるグラフの日本語フォントは、このブログと一緒ではありません(私は自分のPCのフォントを使っていますので)。その点はご了承下さい。

#coding: utf-8
import random
import datetime
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

# ランダムにじゃんけんの手を出力する
def generator(num):
    s = ""
    for i in range(num):
        s += random.choice(["a", "b", "c"])
    return s

# 1.の方法
def judge_set(s):
    s_set = set(s)
    if len(s_set) == 2:
        return 0 # 決着
    else:
        return 1 # あいこ

# 2.の方法
def judge_if(s):
    if "a" in s:
        if "b" in s:
            if "c" not in s:
                return 0 # 決着, aとb
            else:
                return 1 # 全員バラバラ
        elif "c" in s:
            return 0 # 決着, aとc
        else:
            return 1 # aのみ
    elif "b" in s:
        if "c" in s:
            return 0 # 決着, bとc
        else:
            return 1 # bのみ
    else:
        return 1 # cのみ

# 3の方法
def judge_strip(s):
    method = ["ab", "bc", "ca"]
    ans = []
    for i in method:
        p = s
        ans.append(len(p.strip(i)))
    if ans.count(0) == 1:
        return 0 # 決着
    else:
        return 1 # あいこ

# 統計解析
def statistics(lsts):
    tmp = {}
    counter = 0
    for lst in lsts:
        data = np.array(lst)
        ave = data.mean()
        sem = data.std(ddof = 1)/((data.size)**(1/2))
        tmp[counter] = [ave, sem] #
        counter += 1
    return tmp

# グラフを描写
def bargraph(dic):
    mpl.rcParams["font.family"] = "MS Gothic" # 日本語フォント指定
    fig = plt.figure("Statistics")
    name = ["stripで削る", "ifで場合分け", "集合の要素数"]
    color = ["white","dodgerblue", "forestgreen"]
    x = 0
    for i in range(3):
        plt.barh(x, dic[i][0], xerr = dic[i][1], color = color[x], ecolor="black", capsize=5, linewidth = 1, edgecolor = "#000000")
        x += 1
    plt.yticks(range(3), name)
    plt.xticks(size = 14)
    ax = plt.subplot(1,1,1)
    ax.set_xlabel("100人でのじゃんけんの勝負を1回判定するのに必要な時間(ミリ秒)")
    plt.show()

# 全体の処理。実行するとこれが走る。
def main(trial, people):
    t1 = []
    t2 = []
    t3 = []
    for p in range(trial):
        match = generator(people) # じゃんけんの手を決定

        s = datetime.datetime.now() # 測定開始
        result = judge_strip(match)
        e = datetime.datetime.now() # 測定終わり
        t1.append(int((e-s).microseconds))

        s = datetime.datetime.now() # 測定開始
        result = judge_if(match)
        e = datetime.datetime.now() # 測定終わり
        t2.append(int((e-s).microseconds))

        s = datetime.datetime.now() # 測定開始
        result = judge_set(match)
        e = datetime.datetime.now() # 測定終わり
        t3.append(int((e-s).microseconds))

    result = [t1, t2, t3] # 結果を格納
    bargraph(statistics(result)) # 統計解析して、グラフを描写


if __name__ == '__main__':
    main(trial = 10000, people = 1000) # 10000試行、1000人じゃんけん、の意味
スポンサーサイト

Leave a reply






管理者にだけ表示を許可する

Trackbacks

trackbackURL:http://namelessblackcat.blog61.fc2.com/tb.php/278-633d182b
該当の記事は見つかりませんでした。