英語と格闘するが殆ど理解出来ない。チーム名がぴゅあぴゅあこーどに決まる。問題読みに貢献できないまま、大まかな解読が終わる。車と燃料を作るようだが、それぞれは詳細秘密の3進表記で表される。燃料は3進表記そのままではなくて、それを生成する回路で送信する必要がある。それももちろん詳細は秘密ですって、なんぞそれ。リバースエンジニアリングゲーか。リバースエンジニアリングならまかせろー、って感じでもないが、とりあえず回路の仕様を頑張って調べる。0時頃とりあえず適当な回路を投げて反応を見てみる。うえーい、全然わからん。悶々としていると、phoenixさんが表埋められそうと言ってサクサクと表が埋まっていく。何が起こっているんだ。私は並行して回路シミュレータを書く事にした。これも問題の仕様がよくわからず、どういう条件で信号が遅延するのか曖昧なまま書いていたが、なんとかなった。1時頃シミュレータが出来る頃、表も完成していた。シミュレータに入力を突っ込んで鍵を生成してみる。鍵はできたが合ってるかどうか全然わからない。入力をパススルーする回路がなんだか私には良く分からないが作られて、入力が得られる。それを元に検算、シミュレータのバグが取れて、正しい鍵が得られる。素子の挙動と出力すべきキーとサーバの入力(の先頭17文字)は分かったが、果たして如何にしてキーを出力するのか?入力から出力への簡単な回路があるか少し考えてみたが、さっぱり分からない。それも分からないが、本番の燃料投入では17文字以上出力しなければならない。それは即ち不明な入力に対してこちらの意図した列を出力しなければならないということ。入力に対してキーを出力する簡単な回路を見つけても意味がないことに気付く。入力非依存で指定した列を出力できる回路を生成できなければいけない。5時頃nyaさんとphoenixさんが謎の技法により定数回路などをさくさくと作り上げていく。若者の人間離れというのを目の当たりにする。私は他にできることがないので、それらを組み合わせてジェネレータを作ることに。Haskellで書かれたとってもぴゅあぴゅあ(グローバル変数を書き換えまくる)なコードが完成した。この時の回路はパススルー回路と定数加算回路を逆順でつないだだけの単純なもので、1tritあたり約16gateも消費するお粗末なものだった。結局最後までこれと大差のないコードが使われることになる。6時半ごろ、ついに最初の得点が入る。他の人は寝ていたようなので、一人で盛り上がる。8チームほどがサブミットした後だっただろうか。9時頃phoenixさん邸へ移動。任意の列を送れるようになったので、まずは燃料を何とかしようということに。適当な列をひたすら投げまくる。システムのエラーメッセージがどう見てもParsecのエラーです。これはHaskellで書かれているな。3進コードの最初のところにタンク数が入っているらしいことが分かる。数のエンコード方法が大体分かってくる。燃料は3次元配列になっていると睨んでいろいろ突っ込んでみるが、これがなかなかうまくいかない。errorとひたすら言われるが、車と燃料の仕組みを理解せずにやっていたので、そもそも何のdimensionなのか分からない。昼過ぎ相変わらず進展せず。鍵車以外の車が解かれ始めて焦る。あまりにも解読が難航しすぎて眠い眠い。この頃サーバーの反応をWikiにまとめていたが、とても面倒だったので、nyaさんがサブミットして入力と結果を保存するコマンドラインツールとCGIを作り始める。これによってサブミット作業がとても楽になる。CGIは今後継続的に更新されていき、欲しい機能がいつの間にか実装される神インフラとなる。15時頃あまりにも眠かったので1時間ほどうとうとしていると、なんと車と燃料のフォーマットと整数のエンコードがすべて分かったという。そんなバカな?yuizumiさんとphoenixさんが人間離れを起こしたらしい。解読が難航していたのは、リストの長さを表す整数と、行列の成分を表す整数のエンコードが異なっていたせいらしかった。車が読めるようになったので、いまさらになって車のお勉強をする。分かったような分からんような。1次元の2を突っ込むと解けてしまう車が幾つかあるというのが分かったのでサブミット。実は試行錯誤の最中にもなぜか解けている車があったが、ようやくちゃんと理解して送れた。問題の全貌がようやく理解できて、ICFPにはありがちな、遠い遠いスタートラインにようやく立てた。問題が分かったが、これは非線形の整数計画問題なのか?答えが出せるのかこれは?私には全然分からないが、簡単に解けるならこんなの問題になるはずがないということは恐らく間違いない。chunさんが1次元の解をブルートフォースで見つけるプログラムを書いて、いくつかの車に対してトリビアルでない燃料を投入することができるようになった。18時頃もしかして:やることがない?何をやったらいいのか分からない。高次元に対応するにはブルートフォース以外の方法が必要だ。焼きなましならまかせろー、バリバリ。Matchで培った焼きなまし技術を今こそ活かすとき。焼きなましソルバ完成。稀に解ける車もあったが、これでは話にならない。焼きなましで上手くいく類の問題ではなかったのか。プログラムがバグっているのか。焼きなましを信じてこのままやっていいのか。弄って、やっぱり焼きなましでは無理でした。報告した矢先にひどいバグが見つかる。[0-1)の乱数が欲しい場所でrand()と書いていた。これで解けていた問題があるのかは謎だ。これを直すと結構な数の車を解けるようになった。方針は間違っていなかったのか。Division終わるまでしばしのサブミット祭り。Division終了。この時点で5位に入っていたようだが、6位との間をふらふらしていたので、そんなに良い順位ではないだろう。ご飯を食べながら明日以降の作戦会議。ゴルフは最大で収入が2倍近く変わる可能性があるのでおそらく重要だろうと言う事。gusさんが作っているそうなので、それを完成させてもらう方向で。ソルバは車の耐久試験にも使われることになるだろうから、車の強度にとっても重要。眠すぎて電車の中で立っているのが辛かった。回路ジェネレータに明らかな無駄があったので取り急ぎ修正。回路規模が半分ほどになって、おそよ1tritあたり8ゲート。phoenixさん宅へ。私のすることはソルバの強化。焼きなましに用いるスコアとして、満たしている制約式の数を用いていたが、これは離散的過ぎるし、ひとつの制約式を満たすのが完全にランダムでしか見つけられないので良くない。それぞれの制約式が満たされている度をうまく0-1の値にすることにする。最初に、行列の要素のうち満たされているものの個数を入れることにした。単純だが、これだけでもかなり性能向上した。次に各要素がどれぐらい条件に反しているかを考慮するようにする。試行錯誤していろいろ式を試してみるが、この問題は行列を乗算するので係数は非常に大きくなる傾向にあって、故に、ずれている分のlogをとって、さらにそれを0-1に正規化することにした。昼ごろ車の量産体制が整ったようで、少しずつ車が出荷されていく。解かれないかどきどき。ソルバの自動化が行われ始める。ポンコツ車が結構な割合で出荷されてくるので、まず決め打ちのいくつかの行列を突っ込んでみて、次にブルートフォースを走らせて、ダメだったら焼きなましソルバを走らせるような感じ。9時頃20台ほどの車が出荷される。最初の方に出荷した数台以外は解かれていないようだ。私とchunさんはひたすらソルバの改良をする。それに加えて焼きなましが漏らす巨大整数を入力しないと解けないタイプの問題をみんなで各個撃破。このころにはかなりの車を解けるようになっており、相当の収入が得られるようになっていた。見てにやにやしたりするなどする。最終日に向けて私は泊り込みで作業。焼きなましソルバの性能がほぼ完成を迎え、ランダム生成系をほぼ全て廃車にできるようになり、残りの手動生成系を各個撃破した結果、2種類の車を残しほぼ解けるようになった。解けないものの1つは1日目の終了時点頃に投稿された3000番台〜4000番台の車で、一見普通のランダム生成に見えるが、他のとどこかが違うのか何故か解けない。右辺が左辺よりも大きいのでおそらく解の存在する領域がとても狭いのだと思うが、それにしても他の類似の車はさくさく解けるのでこれは大変な謎だった。一つが巨大整数を必要にしているように見えるが手計算では規則性のつかめない問題(45076番など)。同じタイプの車で番号が若くてサイズの小さい試作機のようなのがあったので、もしかしたらと思って焼きなましにかけてみると2x2行列の比較的小さい解が得られた。この解を元に式の意味をphoenixさんと考えていると、紆余曲折の末、これは巨大整数の素因数分解を要求していることがわかった。行列の積で任意の数をエンコードできるとか...。作るのは簡単だけど解くのは難しいのは:素因数分解というのはまあそうだけど、それを実際に制約式にエンコードして出荷してくるというのは脱帽だった。どこかで走らせることに。サイズとしては10^100程度で、これが初めて素因数分解されたのは1990年頃らしいが、果たしてそのころのスパコンに今のPCが追いついているのだろうか。サーバが重くなりすぎて結果が返ってくるのに2分ぐらいかかるようになる。この調子で最終日大丈夫なのかと心配。車のサイズが100文字までになったというtwitter上の噂。そんな仕様変更があったら今回一気にクソゲー化だ。そんなこんなでなるほど4時じゃねーのを見届けて寝る。寝ている間に新車カタログの仕様変更があって、車のスクレイプができなくなっていた模様。対応してもらうが、これでロスした点数は少なくないだろう。夜のうちに増強されたのか、サーバはずいぶん軽くなった。車100文字制限も撤回された模様。私は自動ソルバをくぐり抜けた車を焼いていく作業の繰り返し。車の投稿スピードが上がってきてだんだん辛くなってくる。素因数分解は結局無理っぽいので諦める。12時頃ご飯を食べながら。今朝から新しく提供されるようになった車リストに供給された燃料のサイズが乗っているらしく、それを見ると他のチームの燃料は相当小さいらしい。1文字1-2ゲートのチームもいる。これは結構まずいのではないか。昼頃CGIに2-5チームしか解いてない車のうち自分たちの解けた車の数を表示する機能がついた。それによると、2チームしか解いていない車の実に9割以上がうちが解いたものだということが判明した。にわかには信じがたい数であったが、リストをダウンロードしなおして眺めてみるに本当にそのようだ。それを元に収入を見積もると、200程度はあると出た。2チームの車のうち約半数がうちの燃料の方がサイズが小さいらしい。これはどういう事か?つまりゴルフの必要性は少なくなったということだ。100チームの中でトップを取るのは2チームの中でトップをとるより遥かに難しいが、100チームの中でトップを取ることの意味は殆ど無い。1の車を解いていったほうが遥かに効率が良い。車の生産も終わり、全体の方針としては、後はひたすら出てくる新車をスクラップにしていくという事になる。リストを見ると、鍵生成は6ゲートでできるらしい。それと2のみの行列を出すコードは7ゲートでできるらしい。多くのチームがそれで提出している。これが分かったところで任意の列を生成することはできないだろうから得点にはほとんど影響がないが、7ゲートの方は割とたくさんの車に供給できるので、見つけたら収入が10点ぐらいは増えるかもしれない。たくさんの人がサブミットしてるなら簡単な回路かもしれないので、適当に考えてみるがよくわからず。yuizumiさんが全探索組んで6ゲートの回路が発見される。出てきた回路を見ても、意味はさっぱりわからない。7ゲートの回路は探索が終わらなくて見つからず。夕方頃もはや車をひたすら潰すのみ。潰せども潰せども車はやってくる。終わりに近付いて新車の入荷するペースも加速度的に上がってくる。思い出出荷のような簡単に解けるのががほとんどで、CGIをリロードするたびに数十という単位で車が減っていく。デフォルトパラメータの自動焼きなましソルバで生焼けになってしまった車を手動でじっくり焼きなます。入荷する車は、ポンコツ、簡単に焼ける、硬いがパラメータをうまく選んでじっくり時間をかければ焼ける、手動で簡単に解ける、のおおよそ4種で、前者二つは自動的に消えて、手動が必要なものに対しては各個スペシャルソルバを作成し、硬いが焼けるものに関してはchunさんが膨大なコンピュータリソースにものを言わせて超並列で焼いていく。このころにはソルバのさらなる性能向上により3/4000番台の車も頑張れば焼けるにカテゴライズされていたので、これも超並列で頑張って、最終的には30台程度はスクラップにすることができた。それにしてもあんなに初期に出た割にはなんという硬い車だったのだろう。無限プチプチにも似た楽しさがあったが、さすがに何時間もやり続けるとしんどい。8時頃、まだ1時間もあるのか、などの声も。それでもなんとか、できるだけの車を潰しきって時間終了。完走することができた。反省・感想とにかくとても楽しかった。問題がとても良かったと思う。前半のリバースエンジニアリング部分は蛇足にも見えるが、個人的にはそういうのも楽しめた。行列いじるだけではなんだかICFPという感じがしないような気もするし。参加者同士の問題の出し合いというのはICFPでは新しかったと思う。解けない車が出てきて、そのアイデアに驚嘆したり、さらにゴルフもあり、非常に盛りだくさんだった。独りでやっていたらやることが多すぎて相当厳しかったかもしれないが、そういう意味では、チームに恵まれて十分に楽しめたと思う。nyaさんの強力なインフラとgusさんの車製造のおかげで私は燃料製造に集中することができた。インフラと車がどうなってるのかは今でもよく分かっていない。今回良かったのはとにかくきちんと平行に作業が進んで、個々のタスクもほぼ間違った方向に行かなかった事だと思う。後日談http://icfp.gnumaniacs.org/2010/06/21/whats-this-thing-with-cars-and-fuels/公式ブログに解説記事のようなものがアップされた。呼ばれる問題で燃料はそれを解く方法。'^i...て、知らんがな。我々は分からぬうちになんだか与り知らない問題に挑戦させられていたようです。problemが作られるはずだったと。実際に、これらの両方で成功したと考えているまじかー。うちはひたすら焼きなましただけだけどな。部門に、そのソルバーをエントリーすることが促される。ぶっちゃけどうなんでしょう。こんな急造のものが戦えるんでしょうか。ベンチマークとしてサブミットしていたそのへんのテストは通過していたわけですか。なんか大層な話になってきましたな。供給できる燃料が存在しないことを証明できるか?難しいですね。ATND第五回チームラボ天下一武道会で優勝しました。第三回に続いて二回目です(第四回も参加していましたが、あまりに成績が悪かったので、参加記書くの忘れてました...)。問題http://tam-lab.sakura.ne.jp/tenkaichi/マルチプレイヤー数当てゲームのような感じでした。サーバがある数を保持しているので、その数を当てる。外れていた場合、大きすぎたのか小さすぎたのかが帰ってくる。正解した場合、スコアが1加算され、サーバが保持している数が1増える。サーバがレスポンスを返すのはリクエストしてから1秒後。同時に張れるコネクションは2つまでという制限がありました。当日の様子バトルロワイヤルということで、問題解説中もすでにバトルは始まっています。手動参加してる人が居るってことで、なかなか熱い感じでした。問題聴きながら手動で二分探索してみたら確かに50ぐらいまで既に増えているようでした。手で適当にクエリを投げながらどうするか考えて、とりあえず数当てゲームだったら二分探索じゃないかということで実装を始めました。プログラムはHaskellで書きました。クエリはHTTPで投げるので、httpライブラリを使いました。適当にクエリを投げてみるところ、かなりの割合で503が出ました。その時はどうしてかわからず、そのまま進めていましたが、やはりどうしても効率が悪いのでちゃんと調べてみたら、どうやらhttpライブラリのシンプルAPIがコネクションをcloseしないらしい。手動でコネクションを用意するAPIを使うと、ようやくちゃんと動くようになりました。最初のほうあまりスコアが取れてませんでしたが、それで徐々にスコアが上がってきました。二分探索で数を当てていたのですが、サーバの数字はリアルタイムでカウントアップしていくので、上限と下限の数をアドホックに少しずつ上昇させていくようにしていました。それでしばらくやっていたのですが、よく考えると、そうなってくると二分探索でやる意味がほとんどない。一回正解したらその一秒後に再度クエリを投げられるので、一秒間に他の人たちが正解を当てる回数程度インクリメントして送りつければいい。見た感じでは大体秒速3-4ほどだったので、それをハードコーディングして適当にインクリメントするだけのものに変更しました。なってくると結局、問題は一秒間に他の人たちがどれぐらい正解するかというのをどれだけ精度よく予測するかというところに帰着されます。最初のほうは手で入れていましたが、周りのプログラムの精度がよくなってくるにつれてカウントアップも早くなるので、それに追従するためにちゃんと履歴から計算することにしました。今回の問題の難しいのは、プログラムを改良している最中にも他の人のスコアはどんどん増えていくというところです。プログラムを動かさないと自分のスコアは伸びませんから、なるべく停止時間を短くしなければなりません。改良によって精度が下がるとトータルではかなり損をしてしまうので、プログラムを書き換えるのにかなり勇気が要ります。私は、コネクションが二つ張れるというのを利用して、常時二つプログラムを走らせるという戦略をとりました。ひとつのコネクションでそこそこ精度がいいプログラムをずっと走らせ、もうひとつでいろいろ実験して改良しているプログラムを走らせませます。改良の結果精度がそれなりに向上したら、ずっと走らせている方も切り替えます。いうコネクションが二つというのを生かした開発が出来るのは面白かったと思います。他にも二つのコネクションを強調させながら効率をよくする戦略なども考えられますし、なかなか面白い制限だったと思います。残り1時間ぐらいになった時、いろいろな地味な改良が実を結んだのか一位になりました。残り30分になるとスコアボードが隠され、さらにレスポンスタイムが半分になるとのことでした。残り30分の時点で2位との差が150ほどあったのですが、トータルの得点からすると5%ほどの差だったので、少し精度が悪いと十分逆転されそうでした。レスポンスタイムが半分になることを考慮して、プログラムを改良すべきかどうかなかなか難しいところでしたが、何もしないで負けたら悲しいので結局最後まで改良し続けました。予測する式を微妙にいじったりいろいろしていましたが、結局あまり精度は変わりませんでした。プログラムを止めていた分だけスコアは悪くなったと思います。それで、どきどきしながら結果発表を迎えたのですが、割といい感じのスコアで勝っていました。2位と3位のプログラムは次の数の予測を決めうちの数でやっていたそうですが、レスポンスタイムが変わってから周りの正答率が若干下がったような感じだったので、履歴を用いて予測していた私のプログラムがうまく動いたのかなと思います。感想また優勝できて嬉しいです。今回はヘリコプターをもらいました。ありがとうございました。今回の課題は個人的にはかなり楽しめました。要素は考えられますが、次のような感じでしょうか。次回も近々あるそうなので、また参加させてもらえればと思います。面白いのを考えるのも大変かと思いますが、期待しております。Javahttp://d.hatena.ne.jp/tanakh/20050506#p2のあたりで開発したファミコンエミュレータをJavaに移植しました。ここで公開中http://github.com/tanakh/bjne-javajarもあります。http://github.com/tanakh/bjne-java/downloadsオリジナルのC++版。http://github.com/tanakh/bjneセーブ機能意外はすべて移植しました。マッパは#0〜4と、VRC6を実装しました。動いてるんじゃないかなあと思います。Java?Androidで動かすためです。今のところは普通のAWTアプリです。Javaで辛かったこと符号なし整数型がない。メモリは基本的にbyteの配列で持つことになりますが、そこから取り出すときに気をつけないと負の数になってしまったり、レジスタをbyteなどで持ちますがこれも気をつけないと負の数になったり、アドレスが16ビットだけど、これも気をつけなければ負の数になったりとか、とかく短い長さの符号なし整数を多用するエミュレータではしんどいです。気を付ける箇所が無数にあるのに、コンパイラは何も言ってくれないので、とても怖いです。プリプロセッサがないCPUのエミュレーション部分など、似たような箇所が多いので、C++版ではマクロでまとめていたところがあまりうまく書けなかった。プリミティブ型の参照がない書き換える変数をパラメータ化できない。addr)とかみたいなのができないので、loadHoge(addr)とかをレジスタごとに作ったり...。ジョイスティックAPIがないどうしたものか。Javaで良かったことEclipseC++でもVCを使えばいいという話ですが。LinuxでVCが使いたい...。デバッグがしやすいメモリアクセスのバグがすぐ見つかるので、かなり楽。安心感もあって良いです。スタックトレースが出るし、Eclipseだとクリック一発でコードの当該箇所に飛べるので便利。GUI作りやすい思ったよりはるかに楽だった。クロージャがあればもっと楽なのに。サウンドAPIがいい感じ手動でミキサー書くのがびっくりするほど楽でびっくりした。tosの日記これを読んで、私もちょっと前にHaskellでメモ化をやる方法を考えていたことを思い出したので、書いてみることにします。Haskellでのメモ化は、私のかなり昔の駄文(リアルにびっくりするほど駄文なのでご注意。メモ化の綴りも間違ってます)や、このあたりに日本語の文章があります。そのテーブルを副作用を用いて更新する、というものになっています。これは手続き型言語との対比でとても直接的な実装です。テーブルを更新するために関数全体がモナドになってしまったりして、あまり使い勝手が良くなさそうです。モナドであることを悟らせないために、演算子をモナド化したり、あるいはモナドじゃなくするためにunsafePerformIOを使うなどのトリックも考えられますが、どちらもあまり綺麗には見えません。メモ化においてやりたい事は、テーブルの作成でもテーブルの更新でも無くて、一回計算した値がもう一度計算されないようになっているということです。一度計算したものがもう一度計算されない―――おあつらえ向きの物がHaskellにはあります。簡単のために、メモ化関数の引数は1つの自然数とします(この制約は後である程度ゆるくします)。無限のサイズのテーブルがあると考えます。そこに、その関数(fとしましょう)のすべての引数に対する値が格納されています。1、...というふうにすべて入っています。遅延評価を用いて、各々の値は参照されるまで計算されないようにします。計算したければ、そのテーブルをルックアップするだけです。nが計算され、二回目以降ならすでに計算された値が返されます。問題は、そのテーブルをどうやって表現するかです。効率的にルックアップ出来なければいけませんし、計算されていないノードは存在しないようにしなりません。無限リストからの類推で、無限ツリーというものを考えます。無限ツリーはノードが無限にあるツリーです。ここではリーフはないとします。ノードはすべて内部ノードで、子を持っています。その特殊な場合として、無限完全二分木を考えます。すべてのノードが2つの子をもち、無限に広がっているような木です。これにヒープよろしく、ルートには0を、その子には1と2、さらにその子には3と4と5と6...というように番号をふります。その対応する番号を引数とする関数の値をノードに持たせます。n)でルックアップできます。14...実装メモ化のインターフェースは、fixと同じものを採用します(fixというのは、ここではControl.Monad.fixを指しています。これは一般には"不動点演算子"と呼ばれるものです。不動点演算子に関しては、私のブログの駄文や、Googleに聞いてみてください)。fixをこれから実装するmemofixに挿げ替えるだけで関数がメモ化されるという目論見です。利用例は次の様になります。(n-2)まずは無限完全二分木を定義します。これはリーフを考えなくていいので、とても簡単です。a)次に、このツリーのルックアップを定義します。添字の上位ビットからみて、ツリーを辿っていくだけです。(`div`2)次に、関数の値がすべて詰まった無限ツリーの生成です。ノードの値を計算するための関数を渡してもらって、適当に全部生成するだけです。遅延評価がすべて何とかしてくれます。ix*2+2)さて、あとはメモ化コードを書くだけです。これもとてもシンプルです。memofmemofがメモ化versionのfで、tblがすべての答えの詰まった無限ツリーです。tblは先程のgenTreeにmemofを渡してやれば出来上がりで、memofはfにtblをルックアップする関数を渡してやればいいのです。Integer)..100]実行してみます。...[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,1134903170,1836311903,2971215073,4807526976,7778742049,12586269025,20365011074,32951280099,53316291173,86267571272,139583862445,225851433717,365435296162,591286729879,956722026041,1548008755920,2504730781961,4052739537881,6557470319842,10610209857723,17167680177565,27777890035288,44945570212853,72723460248141,117669030460994,190392490709135,308061521170129,498454011879264,806515533049393,1304969544928657,2111485077978050,3416454622906707,5527939700884757,8944394323791464,14472334024676221,23416728348467685,37889062373143906,61305790721611591,99194853094755497,160500643816367088,259695496911122585,420196140727489673,679891637638612258,1100087778366101931,1779979416004714189,2880067194370816120,4660046610375530309,7540113804746346429,12200160415121876738,19740274219868223167,31940434634990099905,51680708854858323072,83621143489848422977,135301852344706746049,218922995834555169026,354224848179261915075]real0m0.012suser0m0.010ssys0m0.000s動きました。...^C帰って来ず。添字が自然数1つというのは嬉しくありません。データ構造を考えるのが面倒なので、任意の引数を自然数に変換することを考えます。当該データ型のすべてのデータに番号を振ってやって、その番号によってツリーをルックアップするのです。これはその型が可算無限集合でなければいけないのですが、可算無限でない引数をメモ化の引数にするのはここでは考えないことにします。複数の引数はそのまま扱うのはとても難しいので、タプルにするということで妥協します。自然数に変換できるというクラスMemoIxを定義します。逆変換も後で必要になるので用意しておきます。aところで、Haskellの標準ライブラリにIxというクラスがあります。MemoIxと同様に値に対して整数を振るためのクラスですが、これを利用するには値の上限・下限が必要になります。遅延評価でせっかく無限のものを扱えるようになったのに、値の上限・下限を要求されるのは大変イケてないので、これをそのまま使うことは出来ませんでした。具体的な型に対するインスタンスを書きます。Integerは整数であって自然数ではないので、自然数に変換してやる必要があります。正の数を偶数に、負の数を奇数にエンコードすることにします。-((n+1)`div`2)次にタプルを考えます。タプルはペアで表現できるので、ペアのみを考えます。ペアにうまく番号を振るには、次のように斜めに振ってやれば良いです(方法は他にも色々考えられます)。...この番号を頑張って計算します。unindexで二次方程式を浮動小数を用いて解いているのですが、これのせいでIntegerのメリットが失われているのがあまり良くないです(何とかならないでしょうか)。l-ibところでこのコードは自然数への変換の際にかなり数が大きくなります。bに対して、出力の大きさはO(ab)程度です。b)))程度ということになります。文字列型など他の型のMemoIxのインスタンスも考えられますが、それぞれ皆様各自お考え下さいませ。後はツリーとメモ化コードをMemoIxに対応させれば完成です。memof適当な関数を書いてみます。(100,50)実行してみます。...4503056131931081050165600532646379362000real0m0.082suser0m0.070ssys0m0.010s正しく動作しました。インターフェースのバリエーションfixのインターフェースではなくて、次のようなものも考えられます。テーブルを作ってルックアップする関数を返すだけのシンプルなものです。f)これを用いてfibを定義するとこうなります。(n-2)fixのインターフェースよりもこちらの方が書きやすいかもしれません。ソースコード全体のソースコードを再掲しておきます。f)とかいうことを嬉々として書いていたら、http://www.haskell.org/haskellwiki/Memoizationこんなのがあったのですよ...。こっちのほうがツリーのエンコード方法が上手いので、後でコード書きなおします。00:00Haskellでメモ化を行うもう一つの方法Tweet広告を非表示にするもっと読むコメントを書く2010-04-02という夢を見ましたProgramエイプリルフールなので、普段できない、関数型言語を貶しまくるというのをやってみましたが、思った以上に普通になりました。嘘をつくのは自分の心だけにして、それ以外はなるべく説得力のある文章を心掛けましたが、いかがでしたでしょうか。なんだ、分かってるんじゃないか、というご意見も頂きました。ちなみに、嘘でも、Haskellが嫌いだとか嫌になったとかとは書けませんでした。先日のスライドが、関数型の都合の悪いところを無視して良く見えるところだけ書いていたのに対して、関数型のいいところを全く無視して、いかにも駄目そうに書いてみたのが昨日のです。これらは表裏一体で、二つ揃って初めて世界の均衡が取れるという訳です(ほんとかよ)。いう訳で、私は本当に関数型言語に見切りをつけたわけではないので、悪しからず。今後ともより一層のご愛顧を賜りますよう、よろしくお願いいたします。00:00という夢を見ましたTweet広告を非表示にするもっと読むコメントを書く2010-04-01手続き型に改宗しましたProgramつい先日にあのような資料をアップロードして心苦しいのですが、手続き型に改宗することにしました。誰よりも関数型言語を愛し、常日頃よりどうして関数型が流行らないのかを考え続けていた私ですが、10年余りを経てようやく気付きました。関数型がイケてないから流行らないのだと。関数型の人たちは言います。immutableこそ正義であると。?変数に代入できる方が書きやすいに決まっています。書きにくいと思いつつも、Haskellというのは曰く、素晴らしい言語なのだから、そんなことはないはずだと自分をごまかしごまかしこれまで生きてきましたが、そうじゃないんです。変数に代入できる方が書きやすいのは当たり前なんです。モナドなんてへんてこなものを当たり前のように持ち出さなければならない時点でおかしいことに気付くべきでした。私はコンソールから文字を入力したいだけなのに、そんな妙な数学の匂いのするものを利用しなければならないのでしょうか。うまく使えば便利だという話はそれはそれで尤もですが、殆どの場合にそんなものは必要ないでしょう。他の言語を見てご覧なさい。どれも困った様子はないでしょう?型も良くないです。型があれば実行時にバグに悩まされないなどと仰いますが、結局のところ配列の添字がおかしいとか、パターンマッチ漏れとかはわからないんです。コンパイルが通ってそれがそのまま動作するなんて、ほとんど幻想です。それなのに、コンパイルの通りは著しく悪くなります。コンパイル通すだけで一苦労です。私なんて、コンパイルが通らなくてプログラムを完成させるのを諦めたこともしばしばです。それならいっそ、バグっててもいいから動いて欲しいと思います。高階関数も良くないんです。人間というのは、あまり複雑なものを扱えるように出来ていません。関数の時点で難しいんです。それなのに、関数に関数を渡すとか、難しすぎるんです。それが許されると、関数を渡す関数を渡す関数なども考えなければならなくなります。人間の制御出来る範囲を超えています。関数がファーストクラスじゃないというのは、考えてみれば、人間の能力の限界にフィットしたとても現実的な制約だったんです。ループを再帰で表すというのもありますが、無茶もいいところです。再帰というのは難しいんです。私の周りでも、再帰が分からずに躓く人を大勢見てきました。してfor文で簡単に扱えるものを再帰で書かなければならないんでしょう。人は制御の流れをイメージするのは得意だけど、再帰的なものをイメージするのは苦手なのです。冷静になって考えてみれば、とてもシンプルな理由でした。「関数型は書きにくい」。関数型に触ったときは今まで自分がやってきたプログラムとは全く違っていて、それはそれは素晴らしいものに見えました。それはプログラマが罹るはしかのようなものだったのです。10年もの間熱に浮かされていましたが、ようやく目が覚めました。現実が見えてきました。私は手続き型に改宗します。ついでに言いますと、オブジェクト指向もいらないです。純粋手続き型(?)でいいのです。オブジェクト指向でコンポーネント再利用できて生産性向上、というのが典型的なオブジェクト指向プログラマの言い分ですが、皆さん、クラスを作ってそれが他のプロジェクトで再利用できたためしなんてあるでしょうか?デザインパターンなどというものもありますが、あれも必要ないです。ご存じの方はご理解いただけるかと思いますが、あれはとてもややこしいものです。やろうとしていることはどれもとても単純なことのはずなのに、クラス階層図は目を背けたくなるほどの複雑さです。そんな仰々しく天下り的なものを受け入れなければならない時点で、何かがおかしいと気付くべきです。世の中にはC言語があればそれで十分なのでした。Cのプログラムやライブラリを見てみなさい。どれも何の過不足もなくうまく廻っているではありませんか。関数型などを持ち出す必要がありましょうか!そういうことなんです、関数型が一向に流行らないのは。こんな簡単なことに私もようやく気づきました。これからはこのページは純粋手続き型雑記帳としてやっていこうと思います。以前の内容は、とてもお見苦しいものではありますが、若気の至りということで残しておくことにします。落胆された方も中にはおられるかと思いますが、必ずご理解いただけるものだと思っております。これからも変わらぬご愛顧をよろしくお願いいたします。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。