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