2013年1月25日金曜日

Clojure(java)で日付を扱う

Clojure(Java)で日付を扱うには、java.util.Date、java.util.Calendar、java.text.SimpeDateFormatなどを使えば良さそうです。

;;; リテラル
#inst "2013-01-24"
;; => #inst "2013-01-24T00:00:00.000-00:00"
#inst "2013-01-24T00:00:00Z"
;; => #inst "2013-01-24T00:00:00.000-00:00"
(type #inst "2013-01-24")
;; => java.util.Date


;;; 文字列 => java.util.Date
(java.util.Date. "2013/01/24")
;; => #inst "2013-01-23T15:00:00.000-00:00"
(java.util.Date. "2013/01/24 01:02:03")
;; => #inst "2013-01-23T16:02:03.000-00:00"

(def date-format
  (doto (java.text.SimpleDateFormat. "yyyy/MM/dd hh:mm:ss")
    (.setTimeZone (java.util.TimeZone/getTimeZone "UTC"))))

(.parse date-format "2013/01/24 01:02:03")
;; => #inst "2013-01-24T01:02:03.000-00:00"

(-> (doto (java.text.SimpleDateFormat. "yyyy/MM/dd hh:mm:ss")
      (.setTimeZone (java.util.TimeZone/getTimeZone "JST")))
    (.parse "2013/01/24 01:02:03"))
;; => #inst "2013-01-23T16:02:03.000-00:00"


;;; java.util.Date => 文字列
(.format date-format (.parse date-format "2013/01/24 01:02:03"))
;; => "2013/01/24 01:02:03"

;;; UNIX time (1970/01/01 00:00:00 をエポックとする通算秒)
(defn date->unixtime [d]
  (long (/ (.getTime d) 1000)))

(date->unixtime (.parse date-format "1970/01/01 00:00:01"))
;; => 1
(date->unixtime (.parse date-format "1970/01/02 00:00:00"))
;; => 86400

;;; java.util.Date => java.util.Calendar
(defn date->calendar [d]
  (doto (java.util.Calendar/getInstance)
    (.setTime d)))

(date->calendar (.parse date-format "2013/01/24 00:00:00"))
;; => #inst "2013-01-24T09:00:00.000+09:00"
(type (date->calendar (.parse date-format "2013/01/24 00:00:00")))
;; => java.util.GregorianCalendar

;;; 1日毎の日にちのシーケンス
(defn day-seq [day-from]
  (let [c (date->calendar day-from)]
    (lazy-seq
     (cons day-from
           (repeatedly #(do (.add c java.util.Calendar/DAY_OF_YEAR 1)
                            (.getTime c)))))))

(take 3 (day-seq (.parse date-format "2013/01/24 01:02:03")))
;; => (#inst "2013-01-24T01:02:03.000-00:00"
;;     #inst "2013-01-25T01:02:03.000-00:00"
;;     #inst "2013-01-26T01:02:03.000-00:00")

その他、Clojure用のライブラリとして clj-time というのがあるようです。

2013年1月22日火曜日

[Clojure]mapの操作

Clojureでmapを作成・操作する関数のメモ。多くの関数は他のシーケンス・コレクションにも適用可能。

(def a-map {:k1 :v1 :k2 :v2})

;;; get関数でアクセスできる
(get a-map :k1)
;; => :v1
(get a-map :not-found)
;; => nil
(get a-map :not-found :default)
;; => :default

;;; キーワードはマップへのアクセサとして扱える
(:k1 a-map)
;; => :v1
(:not-found a-map)
;; => nil
(:not-found a-map :default)
;; => :default

;;; マップ自体をキーを引数に取る関数として扱える
(a-map :k2)
;; => :v2
(a-map :not-found)
;; => nil
(a-map :not-found :default)
;; => :default

;;; find関数でアクセスするとキーと値のペア(map entry)が取得できる
(find a-map :k2)
;; => [:k2 :v2]
(find a-map :not-found)
;; => nil

;;; すべてのキーを取得する
(map key {:k1 :v1 :k2 :v2})
;; => (:k1 :k2)
(keys {:k1 :v1 :k2 :v2})
;; => (:k1 :k2)

;;; すべての値を取得する
(map val {:k1 :v1 :k2 :v2})
;; => (:v1 :v2)
(vals {:k1 :v1 :k2 :v2})
;; => (:v1 :v2)

;;; 複数のキーに対応する値を取得する
(select-keys {:k1 :v1 :k2 :v2 :k3 :v3} [:k1 :k3])
;; => {:k3 :v3, :k1 :v1}
((juxt :k1 :k3) {:k1 :v1 :k2 :v2 :k3 :v3})
;; => [:v1 :v3]

;;; 一部を抜き出す (sorted-map)
(subseq (sorted-map 1 2 3 4 5 6) <= 3)
;; => ([1 2] [3 4])

;;; 要素を追加する
(assoc a-map :k3 :v3)
;; => {:k3 :v3, :k1 :v1, :k2 :v2}
(assoc a-map :k3 :v3 :k4 :v4)
;; => {:k4 :v4, :k3 :v3, :k1 :v1, :k2 :v2}
(conj a-map [:k3 :v3])
;; => {:k3 :v3, :k1 :v1, :k2 :v2}
(conj a-map [:k3 :v3] [:k4 :v4])
;; => {:k4 :v4, :k3 :v3, :k1 :v1, :k2 :v2}
(merge {:k1 :v1} {:k2 :v2})
;; => {:k2 :v2, :k1 :v1}
(merge {:k1 :v1} [:k2 :v2])
;; => {:k2 :v2, :k1 :v1}
(merge-with concat {:k1 [1 2]} {:k1 [3 4]})
;; => {:k1 (1 2 3 4)}
(into {:k1 :v1} {:k2 :v2})
;; => {:k1 :v1, :k2 :v2}
(into {:k1 :v1} [[:k2 :v2]])
;; => {:k1 :v1, :k2 :v2}

;;; 指定したキーを削除
(dissoc {:k1 :v1 :k2 :v2} :k1)
;; => {:k2 :v2}
(dissoc {:k1 :v1 :k2 :v2} :k1 :k2)
;; => {}

;;; 空かどうかチェックする
(if (empty? {}) :a :b)
;; => :a
(if (empty? {:key :val}) :a :b)
;; => :b
(if (not-empty {}) :a :b)
;; => :b
(if (not-empty {:key :val}) :a :b)
;; => :a
(if (seq {}) :a :b)
;; => :b
(if (seq {:key :val}) :a :b)
;; => :a

;;; 指定したキーが存在するかチェックする
(contains? {} :key)
;; => false
(contains? {:key :val} :key)
;; => true


;;; alist風のベクタからマップを作成する
(def alist [[1 2] [3 4]])

(into {} alist)
;; => {1 2, 3 4}

(apply merge {} alist)
;; => {3 4, 1 2}

(apply conj {} alist)
;; => {3 4, 1 2}

;;; plist風のベクタからマップを作成する
(def plist [1 2 3 4])

(apply assoc {} plist)
;; => {3 4, 1 2}

(apply sorted-map plist)
;; => {1 2, 3 4}

;;; keyのベクタとvalsのベクタからマップを作成する
(def ks [1 3])
(def vs [2 4])

(zipmap ks vs)
;; => {3 4, 1 2}

(apply assoc {} (interleave ks vs))
;; => {3 4, 1 2}

;;; ネストしたマップへアクセスする
(get-in {:a {:b 2}} [:a :b])
;; => 2

(get-in {} [:a :b] :default)
;; => :default

(assoc-in {} [:a :b] 2)
;; => {:a {:b 2}}

(update-in {:a {:b 2}} [:a :b] inc)
;; => {:a {:b 3}}


;;; その他
(frequencies [:a :b :a :a :c]) ; 出現回数
;; => {:a 3, :b 1, :c 1}

(group-by even? [1 2 3 4]) ; 関数の適用結果によるグループ分け
;; => {false [1 3], true [2 4]}