2006/06/25
そういえば、Ligetiが死んだっていうニュースを最近聞いた。何枚かCDを持ってるけど、ふだん聞いているのはピアノ練習曲集ばかりだし、散漫にいくつかの作品を聞いているだけだったせいか一貫した「らしさ」みたいなものをさっぱり感じられず、あまり思い入れがなかった。思い入れがないと持っているCDのライナーノーツさえ読まないので、どんな背景を持った作曲家なのかもよく知らない。まあ、それでもピアノ練習曲集だけは面白かったので、たまに思い出したように聴いていた。多少の調声が感じられる曲があるのも聴きやすかった。
死んだっていうニュースをきっかけに、ピアノ練習曲集(Naxos 8.555777)のライナーノーツとWikipedia(日本語)だけ読んでみた。けっこう面白いオヤジだったらしい。
改めてこういう記事を読むと、いろいろ府に落ちるところがある。どうやら作風はころころ変わってて、ピアノ練習曲はごく最近のライフワークだったのね。Conlon Nancarrowからの影響についてはライナーノーツでも触れられてたので、確かなんだろう。っていうか、ぼく自身、NancarrowのCDはLigetiよりもはるかによく聴いてる。つまり、ぼくはそういうの(自分では勝手に「パラパラした感じ」って言ってますが)が好きってことで、だからLigetiでもピアノ練習曲だけはよく聴いてたんだろう。
たぶん、前期の楽曲のCDはこれからも滅多に聴かないね。音楽には、作者や演奏者と時代を共有している聴者にとっては極端に有意味なものがあると思うんだけど(Free Jazzとか、Zeppelinの初期とか。それがつまらないという意味ではなく、当時のムーブメントとか気分とかいうやつを共有していなければ正直よく理解できないほどの評価をされているんじゃないか、という意味)、Ligetiの初期の楽曲にもそういうのがありそう。晩年のピアノ練習曲も同じなのかもしれないけど、少なくともぼくには共有できる何かがあるっぽい。だから聴く。ようするに趣味の問題といってしまえばそれまでなわけで。
死んだっていうニュースをきっかけに、ピアノ練習曲集(Naxos 8.555777)のライナーノーツとWikipedia(日本語)だけ読んでみた。けっこう面白いオヤジだったらしい。
改めてこういう記事を読むと、いろいろ府に落ちるところがある。どうやら作風はころころ変わってて、ピアノ練習曲はごく最近のライフワークだったのね。Conlon Nancarrowからの影響についてはライナーノーツでも触れられてたので、確かなんだろう。っていうか、ぼく自身、NancarrowのCDはLigetiよりもはるかによく聴いてる。つまり、ぼくはそういうの(自分では勝手に「パラパラした感じ」って言ってますが)が好きってことで、だからLigetiでもピアノ練習曲だけはよく聴いてたんだろう。
たぶん、前期の楽曲のCDはこれからも滅多に聴かないね。音楽には、作者や演奏者と時代を共有している聴者にとっては極端に有意味なものがあると思うんだけど(Free Jazzとか、Zeppelinの初期とか。それがつまらないという意味ではなく、当時のムーブメントとか気分とかいうやつを共有していなければ正直よく理解できないほどの評価をされているんじゃないか、という意味)、Ligetiの初期の楽曲にもそういうのがありそう。晩年のピアノ練習曲も同じなのかもしれないけど、少なくともぼくには共有できる何かがあるっぽい。だから聴く。ようするに趣味の問題といってしまえばそれまでなわけで。
2006/06/23
どんな分野にも売り上げランキングというのがあって、なにかしらモノを作って販売している人間にとっては気になってしかたがない情報なわけですよ。とくに競合が多い場合は。書籍もその例に漏れないんだけど、レコード業界におけるオリコンのような存在がないから、信用できるランキングというものがない(ここで「信用できる」というのは、少なくともエンドユーザやマスコミや同僚や上司や同業他社といった他者に対するアピールに活用する場合を想定している)。その結果、みんな、主に次のようないろんな意味で偏りがあるランキング情報に頼っている。
もうどうしょうもないのは、1番目のランキングで高水準にある書目を見て、それが日本中でベストセラーになっているかのごとく勘違いしている場合。バカだろ。まあ、そんなケースは出版社の中にいない限りお目にかかれないけど、中にいるとしばしばお目にかかる。
2番目のランキングは、もう少し現実を反映している。でも、書店っていうのは、読者として意識している以上に得意分野と不得意分野の売り上げ差があるものだと思う。だから、特定の書店から報告されるランキングだけ目にして「○○の本は売れている」と言い放つ人は苦手だ。
いちばんイラってくるのは、3番目のランキングだけをもってあーだこーだ言われるケースである。つーか、あの「Amazonランキング」って何さ。1時間ごとに更新されるようだけど、Amazonランキングが1000だったら、少なくともその時間帯には日本で1000番目に売れている本なのか? とまあ、そういう感想が(出版社にいる以上)当然だと思うんだけど、これがぜんぜん当然じゃない。瞬間値にすぎないAmazonランキングをやたらに気にしたり、なんか適当なキーワードで検索して一番先頭に表示されることに意味を見出そうとする。そのような場当たり的な検証からは、その書籍が市場で評価されているかどうか判断できませんから! まあ、百歩譲って、それで個人的な満足を得るだけならいい。でも、たとえば恣意的なキーワードで検索して先頭付近に表示されることに満足するゲームを続けてたりすると、そのうち気分が麻痺してきて、まるでそれが真の市場の評価であるかのような錯覚に陥るものなので、ちゅういしてください。
で、何年か前にもAmazonランキングが取りざたされる状況のあいまいさにむかついて、それなら多少なりとも実際のところを検証できるようにしてやれと息巻き、Amazonランキングの推移を長期にわたってグラフにする実験をした。Amazonの書籍ページからAmazonランキングの数字を一時間に一回引っ張ってきて、それをMRTGに流し込んでるだけだけど、何年も続けてると下図のような結果が得られて興味ぶかい。基本的には会社で動かしているものなので、例としてちょっとだけ公開。グラフは、下に行くほどランキングが高くなっていることを表す。


上側のグラフは、ときどき急峻な山(ランキングの落ち込み)があるけど、全体としては地を這うような傾向にある(特に今年の2月以降)。これは、ランキングとしては高値安定なので、そこそこ定番として市場に受け入れられていると思ってよさそうである。ちなみにこの本は、なにをかくそう『プログラミングのための線形代数』です。
一方、下側のグラフ(書名は控えさせていただきます)は、たまに売れてランキングを戻す谷間があるけど、その間はほぼ右肩上がりの傾向にあって、だんだんランキングが下がっていることを示す。つまり、発売後しばらくは売れたかもしれないけど、定番にはならず市場から忘れられちゃって、今ではぽつぽつとだけ売れている本だと思っていい。
まあ、ようするに、バカは使えないけどデータは使いようっていう話でした。
- 社内で発行した本の販売冊数はだいたい分かるので、そのなかでのランキング
- 書店によってはランキング(もしくは実売情報)を公開しているところがあるので、そこから得られる(もしくは推測される)ランキング
- Amazon.co.jpのランキング。各書目のページに表示される数字や、特定のキーワードで検索したときに掲載される順番
もうどうしょうもないのは、1番目のランキングで高水準にある書目を見て、それが日本中でベストセラーになっているかのごとく勘違いしている場合。バカだろ。まあ、そんなケースは出版社の中にいない限りお目にかかれないけど、中にいるとしばしばお目にかかる。
2番目のランキングは、もう少し現実を反映している。でも、書店っていうのは、読者として意識している以上に得意分野と不得意分野の売り上げ差があるものだと思う。だから、特定の書店から報告されるランキングだけ目にして「○○の本は売れている」と言い放つ人は苦手だ。
いちばんイラってくるのは、3番目のランキングだけをもってあーだこーだ言われるケースである。つーか、あの「Amazonランキング」って何さ。1時間ごとに更新されるようだけど、Amazonランキングが1000だったら、少なくともその時間帯には日本で1000番目に売れている本なのか? とまあ、そういう感想が(出版社にいる以上)当然だと思うんだけど、これがぜんぜん当然じゃない。瞬間値にすぎないAmazonランキングをやたらに気にしたり、なんか適当なキーワードで検索して一番先頭に表示されることに意味を見出そうとする。そのような場当たり的な検証からは、その書籍が市場で評価されているかどうか判断できませんから! まあ、百歩譲って、それで個人的な満足を得るだけならいい。でも、たとえば恣意的なキーワードで検索して先頭付近に表示されることに満足するゲームを続けてたりすると、そのうち気分が麻痺してきて、まるでそれが真の市場の評価であるかのような錯覚に陥るものなので、ちゅういしてください。
で、何年か前にもAmazonランキングが取りざたされる状況のあいまいさにむかついて、それなら多少なりとも実際のところを検証できるようにしてやれと息巻き、Amazonランキングの推移を長期にわたってグラフにする実験をした。Amazonの書籍ページからAmazonランキングの数字を一時間に一回引っ張ってきて、それをMRTGに流し込んでるだけだけど、何年も続けてると下図のような結果が得られて興味ぶかい。基本的には会社で動かしているものなので、例としてちょっとだけ公開。グラフは、下に行くほどランキングが高くなっていることを表す。


上側のグラフは、ときどき急峻な山(ランキングの落ち込み)があるけど、全体としては地を這うような傾向にある(特に今年の2月以降)。これは、ランキングとしては高値安定なので、そこそこ定番として市場に受け入れられていると思ってよさそうである。ちなみにこの本は、なにをかくそう『プログラミングのための線形代数』です。
一方、下側のグラフ(書名は控えさせていただきます)は、たまに売れてランキングを戻す谷間があるけど、その間はほぼ右肩上がりの傾向にあって、だんだんランキングが下がっていることを示す。つまり、発売後しばらくは売れたかもしれないけど、定番にはならず市場から忘れられちゃって、今ではぽつぽつとだけ売れている本だと思っていい。
まあ、ようするに、バカは使えないけどデータは使いようっていう話でした。
2006/06/18
パズル「数独」をSchemeによる制約プログラミングで解く
SICPは3.4節の手前で足踏み中。3.3節まで練習問題はひととおり終えたけど、ここで実装しているconstraint programingの例に釈然としないものが残る。
わかんないときは自分で例を作ってみるのがいちばん。要するに
まず決めなければならないのは、「要素を何にするか」。ここではsudokuの各セルを要素として、そのセルが「取りうる数字」を考えることにする。最初は各セルとも、1〜9の数字をどれでも取りうる。
次に関係を定義する。ここでは、「各行」「各列」「各ブロック」を関係とする。「各行」「各列」「各ブロック」などをスロットと呼ぶことにすると、どのスロットも9個のセルを含み、それぞれのセルに1〜9の要素が一つずつ入らなければならない。
例えば4x4のsudokuの場合、セルは16個、スロットは12個になる。セル1〜16とスロットA〜Lの関係はこんな感じ。

この関係についてSICPちっくなconstraint network図を描くのはやっかいだけど、無理に一部分を描けばこんな感じになると思う。

上図の関係を見ながらプロシージャを書いていく。肝心の関係の定義は、とりあえず
この関係だけでは解を求めるには不十分で、実際、これから示すコードで解けるsudokuの問題はほとんどない。
実際に問題を解いてみる。まずは初期化。トップレベルでdefineを繰り返す方法がわからない……。しかたないので、各スロットのセル一覧をリストとして出力するプロシージャで我慢して、それをトップレベルに張り付けてごまかす。
ただし、上記に書いたように最初に与えている関係が不十分なので、解は求まりきってない。
わかんないときは自分で例を作ってみるのがいちばん。要するに
- 要素間の関係を定義し、
- ある要素の値を更新すると、
- 他の要素の値も定義した関係にしたがって更新される
まず決めなければならないのは、「要素を何にするか」。ここではsudokuの各セルを要素として、そのセルが「取りうる数字」を考えることにする。最初は各セルとも、1〜9の数字をどれでも取りうる。
次に関係を定義する。ここでは、「各行」「各列」「各ブロック」を関係とする。「各行」「各列」「各ブロック」などをスロットと呼ぶことにすると、どのスロットも9個のセルを含み、それぞれのセルに1〜9の要素が一つずつ入らなければならない。
例えば4x4のsudokuの場合、セルは16個、スロットは12個になる。セル1〜16とスロットA〜Lの関係はこんな感じ。

この関係についてSICPちっくなconstraint network図を描くのはやっかいだけど、無理に一部分を描けばこんな感じになると思う。

上図の関係を見ながらプロシージャを書いていく。肝心の関係の定義は、とりあえず
- 各スロットに、まったく同じ可能性を持つセルがあったら、ほかのセルからその可能性を取り除く
この関係だけでは解を求めるには不十分で、実際、これから示すコードで解けるsudokuの問題はほとんどない。
(define (make-cell init-possibilities)
(let ((possibility init-possibilities)
(slots '()))
(define (set-my-possibility new-possibility)
(set! possibility new-possibility)
(for-each-slot possibility slots))
(define (connect slot)
(set! slots
(cons slot slots)))
(define (me request)
(cond ((eq? request 'possibility) possibility)
((eq? request 'set-possibility) set-my-possibility)
((eq? request 'connect) connect)))
me))
(define (for-each-slot possibility slots)
(cond ((null? slots) 'done)
(else
((car slots) possibility)
(for-each-slot possibility (cdr slots)))))
(define (set-possibility! cell possibility)
((cell 'set-possibility) possibility))
(define (connect cell slot)
((cell 'connect) slot))
(define (get-possibility cell)
(cell 'possibility))
(define (slot . cells)
(define (one-of-cell-has-new-possibility possibility)
(cond ((= (num-of-same-possibility possibility cells)
(length possibility))
(update-cells! cells possibility))))
(define (me possibility)
(one-of-cell-has-new-possibility possibility))
(define (connect-all-cells-to-me cells me)
(cond ((null? cells)
'done)
((connect (car cells) me)
(connect-all-cells-to-me (cdr cells) me))))
(connect-all-cells-to-me cells me)
me)
(define (update-cells! cells possibility)
(cond ((null? cells)
'slot-updated)
((or (equal? (get-possibility (car cells)) possibility)
(<= (length (get-possibility (car cells)))
(length possibility)))
(update-cells! (cdr cells) possibility))
(else
(set-possibility! (car cells)
(complement (get-possibility (car cells))
possibility))
(update-cells! (cdr cells) possibility))))
(define (complement l1 l2)
(define (include? a l)
(cond ((null? l) #f)
((equal? a (car l)) #t)
(else (include? a (cdr l)))))
(cond ((null? l1) '())
((include? (car l1) l2)
(complement (cdr l1) l2))
(else
(cons (car l1) (complement (cdr l1) l2)))))
(define (num-of-same-possibility possibility cells)
(cond ((null? cells)
0)
((equal? possibility (get-possibility (car cells)))
(+ 1 (num-of-same-possibility possibility (cdr cells))))
(else
(num-of-same-possibility possibility (cdr cells)))))
実際に問題を解いてみる。まずは初期化。トップレベルでdefineを繰り返す方法がわからない……。しかたないので、各スロットのセル一覧をリストとして出力するプロシージャで我慢して、それをトップレベルに張り付けてごまかす。
(define-syntax make9x9cellsためしに解いてみる問題としては、「数学セミナー」の2006年5月号の西川さんの記事41ページに掲載されているものを使うことにした。ちょっとみにくいけど、こんな問題。
(syntax-rules ()
((_ e)
(define e (make-cell '(1 2 3 4 5 6 7 8 9))))
((_ e1 e2 ...)
(begin
(define e1 (make-cell '(1 2 3 4 5 6 7 8 9)))
(define e2 (make-cell '(1 2 3 4 5 6 7 8 9)))
...))))
(make9x9cells
c11 c12 c13 c14 c15 c16 c17 c18 c19
c21 c22 c23 c24 c25 c26 c27 c28 c29
c31 c32 c33 c34 c35 c36 c37 c38 c39
c41 c42 c43 c44 c45 c46 c47 c48 c49
c51 c52 c53 c54 c55 c56 c57 c58 c59
c61 c62 c63 c64 c65 c66 c67 c68 c69
c71 c72 c73 c74 c75 c76 c77 c78 c79
c81 c82 c83 c84 c85 c86 c87 c88 c89
c91 c92 c93 c94 c95 c96 c97 c98 c99
)
(define s1 (slot c11 c12 c13 c14 c15 c16 c17 c18 c19))
(define s2 (slot c21 c22 c23 c24 c25 c26 c27 c28 c29))
(define s3 (slot c31 c32 c33 c34 c35 c36 c37 c38 c39))
(define s4 (slot c41 c42 c43 c44 c45 c46 c47 c48 c49))
(define s5 (slot c51 c52 c53 c54 c55 c56 c57 c58 c59))
(define s6 (slot c61 c62 c63 c64 c65 c66 c67 c68 c69))
(define s7 (slot c71 c72 c73 c74 c75 c76 c77 c78 c79))
(define s8 (slot c81 c82 c83 c84 c85 c86 c87 c88 c89))
(define s9 (slot c91 c92 c93 c94 c95 c96 c97 c98 c99))
(define s10 (slot c11 c21 c31 c41 c51 c61 c71 c81 c91))
(define s11 (slot c12 c22 c32 c42 c52 c62 c72 c82 c92))
(define s12 (slot c13 c23 c33 c43 c53 c63 c73 c83 c93))
(define s13 (slot c14 c24 c34 c44 c54 c64 c74 c84 c94))
(define s14 (slot c15 c25 c35 c45 c55 c65 c75 c85 c95))
(define s15 (slot c16 c26 c36 c46 c56 c66 c76 c86 c96))
(define s16 (slot c17 c27 c37 c47 c57 c67 c77 c87 c97))
(define s17 (slot c18 c28 c38 c48 c58 c68 c78 c88 c98))
(define s18 (slot c19 c29 c39 c49 c59 c69 c79 c89 c99))
(define s19 (slot c11 c12 c13 c21 c22 c23 c31 c32 c33))
(define s20 (slot c14 c15 c16 c24 c25 c26 c34 c35 c36))
(define s21 (slot c17 c18 c19 c27 c28 c29 c37 c38 c39))
(define s22 (slot c41 c42 c43 c51 c52 c53 c61 c62 c63))
(define s23 (slot c44 c45 c46 c54 c55 c56 c64 c65 c66))
(define s24 (slot c47 c48 c49 c57 c58 c59 c67 c68 c69))
(define s25 (slot c71 c72 c73 c81 c82 c83 c91 c92 c93))
(define s26 (slot c74 c75 c76 c84 c85 c86 c94 c95 c96))
(define s27 (slot c77 c78 c79 c87 c88 c89 c97 c98 c99))
(5)(3)( )( )(7)( )( )( )( )これらの初期値を次のように各セルに設定する。
(6)( )( )(1)(9)(5)( )( )( )
( )(9)(8)( )( )( )( )(6)( )
(8)( )( )( )(6)( )( )( )(3)
(4)( )( )(8)( )(3)( )( )( )
(7)( )( )( )(2)( )( )( )(6)
( )(6)( )( )( )( )(2)(8)( )
( )( )( )(4)(1)(9)( )( )(5)
( )( )( )( )(8)( )(1)(7)(9)
(set-possibility! c11 '(5))この時点ですべてのセルの値が更新されちゃっているのがconstraint progamingのおもしろいところ。あとは出力だけしてやればいい。
(set-possibility! c12 '(3))
(set-possibility! c15 '(7))
(set-possibility! c21 '(6))
(set-possibility! c24 '(1))
(set-possibility! c25 '(9))
(set-possibility! c26 '(5))
(set-possibility! c32 '(9))
(set-possibility! c33 '(8))
(set-possibility! c38 '(6))
(set-possibility! c41 '(8))
(set-possibility! c45 '(6))
(set-possibility! c49 '(3))
(set-possibility! c51 '(4))
(set-possibility! c54 '(8))
(set-possibility! c56 '(3))
(set-possibility! c61 '(7))
(set-possibility! c65 '(2))
(set-possibility! c69 '(6))
(set-possibility! c72 '(6))
(set-possibility! c77 '(2))
(set-possibility! c78 '(8))
(set-possibility! c84 '(4))
(set-possibility! c85 '(1))
(set-possibility! c86 '(9))
(set-possibility! c89 '(5))
(set-possibility! c95 '(8))
(set-possibility! c97 '(1))
(set-possibility! c98 '(7))
(set-possibility! c99 '(9))
ただし、上記に書いたように最初に与えている関係が不十分なので、解は求まりきってない。
(define (print-possibilities size . cells)実行結果
(let R ((ls cells) (cnt 1))
(cond ((null? ls)
'done)
((= 1 (remainder cnt size))
(newline)
(display (get-possibility (car ls)))
(R (cdr ls) (+ cnt 1)))
(else
(display (get-possibility (car ls)))
(R (cdr ls) (+ cnt 1))))))
(print-possibilities 9
c11 c12 c13 c14 c15 c16 c17 c18 c19
c21 c22 c23 c24 c25 c26 c27 c28 c29
c31 c32 c33 c34 c35 c36 c37 c38 c39
c41 c42 c43 c44 c45 c46 c47 c48 c49
c51 c52 c53 c54 c55 c56 c57 c58 c59
c61 c62 c63 c64 c65 c66 c67 c68 c69
c71 c72 c73 c74 c75 c76 c77 c78 c79
c81 c82 c83 c84 c85 c86 c87 c88 c89
c91 c92 c93 c94 c95 c96 c97 c98 c99
)
gosh> print-possibilitiesこの結果を漫然と見る限り、スロット内での重複関係を検証するだけでは解に至らないみたいだ。ここから先は、とあるセルの可能性をどちらか選択してみて、整合性がある解を探索していくしかないのだろうか?
gosh>
(5)(3)(2 4)(6)(7)(8)(9)(1 2 4)(1 2)
(6)(7)(2 4)(1)(9)(5)(3)(2 4)(8)
(1)(9)(8)(3)(4)(2)(5)(6)(7)
(8)(2 5)(9)(7)(6)(1)(4)(2 5)(3)
(4)(1 2)(6)(8)(5)(3)(7)(9)(1 2)
(7)(1 5)(3)(9)(2)(4)(8)(1 5)(6)
(9)(6)(1)(5)(3)(7)(2)(8)(4)
(2)(8)(7)(4)(1)(9)(6)(3)(5)
(3)(4)(5)(2)(8)(6)(1)(7)(9)done
2006/06/13
2006/06/10
誰も読む必要がない、ザ・日記が続きます。
朝から実家のある柏へ。レイソル戦のチケットを一緒に観戦する友人Kから受け取る。彼はゴール裏の自由席で観戦するので、試合開始の5時間も前からひたすら並んでいる。僕のほうはバックグラウンド側の指定席なので、一緒に観戦するといっても、試合中はお互いに別々の場所に陣取ることになる。ゴール裏は歌をうたったりして応援しなければならないので、つらいんですよ。
そんなわけで僕には試合開始までたっぷり時間がある。まず、彼の自転車を借りて実家へ。たまに顔を出すというのが一番難しい。主にピアノや猫と遊ぶ。それから別の旧友と待ち合わせて昼食。頼まれていた古い絵本を渡す。彼女と話をしていると、いつも、人間の面白さと社会に対する生産性とは相関しないものだと不思議に思う。いや、単純に「類は友を呼ぶ」なのかもしれない。たぶん、彼女も僕も、周囲から見ると同様に計りがたい類なんだろう。
試合開始時間がせまってきたので、あわただしく別れる。彼女は現役の柏市民だけど、ほとんどの柏市民の例に洩れず、レイソルには興味がない。なんか観戦に行く人達って遠足みたいに大きいバッグ持ってぞろぞろ歩いてるよね、とか、そういう感想がせいいっぱいらしい。
試合は楽しかった。個人的には久しぶりに勝ち試合を見ることができたし。
試合後、友人Kと合流してしばらくぶらぶらしてから、新宿の別な友人たちとの飲み会に参加して実のない一日を締めくくる。実のない会話を渾々と続けられる友人がたくさんいることが休日プレイの成否を決めると思う。そして休日プレイは生きていくのに絶対に必要な時間なわけで。
朝から実家のある柏へ。レイソル戦のチケットを一緒に観戦する友人Kから受け取る。彼はゴール裏の自由席で観戦するので、試合開始の5時間も前からひたすら並んでいる。僕のほうはバックグラウンド側の指定席なので、一緒に観戦するといっても、試合中はお互いに別々の場所に陣取ることになる。ゴール裏は歌をうたったりして応援しなければならないので、つらいんですよ。
そんなわけで僕には試合開始までたっぷり時間がある。まず、彼の自転車を借りて実家へ。たまに顔を出すというのが一番難しい。主にピアノや猫と遊ぶ。それから別の旧友と待ち合わせて昼食。頼まれていた古い絵本を渡す。彼女と話をしていると、いつも、人間の面白さと社会に対する生産性とは相関しないものだと不思議に思う。いや、単純に「類は友を呼ぶ」なのかもしれない。たぶん、彼女も僕も、周囲から見ると同様に計りがたい類なんだろう。
試合開始時間がせまってきたので、あわただしく別れる。彼女は現役の柏市民だけど、ほとんどの柏市民の例に洩れず、レイソルには興味がない。なんか観戦に行く人達って遠足みたいに大きいバッグ持ってぞろぞろ歩いてるよね、とか、そういう感想がせいいっぱいらしい。
試合は楽しかった。個人的には久しぶりに勝ち試合を見ることができたし。
試合後、友人Kと合流してしばらくぶらぶらしてから、新宿の別な友人たちとの飲み会に参加して実のない一日を締めくくる。実のない会話を渾々と続けられる友人がたくさんいることが休日プレイの成否を決めると思う。そして休日プレイは生きていくのに絶対に必要な時間なわけで。
2006/06/06
歯医者。ここ数週間というもの、抜歯した奥歯の跡をどうするかという問題に頭を悩ませていた。方法は4つ。
ブリッジでなければ、普通は入れ歯になるらしい。うーん。この年齢で入れ歯は遠慮したい。で、いっそのこと放置するのはダメなのかと聞いたら、上下左右の歯に支えがない状態になるため、虫歯はともかく歯茎の病気になりやすいくなるらしい。
というわけで、残された治療方法はインプラントしかないっぽい。インプラントは歯茎の骨に支柱を埋め込み、それに人工の歯を設置する方法で、文字通り新しい歯を一本埋め込む。したがって左右の歯を削ることもないし、かぶせものではないので普通に磨ける。ほかの歯への影響が少なく、メンテナンスが容易ってことで、やっぱり歯の治療もモジュール化が重要だな。難点は保険がきかないこと。1本につき30万円くらいみなければならないらしい。あと、僕の場合は土台になる歯茎の骨が再生するのを待たなければならないとのことで、最悪再生しない場合はブリッジをかけるしかないという問題もある。
ものすごく長いスパンで影響を及ぼすことなので、金額の問題は飲む覚悟を決めた。まあ、ぶっちゃけラップトップ一台分だと思えばいいんでしょ。あとは骨が再生するのを願うばかり。
- 放置
- 左右の歯を柱にしてブリッジをかける
- 部分入れ歯
- インプラント
ブリッジでなければ、普通は入れ歯になるらしい。うーん。この年齢で入れ歯は遠慮したい。で、いっそのこと放置するのはダメなのかと聞いたら、上下左右の歯に支えがない状態になるため、虫歯はともかく歯茎の病気になりやすいくなるらしい。
というわけで、残された治療方法はインプラントしかないっぽい。インプラントは歯茎の骨に支柱を埋め込み、それに人工の歯を設置する方法で、文字通り新しい歯を一本埋め込む。したがって左右の歯を削ることもないし、かぶせものではないので普通に磨ける。ほかの歯への影響が少なく、メンテナンスが容易ってことで、やっぱり歯の治療もモジュール化が重要だな。難点は保険がきかないこと。1本につき30万円くらいみなければならないらしい。あと、僕の場合は土台になる歯茎の骨が再生するのを待たなければならないとのことで、最悪再生しない場合はブリッジをかけるしかないという問題もある。
ものすごく長いスパンで影響を及ぼすことなので、金額の問題は飲む覚悟を決めた。まあ、ぶっちゃけラップトップ一台分だと思えばいいんでしょ。あとは骨が再生するのを願うばかり。
2006/05/28
Dexter社のWalkMocsという靴が気に入っていた。靴底がウレタンのような特殊な材質で、革も軟らかく、なにより出来がよくてはきやすい。結婚したころに恵比寿の三越で見つけて購入したもので、そのころには国内に正規代理店があったんだろう。
最近になって靴底がだめになってしまったので修理したいと思い、取扱店を探したんだけど、ない。どうやら日本での取扱がなくなってしまったようだ。ボーリングシューズだけはどっかのボーリング用品店が輸入しているっぽいけど。こんなことなら先週USにいったときに注意して探せばよかった。革はまだまだしっかりしているので、普通の靴底でもいいから何とか修理できないかなあ。
このメーカーの靴が自分の足に合うことは分かっているので、USのWebサイトから購入しちゃうか。WalkMocsというシリーズはなくなってしまったようだけど、このDiscoveryが近い製品みたい。
最近になって靴底がだめになってしまったので修理したいと思い、取扱店を探したんだけど、ない。どうやら日本での取扱がなくなってしまったようだ。ボーリングシューズだけはどっかのボーリング用品店が輸入しているっぽいけど。こんなことなら先週USにいったときに注意して探せばよかった。革はまだまだしっかりしているので、普通の靴底でもいいから何とか修理できないかなあ。
このメーカーの靴が自分の足に合うことは分かっているので、USのWebサイトから購入しちゃうか。WalkMocsというシリーズはなくなってしまったようだけど、このDiscoveryが近い製品みたい。
2006/05/27
パズル「九個の?」をSchemeで解く
かれこれ1年以上も解けていないパズルがあった。ペントミノの一種で、9個のクエスチョンマークを9×8のグリッドに詰めるというもの。以下のページでJavaアプレットが遊べる(はず)。
実は年末にも一回挑戦している。このときは結局、3日3晩かけても計算が終わらなかった。 解法として思い付くのは、グリッドにクエスチョンマークを置く組み合わせを総当たりで調べる方法のみなんだけど、各クエスチョンマークは上下左右裏表に自在におくことができるため、グリッドのどこかにクエスチョンマークを1つ配置するだけで実は208パターンにもなってしまう。そうすると、調べなければいけない組み合わせはペタオーダーになり、今のコンピュータでは一時的に保持することすら物理的に無理な大きさになる。Gaucheに用意されているcombination-for-eachを使えばいい具合に対処してくれるかもしれないと楽観したけど、やっぱりだめだった。ここまでは咋年末の話。
実際にはクエスチョンマークが安易に重なってしまうようなパターンがほとんどなので、それを無視しつつ組み合わせを求めるようにすれば計算量の爆発が抑えられそうなものだ。そのためのプロシージャは先日作った。というわけで、あらためてこの問題に対峙すべく年末のプログラムを書き直してみた。
; 9q-problem.scm ; 2006/5/26 ; ; solver for the "9 questions" quize ; k16.shikano (use srfi-1) (define row 9) (define line 8) ;; question-mark (define (face s n) (call/cc (lambda (break) (cond ((= n 1) (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line)) (break '()) (list s (+ s 1) ; ** (+ s row 1) ; * (+ s (* 2 row)) (+ s (* 2 row) 1) ; ** (+ s (* 3 row)) ; * ; (+ s (* 5 row))))) ; * ((= n 2) (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line)) (break '()) (list s (+ s 1) ; ** (+ s row) ; * (+ s (* 2 row)) (+ s (* 2 row) 1) ; ** (+ s (* 3 row) 1) ; * ; (+ s (* 5 row) 1) ; * ))) ((= n 3) (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line)) (break '()) (list (+ s 1) ; * ; (+ s (* 2 row) 1) ; * (+ s (* 3 row)) (+ s (* 3 row) 1) ; ** (+ s (* 4 row)) ; * (+ s (* 5 row)) (+ s (* 5 row) 1) ; ** ))) ((= n 4) (if (or (> (+ (remainder s row) 2) row) (> (+ (quotient s row) 6) line)) (break '()) (list s ; * ; (+ s (* 2 row)) ; * (+ s (* 3 row)) (+ s (* 3 row) 1) ; ** (+ s (* 4 row) 1) ; * (+ s (* 5 row)) (+ s (* 5 row) 1) ; ** ))) ((= n 5) (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line)) (break '()) (list s (+ s 2) (+ s 3) (+ s 5) ; * ** * (+ s row) (+ s row 1) (+ s row 2) ; *** ))) ((= n 6) (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line)) (break '()) (list s (+ s 2) (+ s 3) (+ s 5) ; * ** * (+ s row 3) (+ s row 4) (+ s row 5) ; *** ))) ((= n 7) (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line)) (break '()) (list s (+ s 1) (+ s 2) ; *** (+ s row) (+ s row 2) (+ s row 3) (+ s row 5) ; * ** * ))) ((= n 8) (if (or (> (+ (remainder s row) 6) row) (> (+ (quotient s row) 2) line)) (break '()) (list (+ s 3) (+ s 4) (+ s 5) ; *** (+ s row) (+ s row 2) (+ s row 3) (+ s row 5) ; * ** * ))))))) ;; available question-mark faces (define valid-face-list (filter (lambda (x) (not (null? x))) (let fs ((s 0)) (if (> s (- (* row line) 1)) '() (let fn ((n 1)) (if (> n 8) (fs (+ s 1)) (cons (face s n) (fn (+ n 1))))))))) ;; check if two lists are distinct with each other (define (distinct? l1 l2) (= (length (lset-union eq? l1 l2)) (+ (length l1) (length l2)))) (define (distinct-cdr ls) (let R ((tail (cdr ls))) (cond ((null? tail) '()) ((not (distinct? (car ls) (car tail))) (R (cdr tail))) (else (cons (car tail) (R (cdr tail))))))) (define (trim-combinations ls n proc) (cond ((> n (length ls)) '()) ((= n 1) (map list ls)) ((> (- n 1) (length (proc ls))) (trim-combinations (cdr ls) n proc)) (else (append (map (lambda (x) (cons (car ls) x)) (trim-combinations (proc ls) (- n 1) proc)) (trim-combinations (cdr ls) n proc))))) (trim-combinations valid-face-list 9 distinct-cdr)
これを9q-problem.scmとして、シェルからtimeした結果。
[05:37:46] k16@debian:~/gauche $ time gosh 9q-problem.scm > 9q-result.txt real 272m22.609s user 266m0.799s sys 0m1.174s
約4時間半。その後、求める組み合わせについてmemoizeとかしてみたりもしたんだけど、実行時間は変わらない。おそらく何か間違ってるんだろう。
気になる結果は、互いに対称かもしれない解が全部で16通り得られた(→解答)。
2006/05/24
2006/05/15
Firefox 1.5でいつのまにか日本語の均等割り付けができるようになってるのにきがついた。
むかしは苦労したものだ。http://k16journal.blogspot.com/2005/05/htmlcss2.html
むかしは苦労したものだ。http://k16journal.blogspot.com/2005/05/htmlcss2.html
2006/05/14
LaTeX(TeX)で文字列中の文字を置換したい。
cf. Character substitution in TeX
http://hisashim.livejournal.com/276024.html
上記のページにほとんど目的の答えがあるので、これをもう少し汎用にしてみた。
いくつか制限がある。判明しているのは以下のとおり。
同じ理由で、\replacecharを入れ子にして使えない。つまり、文字列中の複数の文字を置換したかったら、同様な方法で新しいマクロを定義する必要がある。たとえば、索引の特殊文字をエスケープするコマンドは、次のようにして作れる。
追記:この例では「"」そのもののエスケープができないので注意。
いちおう、実行例。
replacechar.tex
replacechar.pdf
cf. Character substitution in TeX
http://hisashim.livejournal.com/276024.html
上記のページにほとんど目的の答えがあるので、これをもう少し汎用にしてみた。
\newcommand{\replacechar}[3]{{\wordbyword{#2}{#3}#1\end }}\replacecharの第1引数に文字列、第2引数に置換前の文字、第3引数に置換後の文字を指定する。
\newcommand{\cutoff}[2]{{\relax}}
\def\wordbyword#1#2#3{\ifx#3\end \let\next=\cutoff
\else\ifx#3#1#2%
\else#3%
\fi \let\next=\wordbyword\fi \next{#1}{#2}}
いくつか制限がある。判明しているのは以下のとおり。
- 文字列中のスペースは切り詰められる
- ほかのコマンドの中で使うには\string\replacechar{foo}{o}{a}などとする必要がある
同じ理由で、\replacecharを入れ子にして使えない。つまり、文字列中の複数の文字を置換したかったら、同様な方法で新しいマクロを定義する必要がある。たとえば、索引の特殊文字をエスケープするコマンドは、次のようにして作れる。
追記:この例では「"」そのもののエスケープができないので注意。
\newcommand{\indexescape}[1]{{\indexwordbyword#1\end }}\myindexの定義に\stringを使っているのも2番目の制限のため。
\def\indexwordbyword#1{\ifx#1\end \let\next=\relax
\else\ifx#1{!}"!%
\else\ifx#1{@}"@%
\else\ifx#1{|}"|%
\else#1%
\fi\fi\fi \let\next=\indexwordbyword\fi \next}
\def\myindex#1{\index{\string\indexescape{#1}}}
いちおう、実行例。
replacechar.tex
replacechar.pdf
2006/05/12
sumiiの日記 - callccによる排中律の証明
http://d.hatena.ne.jp/sumii/20060507/1147006438
さらに劣化コピー。Schemeのcall/ccで悪魔の契約書を作ってみる。型についてはまったく無知なので、排中律の証明とは関係ありません。
(昨日は会社でhisashimさんに適当な説明をした気がする。ごめんなさい。)
追記:最初のは間違ってたので差し替えました。
で、悪魔は自分が「発話した内容」なんて覚えちゃいない。上のコードでいうと、call/ccの中のcondは覚えちゃいない。あくまでも契約書オンリーなので、後から1億ドル払っても、(B)じゃなかったんだから(A)を履行するってことなんだと思う。
くだんのシナリオは、こんな感じ。
最初の(devils-contract 'B)で悪魔自身は「(B)を選びます」と言っているけど、人間が1億ドル払わなかった時点で実は(A)の契約が成立している。あとから1億円用意(set! able-to-pay? #t)してもだめ(悪魔の契約履行が10年後だったのがひっかけっぽいかも)。
1億円支払い可能な状態で契約し、悪魔が(B)を選択すれば、望みがかなえられるんでしょう。そんな人に悪魔が契約を持ち込むとは思えませんが……
http://d.hatena.ne.jp/sumii/20060507/1147006438
さらに劣化コピー。Schemeのcall/ccで悪魔の契約書を作ってみる。型についてはまったく無知なので、排中律の証明とは関係ありません。
(昨日は会社でhisashimさんに適当な説明をした気がする。ごめんなさい。)
追記:最初のは間違ってたので差し替えました。
(define able-to-pay? #f)この挿話は、悪魔にとって選択肢が(A)と(B)しかありえないことがポイントなんだと思う。つまり、(B)じゃなければ(A)。(B)でもないし(A)でもないってことはありえないドライな世界。上のコードだと、最初のcondが契約書に相当するつもり。
(define remind-contract #f)
(define (devils-contract init-choice)
(let ((ability able-to-pay?))
((lambda (choice)
(cond ((equal? 'first-answer choice)
(display ""))
((and (equal? 'B choice) ability)
(display "Say any wish!"))
(else
(display "Devil gives you one billion dollars"))))
(call/cc
(lambda (continuation)
(cond ((equal? 'A init-choice)
(display "Devil chooses A"))
((equal? 'B init-choice)
(display "Devil chooses B")))
(set! remind-contract continuation)
(continuation 'first-answer))))))
で、悪魔は自分が「発話した内容」なんて覚えちゃいない。上のコードでいうと、call/ccの中のcondは覚えちゃいない。あくまでも契約書オンリーなので、後から1億ドル払っても、(B)じゃなかったんだから(A)を履行するってことなんだと思う。
くだんのシナリオは、こんな感じ。
gosh> (devils-contract 'B)
Devil chooses B
gosh> (set! able-to-pay? #t)
#t
gosh> (remind-contract 'B)
Devil gives you one billion dollars
最初の(devils-contract 'B)で悪魔自身は「(B)を選びます」と言っているけど、人間が1億ドル払わなかった時点で実は(A)の契約が成立している。あとから1億円用意(set! able-to-pay? #t)してもだめ(悪魔の契約履行が10年後だったのがひっかけっぽいかも)。
1億円支払い可能な状態で契約し、悪魔が(B)を選択すれば、望みがかなえられるんでしょう。そんな人に悪魔が契約を持ち込むとは思えませんが……
gosh> (set! able-to-pay? #t)
#t
gosh> (devils-contract 'B)
Devil chooses B
gosh> (remind-contract 'B)
Say any wish!
2006/05/10
2006/05/08
枝刈りをしながら組み合わせを求めたい。つまり、似たような要素を同一視して、集合の総数を絞りながら組み合わせを求めていきたい。
ふつうなら集合をユニークな要素だけで構成しなおしてから組み合わせを求めればいいんだけど、「ユニーク」の意味によってはそれができない場合もある。
たとえばベクトルからなる集合で、「いずれかの項が同じであれば同一視」のような場合。次のような2次元ベクトルからなる集合Aがあるとして、
まず、ふつうの組み合わせを求める combinations プロシージャを次のような戦略で考える。
集合をリスト ls とみなし、その要素から n 個の組み合わせをすべて求めるプロシージャ (noraml-combinations ls n) を、次の 1.と 2.の append として作る。
ふつうなら集合をユニークな要素だけで構成しなおしてから組み合わせを求めればいいんだけど、「ユニーク」の意味によってはそれができない場合もある。
たとえばベクトルからなる集合で、「いずれかの項が同じであれば同一視」のような場合。次のような2次元ベクトルからなる集合Aがあるとして、
A = {(1, 2) (2, 3) (3, 4) (4, 5) (5, 6) (6, 7)}集合Aからすべての項が異なる3つのベクトルを取り出す組み合わせは、
{(1, 2) (3, 4) (5, 6)},の4つになるだろう。この4つを求めるのに、すべての3要素の組み合わせを求めてから相異なる要素で構成されているものを取り出していると、集合が大きくなるにつれ厄介なことになるのが目に見えている。かといって、集合Aをあらかじめユニークな要素だけで再構成することもできない。
{(1, 2) (3, 4) (6, 7)},
{(1, 2) (4, 5) (6, 7)},
{(2, 3) (4, 5) (6, 7)}
まず、ふつうの組み合わせを求める combinations プロシージャを次のような戦略で考える。
集合をリスト ls とみなし、その要素から n 個の組み合わせをすべて求めるプロシージャ (noraml-combinations ls n) を、次の 1.と 2.の append として作る。
- (car ls) と (noraml-combinations (cdr ls) (- n 1)) の cons
- (normal-combinations (cdr ls) n)
(define (normal-combinations ls n)境界条件がなんか複雑で、本当にこれであってるのかよくわからないのはここだけの話。まあ、とりあえず意図通りの結果にはなるみたい。
(cond ((> n (length ls))
'())
((= n 1)
(map list ls))
((> n (+ 1 (length (cdr ls))))
(list ls))
(else
(append
(map (lambda (x) (cons (car ls) x))
(normal-combinations (cdr ls) (- n 1)))
(normal-combinations (cdr ls) n)))))
gosh> test-set枝刈りしながら組み合わせを求めるという本題を達成するには、枝刈りプロシージャ proc を渡して、1.の操作の cdr を proc に変えればいいだろう(2.の部分の cdr はリストの再帰的な操作のためのものなので、proc に変える必要はない)。
((1 2) (2 3) (3 4) (4 5) (5 6) (6 7))
gosh> (normal-combinations test-set 3)
((#0=(1 2) #1=(2 3) #2=(3 4)) (#0# #1# #3=(4 5)) (#0# #1# #4=(5 6))
(#0# #1# #5=(6 7)) (#0# #2# #3#) (#0# #2# #4#) (#0# #2# #5#)
(#0# #3# #4#) (#0# #3# #5#) (#0# #4# #5#) (#1# #2# #3#)
(#1# #2# #4#) (#1# #2# #5#) (#1# #3# #4#) (#1# #3# #5#)
(#1# #4# #5#) (#2# #3# #4#) (#2# #3# #5#) (#2# #4# #5#)
(#3# #4# #5#))
(define (trim-combinations ls n proc)枝刈りプロシージャとしては、ベクトルが相異なることを表現する distinct? と、リストとして表した集合から car と相異なる cdr を導く distinct-cdr 用意する。gauche の lset-union は、(use srfi-1) が必要だね。
(cond ((> n (length ls))
'())
((= n 1)
(map list ls))
((> (- n 1) (length (proc ls)))
(trim-combinations (cdr ls) n proc))
(else
(append
(map (lambda (x) (cons (car ls) x))
(trim-combinations (proc ls) (- n 1) proc))
(trim-combinations (cdr ls) n proc)))))
(define (distinct? v1 v2)実行結果。
(= (length (lset-union eq? v1 v2))
(+ (length v1) (length v2))))
(define (distinct-cdr ls)
(let R ((tail (cdr ls)))
(cond ((null? tail) '())
((not (distinct? (car ls) (car tail)))
(R (cdr tail)))
(else
(cons (car tail) (R (cdr tail)))))))
gosh> (trim-combinations test-set 3 distinct-cdr)しかしアレだな。lengthとか使いすぎなので、あまり効率よくないんじゃないか、これ。
((#0=(1 2) #1=(3 4) (5 6)) (#0# #1# #2=(6 7)) (#0# #3=(4 5) #2#) ((2 3) #3# #2#))
2006/05/06
『RailsによるアジャイルWebアプリケーション開発』の制作方式
ここのところ『RailsによるアジャイルWebアプリケーション開発』の制作方式について意見を求められる機会が何回かあったので、もう半年も前のことだけどまとめてみる。
前提として、ふつうのコンピュータ書籍の制作過程には昨日定義した「後戻り困難ポイント」があり、それが原因で誰かが泣いている。誰も泣かないようにするには、昨日愚痴った「後戻り困難ポイント」を埋めてしまえばいい。それには、現在の手作業によるページレイアウトの工程(つまり組版工程)を放棄して、内容に責任を持つべき人間が印刷の直前までハンドリングできるようにすればいい。
『RailsによるアジャイルWebアプリケーション開発』の制作では、この方針を実現すべく、以下のような方法を採用した。
たぶん、こんなのはソフトウェア開発では当たり前のことなんだと思う。ソースコード(原稿)を版管理し、それをデイリービルド(組版)しながら開発(制作)するってだけの話なんだから。ところが現在のコンピュータ書の制作では、コンパイルやビルドに相当する「原稿を組版にする作業」が完全に手作業なため、組版した後で判明した問題を原稿に戻って直したり、直して組版し直した結果をすぐに確認することができない†1。
実現にあたっては障壁もいくつかあった。まず、原稿がLaTeXじゃない。また、仮にLaTeXであっても、それだけでは印刷所に安心してデータを渡すことはできない(印刷所の環境で同じようにコンパイルできる保障がない。そのため、印刷所の環境でコンパイルした出力を確認するという作業が不可欠になってしまう)。
印刷所に渡すデータの問題については、PDF/X-1aという業界標準があるので、それに準拠したPDFを生成できれば問題ない。これについては「Debianにotfパッケージをインストールして、dvipdfmxでOpenTypeフォントを埋め込んだPDFを吐き出すまで」を参照。
原稿がLaTeXじゃない問題については、まず原稿がどんな形式だったか示す必要があるだろう。こんな感じのテキストデータだった。
このような「自然言語+簡易マークアップ」の構造を読み取って適切なLaTeXのスタイルに対応付けるためのフィルタを用意する必要がある。今回はゆるい規則を処理しなければいけないこともあって、後から柔軟にフィルタの仕様を変更できるようなGaucheスクリプトを正月にでっちあげた(私的に余暇を利用して作ったものなので、後日公開する予定。いま見返すと謎な処理が多いけど、なんとか目的の動作は実現している)。
上記の例の変換結果は以下のとおり。
さて、ここまでは、この方法が従来の方法に比べて万能であることを意図的に強調してきたけれど、現実には適用できないプロジェクトが大半である。著者や制作業者の並々ならない協力が必要なこと、全員がアクセスできるサーバ環境が必要なこと、編集者に現状に対する問題意識が少ないこと、など原因はいろいろある。今回はとくに変換スクリプトをSchemeで作っちゃったりしたので、問題意識のある編集者であっても実際に使ってもらいにくい。
希望はある。こういった問題を解決して汎用性の高いツールにまとめあげる大仕事を身を挺して背負ってくれそうな人がいる。できる限りの協力はしようと思う。
もちろん例外はあって、最初から(商品として妥当な)レイアウトを含めて執筆された(執筆者のローカル環境以外でも同様にコンパイルが通る)LaTeXの原稿があれば、少なくとも執筆者は現在でも最後まで原稿を修正できる。ただし、編集者にLaTeXのスキルがなかったり、印刷所でコンパイルが通らなかったりして、必ずしもうまくいくとは限らない。
前提として、ふつうのコンピュータ書籍の制作過程には昨日定義した「後戻り困難ポイント」があり、それが原因で誰かが泣いている。誰も泣かないようにするには、昨日愚痴った「後戻り困難ポイント」を埋めてしまえばいい。それには、現在の手作業によるページレイアウトの工程(つまり組版工程)を放棄して、内容に責任を持つべき人間が印刷の直前までハンドリングできるようにすればいい。
『RailsによるアジャイルWebアプリケーション開発』の制作では、この方針を実現すべく、以下のような方法を採用した。
- 印刷所に納品するデータはフォント埋め込みのPDF
- そのPDFはLaTeXによる組版データから生成する
- しかし原稿はLaTeXではないので、原稿をLaTeXに変換するスクリプトを用意する
- 原稿はSubversionで版管理する
たぶん、こんなのはソフトウェア開発では当たり前のことなんだと思う。ソースコード(原稿)を版管理し、それをデイリービルド(組版)しながら開発(制作)するってだけの話なんだから。ところが現在のコンピュータ書の制作では、コンパイルやビルドに相当する「原稿を組版にする作業」が完全に手作業なため、組版した後で判明した問題を原稿に戻って直したり、直して組版し直した結果をすぐに確認することができない†1。
実現にあたっては障壁もいくつかあった。まず、原稿がLaTeXじゃない。また、仮にLaTeXであっても、それだけでは印刷所に安心してデータを渡すことはできない(印刷所の環境で同じようにコンパイルできる保障がない。そのため、印刷所の環境でコンパイルした出力を確認するという作業が不可欠になってしまう)。
印刷所に渡すデータの問題については、PDF/X-1aという業界標準があるので、それに準拠したPDFを生成できれば問題ない。これについては「Debianにotfパッケージをインストールして、dvipdfmxでOpenTypeフォントを埋め込んだPDFを吐き出すまで」を参照。
原稿がLaTeXじゃない問題については、まず原稿がどんな形式だったか示す必要があるだろう。こんな感じのテキストデータだった。
■H1■はじめにこれは下訳をお願いした業者さんがよく使っている形式(というか編集指示)を拡張したものだけど、多かれ少なかれどこも似たような「マークアップ」を使っているようだ。見てわかるように、あくまでも人間が手作業で組版するためのコメントみたいな指示書き程度しか施されていない。コンピュータでそのまま処理するにはちょっと厄介な状態といえる(しかも、バリデータなんてないから、けっこういいかげん)。
■H2■本書の読み方
本書は初めてRailsに触れる人うんぬん……
★Rubyのコード
本書にはRubyのコードがたくさん出てきます。
しかも予約語は太字に、ダブルクォーテーション内はスラントにしなくちゃなりません。
◆→コード←◆ruby
puts "Hello World"
◆→ここまでコード←◆
ちなみに★で始まってる部分は小さな見出しつきの項目になります。
ここは、また本文に戻ってます。
● 箇条書きもあります。
こんなふうに、なんとなく箇条書きな部分がタブで示されています。
● もうひとつ箇条書き
また本文。
◆→コラム←◆Joeの疑問
枠で囲った記事もあります。「David曰く」「Joeの疑問」のほかに、何も指示のないコラムもあります。
◆→ここまでコラム←◆
1. 箇条書きとは別に
2. 連番もあります
■H3■もっと細かい本書の読み方
脚注もつけなければ◆→訳注 ←◆
■H2■終わりに
おしまい。
このような「自然言語+簡易マークアップ」の構造を読み取って適切なLaTeXのスタイルに対応付けるためのフィルタを用意する必要がある。今回はゆるい規則を処理しなければいけないこともあって、後から柔軟にフィルタの仕様を変更できるようなGaucheスクリプトを正月にでっちあげた(私的に余暇を利用して作ったものなので、後日公開する予定。いま見返すと謎な処理が多いけど、なんとか目的の動作は実現している)。
上記の例の変換結果は以下のとおり。
\chapter{はじめに}%このLaTeXの出力から書籍にするには、さらに各要素についてスタイルを定義する必要がある。実はこの作業がいちばん大変なところなんだけど(LaTeXのいい加減な規則や拡張に起因)、これは会社の業務として作成したものなので今のところ公開できない。まあ、LaTeXの泥臭いところが満載なので、実際のところあまり面白くないし。
\section{本書の読み方}%
本書は初めてRailsに触れる人うんぬん……%
%
\begin{entry}%
\item[Rubyのコード]
\item 本書にはRubyのコードがたくさん出てきます。
\item しかも予約語は太字に、ダブルクォーテーション内はスラントにしなくちゃなりません。
\begin{ruby}
puts \codesl{"Hello World"}
\end{ruby}%
\item ちなみに★で始まってる部分は小さな見出しつきの項目になります。%
\end{entry}%
%
ここは、また本文に戻ってます。%
%
\begin{myitemize}%
\item 箇条書きもあります。%
こんなふうに、なんとなく箇条書きな部分がタブで示されています。
\item もうひとつ箇条書き%%
\end{myitemize}%
%
また本文。%
%
\begin{column}{J}{の疑問}
枠で囲った記事もあります。「David曰く」「Joeの疑問」のほかに、何も指示のないコラムもあります。
\end{column}%
%
\begin{enumerate}%
\item 箇条書きとは別に%
\item 連番もあります%%
\end{enumerate}%
%
\subsection{もっと細かい本書の読み方}%
脚注もつけなければ\footnote{\kern-.5zw[訳注]}%
%
\section{終わりに}%
おしまい。
さて、ここまでは、この方法が従来の方法に比べて万能であることを意図的に強調してきたけれど、現実には適用できないプロジェクトが大半である。著者や制作業者の並々ならない協力が必要なこと、全員がアクセスできるサーバ環境が必要なこと、編集者に現状に対する問題意識が少ないこと、など原因はいろいろある。今回はとくに変換スクリプトをSchemeで作っちゃったりしたので、問題意識のある編集者であっても実際に使ってもらいにくい。
希望はある。こういった問題を解決して汎用性の高いツールにまとめあげる大仕事を身を挺して背負ってくれそうな人がいる。できる限りの協力はしようと思う。
もちろん例外はあって、最初から(商品として妥当な)レイアウトを含めて執筆された(執筆者のローカル環境以外でも同様にコンパイルが通る)LaTeXの原稿があれば、少なくとも執筆者は現在でも最後まで原稿を修正できる。ただし、編集者にLaTeXのスキルがなかったり、印刷所でコンパイルが通らなかったりして、必ずしもうまくいくとは限らない。
2006/05/05
ふつうのコンピュータ書籍がどうやってつくられているか。
といいたいとこなんだけど、現実はこんなにシンプルじゃない。問題なのは、各ステップがほとんど分裂していること。
上にあげた4つの「後戻り困難ポイント」のうち、最後の1つは物理的にどうしょうもない。どんな製造業にも後戻りできない一歩っていうのがあって、書籍制作の場合にはそのひとつが印刷工程なんだと思う(流通するまでは本当の意味で後戻りできないわけじゃないんだけど)。
で、残りの3つの「後戻り困難ポイント」に直面した場合どうなるか。従来ありがちなのは、順に
残りは読者が泣いていないケース。つまり、後戻りが不要だった(原稿が完璧で編集も完璧で組版も完璧)か、どっかの時点で困難を乗り越えて後戻りに成功した本である。「後戻りに成功」って書くと、プロジェクトX的な何かっぽくて聞こえがいいけれど、そんなわけはない。誰かがどこかで泣きながら困難な後戻りをやっている。誰がやっているかっていうと、出版社は著者に頭があがらないし、制作業者は出版社に頭があがらないから、推して知るべし。
すっかり愚痴っぽくなってるけど、ようするに現在の書籍制作は前近代的な「誰かが泣く」ソリューションに負っている。もちろん各工程で何も問題がおきなければいいんだけど、文筆のプロではない著者に本業の傍らで執筆してもらい(しかも半ば善意で)、それを一人の編集者が何本も同時にハンドリングし、安い単価でMacオペレータに手作業で組版してもらっている限り、デフォルトで誰かが泣いている。ということは、現在のやり方には何かしら問題がある。
どこに問題があるんだろう。考えられる可能性は、こんなとこ。
ではどうするか。いちばんキモなのは、内容に責任を持つ人間が最終的なページレイアウトまでをハンドリングできるようにすること。紙に赤字で修正指示を出して……という他人任せな方法は極力回避する。これには2つメリットがある。
で、ページレイアウトって、正直Webページ程度の表現力があれば事足りるものが多い。Webページであれば、公開する直前まで執筆してる人間が内容を修正し、それにCSSなりでスタイルを当てれば十分なコンテンツになる。しかも、執筆している人間に公開の直前まで許されている修正は、「てにおは」レベルのものじゃない。文章の階層構造はもちろん、説明の順番や図の配置まで、全部修正できる。どうして書籍の制作ではWebページのようにぎりぎりまで原稿を修正することができないのだろう。
Webブラウザと紙ではメディアとしての性格が違うという人も(DTPによる書籍制作にかかわってきた人のなかには)いるだろうけど、それは説得力がない。LaTeXとか、20年前からふつうに同じようなことができてたわけで、紙の制作に限って技術的な制限があるなんていうのはDTPソフト会社にだまされているだけだ。
- 企画
- 執筆や翻訳
- 編集
- 組版
- 印刷製本
といいたいとこなんだけど、現実はこんなにシンプルじゃない。問題なのは、各ステップがほとんど分裂していること。
- 編集してから執筆をやり直してもらうことは、よほどのことがない限りできない。
- 組版してから編集をやり直すことは、実際には頻繁にやらざるをえないんだけど、とても手間と時間がかかる。
- 書いてはみたけど時間が経ちすぎて市場性がいまいちになっちゃったね。でも企画からやり直すわけには……
- 当然のことながら、印刷してから誤植が見つかっても直せない。
上にあげた4つの「後戻り困難ポイント」のうち、最後の1つは物理的にどうしょうもない。どんな製造業にも後戻りできない一歩っていうのがあって、書籍制作の場合にはそのひとつが印刷工程なんだと思う(流通するまでは本当の意味で後戻りできないわけじゃないんだけど)。
で、残りの3つの「後戻り困難ポイント」に直面した場合どうなるか。従来ありがちなのは、順に
- 読者が泣く
- 制作業者が泣く
- 担当者(i.e. 出版社)が泣く
- 著者が泣く
残りは読者が泣いていないケース。つまり、後戻りが不要だった(原稿が完璧で編集も完璧で組版も完璧)か、どっかの時点で困難を乗り越えて後戻りに成功した本である。「後戻りに成功」って書くと、プロジェクトX的な何かっぽくて聞こえがいいけれど、そんなわけはない。誰かがどこかで泣きながら困難な後戻りをやっている。誰がやっているかっていうと、出版社は著者に頭があがらないし、制作業者は出版社に頭があがらないから、推して知るべし。
すっかり愚痴っぽくなってるけど、ようするに現在の書籍制作は前近代的な「誰かが泣く」ソリューションに負っている。もちろん各工程で何も問題がおきなければいいんだけど、文筆のプロではない著者に本業の傍らで執筆してもらい(しかも半ば善意で)、それを一人の編集者が何本も同時にハンドリングし、安い単価でMacオペレータに手作業で組版してもらっている限り、デフォルトで誰かが泣いている。ということは、現在のやり方には何かしら問題がある。
どこに問題があるんだろう。考えられる可能性は、こんなとこ。
- 紙の本が儲からなすぎ。何事も品質を求めれば金がかかるけど、金をかけようとしないので品質が悪くなり、読者が泣く。あるいは、何とか限られた金額で品質を高めようとして制作業者が泣く。編集者もサービス残業漬けになって泣く。
- みんな紙が好きすぎ。とにかく紙に印刷されたものベースでしか制作が進まない。内容に責任を負うべき人間が制作にかかわる唯一の方法は、紙へ手作業で赤字を書き入れること。それをもとに、内容には関与しない人間が、やはり手作業でちまちまとデータを修正する。赤書きした本人が修正結果を確認できるのは数週間後だったりする。修正作業者が内容を理解しているわけではないので、その作業が新たな問題を生むことも少なくない。
- 制作技術の進歩がなさすぎ。ページレイアウトを作るのに、いまだに植字工が活版を組んでいるのと原理的に同じ作業をコンピュータを使って手作業でやってる。一度組んでしまったものをスケジュールを崩さずに直すのは不可能。あとどうしょうもなくバカバカしいのは、節番号や図暗号の連番をふったり、ページ番号を参照したり、索引のページを解決したりするのが、全部手作業なこと。そんなのLaTeXやMS Wordでも自動でできるって(DTPソフトによってはできるものもあるけど、オペレータの数が限られていて一般には利用されていないというオチ。このへんも手作業な世界観が支配的で情けなくなるところ)。
ではどうするか。いちばんキモなのは、内容に責任を持つ人間が最終的なページレイアウトまでをハンドリングできるようにすること。紙に赤字で修正指示を出して……という他人任せな方法は極力回避する。これには2つメリットがある。
- ありえないミスがなくなる。赤字の修正指示は字の汚い人間にとって苦痛なだけでなくリスキーでもある。それを見てデータを直す人間が内容を理解していれば対応できるけど、専門書でそんなことは期待できないわけで。とくに数式をよろしく対応してもらうのは絶望的
- 制作期間を短縮できる。紙をやりとりするのは時間の無駄。
で、ページレイアウトって、正直Webページ程度の表現力があれば事足りるものが多い。Webページであれば、公開する直前まで執筆してる人間が内容を修正し、それにCSSなりでスタイルを当てれば十分なコンテンツになる。しかも、執筆している人間に公開の直前まで許されている修正は、「てにおは」レベルのものじゃない。文章の階層構造はもちろん、説明の順番や図の配置まで、全部修正できる。どうして書籍の制作ではWebページのようにぎりぎりまで原稿を修正することができないのだろう。
Webブラウザと紙ではメディアとしての性格が違うという人も(DTPによる書籍制作にかかわってきた人のなかには)いるだろうけど、それは説得力がない。LaTeXとか、20年前からふつうに同じようなことができてたわけで、紙の制作に限って技術的な制限があるなんていうのはDTPソフト会社にだまされているだけだ。
2006/04/25
今日の一行 2006-04-20 [quiz] 部分木の格上げ
http://oss.timedia.co.jp/index.fcgi/kahua-web/show/ossz/oneline/2006-04-20
「述語と木を与えて,述語を満すラベルの付いた部分木をその親の直ぐの弟にする関数を書け.」という問題。またまた出遅れ気味の回答。この翌日で、逆に部分木を格下げする問題も出題されているけど、こちらはカテゴリがquizではないらしいので手をつけないことにしよう。この問題だけで力尽きたともいう。
いかにも、SICPの前半を読んでいる最中です、という雰囲気のただよう回答にみえる。きをあつかうのは大変だ。
http://oss.timedia.co.jp/index.fcgi/kahua-web/show/ossz/oneline/2006-04-20
「述語と木を与えて,述語を満すラベルの付いた部分木をその親の直ぐの弟にする関数を書け.」という問題。またまた出遅れ気味の回答。この翌日で、逆に部分木を格下げする問題も出題されているけど、こちらはカテゴリがquizではないらしいので手をつけないことにしよう。この問題だけで力尽きたともいう。
(define (lookup subtree pred)
(let lookup-for-suns ((suns (cdr subtree)))
(cond ((null? suns) '())
((pred (caar suns)) (car suns))
(else
(lookup-for-suns (cdr suns))))))
(define (include? subtree pred)
(let check-for-suns ((suns (cdr subtree)))
(cond ((null? suns) #f)
((pred (caar suns)) #t)
(else
(check-for-suns (cdr suns))))))
(define (without-grandchild? tree)
(or (null? (cdr tree))
(let without-children? ((suns (cdr tree)))
(cond ((null? suns) #t)
((null? (cdr (car suns)))
(without-children? (cdr suns)))
(else #f)))))
(define (extract tree pred)
(cond ((null? tree) '())
((pred (caar tree))
(extract (cdr tree) pred))
(else
(cons (car tree)
(extract (cdr tree) pred)))))
(define (rankup tree pred)
(if (without-grandchild? tree)
tree
(cons (car tree)
(let rankup-for-suns ((suns (cdr tree)))
(cond ((null? suns) '())
((include? (car suns) pred)
(cons
(cons (caar suns)
(extract (cdar suns) pred))
(cons (lookup (car suns) pred)
(rankup-for-suns (cdr suns)))))
(else
(cons (rankup (car suns) pred)
(rankup-for-suns (cdr suns)))))))))
(define test-tree
(list 'A (list 'B (list 'F)
(list 'G (list 'L)
(list 'M)))
(list 'C (list 'H)
(list 'I (list 'N)
(list 'O (list 'P)
(list 'Q)))
(list 'J))
(list 'D)
(list 'E (list 'K))))
(define eq-I? (lambda (x) (eq? 'I x)))
gosh> test-tree
(A (B (F) (G (L) (M))) (C (H) (I (N) (O (P) (Q))) (J)) (D) (E (K)))
gosh> (rankup test-tree eq-I?)
(A (B (F) (G (L) (M))) (C (H) (J)) (I (N) (O (P) (Q))) (D) (E (K)))
いかにも、SICPの前半を読んでいる最中です、という雰囲気のただよう回答にみえる。きをあつかうのは大変だ。
2006/04/24
先週、奥様が自分の歯医者にいくというのでくっついていって検診してもらったら、レントゲンをとられて奥歯を一本抜くことになった。で、いまさっき抜いてきた。小学校のころいれた銀歯で、ずいぶん前からぐらついていたのは自分でも知ってたんだけど、こういうのってどうしても放置しちゃう。結局、長年の間にクラウンの中はすっかりぼろぼろになっていた。レントゲン写真を見て、自分から「抜いてください」とお願いしたくなるくらい。へたに周りの歯がしっかりしているものだから、かえって痛々しさが目立つ。
歯を抜くのは初めてなので、ここ一週間は夜も眠れないくらいびくついていた。だいたい、人間が痛みに耐えるときは歯をくいしばるものなのに、歯を抜くってことはそれができない。麻酔をかければ痛くないっていうけど、その麻酔の注射を歯茎に射すわけじゃん。麻酔されるまで診察台の上で終始手をきつく握り合わせていたら、先生に「そんなに心配しなくて大丈夫ですよ」といわれてしまった。実際、ほとんど痛くなかった。歯を抜く作業も手際がよくて、すーっと何かが抜けていく感覚だけ。ここ一週間、歯を抜く痛みのイメージトレーニングをひそかに繰り返していたので、脳内で大量のエンドルフィンが生成されていただけかもしれない。もちろん、先生の腕もいいんだと思う。彼の一連の作業の流れを見ていればわかる。抜いた後に再度レントゲン撮影して状態を確認するのも好感がもてる。でも、本当に痛み出すのは麻酔が切れる今晩からだっていうしなあ。幸いなのは、頼みの綱として痛み止めと化膿止めをもらったこと。しかも、ロキソニンとケフラールだった。これならうちに山ほどあるので、使い切ってもちょっと安心。
歯を抜くのは初めてなので、ここ一週間は夜も眠れないくらいびくついていた。だいたい、人間が痛みに耐えるときは歯をくいしばるものなのに、歯を抜くってことはそれができない。麻酔をかければ痛くないっていうけど、その麻酔の注射を歯茎に射すわけじゃん。麻酔されるまで診察台の上で終始手をきつく握り合わせていたら、先生に「そんなに心配しなくて大丈夫ですよ」といわれてしまった。実際、ほとんど痛くなかった。歯を抜く作業も手際がよくて、すーっと何かが抜けていく感覚だけ。ここ一週間、歯を抜く痛みのイメージトレーニングをひそかに繰り返していたので、脳内で大量のエンドルフィンが生成されていただけかもしれない。もちろん、先生の腕もいいんだと思う。彼の一連の作業の流れを見ていればわかる。抜いた後に再度レントゲン撮影して状態を確認するのも好感がもてる。でも、本当に痛み出すのは麻酔が切れる今晩からだっていうしなあ。幸いなのは、頼みの綱として痛み止めと化膿止めをもらったこと。しかも、ロキソニンとケフラールだった。これならうちに山ほどあるので、使い切ってもちょっと安心。
登録:
投稿 (Atom)