jyanjayakaの日記

はやめのリリース、しょっちゅうリリース

Numeronアルゴリズムのpythonソースコード

ここで議論したNumeronアルゴリズム(戦術T2')をpythonで実装した。
一行一行注釈を付けていく。*1*2


main.py

# -*- coding: utf-8 -*-
N = 3 #ゲームで扱う桁数

import functions

P = []
f = []
c = [[]]

a = [7,1,9]

#setup
functions.Generate_P(P)
functions.Generate_P0(P)
print 'possible = ', functions.Calc_P_size(P)
print ''

count = 0
while True:
    functions.Make_call(P, c)
    count += 1
    print 'c = ', c[0]

    functions.Make_feedback(f, c[0], a)
    print 'feedback = ', f

    if f[1] == 3:
        print 'answer is ...', c[0]
        print 'try = ', count
        break
    else:
        functions.Generate_next(P,f)
        f = []
        print 'possible = ', functions.Calc_P_size(P)
        print ''



functions.py

# -*- coding: utf-8 -*-
N = 3

#推測値cと正解値aを比べてfeedback fを作成する
def Make_feedback(f, c, a):
    f.append(c)
    f.append(Eat(c,a))
    f.append(Bite(c,a))

#Pから推測値を抽出する
#Pの最初の数を推測値とする
#推測値はcに格納する
def Make_call(P, c):
    for i in range(10 ** N):
        if P[2 * i] == True:
            c[0] = P[2 * i + 1]
            break
        else:
            pass

def Generate_next(P, f):
    for i in range(10 ** N):
        if P[2 * i] == True: #signがTrueの場合のみ考えれば良い。Falseならそれ以上触れる必要がない。
            P[2 * i] = Sign(P[2 * i + 1], f)
        if P[2 * i] == False:
            pass

#P0を生成する
def Generate_P0(P):
    for i in range(10 ** N):
        if Judge(P[2 * i + 1]) == True:
            pass
        if Judge(P[2 * i + 1]) == False:
            P[2 * i] = False

#numがゲームルールに適合しているか判定する
def Judge(num):
    for i in range(N):
      for j in range(N):
        if i != j and num[i] == num[j]: #桁は異なるのに数字は一致する=重複あり
          return False
        else:
          pass
    return True

#Pを生成する
def Generate_P(P):
    num = []
    for i in range(10 ** N):
        P.append(True)
        Convert_int_to_num(num,i)
        P.append(num)
        num = [] #初期化

#integer intを各桁毎に分解してデータ型numへ変換する
def Convert_int_to_num(num, int):
    for i in range(N):
        num.append(int % 10)
        int /= 10
    num.reverse()

#|P|を計算をする
#|P|を返す
def Calc_P_size(set_num):
    count = 0
    for i in range(10 ** N):
        if set_num[2 * i] == True:
            count += 1
        else:
            pass
    P_size = count
    return P_size

#num gの feedback fに対するsign sを計算する
#num gがfeedback fに適合していればTrue, 適合していなければFalseを返す
def Sign(num_sbj, feedback):
    if Eat(num_sbj, feedback[0]) == feedback[1] and Bite(num_sbj, feedback[0]) == feedback[2]:
        s = True
    else:
        s = False
    return s

#num gがnum cに対して返すeatを計算する
#eatは桁数も数字も合っているものの数
def Eat(num_sbj, num_src):
    count = 0 #初期化
    for i in range(N):
        if num_sbj[i] == num_src[i]:
            count += 1
        else:
            pass
    eat = count
    return eat

#num gがnum cに対して返すbiteを計算する
#biteは数字はあっているが桁数が異なるものの数
def Bite(num_sbj, num_src):
    count = 0
    for i in range(N):
        for j in range(N):
            if j != i and num_sbj[j] == num_src[i]: #桁数が一緒の時はたとえ数字が一致してもカウントしない
                count += 1
            else:
                pass
    bite = count
    return bite



実行結果

possible =  720

c =  [0, 1, 2]
feedback =  [[0, 1, 2], 1, 0]
possible =  126

c =  [0, 3, 4]
feedback =  [[0, 3, 4], 0, 0]
possible =  40

c =  [5, 1, 6]
feedback =  [[5, 1, 6], 1, 0]
possible =  9

c =  [5, 7, 2]
feedback =  [[5, 7, 2], 0, 1]
possible =  4

c =  [7, 1, 8]
feedback =  [[7, 1, 8], 2, 0]
possible =  1

c =  [7, 1, 9]
feedback =  [[7, 1, 9], 3, 0]
answer is ... [7, 1, 9]
try =  6



main.pyについての注釈

  • ソースコードを意味の単位で記述したいので関数を多用する。
  • メインのコードをmain.pyとし、関数定義を集めたものをfunctions.pyとする。
  • オブジェクト指向プログラミング(OOP)は一切使っていない。そのため完全に意味の単位で記述できていない。


# -*- coding: utf-8 -*-

pythonが日本語と相性が悪いので、最初にこう書いておくと良いおまじない。これ無しだとコメントすら日本語で書けない。

N = 3

Nは桁数を表す。Numeronでは3桁の数を用いているが、別に4桁でも構わない。ソースコードは一般的にN桁の数当てゲームのアルゴリズムとなっている。4桁の数当てがやりたいならN=4とすればよい。

P = []
f = []
c = [[]]

ここでの記法に従う。ソースコードで数学のように簡潔な記号法を使う問題点はコードの視認性の低下である。コードの視認性を高めるためには、変数の名前を一目見て内容が分かるようなものにするのが望ましい。ただ今回はここの議論を前提とするので、あえて数学的な記号法を用いている。本来は、例えばPはPossible、fはfeedbackとか省略しない方が良いのかもしれない。

a = [7,1,9]

正解数を719と設定する。
正解数を設定し直す度にコードを書き直すのは面倒だし、コードを変更すること自体望ましくないように思える。
コンソールから正解数を取得するようなプログラムを書くことも出来るが、(面倒なので)やめておいた。このプログラムにUI等の完成度は求めない。

while True:

数を当てるまで推測をし続けるので、ループを設定する必要がある。
ループの回数は不明なのでwhileを使う。無限ループで、答えが見つかったらループを抜ける。

if f[1] == 3:

fの構造上、f[1]にはeat数が保管されている。
それが3であるということは、推測と答えが一致したことを意味する。
なのでここでbreakによりループを抜ける。

functions.pyについての注釈

  • どんな関数を定義するかはセンスの問題。
  • なるべく意味の単位で関数化したつもり。
  • ここで定義したデータ構造が、いかにアルゴリズムに影響を与えるかが良く分かる

*1:ソースコードはpython2系での記述。

*2:ソースコードにはコメントが入っていたが後で注釈を付けるので省いた。