18日金曜日から21日月曜日まで、ICFP Programming Contest 2010 に参加。
今回は初めは俺言語で出るために一人で出ていたのだが、途中でチーム Oh!tomaton に
合流した。チーム Oh!tomaton は、tsukuno, kinaba, mayah の3人。順位は27位のようだ。二人のブログは、d.y.d. となんとなくな日々のコメントにある。
あと、俺言語で出るという目標を掲げていたが、一応一人の時は使っていた。機能は絶対的に足りないので OCaml と ruby も併用していたが...。3人になってからは OCaml, ruby, C++, Java, D あたりが使われていた。tsukuno は PHP も使っていたらしい。
はじめ
とりあえず問題を 15 分ぐらい書けて読む。
今回のタスクは、お金を稼ぐこと! らしい。どうやってお金を稼ぐかというと「車」に対して「燃料」を供給することで利益をえることができるということのようだ。ふむ。
「車」とは、複数の chamber を持ち、各 chamber は upper と lower の2本の pipe からなり、pipe は複数の section の列からなる。各 section は燃料タンクに繋がれている。燃料タンクは最大で6つである。「車」は ternary stream (3進数ストリーム) でエンコードされているので、どのような「車」かは自分で decode しなければならない。また、「車」がどのように encode されているかは推測しなければならないし、ternary stream の仕様も自分で推測しなければならない。他にも色々細かい規則がある。推測多いな...。
「燃料」は、「車」によって次のように使われる。
out(k) = c(n, 1)in(1) + c(n, 2)in(2) + ... + c(n, k) in(k)
まだなんのことか分からない。
「燃料」が「車」に合うとは、全ての chamber で、upper pipe と lower pipe に同じ空気を送り込んだ場合に、upper の方が lower より大きくなる(一部の pipe に関しては等しくてもかまわない)ことを言う。まだ何のことか分からない。
「燃料」は「工場」によって作られる。「工場」はゲートを組み合わせたもので、ternary stream を入力にするとなんらかの ternary stream を出力する。ゲートは2入力2出力で、工場は1つの外部出力と外部入力を持つ。工場のフォーマットも推測しなければならない。また推測...。
1日目
とりあえず、まずなんとかできそうなのがゲートを推測することぐらいなので、工場を適当に造って推測する。工場のフォーマットはちょっと考えればすぐ分かるようになっている。工場の外部入力がそもそも分からないので推測がしにくい。
試していると
X:
:
X
という工場が通ることが分かった。これは入力をそのまま出力に出すものなので、これで入力が判明する。
次に、
i
| |
+---+
| |
+---+
| |
a b
この外部出力を a, b とつなぐととりあえず値が出るので、
i
| |
+---+
| |
+---+
| |
+---+
| |
+---+
| |
X
とすることで、入力が分かっている状態で左の出力と右の出力が外部出力につなげるので、という感じでゲートの推測をしていく。
次に、しばらくたって燃料は prefix をもたなければならないというのをとばし読みしていたことに気がつく。しかしこの辺で推測大会でやる気なくなってきた。
というか、ここまでだとまともなプログラミングがない。一応俺言語でゲートの推測とかしていた。
1日目夜 oh!tomaton に合流
一人でやるのがつらかったので oh!tomaton に合流。ternary encoding の仕様を探る。kinaba タソが整数の encoding の初めの方を見つけたので、試しているとだんだん分かってくる。list は絶対 empty list で最後終わると信じて読もうとしていたのだが、そんなことはなくリストの数が先に来る encoding だった。
2日目
この辺で ternary encoding を encode/decode するプログラムを作った。
それでようやっと問題の本質が分かってくる。問題を例を挙げて説明すると
- pipe は 燃料 [0] から [5] の列である。
- 燃料とは、正方行列である。何次元かは分からない。
- 空気とはベクトル v である。
- 空気を pipe に通すと、燃料の行列を乗算したものになる。
- upper と lower に任意のベクトル v をかけたとき、全要素が upper の方が大きく(もしくは等しく)なるように、燃料 [0] から [5] を推測して定める。
となる。あとは、燃料は提出するときに ternary encoding で encode しなければならない。この形式も推測しなければならない。また、実際に提出するのは、それを出力するような工場である。
所望の ternary encoding を作る工場を生成するプログラムは kinaba たそにまかせた。あと ruby で CUI から submit するものとか、車のリストを取ってくるモノとかも2日目が始まるまでに作られた。
燃料の行列を求める方法がさっぱり分からないので、焼き鈍し法で探索でもするかということに。焼きなまし法 ライブラリでググって出てきた nodchip さんのテンプレートを使って書いた。とりあえず1次元から始めて7次元ぐらいまで試すものを書いた。
まず、行列をランダム生成。(左辺) > (右辺) を満たさない場合は (左辺) - (右辺) の絶対値をエネルギーにする。適当に行列の値を動かしてみて、エネルギーが下がる方向にすすむようにする。10 秒ほど試して、一番良いモノを採用する。
という感じでやっていたら、なんか知らんけどいくつかが解けるようになる。これで解けないのは tsukuno が人手で解くという感じのことをやっていた。
この辺で、車の仕様を受け取ると、デコード、焼きなまし、回路生成、サブミットを自動化するものを書いた。回路生成は kinaba と tsukuno になげっぱなし。
あとは必死にやきなまし法の改良を重ねていた。
2日目も終わる頃、車はそろそろ作らないとヤバイだろうという話になったので、kinaba タソにまかせる。アイデアだけとりあえず適当にだした。3次元で行列を5つランダム生成して、適当に大量に生成した不等式で満たされるモノを列挙し、残り1つの行列を10次元にして、今までのを満たさない不等式の中から、不等式を満たすようになるように項を決めてみるとかいうもの。今までの不等式は行列の区分けをしてしまえば、左上3次元だけ要素を持つような10次元正方行列になるので満たせる。また新しいものは満たすように作ったので満たせる。しかし、大きいのを送るとエラーになるらしい。実際は5次元ぐらいでランダム生成して送っていたらしい。
3日目
何をやったらいいのか分からない。とりあえず、solver で解けないものを眺めていると [4][5] を大量にかけまくっているものとかがあったので、[4] や [5] をとりあえず I だと思って焼きなましてみるとか、そういう個別対応をやっていた。全体的に人手が足りない。
あとはやきなましソルバーをずっと回していた。
残り何時間かで突然 tsukuno が回路ゴルフを始める。kinaba ソルバーは任意の n 個を出すのに 24 + 3n かかっていたが、1.66n + α ぐらいで出せるようになる。このソルバーで全部再サブミット。自動ソルバーもそっちに。
残り2時間ぐらい、もうなにをやっていいのか分からずあきらめムード。
で、タイムアップ。
感想
最初の推測大会いらないと思う。プログラミングせずに投げてしまった人も多かったのではないかという印象。最近スタートラインが全体的に遠い。
行列が解けるようになってきてからは結構おもしろいと思えるようになってきた。
来年以降はもっと最初の敷居を低くすべきだと思う。その点、昔の ICFP Programming Contest で出た、とりあえず VM の仕様が与えられるので実装せよとかいうのは良いのではないかという印象。