app of incanter
フレディリック・フォーサイスの過去の小説を3タイトル借りてきた。いずれも古いもの なので、図書館ではお蔵入りしてたやつ。おネーさんの手を余り煩わせたくないので、大人借り。
『神の拳(上・下)』湾岸戦争を題材にしたやつ。イラクのフセインが大量破壊兵器を持ってる ぞと西側に脅しをかけてクェートに侵攻したのが事の始まりだった。
原爆を作ったはいいが、運搬手段が無い。某ロケット博士が、ロケットなんて大儀だのうってんで、 奇想天外な方法を編み出した。でっかい大砲を作って、それで打ち出せばいいじゃん。 で、その砲身を鉄パイプと偽って輸入。砂漠の某所に設置。
バクダットに多国籍軍が攻めてきたら、それをぶっ放すとな。化学兵器や生物兵器も恐い けど、原爆の方が更に効率よく事を運べる。これを阻止せよと、英国の某に命令が下る。 真実と想像を組み合わせた小説だけど、門外漢が読むと、どこまでが真実か判定もつかず、 一気読み必須でした。
次は、『第四の核(上・下)』鉄の女サッチャーさんの頃の話。鉄のカーテンの向こう側の 指導者が、死ぬ前に大きな事をしたいと野望を抱く。イギリスを東ベルリンみたいにしたいとな。
で、その方策はと言うと、密かにイギリスへ核を持ち込み、爆発させちゃえ。そうすれば、 労働党と言う東側信奉の衆が覇権を握ってくれるだろう。
問題は、どうやって核を持ち込むかだ。原爆を12の部品に分けて、それぞれをクーリエに 運ばせる。後は現地で組み立てればOK。
原始的な原爆の材料はウラン。天然のものには、U235が0.7%含まれている。それを純度93%以上に 精製したのを35kg程用意する。用意した途端に臨界しちゃうんで、それは現地で行う事。
U235の砲丸を作る。真ん中に穴を空けておく。おかげで、まだ臨界には至らず。その穴に U235の棒を差し込む。それで臨界状態になる。でも、臨界になったと言え爆発的に事が 運ぶ訳では無い。
そこで、物事が爆発的に進むように、触媒を用意する。その触媒は、リチウムと某元素の 組み合わせ。これがアルファー線源となって、反応が加速される。原爆の為の信管ですな。
原爆の作り方は、検索のNGワード らしいけど、興味ある方はどうぞ。(検索したばっかりに、怪しい人に尾行されても、当方は 責任取れません。尾行の探知方法とかは、フォーサイスの小説にも出てくるぞ。)
もう一冊は、『ジャッカルの日』フランスの昔の大統領だったドゴール将軍を狙う、暗号名 ジャッカルと言う暗殺者とそれを阻止しようと言う警視の話。
このジャッカルさんが ゴルゴ13に出てくる あの方にそっくり。ひょっとして著者の方は、東洋の漫画を参考になさったのかしらん? ゴルゴの漫画が出たのは1968年つう事だから、微妙だな。
ジャッカル本の翻訳本は、1972年刊行。出張の折りの 長旅中に読んだ記憶がある。ゴルゴ本は図書館には無いだろうから、ブックオフにGOかな。 連載がずっと続いてるって、もう日本の文化ですよ。離散しないうちに、蔵書キボンヌ。
読書メーター 7冊 / 2509頁 / 10600円
zip include map
ふと、clojureにはhaskellとかに有るzip相当品が無いかと思った。lein replの補完機能で 洗い出してみると、zipmapが出て来るんだけど、求めている動作をしてくれない。
ひょっとしてclojureの事だからmapを拡張してないかと当たりを付けた。ソース嫁ってんで 開いてみたら、
([f coll] (lazy-seq (when-let [s (seq coll)] (if (chunked-seq? s) (let [c (chunk-first s) size (int (count c)) b (chunk-buffer size)] (dotimes [i size] (chunk-append b (f (.nth c i)))) (chunk-cons (chunk b) (map f (chunk-rest s)))) (cons (f (first s)) (map f (rest s))))))) ([f c1 c2] (lazy-seq (let [s1 (seq c1) s2 (seq c2)] (when (and s1 s2) (cons (f (first s1) (first s2)) (map f (rest s1) (rest s2))))))) :
こんな具合に複数のシーケンスをサポートしてた。後でclojure本を調べてみたら、さりげなく 書いてあったよ。本を読んでいる様で見落としがあるな。勿体ない事です。そして、上記の コードを見ると、lazy-seqとかseqってこういう使い方をするんだと、生きた実例に遭遇した喜びをひしひしと感じますよ。
CSV R/W
incanterを前回思い出したように触ってみた。折角なのでこれで軽いアプリでも書いてみるか。 何にする? Webのデータをスクライビングして統計処理ってのが定番だろうけど、何故か Web関係には触りたくない。
で、何度も登場してる血圧データの見える化。同級会の時、ご他聞に洩れず病気自慢が繰り広げ られ、横に座ったあの子は、血圧を左右の腕でご丁寧に測っているとか。大事そうにデータの メモを財布から取り出して見せてくれたよ。血圧データと言えども個人情報ですから、財布に 仕舞うのは当然か。まあ、年寄り御用達のアプリであります。
データの保存をどうする? incanterだとexcelファイルを扱えるようだ。勿論、CSVファイルも 簡単に読み書き出来る。
bld.core> (def dat (read-dataset "2013.csv" :header true)) ;;=> #'bld.core/dat bld.core> dat ;;=> | :ymdh | :hi | :low | :pls | |-----------+-----+------+------| | 130101:03 | 118 | 80 | 60 | | 130101:21 | 107 | 67 | 72 | | 130102:05 | 121 | 76 | 57 | | 130102:21 | 121 | 72 | 71 | | 130103:04 | 129 | 85 | 60 |
さすがにRを手本にしたってぐらいなんで、それっぽくなってるな。
ファイルに落とすには
(save dat "aa.csv" :header ["ymdh" "hi" "low" "pls"])
のようにする。ヘッダーを指定しないと、勝手に :col-0 :col-1 とかって付いて しまうので、積極的に指定してる。unix的には、無い方が有り難いんだけど(外部でcatとか して、複数のファイルをまとめるとかの場合)、offに出来ないので諦めている。
読み込みの方のheaderしては、デフォでfalseになってるので、どうもCSVファイルを使って 永続化するとかの意図は無いみたい。
stats
統計データは、$rollup ってのが使えるみたいだけど、Rで言うサマリーみたいに 複数の基本データが取れない。従って分解して取ってみる。
bld.core> (mean ($ :hi dat)) ;;=> 119.2 bld.core> (sd ($ :low dat)) ;;=> 6.96419413859206 bld.core> (apply max ($ :pls dat)) ;;=> 72 bld.core> (median ($ :hi dat)) ;;=> 121.0
基本機能は入っているな。後は、相関とかだな。血圧は左腕で測るべきか右腕が良いのか? 両方測って、相関取ってみれ!
CRUD
血圧プログラムをPythonで書いた時は、データの永続化にピクルスを使った。今回はCSVファイル をそのまま使いたい。読み書きは上で見たようにincanterがサポートしてるから、それを そのまま使えばいいけど、追加とか修正はどうする? いわゆるデータベースのCRUD問題だな。
まず、一番大事なデータの追加。これはサポートされてた。
bld.core> (conj-rows dat ["140811:04" 110 73 57] ) ;;=> | :ymdh | :hi | :low | :pls | |-----------+-----+------+------| | 130101:03 | 118 | 80 | 60 | | 130101:21 | 107 | 67 | 72 | | 130102:05 | 121 | 76 | 57 | | 130102:21 | 121 | 72 | 71 | | 130103:04 | 129 | 85 | 60 | | 140811:04 | 110 | 73 | 57 |
こんな具合に、最後に追加してく事が出来る。途中への追加とかは、うーん。後で並べ替え する事で、お茶を濁すか。それじゃ、削除とか修正はどうよ? 調べてみたけど、そんな機能は 無いみたい。
一旦datasetをばらしておいて修正した後、またdatasetに再構築かな。ばらすには、 to-listとかto-mapが使えそう。
bld.core> (to-list dat) ;;=> (("130101:03" 118 80 60) ("130101:21" 107 67 72) ("130102:05" 121 76 57) ("130102:21" 121 72 71) ("130103:04" 129 85 60)) bld.core> (pp) (("130101:03" 118 80 60) ("130101:21" 107 67 72) ("130102:05" 121 76 57) ("130102:21" 121 72 71) ("130103:04" 129 85 60))
醜いのでppを使って直前の結果(*1)を再表示してみた。この形式なら頑張れば、修正とか 削除は何とかなりそう。なお、便利なppやpprintをemacs上から使うには、(use 'clojure.pprint) しておく事。
bld.core> (pprint (to-map dat)) {:pls (60 72 57 71 60), :low (80 67 76 72 85), :hi (118 107 121 121 129), :ymdh ("130101:03" "130101:21" "130102:05" "130102:21" "130103:04")}
こちらは、列志向の表現になってました。元に戻すには、to-datasetを使うようですが、棘の道が用意されてましたよ。 なお、datasetってのは、
Dataset record Fields: [column-names rows] Protocols: Interfaces: clojure.lang.IHashEq, clojure.lang.IKeywordLookup, clojure.lang.ILookup, clojure.lang.IObj, clojure.lang.IPersistentMap, java.io.Serializable, java.util.Map
こういう物らしいです。
bld.core> (col-names dat) ;;=> [:ymdh :hi :low :pls] bld.core> (pprint (:rows dat)) ({:pls 60, :low 80, :hi 118, :ymdh "130101:03"} {:pls 72, :low 67, :hi 107, :ymdh "130101:21"} {:pls 57, :low 76, :hi 121, :ymdh "130102:05"} {:pls 71, :low 72, :hi 121, :ymdh "130102:21"} {:pls 60, :low 85, :hi 129, :ymdh "130103:04"})
但し、(:rows dat) は、直接構造にアクセスしてるので、hackと言う事で、これを使う場合は あくまで自己責任で。 それから、データの並び替えは簡単に出来る。
bld.core> ($order [:ymdh :hi :low :pls] :desc dat) ;;=> | :ymdh | :hi | :low | :pls | |-----------+-----+------+------| | 130103:04 | 129 | 85 | 60 | | 130102:21 | 121 | 72 | 71 | | 130102:05 | 121 | 76 | 57 | | 130101:21 | 107 | 67 | 72 | | 130101:03 | 118 | 80 | 60 |
そして、条件指定による抽出。
bld.core> ($where {:hi {:$lt 120}} dat) ;;=> | :ymdh | :hi | :low | :pls | |-----------+-----+------+------| | 130101:03 | 118 | 80 | 60 | | 130101:21 | 107 | 67 | 72 |
使える演算子は、query-datasetの所に書いてあるけど、
:$gt, :$lt, :$gte, :$lte, :$eq, :$ne, :$in, :$nin, $fn
主として数字関係だな。頑張れば、独自の関数も定義出来るようだけどね。
ymdhは見易さから間にコロンを挟んでいたけど、これだと文字列扱いになってしまって、 抽出が面倒。見易さを諦めて、数値として扱うか。
以下、アプリを書くための材料集めです。
date time
お次は、日時を得る関数だな。clojureのcoreにはそんなの無い。Javaがそのまま実行出来る んだから、それでいいじゃんと言う態度。ごもっとも、正しい主張です。ネットワーク関係は 無いのーーーとか、言われるに決まってますから。
でも、日時の扱いをJavaに頼るんじゃ、そりゃあんまりだろうって人が腰を 上げてくれて、 clj-timeなんてのが公開されてます。
オイラーは、年月を得たいので、
(require '[clj-time.core :as t] '[clj-time.format :as tf] '[clj-time.local :as tl]) ; (tf/unparse (tf/formatter-local "yyyy/MM/dd kk:mm:ss") (tl/local-now)) (def yymm (tf/unparse (tf/formatter-local "yyMM") (tl/local-now)))
良く使いそうな技
文字列を数値に直す。
bld.core> (Integer/valueOf "99123123") ;;=> 99123123 (Integer. "1234") (Long/valueOf "-9223372036854775808")
正規表現で、スペース区切りから単語を取り出す。
bld.core> (re-seq #"\w+" "3344 abc def 123 ") ;;=> ("3344" "abc" "def" "123")
キーワードの文字列化と、文字列からキーワード化。
bld.core> (name :foo) ;;=> "foo" user> (keyword "fuga") ;;=> :fuga