2007/10/20

やっぱりSocket370のマザーなんて壊滅だった。唯一すぐ手に入ったまっとうな商品が、SuperMicroのP3TSSEで、実売は23000円。どう考えても2007年の買い物じゃないけど、しかたない。メーカー取り寄せとのことだったけど、「Your Best P4 Motherboard」と書かれた箱に入ってやってきたので、もうメーカーにもまともな在庫はないのかもしれない。

supermicro p3tsse

でもこれ815EだしDDRは使えないんだよな。PC133のメモリも何枚か持っていたはずなんだけど、こないだ古いPCを処分したときに一緒に引き取ってもらっちゃたみたい。さすがにもう使うことはないだろうと思ったと思われる。しかたないのでメモリも買う。512MBで20000円。げー。いまどきPC133のSDRAMって信じられないくらい高額なのね……。6000円くらいのへなちょこなバルク品も試してみたんだけど、認識されなかった。最近はメモリの相性なんてほとんど気にしなくなってたので、ここでふたたび時代に取り残されている感。

2007/10/16

どうやらマザーボードを買い換えないとだめらしい。買い換えるのはいいんだけど、いまどきDDR PC2100に対応したまともなSocket370のボードなんて手に入るのかなあ。


やっぱりTualatinだよね。


tualatin

2007/10/12

SXMLから、ある属性を持つ要素をSXPathを使って取り出したい。たとえば英語と日本語の文章からなるこんなデータから、lang属性が"ja"の要素を取り出したい。
(define e
'(*TOP*
(p (|@| (lang "ja"))
"こんにちはこんにちは"
(emph "日本語です"))
(p (|@| (lang "en"))
"hellohello"
(emph "I got english"))
(m (p (|@| (lang "ja"))
"あしたは休み")
(p (|@| (lang "en"))
"I can't work any more"))))
どうやらSXPathで遊ぶときは、「真偽を返す関数」をsxml:xxxみたいな名前の関数に渡してコンバータを作り、それを要素に適用するというのが常套っぽい。いまは属性をもとに評価したいので、「真偽を返す関数」としては「lang属性の値が"ja"かどうかテストするプロシージャ」になるんだろうな。そして木全体をめぐりたいので、sxml:descendantで作ったコンバータをルートノードに適用すればよさそうだ。
(use sxml.sxpath)
(use sxml.tools)

(define (f r n v)
((sxml:descendant
(lambda (e) (equal? "ja" (sxml:attr e 'lang))))
r))

(define q (sxpath `(,f)))
実行結果
gosh> (q e)
((p (|@| (lang "ja")) "こんにちはこんにちは" (emph "日本語です"))
(p (|@| (lang "ja")) "あしたは休み"))

2007/10/09

つっかかるようなシューベルトが好きといえばわかる人にはわかるとおり、ぼくはアファナシエフの弾くシューベルトが好きだ。で、10月1日に彼が来日してシューベルト弾くというので、トッパンホールにいってきた。さすがに大御所の演奏会だけあっていい値段だったし、聴衆もおじいさんおばあさんが多くてなんだかなーという感じ。彼らの大半はシューベルトだけが目当てなんだろうな。でも残念でした。この日、アファナシエフが弾きたかったのは、途中の休憩をはさんで演目の真ん中に演奏したシルベストロフだったらしい。それをはさんで演奏したシューベルトの即興曲は、明らかに軽く流してた。すごくうまいけど。

そもそもシューベルトの特にピアノ曲は、例えばかわいい女の子といっしょにいる最中に「この楽しい時間はどうしていつか終わってしまうんだろう」みたいに思い始めてしまったときのあの何ともいえない気分が永遠に引きのばされる感じがたまらないと思うんだけど、そういう雰囲気には欠ける演奏だった。楽しい(pleasure)が幸せ(happy)に結び付くとは限らないってダライ・ラマは言ってるけど、だからこそ「楽しさをなんとか引きのばして幸せを錯覚したい」っていう気分にきゅんとなるわけで、そうでないシューベルトは老後の楽しみにはいかもしれないけど(なにしろアファナシエフはすごくうまい)、ぼくが聴きたいのとは違う。

アファナシエフはピアノソナタ18番をレコーディングしてるけど、そこではこの錯覚した幸せ感を満喫できる。こないだの高橋アキの13番にも同じ印象を受けた。10月1日のアファナシエフは、むしろシルベストロフの曲で、この感じを演奏者として楽しんでいた気がする。そういえば高橋アキはアンコールにサティの「おまえがほしい」を弾いて、それを聴いていたときは「ぶちこわしじゃん」と思ったけど、あの堂々巡り感も同じような世界観なのかもしれない。

2007/10/06

自分用のメモ。なんかこんな感じのリストがあるとする。
(define life
'(i (value "2007")
"年"
(ii (value "10")
"月"
(iii (en "9")
"日"))
(delimiter "/")
(value "2009")
"年"
(ii (value "1")
"月"
(iii (en "31")
"日"))
(iii (value "10")
"日")))
一番上の階層のテキスト情報だけを抜きたい。
つまり、iiとiiiのタグの子孫を飛ばして読んでいきたい。結果として得たい文字列は、"2007年/2009年"。

SXPathを使う。
(use sxml.sxpath)

(define (text-self elem)
(sxml:string ((node-self (ntype?? '*any*)) elem)))

(text-self
(cons 'dummy
((sxml:child (sxml:invert (ntype-names?? '(ii iii)))) life)))

2007/09/30

9月20日に高橋アキのピアノドラマティック・シリーズ #5にいってきた。高橋アキについては、一時期よくサティを弾いていた人という認識しかなかったし、今回の演目にもシューベルトの13番とかあったので、もっと「耳にやさしい」コンサートかと思ってたよ。自分のなかでシューベルトブームだったのと、チェロのローハン・デ・サラムが競演してコダーイの無伴奏チェロとかドビュッシーのソナタとかやるということだったので、それなら奥さまがチェロを聴いてみたがっていたしちょうどいいかと思ってチケットを買った。

で、このローハンおじさん、高橋アキがモンポウとかシューベルト13番のような聴き心地のいいクラシカルな演目をやるといってるところに「オレにコダーイをひかせろ」といって殴り込んできただけのことはある。メロディックなさじ加減ゼロ。たとえばヨーヨー・マのひくコダーイは、このむちゃくちゃハードな技巧の曲を可能なかぎり美しく聴かせようみたいなサービス精神に溢れているんだけど、ローハンおじさんにそんな気配はない。最後の一音なんて、ほとんど放り投げてるもんな(ヨーヨーのCDでは、たっぷり響かせて終わる)。もう、この演奏だけでファンになりました。翌週の27日にも渋谷でローハンおじさんが聴けるというので、もちろんそちらにもいってきた。

27日のほうは、フルートのカリン・レヴァインと競演で、現代曲オンリー。50人くらいしか聴衆のいない小さな演奏会だったけど、これがまた強烈だった。ローハンおじさんはコダーイに加えて、クセナキス(本人はゼナキスと発音してた)と松村禎三(!)のソロ曲を演奏してくれた。クセナキスはもともとプログラムにあって、期待もしてたんだけど、ゆうに期待を裏切ってくれる。松村禎三は、追悼として当日プログラムに追加されていた。17絃箏のための祈祷歌をチェロ独奏むけにアレンジしたものらしい。最初からチェロ独奏の曲でしょうっていうくらいの完成度なんですが、それは松村禎三の曲の力ですか、それともローハン・デ・サラムのうまさですか。なんかいま東京でレコーディングしているらしいんだけど、コダーイはもちろん、このクセナキスのKottosと松村禎三をなんとか収録していただけないものでしょうか。

この日はフルートのカリン・レヴァインさんもよかったな。とくにカイヤ・サーリアホとジャチント・シェルシ。フルートってこんなにいろんな音が出る楽器だったんですね。バスフルートやアルトフルートの独奏曲を生で聴く機会があるとは思いませんでした。

高橋アキを忘れていたわけではなく、9月20日の演奏にはものすごく共感するんだけど、いかんせん後にやった9月27日の演奏会の印象のほうが強烈に残っているのですっかり話がずれた。とくに、つっかかるようなシューベルトは、そうでなければシューベルトをあえて聴く意味はないよねと個人的にはすごく同意したい。

2007/09/23

現在の書籍の製作スタイルでは、横長のモニターを縦にして使うほうが都合がいい。とにかくビルドしたPDFのページを画面いっぱいに大きくして確認したいのである。そして書籍のページというものは横長ではなく縦に長い。ところが職場で使っている24インチのモニターはスタンドがへぼくて縦にして使うことができない。それでスタンドをはずして机の上に平置きにして無理やり縦にして使ってみた。

R0011978

これがあんがい使える。廃熱も大丈夫なようだ。

本当はPDFを見開きで確認したいので、このサイズのモニターが二枚はほしいところ。もちろんちゃんとアームつきでな。

2007/08/20

先週、2004年ごろには Gauche に KAKAI のライブラリがあったらしいとか書いたけど、sourceforge のページから今でもダウンロードできると shiro さんに教えていただいた。ありがとうございます。(そして、ろくに確認せず適当なことを書いていてすみません。)

しかもインストールしたら使えた。
(use text.kakasi)
(kakasi-begin :JH :p)
(display (kakasi-convert "素子"))
(newline)
(kakasi-end)
これを kakasi-trial.scm とすると、
$ gosh -V
Gauche scheme interpreter, version 0.8.10 [utf-8,pthreads]
$ gosh kakasi-trial.scm
{もとこ|そし}

2007/08/17

[2010年6月3日 追記]改良版はこちら

emacs で、検索パターンをその後の編集中ずっとハイライトにしたい。インクリメンタルサーチの結果でもハイライトされるけど、あれだと編集をはじめるとハイライトが解除されてしまうので、使えない。
具体的には、作業中のバッファであるパターンを一時的にハイライトして、それを確認しながらを作業するための方法がほしい。こんな具合。

highlight

この例は、 ja タグで囲まれた部分をハイライトするようにしたところ。普段はハイライト不要なんだけど、たとえば「 ja にも en にも出てくる語を検索して周辺の文章を編集したいんだけど en 内にあるほうは無視してもいいや」といった作業に便利じゃない?

で、とりあえず作ってみた関数。
;;; let designated pattern be highlight
(defun highlight-regexp (re)
(interactive "sRegexp: \n")
(make-face 'my-highlight-face)
(set-face-foreground 'my-highlight-face "black")
(set-face-background 'my-highlight-face "yellow")
(defvar my-highlight-face 'my-highlight-face)
(setq font-lock-keywords (list (list re 0 my-highlight-face t)))
(font-lock-fontify-buffer))
M-x highlight-regexp すると正規表現の入力を促されるので、そこで適切なパターンを指定すると、上の例のようにハイライトされる。そういえば解除するときのことは考えてなかった。あと、上の例ではなんとなく改行をまたいだマッチに成功してるけど、emacs の正規表現である以上、改行をまたいだパターンのマッチは期待通りにいかないと覚悟すべき。やっぱりこのエディタは、本当のところは文章のパワー編集には向かないんじゃないだろうか。

まあ問題はあるけど、ここまではよかった。

実はいま c さんに、「xyzzy で同じことがしたい」と脅されている。xyzzy は普段使ってないから、色の付け方とかわからんのですよ……。
Windows なら EmEditorとかで GUI のメニューから同じようなことができるから、とりあえずそっちでいいんじゃないかなあ。だめ?

2007/08/16

2007/08/15

cさんに「索引の読み仮名をひらがなで明示的に指定するのは前時代すぎてあほみたいだ。休み明けまでに何とかしとけ」と恫喝されたので、彼女が休みのあいだに急いで対策することにした。

まあ、実際の cさんはそんなひどい言い方をする人ではなく、ちょうかわいいんだけど、たしかに LaTeX ベースで本を作っていると索引のふりがな入力がうっとうしい。労力の問題だけでなく、原稿にひらがなが氾濫して読みにくくなるという意味でもうっとうしい。

漢字かな変換にはKAKASIを使うのが常套なんだろうな。Gauche には Ruby とちがって KAKASI のライブラリはないけれど、ほとんどジョーカーみたいな c-wrapper がある。
これで libkakasi.h に宣言されているCの関数が Gauche から使える。
(use gauche.charconv)
(use c-wrapper)
(c-load-library "/usr/lib/libkakasi.so.2.1.0")
(c-include "/usr/include/libkakasi.h")

(define (kanji->hira str)
(let ((base-ces "utf-8")
(kakasi-ces "iso2022jp"))
(kakasi_getopt_argv 3 '("kakasi" "-JH" "-p"))
(ces-convert
(x->string (kakasi_do (ces-convert str base-ces kakasi-ces)))
kakasi-ces base-ces)))
KAKASIがUTF-8を扱えないのが厄介だけど、それ以外はとても素直に Gauche で漢字かな変換ができる。

gosh> (kanji->hira "素子")
{もとこ|そし}
さて、この「素子」のようにユニークな読みを決定できない項目があると困っちゃうんだけど、「もとこ」か「そし」かの判断を機械的にすべきではなさそうだ
(「この素子を開発したのは素子さんです」問題)。だから、こういうのだけは人力であらかじめ指定しておくのが最適な対応だと思うのですが、それでかまわないでしょうか?> cさん
こんなふうにマークアップ原稿がLaTeXへと変換されるようにします。
<p>
この素子を開発したのは素子さんです。
<indexterm><i1 sortas="そし">素子<i2>開発者</i2></i1></indexterm>
<indexterm><i1 sortas="もとこ">素子</i1></indexterm>
<indexterm><i1>開発者</i1></indexterm>
</p>

この素子を開発したのは素子さんです。\index{そし@素子!かいはつしゃ@開発者}\index{もとこ@素子}\index{かいはつしゃ@開発者}



参考:
今日の一行::ひらかなのインデックス の cut-sea さんの解
ごとけんさんの ruby-kakasi の kakasi.c

2007/08/10

もう、PostScriptでフィボナッチ数列くらいなら昼休みにコーヒー飲みながらでも考えられる。
/fib {0 1 2 index -1 1 {pop exch 1 index add} for} def

ただ、せっかく PostScript なので、結果を印刷とかもしたい。印刷には show オペレータを使うわけだけど、show は文字列(string)しかとれない。fib オペレータが返すのは数値(integer)なので、文字列の型に変換する必要がある。

PostScript で型を文字列に変換するには、=string cvs とすればいいようだ。
%!
/fib {0 1 2 index -1 1 {pop exch 1 index add} for} def
/Palatino-Linotype findfont 300 scalefont setfont
10 10 moveto
11 fib == =string cvs show
これでページの左下に大きく「89」と印刷される。やっぱりPalatinoフォントの数字は美しい。

fib-11

さらにグラフなど描いてみる。
%!
/fib {0 1 2 index -1 1 {pop exch 1 index add} for} def
20 setlinewidth

1 1 21 { % for
/i exch def
/x 27 i mul def

0 setgray
/Palatino-Linotype findfont 10 scalefont setfont
10 x 5 sub exch moveto
i fib == =string cvs show

/r {rand i mod 21 div} def
r r r setrgbcolor
newpath
x 30 moveto
i fib == 10 div 30 add x exch lineto
stroke
} for

showpage

fib

2007/08/07

PostScript で階乗のつづき。こんどは for 文で。
/func {1 exch -1 1 {mul} for} def
ようするに、自分がしたい操作に必要な変数が、適切な数だけ適切な順番でスタックに積まれているようにすればいいらしい。そしてスタックというやつからは、直前に積んだものだけを取り出すことができる。

たとえば上記で定義した階乗のオペランド func を以下のように呼び出すと、
GS> 5 func
まず 5 がスタックに積まれる。この 5 は「funcへの引数」のつもりなんだけど、スタックから見るとそんなつもりはなくて、ただ値が積まれただけ。次は func を積むんだけど、func は上記のように定義されているので、その定義の一番最初にある 1 がスタックに積まれる。この時点のスタックの状態はこんな感じ。
 1 
---
5
func の定義によれば、次は exch だ。これは、それまでスタックの1番上にあった要素とその下の要素を入れ替える。つまり、スタックの状態はこうなる。
 5
---
1
さらに -1 と 1 を順番に積んで、スタックの状態はこうなる。
 1 
---
-1
---
5
---
1
ここで、本文が mul だけの for が登場する。for というオペレータは、スタックの値を 3つ消費し、それぞれの値を深いほうから順番に「繰り返しの最初」「繰り返しの更新」「繰り返しの終わり」として本文を繰り返す。ただし毎回の繰り返しでは、スタックの先頭に、そのターンにおける変数のようなものが積まれる。こう書くと複雑だけど、ようは最初に本文を実行するときには「5」が、2回目は「4」が、...、5回目は「1」がスタックの先頭に積まれるということ。つまり1回目の繰り返しのとき、スタックの状態はこう。
 5
---
1
本文の mul は、このスタックから値を 2つ取り出して、それらの積をあらためてスタックに積む。したがってスタックの状態は、
5
2回目の繰り返しに際してスタックの先頭に「4」が積まれる。
 4 
---
5
このスタックで mul を適用すると、
 20
3回目の繰り返しに際してスタックの先頭に「3」が積まれる。
 3 
---
20
mul を適用して
 60
4回目の繰り返しに際してスタックの先頭に「2」が積まれる。
 2 
---
60
mul を適用して
120
5回目の繰り返しに際してスタックの先頭に「1」が積まれる。
 1 
---
120
mul を適用して
120
おしまい。こうして最後のスタックの値を取り出せば(そのためには == を使う)、5の階乗の値が得られる。

たぶん用語の使い方はいいかげん。はやく教科書こないかな。

2007/08/06

PostScript が意外におもしろいので真剣に勉強してみようと思う。教科書は、Web で PDF が全部公開されている "Thinking in PostScript" に決めた。書籍はもうとっくに絶版らしい。でも物理的な本が手もとにないとつらいんだよなあ。Amazon マーケットプレイスにも出品されているけどバカみたいに高額なので(6000円以上)、US の同様のサービスに注文した(600円くらい)。まだ届かない。出荷された気配もない。もう待ちきれないよう。

というわけで、試行錯誤しながら階乗を考えてみた。
 /func {dup 1 eq {1 mul} {dup 1 sub func mul} ifelse} def
実行結果。
GS> 10 func ==
3628800
GS> 20 func ==
2.43290202e+18
GS> 100 func ==
inf.0
どうやら再帰的なオペレータの定義ができるらしい。はじめは、ふつうに for を使って解こうとしたんだけど、わかりませんでした。

ところで Emacs の ps-mode は GS のビューワーと連動して出力結果がリアルタイムで見られてすごい。便利すぎ。ただしお絵描きを始めると日付が変わるようだ。

2007/06/23

Schemeの多値の正体は継続

SICPでは「多値」を表立って使うことはない。ただ、5.2.2で2種類のリストをやりとりするときに2変数のプロシージャを使った一見するとトリッキーな処理が登場して、これが実は多値なんだよという種明かしを脚注4でやっている。さらに、そのトリッキーな2変数プロシージャのことを継続(continuation)と呼ぶと説明している。

多値も継続だったのか。

噛みしめるために小さな例を考えてみた。つぎのプロシージャ one は、プロシージャ sincos が返す 2 値を受け取り、その 2 乗和を返す。sincos は引数の角度に対する sin の値と cos の値を返すので、プロシージャ one は引数にどんな数値を指定しても実数の 1 を返す。
(define (one rad)
(receive (sin cos)
(sincos rad)
(+ (* sin sin) (* cos cos))))

(define (sincos rad)
(values
(sin rad) (cos rad)))
このプロシージャ one は、values や receive という組み込みの多値のしくみを使わなくても、つぎのように継続を表すプロシージャ cont を使って多値を模倣できる。
(define (one rad)
(sincos
rad
(lambda (sine cosine)
(+ (* sine sine) (* cosine cosine)))))

(define (sincos rad cont)
(cont (sin rad) (cos rad)))
いちおう実行結果。
gosh>(one 123456)
1.0
gosh>(one -9876)
1.0


まとめ

計算の一部または全部をどうにかしたい場合があって(よその関数に渡したいとか、ちょっと保留しておきたいとか)、そのときの定石は「プロシージャでくるんでしまえ」。で、そういうプロシージャのことを「継続」(continuation)と呼ぶ。

2007/06/18

ついやってしまった。

Functional Programming IAT
関数型指数(潜在的な関数型プログラミングの嗜好度)をはかる IAT
http://dame.dyndns.org/misc/fpiat/


「あなたの関数型指数は 0.62331761674765 です。正が関数型、負が手続き型です。」

でも、設問では"Lisp"(Schemeではない)が関数型言語とされているんだけど、それはどうなの?

2007/06/17

練習問題を放置したままだったSICPの第5章をぽちぽち再開。こうやって再読してみると、すっかりどんな話だったか忘れてる。やっぱり手を動かさないで本を読んでいるだけじゃなんにも身につかない。編集という、まさに読んでいるだけの職業に自分が従事している現実を呪うのはこういう瞬間だ。ただの逆ギレだけど。

というわけで第5章の練習問題に着手しはじめた。ところがすぐに困っちゃったのは、この章の内容がGauche(などのSchemeインタプリタ)で式を実行すれば確かめられる話じゃないこと。とくに5.2節でレジスタマシンのシミュレータをSchemeで作るまでは、紙と鉛筆でデータの流れを図示したりしながら、脳内レジスタマシンを妄想して読み進めるしかない。「レジスタマシンの動作を手でシミュレートしろ」といった問題を考えるのは楽しいんだけど、どうにも「わかった気になっているだけかも」という懸念がぬぐえないのが気持ち悪い。どうでもいいけど、専門書や専門雑誌の編集者を楽しく長く続けていくのに求められる最強のスキルは、「わかった気になったところで留まっていられる」ことだとおもう。前々から気がついてはいたけれど、最近になってひどく実感するようになった。こういう感情をわざわざ書きとめているということは、そういうことだ。ここまでどうでもいい話。

この気持ち悪さはレジスタマシンがあれば解決する。かといって5.2節でシミュレータを作るまで待ってられないし(経験上、1問でも練習問題をぬかすと最後まで練習問題に手をつけずに読了する。帰納法で証明できたらかっこいいかもね)、そもそもSchemeでシミュレートするっていうのも気持ちが悪い。最初にこの章を読んだときは「これは教科書として画期的なアイデアだ」と思ったけど、実際はめんどくささのランクをひとつ繰り上げているだけなような気もする。

アセンブリというわけにもいかないので、Cで書いてみることにした。たとえばフィボナッチ数列の第n項を求めるレジスタマシンのコントローラ(原書512ページのFigure5.12)。
/* Implementation of a recursive fibonachi machine in C.
(Figure 5.12 of "SICP")
2007/6/17
k16.shikano@gmail.com
*/

#include <stdio.h>
#include <stdlib.h>
#define STACKSIZE 100

void fibloop();
void afterfib1();
void afterfib2();
void fibdone();
void immediate();
void rtc(int);

void initstack();
void save(int);
int restore();

int cont = 0;
int val;
int n;

main(int argc, char *argv[])
{
initstack();
n = atol(argv[1]);

fibloop();
}

void fibloop()
{
while(n >= 2)
{
save(cont);
cont = 1;
save(n);
n = n - 1;
}
immediate();
}

void afterfib1()
{
n = restore();
cont = restore();
n = n - 2;
save(cont);
cont = 2;
save(val);
fibloop();
}

void afterfib2()
{
n = val;
val = restore();
cont = restore();
val = val + n;
rtc(cont);
}

void immediate()
{
val = n;
rtc(cont);
}

void fibdone()
{
printf("answer = %d\n", val);
exit(1);
}


/* return to continue */
void rtc(int c)
{
switch(c)
{
case 0: fibdone();
case 1: afterfib1();
case 2: afterfib2();
}
}


/* naive stack implementation */
int stack[STACKSIZE];
int *pstack;
int *pinit;

void initstack()
{
pstack=stack;
pinit=pstack;
}

void save(int val)
{
if (pstack > pinit+STACKSIZE){
perror("Reached Stack End");
exit(1);
}

*pstack=val;
++pstack;
}

int restore()
{
if (pstack == pinit){
perror("Reached Stack Head");
exit(1);
}

--pstack;
return *pstack;
}
スタックの機能をでっちあげて、Figure5.12にあるSchemeっぽい式をもじどおりCに置き換えただけ。コンパイルして実行すると(想定内のセグメンテーション違反はおこすけど)あっさりうごく。
k16@debian:~/play $ ./fib 24
answer = 46368
k16@debian:~/play $ ./fib 25
セグメンテーション違反です

これは、Cでフィボナッチを書きました、という話ではなく、これまでSchemeというレイヤでプログラミングしているときに再帰関数だと思っていたコレが、
(define (fib n)
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
その下のレイヤでは上記のCのコードのようなレジスタへの代入とスタックへの出し入れだけの形(現代のコンピュータがかろうじて完璧に扱える形)に解くほぐせて、しかも動く、という話なんだよね。プログラミング言語っていうのは、もしかして、ここで手動で試してみたコードからコードへの変換みたいな処理を自動的に行うしくみのことなのか?

なんだかこの本を読みはじめたときのようなわくわく感が。

2007/05/31

地球の裏側までトンネルを掘ってボールを落としたらどうなるかという話題になった。空気がなくて、他の天体の重力の影響を無視できて、トンネルが地球の重心を通る直線で、トンネルの壁が重力によって押しつぶされなくて、なんかほかにもいろいろ条件を満たすなら、ボールは反対側の地表付近まで届く。トンネルの途中でぴたっと止まっちゃうことはない。力学は昔も今もさっぱり自信がないんだけど、その理由を文章で説明するとしたらこんな感じになると思う。
ボールはトンネルの真ん中まで落ちるあいだ、つねに一定の加速度で移動する。
つまり、どんどん速度が増える。
トンネルの真ん中付近を突っ切るときが最速で、そこから先は正反対の向きの加速度で移動する。
つまり、だんだんゆっくりになる。
そのうち前半の行程で得たエネルギーを使い果たし、反対側の地表付近で一瞬静止して、今度はもときた方向へ落ちていく。
以下繰り返し。

で、こういう問題を考えるときは「地球の中心に全質量が集中している」と見たてることになっているわけだけど、その理由を説明するのがむずい。この場合の「見たて」は、たとえば数学で「0.99999… = 1」とか規定するのと違って、そう考えると議論に都合がいいからという性質だけのものじゃなく、もっと本質的な話だったはず(もちろん、どんな理学的な説明だって「そのほうが都合がいいから」って言い方はできるんだろうけど……)。で、昼休みにWikipediaを見てみたら、あっさり証明がのっていた。(読んではいない)

Shell theorem
http://en.wikipedia.org/wiki/Newton%27s_sphere_theorem

もしボールがトンネルを移動している最中に球に地球が真っ二つに割れたら?という話も出たけど、それまでにボールが得ている運動エネルギーと変化した周囲の重力から得るエネルギーとが均衡するように動くとしか……(実際には地球を真っ二つに割ることになった外因からのエネルギーがいちばん大きく影響するんじゃない?)

2007/05/28

パズル「グリッド色分け問題」少し改良版

「組合せ最適化」をぱらぱらめくったけど安直な方法が見つけられなかったので、オクトーバーフェストに行ってビールをのみながらほげほげ考えていたら、行ごとに組み合わせを求めつつ枝刈りして、それを枝刈りしながら列に集めれば、ずいぶんメモリを省略できるはずだと思いついた。実際これはうまくいって、4x4程度なら瞬時に計算できる。

たとえば10番目に得られた結果。10番目であることに特に意味はない。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


(use srfi-1)
(use util.stream)

(define (line-colorings width colors pred)
(cond ((= 1 width)
(apply stream (zip (iota colors colors -1))))
((= 0 colors)
stream-null)
(else
(let R ((top colors))
(if (= 0 top)
stream-null
(stream-append
(stream-filter
pred
(stream-map (cut cons top <>)
(line-colorings (- width 1) colors pred)))
(R (- top 1))))))))

;; (stream of lists) -> (stream of lists)
(define (grid-colorings hight rows patterns pred)
(cond ((= 1 hight)
patterns)
((stream-null? patterns)
stream-null)
(else
(let R ((top (stream-car patterns))
(rest (stream-cdr patterns)))
(stream-append
(stream-filter
pred
(stream-map (cut append top <>)
(grid-colorings (- hight 1) rows patterns pred)))
(if (stream-null? rest)
stream-null
(R (stream-car rest) (stream-cdr rest))))))))

(define (tiles lines rows colors)
(stream-filter
(cut egalite? <> (quotient (* lines rows) colors) colors)
(grid-colorings lines rows
(line-colorings rows colors
(cut stripe? <>))
(cut staggered? <> rows))))


;;;; predicates
; Doesn't the list contain adjacent cells with same color?
(define (stripe? ls)
(cond ((null? ls)
#t)
((null? (cdr ls))
#t)
(else
(let ((v (car ls))
(w (cadr ls)))
(and (not (= v w))
(stripe? (cdr ls)))))))

; Doesn't the list contain vertically adjucent cells with same color?
(define (staggered? ls rows)
(let R ((rows (transpose ls rows)))
(if (null? rows)
#t
(and (stripe? (car rows))
(R (cdr rows))))))

; Does each color appear at least n times?
(define (egalite? ls n c)
(let R ((c c) (ls ls))
(cond ((= c 0) #t)
((< (length ls) n) #f)
(else
(receive (the-colors rest)
(partition (cut = c <>) ls)
(and (>= (length the-colors) n)
(R (- c 1) rest)))))))



;;;; some list utils
(define (group ls n)
(receive (front end)
(split-at ls n)
(if (null? end)
(list ls)
(cons front (group end n)))))
(define (transpose ls rows)
(apply zip (group ls rows)))

2007/05/26

パズル「グリッド色分け問題」をSchemeで解く

自宅のトイレにはディック・ブルーナのポスターがもう何年も張ってある。全体がグリッドに仕切られていて、そのひとつひとつに彼の代表作から抜き出した絵が並べられてるんだけど、そのうちの1枚に描かれているテーブルクロスの柄が気になってしょうがない。

R0011516

どうしてこのテーブルクロスは、きちんと格子が塗り分けられていないんだろう。左下で青いマスが横に並んじゃっているのがどうにも気持ち悪い。行列の成分でいうと33と34の2つ。もしスプーンがおかれてなかったら境界が識別できないじゃないか。4x4のグリッドを3色で塗り分けるパターンなんていくらもあるだろうに。

というわけでパターンを求めてみる。

戦略は、まず塗り分けのパターンをすべて求めて(つまり隣同士が同じ色に塗られるパターンを含む)、そのうちで3色をちゃんと使ってうまく塗り分けられているものを取り出す。

「3色をちゃんと使ってうまく塗り分けられている」はどう評価しよう? 
とりあえず、どの色も少なくとも5回(16÷3)は使われていて、隣同士が違う色になっていればよしとしよう。(デザイン上のよしあしを評価するとしたら何を考えたらいい?)

4x4のグリッドを塗り分けるパターンは、長さ16のリストであらわすことにする。使う色は1,2,3という数値で表す。つまり、リストの各要素には、各マスの色を意味する1,2,3のいずれかの数値がはいる。これをグリッドの左上→右下という順番で並べる。たとえば以下のような塗り分けは、(1 3 2 3 3 2 1 2 1 3 2 1 3 2 1 3) というリストであらわすことにする。( 1:青、2:オレンジ、3:緑)

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


隣同士が別の色で塗り分けられているかどうかは、ベタに縦横の関係を調べる patch? というプロシージャを定義して、それで済ませることにする。
(define (stripe? ls)
(cond ((null? ls)
#t)
((null? (cdr ls))
#t)
(else
(let ((v (car ls))
(w (cadr ls)))
(and (not (= v w))
(stripe? (cdr ls)))))))
(define (patch? ls n m)
(and (let L ((lines (group ls n)))
(if (null? lines)
#t
(and (stripe? (car lines))
(L (cdr lines)))))
(let R ((rows (transpose ls n)))
(if (null? rows)
#t
(and (stripe? (car rows))
(R (cdr rows)))))))
ところでgroupとtransposeは、いつも使いたいときに一瞬探すんだけど見つからなくて、そのたびに下手な実装をしてる気がする。今回はこんなんで。
(define (group ls n)
(receive (front end)
(split-at ls n)
(if (null? end)
(list ls)
(cons front (group end n)))))
(define (transpose ls rows)
(apply zip (group ls rows)))

塗り分けパターンをすべて求めるにはどうしたらいいか。

たとえば、3つのマスを3色で塗り分けるやり方をすべて求めることを考えてみよう。以下のようなマスA,B,Cを緑黒赤の3色で塗り分けるパターンをすべて求めたい。

A
B
C


いま、都合よく R という関数があって、これを使うと2マスを3色で塗り分けパターンが全部求められるとしよう。R を使えば、以下の 3つの結果をよせ集めることで、3マスの塗り分けパターンを求めることができる。
  1. Aを緑に塗って、BとCは R に従って塗り分ける全パターン
  2. Aを黒に塗って、BとCは R に従って塗り分ける全パターン
  3. Aを赤に塗って、BとCは R に従って塗り分ける全パターン
ようするに、うしろの塗り分け方さえ全部求まってれば、先頭の色だけとっかえひっかえした結果をよせ集めることで、全体の塗り分けがすべて得られる。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


このアイデアをストリームを使ってダイレクトにコードにするとこんな感じ。Scheme だと簡単すぎ。
(use srfi-1)
(use util.stream)

(define (gen-colorings width colors)
(cond ((= 1 width)
(apply stream (zip (iota colors colors -1))))
((= 0 colors)
stream-null)
(else
(let R ((top colors))
(if (= 0 top)
stream-null
(stream-append
(stream-map (cut cons top <>)
(gen-colorings (- width 1) colors))
(R (- top 1))))))))
あとは、先に定義した patch? を使ってストリームをフィルタリングすればいい。どの色もだいたい平等に塗られているかどうかを調べるプロシージャ egalite? も定義しておいて、ここであわせてフィルタリングする。

(define (tiles n m c)
(stream-filter
(lambda (tone)
(and
(egalite? tone n c)
(patch? tone n m))
(gen-colorings (* n m) c)))

(define (egalite? ls n c)
(let R ((c c) (ls ls))
(cond ((= c 0) #t)
((< (length ls) n) #f)
(else
(receive (the-colors rest)
(partition (cut = c <>) ls)
(and (>= (length the-colors) n)
(R (- c 1) rest)))))))
これで (tiles 4 4 3) とかって実行すれば、塗り分けパターンを順次計算してくれるストリームが帰ってくる。はずなんだけど、実際には組み合わせが爆発しちゃう。16マスを3色に塗り分けようと思ったら 316 = 43,046,721 のパターンがありうるわけで、すべての塗り分けパターンからなるリストを作ったりすると巨大なリストになって身動きが取れなくなると思ってストリームを使ってみたんだけど、どのみち4x4=16マスの3色塗り分けを全部求めるのは無理だったもよう。
しかたがないので、問題を1行小さくして 3x4 の横長のテーブルクロスの3色塗り分けを求めてお茶を濁すことにした。
gosh> (define s (tiles 4 3 3))
s
gosh> (stream->ref s 1)
(3 2 1 3 2 1 3 2 1 2 1 3)
gosh> (stream-ref s 2)
(3 2 1 3 2 1 2 1 3 2 1 3)
gosh> (stream-ref s 3)
(3 2 1 2 1 3 2 1 3 2 1 3)
gosh>
最初に得られる結果を先の色定義( 1:青、2:オレンジ、3:緑)で塗り分けると、こうなる。

 
 
 
 
 
 
 
 
 
 
 
 


ついでに、このHTMLテーブルを描くのにでっちあげた補助関数。
(define (tile->html tile rownum)
(define (num->color n)
(cond
((= 1 n) "blue")
((= 2 n) "orange")
((= 3 n) "green")
(else "black")
))
(define (line->str line)
(string-append
"<tr>\n "
(apply string-append
(map (lambda (cell)
(string-append "<td style=\"background-color:"
(num->color cell)
"\"><div style=\"width:1em\">&nbsp;</div></td>"))
line))
"\n</tr>\n"))
(define (lines->str lines)
(string-append "<table>\n"
(apply string-append (map (cut line->str <>) lines))
"</table>\n"))
(lines->str (group tile rownum)))
しかし、tilesの引数は順番を逆にするべきだったな。行と列がいれかわってて紛らわしいことこのうえない。