jyanjayakaの日記

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

統計学を現実世界に適応するための仮定について

統計学は、他の様々な現実を説明しようと試みる理論と同じく、現実に対して何らかのモデルを用意する。そのモデルが現実を上手く表現できていればいるほど、統計学は現実に対して有効な記述を行うことが出来る。

 

統計学が用意しているのは、個々のモデルについての理論的枠組みであって、それが実際に現実に当てはまるかどうかはこちらの判断に委ねられている。

こういう事情は例えば幾何学にもある。ユークリッド幾何学は数学的な理論であるが、これは現実の空間に対する精度の良いモデルとして用いられている。しかし、より精度の高いモデルも提案されていて、それは一般相対性理論を背景とした、リーマン幾何学*1である。

ユークリッド幾何学が我々の現実とどれだけ乖離しているのかという問題は、ユークリッド幾何学というモデルで考察できる範囲を超えている。それは物理学等が答えるべき問題である。

統計学も同じことなのだが、我々の直感から構成されているユークリッド幾何学と違って、統計学の提供するモデルを現実に当てはめることには、しばしば精神的ギャップを覚えることがある。

 

 

*1:正確には擬リーマン幾何学

確率論

確率というのは物事の起こりやすさを数値化したものである。

 

物事には起こりやすいものと、そうでないものがある、というのは経験的な事実である。そこでこの起こりやすさを数値化しようというのが、確率の基本的なアイデアである。

 

面白いのは、物事の起こりやすさを調べるだけで、2つの物事の関係性調べることが出来るという点である。

例えばAが起きたら確実にBが起きるという場合、AとBには何らかの関係があると言っていいだろうし、逆に、Aが起きようが起きまいが、Bが起きる確率に変化がない場合、AとBの間に関係があるというのは無茶だろう。

 

こうして、確率を使えば、物事の関係性や因果関係などを調べることが出来る。

 

この考え方を今のように定性的な表現から、確率論の枠組みのなかで数量的に定式化したのがベイズの定理である。

 

 

 

ガロア理論4

3乗根について

「3乗してAになる数」をAの3乗根と呼ぶ。

一般にこれは3つあって、その中の一つを我々が選んで {}^3 \sqrt{A}と表すことになる。問題は {}^3 \sqrt{A}を3つのうちどれにするか広く知られた基準が存在しないということだ。Aが実数であれば {}^3 \sqrt{A}をAの3乗根の中で実数のものとするというのが整合性の取れた自然な方法であるが、Aが実数でなければ、そのような方法は存在しない。

といってもこれはそこまで深刻な問題ではなく、こちらが勝手に決めていいのだから、場合によっては便利である。一度 {}^3 \sqrt{A}を決めた後はそれを変えないことだけを意識していれば良い。

また、これら三つを複素平面上で見てみると、

 {}^3 \sqrt{A},{}^3 \sqrt{A} \omega,{}^3 \sqrt{A} \omega^2

という関係のあることが分かる。ただしここで \omega複素平面上で120°回転を表す複素数である。*1 

*1:代数の文脈で言えば \omegaは1の3乗根に他ならない。

ガロア理論3

3次方程式の解の公式を求める

3次方程式

 x^3 + a_2 x^2 + a_1 x + a_0 = 0

の解の公式を求めてみよう。

3次方程式でも当然xの分離の問題を解決する必要がある。そこで、2次方程式で上手く行った方法を3次方程式でも使えないか考えてみる。2次の場合は平方完成を用いた。3次の場合では立方完成とでも呼ぶべきだろうか。

適当な変数変換

 x = Ay + B

を行うことによって、2次と1次の項を同時に消滅させることが出来れば良い。もちろんもっと一般的な変数変換を行うことも可能だが、とりあえずまずは平方完成の場合に沿った形での1次変数変換を考えている。

しかしよく考えると一般性を失わずにA=1と仮定して良い。*1

 このような変数変換を与えるBが存在しないことは、計算によって直接確かめることが出来る。計算は何のテクニックも必要ない初等的なものなので省略するが、結局変数変換

 x = y +B

を施すと、yの2乗と1乗の係数はそれぞれ

 a_2 + 3B

 3B^2 + 2a_2 B + a_1

となる。

この両式を満たすBは一般に存在しない。

これは考えてみれば計算するまでもなく分かることで、我々は一つの変数Bを使って、2つの変数 a_2, a_1を消去しようとしているのだから、上手くいくはずがない。上手くいくのは a_2, a_1の間に特別な関係のある場合だけだが、それは一般的ではない。したがって立方完成は一般には不可能である。

しかし今の考察から分かるように、一つの変数Bを用いれば、一つの変数を消すことが出来ることは期待できる。そこで a_2, a_1のどちらを消去するか選ぶわけだが、 a_1は常に消去できるわけではない事がわかる。というのは 3B^2 + 2a_2 B + a_1=0はBについて2次式であるから、場合によってはBが実数でなくなる可能性があるのだ。我々は今のところ、実係数の方程式を考えているからこれはよろしくない。変換後も方程式は実係数であることが望ましい。そこで消去するのは a_2ということになる。 a_2 + 3B=0はBについて1次であるから問題ない。これをBについて解くと

 \displaystyle{B = - \frac{a_2}{3}}

となる。

 結局、最初の3次方程式

 x^3 + a_2 x^2 + a_1 x + a_0 = 0

は、変数変換

 \displaystyle{x = y - \frac{a_2}{3}}

を行うことで、2次の項が消去された

 y^3 + py + q = 0

という方程式に書き換えることが出来ると分かった。

 p, qはもちろん a_2, a_1のある組み合わせなのだが、それは特に重要ではない。我々は一般に

 y^3 + py + q = 0

という3次方程式が解けるかどうか考察するからである。

さて、それではこの3次方程式はどうやって解いたらよいだろうか。

 

テクニックについて

これからその方法を見てゆくわけだが、これは単にテクニックの問題である。つまり上手い式変形を重ねることで、既に解き方のわかっている方程式へ還元するという方法論だ。そのためには、もちろん第一にその「上手い式変形」を見つけなければいけないのであるが、それは当然難しい。(難しくなければ「上手い」などと言われない!)そこで、天才のひらめきや、長年の努力が必要になってくる。

そういうテクニックを鑑賞するのも楽しいのだが、我々の目標は方程式というものの本質的な理解を推し進めることにある。だから、どんなに派手なテクニックでも、それがある特定の場面でしか使えないのであれば、あまり価値があるとは言えない。「役に立つが3次の場合にしか使えない」といったもののことだ。それよりもどんな次数の方程式にでも適用できる、普遍的な考え方、フレームワーク=理論の方が好ましい。

しかし、理論は帰納によって生まれるのであるから、個々のテクニックを蔑ろにする訳にはいかない。3次のテクニック、4次のテクニックを見て、それらがなぜ上手く行くのか、その背後にあるより普遍的な構造は何なのかを考察することで、理論が深まってゆく。*2なのでそもそも3次、4次のテクニックが見つかっていなければ、普遍的な理論など出来ないのだ。

理論が出来る前、そこは天才のひらめきが支配する混沌とした世界である。だが、一度理論が完成し、概念が整理され、視界がひらけてしまうと、かつてのテクニックはただのアルゴリズムとなり、ひらめきはシステムの影に消えてしまう。しかしそれでも先人達が未開の世界へ飛び込み、道を作らなければ理論は出来ないのである。

 

簡約化された三次方程式 y^3 + py + q = 0の解法

それではテクニックを鑑賞しよう。

まず

 y = u + v

とおく。これを方程式に代入して整理すると

 \left( u^{3}+v^{3}+q\right) +\left( 3uv+p\right) \left( u+v\right) =0

となる。これをu,vについての方程式と見て、u,vを求めることができれば、u+vで元の方程式の解も求まる。解が一つ見つかれば方程式を因数分解出来るから、後は2次方程式を解くだけである。つまり最初の解を見つけるのが問題なのだ。

方程式が一つで変数が二つあるから、この方程式を満たすu,vの組みは一般には無数にあると考えられる。そこで、それらの組みの中から、特に分かりやすい解、つまり

 u^{3}+v^{3}+q=0

 3uv+p=0

を満たすものを選ぶ。下の式を3乗してやれば、 u^3, v^3

 \displaystyle{t^{2}+qt-\dfrac {p^{3}}{27}=0}

という2次方程式の解だと分かる。(解と係数の関係。)

この2次方程式の解を t_1, t_2とする。ここから先へ進む前に、冪乗根について復習しよう。

 

(次回へ続く)

*1:仮にこの変数変換で、3次式が

 A^3 y^3 + C = 0

と書き換えられたとする。更に変数変換

 y = z/A

を行うことで

 z^3 +C = 0

となる。二度の変数変換は結局一つの変数変換

 x = z + B

にまとめることが出来る。よってAは最初から1としてよい。

*2:方程式論で言えば、それはラグランジュによって行われた。それ以前に知られていた方程式の解法を詳細に分析することで、その背後にある普遍性を見つけたのだ。

ガロア理論2

方程式を解くというのは、歴史的に見れば解の公式を求めることと同義であった。ここではそもそも解の公式とは一体何なのか、反省してみる。

 

解の公式とは何か

方程式が決まれば、その解が定まる。「方程式が決まる」ということをもう少し詳しく説明すれば、それは係数が決まることである。n次方程式にはn+1個の係数があるが、定数倍は方程式の解を変えないので、n次の係数を1と仮定して一般性を失わないから、結局n次方程式はn個の係数 (a_0, a_1, \cdots , a_{n-1}) \を決めれば決定されると言える。

最初に書いたように、我々は解そのものに興味があるのではない。 (a_0, a_1, \cdots , a_{n-1}) \からどうやって解が定まるのか、ということに興味がある。この過程fに注目し、それを「代数的に」表現をしたものが解の公式である。「代数的に」というのは、「足す・引く・掛ける・割る・ルートを取る」という5種類の操作だけを許すという意味である。

わざわざ「代数的に」と付けたのは、本来 (a_0, a_1, \cdots , a_{n-1}) \から解が決定される過程が、別に代数的に表現する必要性が無いからである。(例えば三角関数を取るという操作を含めてもいいはずだ。)つまり我々は「代数的に」と言って、自らに制限を課していることになる。

似たような状況に、作図がある。ある図形が作図できるか、というのは暗に「定規とコンパスのみを用いて」という制限が課されている。例えば角の三等分を作図せよという問題は、分度器を使えば容易いのであって、「定規とコンパスのみを用いて」という制限が加わって初めて難問足り得る。*1

制限というのはマイナスイメージを伴って用いられることが多いが、制限をすることがクリエイティビティを生み出すということもある。例えばサッカーを見てみると良い。手を使わないで足だけでボールを扱え、という制限を課しているが、それによって華麗な足技を我々は鑑賞することが出来る。

ガロア理論でも同じことだ。過程fを代数的に表現せよと、問題を解く手法を制限したから、このような豊かな理論が生まれたのである。

 

解の公式を求めてみる

まず手始めに、思いっきり簡単なところからスタートしてみよう。

1次方程式

 a_1 x + a_0 = 0

の解の公式を求めてみる。

等式の性質を利用すればこれをxについて解くのは難しくない:

 \displaystyle{ x = - \frac{a_0}{a_1} }

これは与えられた方程式の解xを係数だけを用いて明示的に与えている。そしてさらに代数的な手段のみを用いて、xを求める過程を表現している。よってこれは確かに1次方程式の解の公式である。

 

次に、2次方程式

 a_2 x^2 + a_1 x + a_0 = 0

の解の公式を求めてみる。

1次方程式の場合のように、等式の性質を素朴に利用したのでは、2次方程式は解くことができない。なぜならxが2つの項に分離して存在しているからである。この分離の問題を解消するためには、平方完成と呼ばれるテクニックを用いる必要がある。

以下、計算式を簡略化するため、方程式の最高次の係数は1としよう。

平方完成とは要するに、

  x^2 + a_1 x + a_0

という2次式を

  \displaystyle{ \left ( x + \frac{a_1}{2} \right )^2 - \frac{a_1^2}{4} + a_2}

と変形することである。

ここで \displaystyle{y = x + \frac{a_1}{2}}で新しい変数yを定義すれば、上の式はyを用いて

  \displaystyle{ y^2 - \frac{a_1^2}{4} + a_2}

と書ける。

平方完成を単に式変形のテクニックとして説明するのも良いが、このように変数変換によってより解きやすい式に書き換えるという見方の方が分かりやすい。

つまり変数変換 \displaystyle{x = y - \frac{a_1}{2}}を行うことで、xの2次式

  x^2 + a_1 x + a_0

を、新しい変数yによる2次式

  \displaystyle{ y^2 - \frac{a_1^2}{4} + a_2}

に書き換えることが、平方完成だというわけだ。2つに分離していた変数が一つの項にまとまったのが分かる。こうして分離の問題を克服できたことに注意する。

変数変換によって問題を単純化するということは広く行われることであり、例えば微分方程式を解く時にも用いられる。物理学で座標を問題の状況にあったものに取り替えるというのも、結局は変数変換で微分方程式を解きやすいものに書き換えているだけなのだ。

平方完成というテクニックも、こう見ると、変数変換というテクニックの一つの方法に過ぎないことが分かる。

一旦平方完成をしてしまえば、

  \displaystyle{ y^2 - \frac{a_1^2}{4} + a_2 = 0}

を変形して

 y^2 =  \displaystyle{\frac{a_1^2}{4} - a_2}

となる。

この先へ進む前に、ここで一度虚数iの復習をしておく。

 

虚数の復習

最も基本的な事実からスタートする。それは「どんな実数も2乗すると0以上となる」というものだ。従って「2乗して負になる数」は実数には存在しない。

そこで数の概念を拡張して「2乗して負になる数」も我々の数のカタログに含めることにしよう。それはそれで良いのだが、何かその数を表現する記号を新しく用意しなければならない。なぜなら実数だけ扱ってきた我々は、そのような数を表す記号を持ち合わせていないからである。

実は、「2乗して負になる数」より前にも、我々は新しい数が出てくる度に、新しい記号を導入して、その新しい数をなんとか表現してきた。自然数1,2,3...を拡張して、新しく「負の数」を導入した時、それを表すためにマイナスという記号を導入した。また、何もないことを表す数を導入した時、0という記号を導入した。こうして整数...,-3, -2, -1, 0, 1, 2, 3,...を書き表せる。次に0と1の間の数、つまり少数を導入した時、0.5あるいは1/2という分数の記法を導入した。こうして我々は有理数を書き表せる。次に無理数を導入した時、我々はルート記号を導入した。ただしここで今までと事情が変わって、ルート記号を導入しただけでは、全ての無理数を書き表すことは出来ない。これは無理数の定め方に問題がある。無理数とは「有理数以外の全ての実数」と定義される。有理数以外なら何でもいいのだから、無理数は単一の記号法で書き表すにはあまりにヴァラエティに富みすぎているのだ。

さて、兎に角も「2乗して2になる数」なら、これを \sqrt{2}と書き表せるようになった。「2乗して負になる数」についても、同じようなことをしようというのである。負の数のなかで、最も基本的な数は-1であろうから、これを基準として、「2乗して-1になる数」をiと書き表すことにする。ただし、ここで一つ注意して置かなければいけないことがある。それはiも-iも2乗すると-1になるということである。つまり単に「2乗して-1になる数」と言っても、数が一つに定まらないのである。同じことはルート記号の場合にもあって、単に「2乗して2になる数」といっても定まらない。そこで \sqrt{2}は「2乗して2になる数」の中でも、正のものを表すという約束をする。

しかし残念ながら、虚数に正とか負とかいう概念は適応できない。このことは簡単に見ることが出来る:もし仮に虚数に正負の概念が適応出来たとすると「2乗して-1になる数」のうち、正のものをiとおける。もちろん

 i \gt  0

である。しかし両辺を2乗すると

 -1 \gt 0

となって矛盾を生ずる。したがって虚数に正負の概念は無い。

どちらをiと書き表すか、明確な基準がないから、ここに大きな選択の自由が生まれてしまう。「私のiは貴方の-iだった」ということが起こりうる。

しかし、これは問題とはならない。「2乗して-1になる数」のどちらをiと書き表すことにしても良いのである。なぜか? それは、iが「本当は」どちらの虚数を表しているのか誰にも分からないからである。

卑近な例を取ってみよう。今、瓜二つで全く、誰にも(親にでさえも!)区別出来ない双子がいたとしよう。双子の一方にA、もう一方にBと名付けたとしよう。いや……「名付けたとしよう」と言ったが、考えてみて欲しい、そもそも二人は区別できないのだから、どちらがAかは重要ではないのである。せいぜい言えるのは「Aでない方がB」(あるいはその逆「Bでない方がA」)ということだけである。つまり二人の関係は相対的なものでしかないのだ。もちろん、もし仮に双子を区別する方法があれば、話は別になる。この場合二人の関係は絶対的であり、どちらをAと名付けるのかはちゃんと意味がある。

別の言い方をすると、我々は虚数について語る時「どちらかをiとすれば、もう一方は-iとなる」という、2つの虚数についての相対的な関係性だけを知っていれば十分なのである。「2乗すると-1になる数」は2つあって、それを i, -iと表す……と言えばこれからの議論にとって十分であり「2つのうちどちらをiとしたか」という情報は一切必要ないのである。この情報の不必要性が、逆にiと-iの相対性を表しているとも言える。

さて、iと-iの相対性が明らかになったところで、次のステップに移ろう。

Aを正の数としたとき、「2乗して-Aになる数」は i\sqrt{A}, -i\sqrt{A}の2つである。ここで \sqrt{-A} = i\sqrt{A}と定めて、ルート記号を負数に拡張する。ちなみに、こう約束すると \sqrt{-1} = iとなる。( i = \sqrt{-1}でiを定義できないことに注意しよう。なぜなら虚数を定義する前は、負数に対するルート記号が定義されていないからである。)

 取り敢えず一旦、この辺りで虚数の復習を終わろう。

 

 2次方程式の解の公式

 y^2 =  \displaystyle{\frac{a_1^2}{4} - a_2}

から

 \displaystyle{y = \pm \sqrt{\frac{a_1^2}{4}-a_2}}

を得る。もちろんルート記号の中身が負でも問題ない。

こうして2次方程式を解くことが出来た。xの値が欲しければ変数変換が

 \displaystyle{x = y - \frac{a_1}{2}}

であったことを思い出せばよくて、

 \displaystyle{x = - \frac{a_1}{2} \pm \sqrt{\frac{a_1 ^2}{4} - a_2}}

を得る。これが2次方程式の解の公式である。

 

 

次に、3次方程式

 x^3 + a_2 x^2 + a_1 x + a_0

の解の公式を求めてみよう。

 

 

(次回へ続く)

 

(番外編)虚数は発明か、発見か

「2乗して-1になる数」をi,-iと書き表すことにした。ここでふと気になるのは、「2乗して-1になる数」はiと-iだけか?ということである。数の世界はもっと広くて、実は「2乗して-1になる数」はもっとたくさんあることだって有り得るのではないだろうか?

こう考える時、その人は「数はそこに最初から在って、人間はそれを発見しているだけである」という立場に立っていると言える。しかし、本当にそうだろうか? 我々は実数を飛び越えた時点で、新しい数を創造してしまったのではないだろうか? (この立場に立つと、虚数の存在という問題に頭を悩ませる必要はなくなる。)

いや、そもそも実数の存在だって怪しい。

果たして数とは人間の知的活動とは別に存在するものなのか、それとも数は人間が作り出した道具に過ぎないのか......

この壮大な問題に回答を出すことは諦めて、次のもう少し実際的な事実を述べるに留めよう:

論理的に矛盾がなければ、それは数学の対象足り得る

例えば「2乗して-1になる数」にはi,jという2つが「独立に」あるとしよう。ここで「独立に」という意味は a + bi + cj = 0であるのは a = b = c = 0の時のみに限るということである。(もしそうでなければ、jは複素数に過ぎないことになり、新しい数を導入したことにならない。)

ここで問題になってくるのはiとjの積が何になるかである。これは少し計算すると分かることだが、 ij = a + bi + cjを満たすような実数a,b,cは存在しない。つまり、jのような数をそれまでの数の体系と無矛盾に定義することは(少なくとも複素数の素朴な拡張としては)できない。

しかし、4元数の存在を見れば分かるように、工夫すれば「2乗して-1になる数」がi,j,kの3つある数体系を無矛盾に構築することが出来る。

複素数や4元数の存在に頭を悩ませない方法は、それらをチェスのルールのようなものだと思うことである。つまり複素数や4元数は人間が想像力豊かに生み出した架空の数であり、それらは一定のルールに従うことを要請されているものだと考えるのだ。複素数の計算をする時、単にルールに従って記号を操作しているに過ぎないと割り切ってしまえばよい。これならどんな突飛な概念が出てきたとしても許される。

ただしそれでも唯一要請されることがあって、それはルールそれ自体に矛盾が無いことである。あるいはそれまであったルールとの整合性がちゃんと取れていることである。逆に言えば、これさえ守られていれば、その新しい概念は数学的対象足り得るのだ。

しかし、例えば複素数は、それを「数学者の空想の産物」として除くにしては、あまりにも便利すぎることを心に留めておく必要はあるだろう......

*1:ちなみに、定規とコンパスだけを使って与えられた任意の角を三等分することは不可能であることが証明されている。

ガロア理論

ガロア理論は代数方程式についての理論だ。そこで、ガロア理論を学ぶ前に、まずそもそも代数方程式とは何か、方程式とは何かについて考えておくことは良い準備になるだろう。

 

そもそも論

代数方程式について考える前に、そもそも方程式とは何か考えてみよう。方程式と名のつくものをよく観察すると、方程式に共通するものが見えてくる:方程式とは何か分からないものがあって、それについてのヒントを表したものだ

分からないものが数で、それについてのヒントが代数的に与えられたものが、つまり代数方程式

 a_n x^n + a_{n-1} x^{n-1} + \cdots + a_1 x^1 + a_0 = 0

である。

分からないものが関数であり、それについてのヒントが微分で与えられていれば、それは微分方程式と呼ばれる。

ちなみに、もちろん分からないものが数であったとしても、

 \sin x = x

のように、ヒントが代数的に与えられていないものも、もちろん考えることができる。

このように様々な方程式が考えられるわけだが、そんな中でガロア理論は特に代数方程式を扱う。なので以下では方程式といえば代数方程式を指すことにする。

 

さあ方程式を解こう。でも、その前に

方程式があればそれを解きたくなるのが人の性である。

代数方程式は紀元前からその解き方が研究されてきた。ガロア理論はある意味で人類の長大な代数方程式論研究の集大成と言える。

また、微分方程式もその解法が盛んに研究されている。それは微分方程式がこの世界の基本法則を表現するものとしてよく使われるからである。例えば力学で言えば、物体の運動の様子が分からないもの(時間を変数とする未知関数)であり、それについてのヒントがニュートン運動方程式と呼ばれる微分方程式で与えられる。

 

このように方程式は解くことが注目されがちであるが、実はその前に考えておくべきことがある。それはそもそもその方程式に解が存在するのか、ということである。例えば今適当に方程式を作ってみる:

 x^4 + 3x^3 - x^2 + x - 6 = 0.

この方程式を満たすような数が本当に存在するかどうかは、明らかなことではない。もっと複雑な方程式も考えることができるが、そのような複雑な条件を満たすような数はちゃんとあるのだろうか?

この疑問について決定的な回答を与えるのが、いわゆる代数学の基本定理である。要は「複素数まで探索の範囲を広げれば、どんな方程式にも解が存在する」ということだ。しかもただ存在するだけでなく、\(n\)次方程式には(重複度を含めて)\(n\)個の解があることまで保証されている。

 

 方程式を解くとは一体どういうことか

方程式に解が存在することは保証されたので、安心して方程式を解こう。

 

方程式を解くというのは、その方程式を満たすような数を明らかにすることだ。そのための一つの方法として、コンピュータによる計算がある。実際、ニュートン法などの方程式を解くアルゴリズムが知られている。これによれば、任意の方程式を任意の精度で解くことができる。

しかしここである問題が生じる。方程式が与えられた時、それをコンピュータで解いたとしても、何か不満足を感じるのである。よくよく考えてみると、それはコンピュータで方程式の解を求めたところで、方程式についての本質的な理解が深まらないということから来ることに気がついた。もちろん、単に方程式の解が欲しいということもある。例えば先に挙げた微分方程式で言えば、微分方程式の解を兎に角出してしまえば、それが実際的に役に立つ。

そこで方程式を単に解くだけではなく、方程式について何か本質的な理解ができるような方法が欲しいのである。

f:id:ziguzaku:20171005231324j:plain

 

 

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:ソースコードにはコメントが入っていたが後で注釈を付けるので省いた。