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]}

2012年12月30日日曜日

Clojureでテキスト入出力

Clojureでテキスト入出力を行う方法のメモ。

1 テキストファイル

;; 出力
(spit "test.txt" "Hello, World")

;; 入力
(assert (= "Hello, World" (slurp "test.txt")))

2 文字列

(with-open [in (java.io.StringReader. "Hello")]
  (assert (= \H (char (.read in))))
  'ok)

(with-open [in (clojure.java.io/reader (.getBytes "Hello"))]
  (assert (= "Hello" (.readLine in)))
  'ok)

(with-in-str "hoge"
  (assert (= "hoge" (read-line))))

3 XML

;; [org.clojure/data.xml "0.0.6"]
(require '[clojure.data.xml :as xml])
(xml/parse-str "<x id='1'><y>a</y><y id='2'>b</y></x>")

;; [enlive "1.0.0"]
(require '[net.cgrand.enlive-html :as enlive])
(let [s "<x id='1'><y>a</y><y id='2'>b</y></x>"
      x (with-in-str s (enlive/xml-resource *in*))]
  (assert (= '("b") (:content (first (enlive/select x [:#2]))))))

4 CSV

;; [org.clojure/data.csv "0.1.2"]
(require '[clojure.data.csv :as csv])

(let [s "a,\"b,c\",d\ne,fg,h\n"]
  (println (csv/read-csv s))
  (csv/write-csv *out* (csv/read-csv s))
  (assert (= s (with-out-str
                 (csv/write-csv *out* (csv/read-csv s)))))
  'ok)

5 JSON

;; [org.clojure/data.json "0.2.0"]
(require '[clojure.data.json :as json])
(let [m (json/read-str "{\"k1\": 1, \"k2\": \"val\"}")]
  (println m)
  (assert (= m (json/read-str (json/write-str m))))
  (with-open [out (clojure.java.io/writer "tmp.json")]
    (json/write m out))
  (with-open [in (clojure.java.io/reader "tmp.json")]
    (assert (= m (json/read in))))
  'ok)

;; オプションとして :key-fn や :value-fn を指定すると read/write 時にキーと値を変換できる
(println (= {:key 1.0}
            (json/read-str "{\"key\" : 1}"
                           :key-fn keyword
                           :value-fn #(double %2))))

6 INIファイル

;; [clojure-ini "0.0.1"]
(require '[clojure-ini.core :as ini])

(spit "test.ini" "[a]\nb=c\nd=e\n")
(assert (= {:a {:b "c", :d "e"}}
           (ini/read-ini "test.ini" :keywordize? true)))

2012年12月16日日曜日

[メモ]衛星の高度から速度・周期を求める

衛星の高度から速度・周期を求める式が JAXAのページ に書いてありました。

6378[km]赤道半径
H [km]地表からの高度
V [km/s]速度
T [s]周期

$V[km/s] = (\frac{398600[km^3/s^2]}{(6378[km] + H[km])})^\frac{1}{2}$

$T[s] = \frac{2\pi(6378[km] + H[km])}{V[km/s]}$

(defn v [h]
  (Math/sqrt (/ 398600 (+ 6378 h))))

(defn t [h v]
  (/ (* 2 Math/PI (+ 6378 h)) v))

(defn h->t [h]
  (t h (v h)))
;; 静止軌道(35786km) で 23時間56分
user=> (h->t 35786)
86163.61830152525

user=> (/ *1 3600)
23.934338417090345

Racketのサンドボックス機能

Racketには評価時のリソースの利用を制限するサンドボックス機能があります。

このあたりを参照

使用可能なメモリを制限して式を評価するプログラムを書いてみます。

#lang racket
(require racket/sandbox)

(define e (parameterize ((sandbox-memory-limit 1)) ;; 1MByte
            (make-evaluator 'racket/base)))

;; 1KByteくらい (1MByte制限)
(printf "~s\n" (vector-length (e '(make-vector 1000))))
(flush-output)

;; 1MByteくらい (制限なし)
(printf "~s\n" (vector-length (make-vector 1000000)))
(flush-output)

;; 1MByteくらい (1MByte制限)
(printf "~s\n" (vector-length (e '(make-vector 1000000))))
(flush-output)
> racket test.rkt
1000
1000000
out of memory 
  context...:
   /usr/racket/collects/racket/sandbox.rkt:355:0: call-with-limits
   /usr/racket/collects/racket/sandbox.rkt:403:0: call-with-custodian-shutdown
   /usr/racket/collects/racket/private/more-scheme.rkt:146:2: call-with-break-parameterization
   /usr/racket/collects/racket/sandbox.rkt:760:5: loop

[メモ]C#で時刻文字列を扱う・XMLファイルを読み込む

C#で時刻文字列のパース・フォーマット指定して文字列に変換

using System;
using System.Globalization;

class Test
{
  public static void Main()
  {
    DateTime tmp = DateTime.Parse("2012-12-16T00:00:00Z");
    Console.WriteLine(TimeZoneInfo.ConvertTimeToUtc(tmp).ToString("yyyy/MM/dd HH:mm:ss"));
    return;
  }
}
2012/12/16 00:00:00

C#でXMLファイルをパース

// mono-csc xml.cs -r:System.Xml.Linq.dll
using System;
using System.Xml;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;

// test.xml
// <a>
//  <b>b1</b>
//  <b>b2</b>
//  <c>c1</c>
// </a>

class Test
{
  static void Main()
  {
    var doc = XElement.Load("test.xml", LoadOptions.SetLineInfo);
    var elems = 
      from x in doc.Descendants()
      select x;

    foreach(var e in elems)
      {
        var text = String.Format("{0}:{1}", ((IXmlLineInfo)e).LineNumber, e.Value);
        Console.WriteLine(text);
      }

    return;
  }
}
2:b1
3:b2
4:c1

Clojureのリテラルその他

リテラル
内容X(type 'X)
10進数2 => 2java.lang.Long
8進数010 => 8java.lang.Long
16進数0x10 => 16java.lang.Long
2進数2r10 => 2java.lang.Long
36進数36r10 => 36java.lang.Long
BigInt3N => 3Njava.lang.BigInt
小数1.1 => 1.1java.lang.Double
小数1.0E8 => 1.0E8java.lang.Double
BigDecimal2.2M => 2.2Mjava.math.BigDecimal
有理数22/7 => 22/7clojure.lang.Ratio
シンボルabc あいう !clojure.lang.Symbol
キーワード:a => :aclojure.lang.Keyword
キーワード::a => :user/aclojure.lang.Keyword
文字列"abc" "\t\n"java.lang.String
文字\a \あ \spacejava.lang.Character
リスト(1 2)clojure.lang.PersistentList
空リスト()PersistentList$EmptyList
ベクタ[1 2]clojure.lang.PersistentVector
マップ{:k1 :v1 :k2 :v2}clojure.lang.PersistentArrayMap
セット#{:a :b :c}clojure.lang.PersistentHashSet
nil (null)nilnil
truetruejava.lang.Boolean
falsefalsejava.lang.Boolean
正規表現#"\d{2}\w+"java.util.regex.Pattern
コンストラクタ#java.lang.Double[1.1]クラス・レコード・タイプのインスタンス


読み込み時に他のフォームに置き換えられる表現
内容X'X
Quote'a(quote a)
Deref@a(deref a)
typehint^String^{:tag String}
Var-quote#'a(var a)
無名関数#(…)(fn [args] (…))
S式コメント#_Xフォーム X をコメントとして扱う
Syntax-quote`(…)-
読み込み時の評価#=(+ 1 2)3