pythonのリスト操作が強力すぎる

まず、下のソースを見てください。

def checkio(data):
    if len(data) == 1:
        return data[0][0]
    else:
        r = range(len(data))
        exp = [data[i][0] * checkio([data[x][1:] for x in r if x != i]) for i in r]
        return sum(exp[::2]) - sum(exp[1::2])

これ、checkioの課題で書いたコードなんでcheckioって名前ついてますが、dataで与えられる大きさNの正方行列の行列式を返します。例えば

data = [[1, 2], [2, 3]]
assert checkio(data) == -1

なんていう感じです。checkio(data)って計算させると、-1とか正解してくれちゃうんですよ。

それで、何がいいたいかというと、たった7行なんですよ。7行で行列式が計算できちゃう。今はどうだか知りませんが、ある行列の行列式を計算するっていうのは高校の数学の演習問題程度にはなっちゃう課題なんですよね。それが7行で記述できちゃう。今時の高校生は、たった7行のプログラムをpythonで書いてしまえば、その日の授業の演習問題の正解が計算できちゃう。余った時間で部活をするも良し、テレビを見るのもよし、デートにいくのも良し。青春を満喫できるわけですよ。

それで、たった7行で行列式を計算するためには、いちいち細かい計算の仕方を手取り足取りプログラムしていちゃだめナンですよ。今時そんなヤリ方は時代遅れ。プログラムには定義だけを記述します。実際に今回のソースには、len(data)==1の場合と、それ以外の場合に、何を返すかの定義を行なっているにすぎません。

ところで、定義を簡明に記述するためには、それなりの道具が必要です。今回の課題でいえば、正方行列dataの部分行列を簡単に記述できる必要があります。具体的には、6行目のcheckio再帰呼出で引数として使われている部分ですね。

[data[x][1:] for x in r if x != i]

これが、dataの部分行列data'を定義しているンです。ここで使われているのが、リスト内包表記と、スライスで、いずれもpythonでとても強力だと評価されている機能です。

まずはスライスの説明から。
一般的には、list[n]でlistのn番目の要素にアクセスできますが、pythonではlist[n:]でlistのn番目以降の部分リストを取得できます。list[n:m]でlistのn番目からm-1番目までの部分リストを取得できます。list[-n:]でリストの後ろからn番目から最期までの部分リストが取得できます。さらにlist[::2]で、リストの(先頭を0番目と数えた場合の)偶数番目の要素をあつめた部分リストを取得できます。list[1::2]だったらリストの奇数番目の部分リストってことですね。今回の例でいえば、data[x][1:]と書くと、dataのx行目の1列目から最期までの部分リストが取得できる訳ですね。これってスゴいと思いませんか?

スライスを使えば、dataの任意の行の部分行列が取得できるのはわかりましたが、それだけでは不十分です。今回の例でいえば、dataの0行目からN-1行目までのうち、i行目を除く全ての行をリストアップしなければなりません。そこで活躍するのがリスト内包表記です。それで、リスト内包表記について丁寧に説明しようと思いましたが、すっかり酔いも回ってきて面倒くさいので止めます。っていうかもう説明書いたような気がする。どーしても知りたい人はググってください。それじゃ。

(追記)
N=10の浮動小数点を要素にもつ正則行列行列式を計算させた所、15秒ちょっとでした。AMD Athlon II 640プロセッサでlinux上で走らせてみました。N==2の場合の計算式を与えてやると(たった2行の追加)、計算時間は半分になります。
実行するコード

def checkio(data):
    if len(data) == 1:
        return data[0][0]
    elif len(data) == 2:
        return data[0][0] * data[1][1] - data[0][1] * data[1][0]
    else:
        r = range(len(data))
        exp = [data[i][0] * checkio([data[x][1:]
                                     for x in r if x != i]) for i in r]
        return sum(exp[::2]) - sum(exp[1::2])

if __name__ == '__main__':
    data = [[1.2, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            [2, 3, 3, 5, 6, 7, 8, 9, 10, 1],
            [3, 4, 5, 6, 7, 8, 9, 10, 1, 2],
            [4, 5, 6, 7, 8, 9, 10, 1, 2, 3],
            [5, 6, 7, 8, 9, 10, 1, 2, 3, 4],
            [6, 7, 8, 9, 10, 1, 2, 3, 4, 5],
            [7, 8, 9, 10, 1, 2, 3, 4, 5, 6],
            [8, 9, 10, 1, 2, 3, 4, 5, 6, 7],
            [9, 10, 1, 2, 3, 4, 5, 6, -7.4, 8],
            [10, 1, 2, 3, 4, 5, 6, 7, 8, 9],
            ]
    print(checkio(data))

実行結果

dwarfjay $ python -m cProfile determinant.py                                                               
-6017528000.0
         12802310 function calls (10195810 primitive calls) in 7.952 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   11.742   11.742 determinant.py:1(<module>)
2606501/1    4.508    0.000   11.742   11.742 determinant.py:1(checkio)
  2606500    2.411    0.000    2.411    0.000 determinant.py:8(<listcomp>)
        1    0.000    0.000   11.742   11.742 {built-in method exec}
  6005103    0.554    0.000    0.554    0.000 {built-in method len}
        1    0.000    0.000    0.000    0.000 {built-in method print}
  1584202    0.479    0.000    0.479    0.000 {built-in method sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

八面鏡

龍穴炉内撃破
主人公レベル15くらい。
猪Lv40 x 1
犬Lv40 x 2
八極炉 x 1, MP回復草 x 1
割と苦戦。アレックスの回復が強くて一回目は失敗。アレックスが死にそうなタイミングで八極炉を使ったらいけた。運次第の攻略。

仙窟内撃破
攻撃部屋があれば何とでもなる感じ。易しい。

昨日の続き

http://d.hatena.ne.jp/dwarfjay/20131002/1380710917の続きです。世の中にはxargsというのがあることを思い出しました。

perlなら、

ldd /sbin/e2fsck | perl -wnle '/(\/lib\d*\/[\w-]+\.so\.*[\d\.]*)/ and print $1' | xargs cp -vLt .

awkなら、

ldd /sbin/e2fsck | awk 'NF==2 {print $1}; NF>2 {print $3}' | awk '/^\//' | xargs cp -vLt .

awkの場合は、制御構文だとか変数管理の煩わしさを避けるために2段階で呼びだしてますが、それはそれでわかりやすいという美点もあると思います。
ただ、将来lddの出力フォーマットが作者の気紛れで変更になる可能性なんかもあるんで、perlバージョンを使おうかな、と思います。

perlを使ったらどうなる?

http://d.hatena.ne.jp/dwarfjay/20131002/1380699244を、perlを使ったらどうなるかというと。

ldd /sbin/e2fsck | perl -wne '/(\/lib\d*\/[\w-]+\.so\.\d*)/ and printf "cp -L %s .\n",$1;'

というわけで、こっちでもいいな。

AWKが便利すぎて泣ける

initramfsを使ってLVM上のパーティションをルートにしている。あまりにも素朴な自作initramfsにはfsckすらなかったのだが、500回以上もfsckなしでルートをマウントしていたという事実が判明。これではイカンということで、initramfsでfsckできるようにしようとした時に、AWKがすごく便利だった、というお題です。

さて、基本的には、e2fsckをinitramfsにコピーして、initスクリプトで呼びだせばよいだけなのだが、ここでひとつ問題が。e2fsckに必要なライブラリを全てinitramfsにコピーしなけりゃならない。

必要なライブラリは、lddを実行すれば得られる。

ldd /sbin/e2fsck

結果は、

	linux-vdso.so.1 (0x00007fffd6470000)
	libext2fs.so.2 => /lib64/libext2fs.so.2 (0x00007f316e2a2000)
	libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007f316e09e000)
	libblkid.so.1 => /lib64/libblkid.so.1 (0x00007f316de63000)
	libuuid.so.1 => /lib64/libuuid.so.1 (0x00007f316dc5e000)
	libe2p.so.2 => /lib64/libe2p.so.2 (0x00007f316da56000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f316d6a6000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f316d489000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f316e4e7000)

ここで、/lib64/以下のライブラリを全てカレントディレクトリにコピーすればよいことにしよう。もちろん、いちいち手で入力するなんてやってられない。目当てのファイル名は1カラム目か3カラム目にあるので…

ldd /sbin/e2fsck | awk 'NF==2 {print $1}; NF>2 {print $3}'

結果は、

linux-vdso.so.1
/lib64/libext2fs.so.2
/lib64/libcom_err.so.2
/lib64/libblkid.so.1
/lib64/libuuid.so.1
/lib64/libe2p.so.2
/lib64/libc.so.6
/lib64/libpthread.so.0
/lib64/ld-linux-x86-64.so.2

一行目のlinux-vdso.so.1はカーネル内にあるからコピーする必要はない。それで、この出力をさらにawkを使って整形する。

ldd /sbin/e2fsck | awk 'NF==2 {print $1}; NF>2 {print $3}' | awk '/^\// {print "cp","-L",$0,"."}'

結果は、

cp -L /lib64/libext2fs.so.2 .
cp -L /lib64/libcom_err.so.2 .
cp -L /lib64/libblkid.so.1 .
cp -L /lib64/libuuid.so.1 .
cp -L /lib64/libe2p.so.2 .
cp -L /lib64/libc.so.6 .
cp -L /lib64/libpthread.so.0 .
cp -L /lib64/ld-linux-x86-64.so.2 .

あとは、これをbashにでも渡してやればok.

ldd /sbin/e2fsck | awk 'NF==2 {print $1}; NF>2 {print $3}' | awk '/^\// {print "cp","-L",$0,"."}' | /bin/bash

これを、一本のスクリプトで実現しようとすると、割と面倒臭いことになると思うんだけど、パイプとawkを使えば殆んどやりたい部分だけを記述すればok。非常にラク

かえる弦を使ってみました

これまで使っていた、飛翔弦との比較。15キロのグラス弓(寺内の桂)並寸、矢尺86センチ、2014シャフト、矢束93センチです。

  1. 飛翔弦

やわらかくて折れにくい。丈夫で切れにくい。一本700円くらい。
弦音は「ビィン」っていう感じ。時々、離れたあと「ブーン」と鳴ることがある。
弦の復元スピードが早くて、ミスをすると前に抜ける傾向があり、矢色が出やすい。
矢所はすごく良くまとまる。一手持って立射だと(同じ足踏み、同じような胴造り・離れなら)、近的で甲矢と乙矢の間隔が3センチくらいになることがしばしば。

  1. かえる弦

硬い。一本500円くらい。
弦音は「パキン」という枯れ枝を折るような音がする。
弦の復元スピードは飛翔弦と比べてやや遅く感じる。ミスると後に抜けることが多い。色が出にくい。まだ矢数をかけていないので、耐久性その他不明。

まとめると、飛翔弦の方がタイミングにシビアで、ミスった時に色が出たり前に外したりと極めて格好悪い結果になる。自分は、時々弓手を振り込む癖があるので、振り込んだ時に絶対に的中しないかえる弦の方が合っているかもしれない。

飛翔弦→かえる弦の乗りかえは簡単だったけど、逆は大変な気がする。っていうか大変だった。

棒矢のメンテナンス

巻藁に使う棒矢には、矢尺の目安となる印をつけています。最近熱心に巻藁を引いているので、この印が痛んできたようです。
arrow 1
こちら側は弓にあたる側。それほど減っていません。

arrow 2
上から見ると、反対側は大分磨り減っているのがわかります。

arrow 3
こちら側は、完全に磨り減ってしまっています。

arro2 4
新たに巻き直し。今回は黄色にしました。