「4つの4」パズルを解くアルゴリズム。前回は新しいアルゴリズムの基礎となる考え方を具体的に説明しました。今回は「自律的思考」と名づけたこのアルゴリズムの強力さを、実演してみます。
今回の方法は速度重視(速度面での最適化)であり、4つの4を使った0~1000までの数の作り方が、手元の環境では10秒以内(最短5秒程度)でリストアップされます。 これまでで最も高速だった「ラムダ関数バージョン」ですら、100や200までのリストを作るには1分くらいかかりました。当初は100までのリストを得るのに6分以上かかっていたのです。それが1000まで10秒以下になったのだから、大きな進歩と言うべきでしょう。(いちばん初めは0~30までの作り方だけで10秒くらいかかってたよ。)
なお、「2000以下に4つの4を使っての作り方が分からない数が数個ある」と先日、書きましたが、それも解決しました。0から2000までの数は、すべて4つの4を使って(前回までに定義した範囲内で)表現できます。
前回、説明したようにして、階乗や総和も実装します。あらゆる可能な式表現を検索対象にしたとしても、たかだか有限時間で終了するので数学的には「解けた」ということになりますが、人間の時間で考えると、手元のパソコンでせいぜい数分くらいで答が出てほしいでしょう。
そのための基本的な手段は、捜索範囲を制限することです。
第一の制限は、論理的に無意味な(あるいは、ほとんど無意味な)範囲の切り捨て。例えば、マイナスの数は捜索範囲から外してかまいません。計算の途中でマイナスの数が出てきて最終的にプラスになることもありえますが、マイナスの数を経由して作れる数は、必ずマイナスの数を経由しなくても作れるので、結局、マイナスの数は考えなくて良いです。また、例えば「√2乗」のような、無理数乗の計算も、最終的に整数を作るというパズルの目的のうえでは、まったく無意味と言っていいでしょう。
第二の制限は、速くするための制限。例えば、1000までの数を作る場合において、途中で2000より大きくなったら、その方向は捨ててしまってもかまいません。実際には、いったん2000より大きい数を経由してから、エレガントに目的の数にたどりつくような計算法もありえます。しかし、答がきれいでなくても、とにかく計算が合えばいい、という速度重視の立場からは、なるべく少ない材料でやりくりしたほうがトクです。同じ理由から、途中で小数が発生したら、それは捨ててかまいません。整数の組み合わせだけで1000までの数をぜんぶ作れるからです。
例えば次のようになります。
function isInt( n ) { return ( n - Math.floor(n) == 0 ); } var new_value = oUnaryOperators[ op_name ]( proto_value ); if( new_value === void 0 ) continue; if( new_value < 0 || new_value > TOO_BIG || !isInt(new_value)) continue;
単項演算子、二項演算子も、範囲外の数が来たらまじめに計算しないでただちにvoid 0
を返して計算をうち切ります。このことによって —— 特に小数をいっさい使わないようにすることによって —— 驚異的なスピードアップが図れます。小数の端数がつく数も考慮した場合に数分ないし数十分以上かかるものが、整数だけでやると10秒以内でできるようになります。もっとも得られるパズルの解は最もシンプルなものとは限りません。例えば、
10 = Σ( 4+4×(4-4) )
などという変てこな出力が出るかもしれません。計算は合ってますが、そんな面倒なことしなくても、
10 = (44-4)/4
のほうが、シンプルで「良い」答でしょう。どうしたら最もシンプルな解答ができるか —— つまりどうしたら式表現を最適化できるか —— は、べつの問題であり、次回以降に説明します。解答速度を高速化することと、式の美しさを最適化することとはトレードオフの関係にあり、なんでもいいからとにかく速く答を見つける、ということは、考え得るすべての式表現のなかでいちばんきれいなものを見つける、ということとは、異なる方向性にあります。
var oBuffer = new Object(); oBuffer["44"] = "44"; mix( eva1, eva1, oBuffer , 2 ); var eva2 = think_about( oBuffer , 2 );
前に eva[1], eva[2] , ... とオブジェクトの配列で書きましたが、この書き方だとIEが異様に遅くなるようなので、上のようにベタの eva1, eva2, ... に書き換えました。本当は配列で書いておくほうがあとで簡単化しやすいのですが、まぁ、意味は同じことです。