Programming

Clojrueのmapの使い方(データ構造の方)

Shibuya.lispでClojureはデータ構造が貧弱といわれていましたが、Mapはもうちょっと評価されてもいいと思うので使い方メモ。

Clojure1.1.0で確認。

Map作成

リテラルは{}です。key valueの組み合わせを並べます。必ず要素数は偶数。 またClojureでは半角カンマは空白とみなされるので、key valueの後に半角カンマを入れると見やすい。

user> (def m {:a 1 :b "a" :c 'foo})
#'user/m
user> m
{:a 1, :b "a", :c foo}
user> (class m)
clojure.lang.PersistentArrayMap

リテラルのはArrayMapですね(仕様ではない気がするが)。array-map関数でも作成できます。

user> (def am (array-map :a 1 :b "a" :c 'foo))
#'user/am
user> am
{:a 1, :b "a", :c foo}
user> (class am)
clojure.lang.PersistentArrayMap

HashMapはhash-map関数でつくれます。入力と順番が違う点に注意。

user> (def hm (hash-map :a 1 :b "a" :c 'foo))
#'user/hm
user> hm
{:a 1, :c foo, :b "a"}
user> (class hm)
clojure.lang.PersistentHashMap

sorted-mapもあるよ。

user> (def sm (sorted-map :a 1 :b "a" :c 'foo))
#'user/sm
user> sm
{:a 1, :b "a", :c foo}
user> (class sm)
clojure.lang.PersistentTreeMap

普通のMap

要素取得

(key map)または(map key)の形式で値取得。

user> (:a m)
1
user> (:b m)
"a"
user> (m :a)
1

keyを先頭にできるのはkeyがSymbolKeywordのときだけだと思う。多分。
この場合、keyを関数のように適用できる利点がある。変な例だけど

user> (map :b [m m m])
("a" "a" "a")

関数ではgetが使える。

user> (get m :a)
1

keyやvalueのシーケンスがほしいときはkeysvalsで。

user> (keys m)
(:a :b :c)
user> (vals m)
(1 "a" foo)

要素追加(更新)

assocです。

user> (def updated-m (assoc m :d 100))
#'user/updated-m
user> updated-m
{:d 100, :a 1, :b "a", :c foo}
user> (assoc m :d 100 :e 200)
{:e 200, :d 100, :a 1, :b "a", :c foo}
user> (assoc m :b 100)
{:a 1, :b 100, :c foo}

immutableなのはいわずもがな。

要素削除

dissocです。

user> (dissoc updated-m :c)
{:d 100, :a 1, :b "a"}
user> (dissoc updated-m :d :c)
{:a 1, :b "a"}

immutableな(ry

ネストしたMap

便利なデータ構造として使うからにはネストしたい。たとえばこんな感じ。

(def nested-map {:lisp {:cl {:c 'sbcl, :java 'abcl},
                        :scheme {:c 'gauche, :java 'kawa}}, 
                 :ruby {:c 'matz-ruby :java 'jruby}})

いままでの知識だけでsbclをaclに変えたいとき、

user> (let [lisp-map (:lisp nested-map),
            cl-map (:cl lisp-map)]
       (assoc lisp-map :lisp (assoc cl-map :c 'acl)))
{:lisp {:c acl, :java abcl}, :cl {:c sbcl, :java abcl}, :scheme {:c gauche, :java kawa}}

こんな感じになります。さらにネストが深くなると。。。目も当てられないことになりますね。
こんなときのためにちゃんと関数が用意されています。

要素追加(更新)

assoc-inです。使い方は(assoc-in m [k & ks] v)。↑の例だと

user=> (assoc-in nested-map [:lisp :cl :c] 'acl)
{:lisp {:cl {:c acl, :java abcl}, :scheme {:c gauche, :java kawa}}, :ruby {:c matz-ruby, :java jruby}}

追加ももちろんできる。

user> (assoc-in nested-map [:lisp :cl :lisp] 'oreore-lisp)
{:lisp {:cl {:lisp oreore-lisp, :c sbcl, :java abcl}, :scheme {:c gauche, :java kawa}}, :ruby {:c matz-ruby, :java jruby}}

ちなみにdissoc-inはない。

要素更新(関数適用版)

現在の要素に関数を適用させて値を更新するupdate-inってのもある。使い方は(update-in m [k & ks] f & args)
変な例だけど、こんな感じ。

user> (update-in nested-map [:lisp :cl :c] str "-1.0.36") 
{:lisp {:cl {:c "sbcl-1.0.36", :java abcl}, :scheme {:c gauche, :java kawa}}, :ruby {:c matz-ruby, :java jruby}}

値取得

get-inもあるよー。使い方は(get-in m ks)

user> (get-in nested-map [:lisp :cl :java])
abcl

なんで(get-in m k & ks)にしなかったんだろ。。(assoc,updateと合わせるためかな。)


とりあえず、mapの使い方はこんな感じです。(他にも破壊的なassoc!dissoc!もあるけど、使わないほうが良いと思う。)
まあまあ使えると思いませんか?

Created at : 2010-03-23 01:50:57   Updated at : 2010-03-23 03:33:40
Category : Programming::Lisp::Clojure

Clojure+LeiningenでGoogle App Engineアプリ作成!

宣言していた通り、Clojure+Leiningenでやや簡単にGAEアプリをつくれるようにしてみました。
blankプロジェクトはこちら
Compojureは最新の0.4.0のスナップショットをjarに固めてClojarsにおきました。
leiningenのインストールがまだの場合はこちらを参考にインストールしてください。

Quick Start

Google App Engine SDKを用意していない場合はここからDLしてきてbinディレクトリをパスに追加してください。いまのところ1.3.1対応です(appengine-java-sdk-1.3.1.zip)

$ git clone git://github.com/making/clj-gae-blank.git
$ cd clj-gae-blank
$ lein compile
# 開発版サーバ起動
$ dev_appserver war # -> http://localhost:8080/にアクセスすると「Hello!」と出るはず。
# 本番環境へデプロイ
$ vi war/WEB-INF/appengine-web.xml # applicationタグ内に自分のAPPIDを記入
$ appcfg.sh update war # -> http://APPID.appspot.com/にアクセスすると「Hello!」と出るはず。

あとはソースみていじってください。コードは数行しか書いてないから雰囲気でわかると思います。

注意

  • lein replでwar/WEB-INF/classes, libにパスが通っていないみたいなので、projectホームに、シンボリックリンクを貼った方が良いです。ln -s war/WEB-INF/classes,ln -s war/WEB-INF/lib。lein-swakを使う場合も同様。
  • ローカルサーバ立ち上げた後、コンパイルしてもサーバ再起動しないと反映されません(いまのところ)
  • spinupに10秒くらいかかるのでちょっと遅いです。
  • データストア用の関数はまだ用意していません。
  • Compojure0.3.2と0.4.0は中身が別物。Ringに乗っとられた。CompojureとRingの話は今度記事書く。

Created at : 2010-03-22 06:15:56   Updated at : 2010-03-22 06:31:06
Category : Programming::Lisp::Clojure::Leiningen

Clojure+Hadoop(Cloud) = Cloudure(第一歩)

前回のClojureでHadoopのサンプルのうち、定型処理っぽい部分をマクロにしてみました。
プロジェクト名はClojure + Cloud = Cloudureです。
名前負けしてます。まだいまいち。(ClojureもHadoopもそんな使った事ない。。)

2010/03/07 Clojarsにデプロイしました(namespaceを変えました)
2010/03/22 namespaceをam.ik.cloudureに戻しました

マクロの定義はいまのとここんな感じ。使い方は

;; Mapper、Reducerを別関数にする場合
(defn hello-map [key value context]
  (.write context (Text. key) (IntWritable. (Integet/parseInt value))))
(defn hello-reduce [key values context]
  (.write context key (IntWritable. (reduce + (map #(.get %) values)))))
(defmapreduce hello
  :mapper hello-map
  :reducer hello-reduce)
;; Mapper、Reducerもいっしょに定義する場合
(defmapreduce hello
    :mapper ([key value context] (.write context (Text. key) (IntWritable. (Integet/parseInt value))))
    :reducer ([key values context] (.write context key (IntWritable. (reduce + (map #(.get %) values))))))

前者を展開した場合、

(do
 (gen-class
  :name
  am.ik.cloudure.core.hello.mapper
  :extends
  org.apache.hadoop.mapreduce.Mapper
  :prefix
  "hello-mapper-")
 (defn hello-mapper-map [G__2310 G__2311 G__2312 G__2313]
   (hello-map G__2311 G__2312 G__2313))
  (gen-class
   :name
   am.ik.cloudure.core.hello.reducer
   :extends
   org.apache.hadoop.mapreduce.Reducer
   :prefix
   "hello-reducer-")
  (defn hello-reducer-reduce [G__2314 G__2315 G__2316 G__2317]
    (hello-reduce G__2315 G__2316 G__2317))
  (defn get-hello-job
      ([] (get-hello-job true))
    ([set-jar?]
     (let [job (org.apache.hadoop.mapreduce.Job.)]
       (if set-jar?
           (.setJarByClass job (forName "am.ik.cloudure.core")))
       (doto
        job
        (.setMapperClass
         (forName "am.ik.cloudure.core.hello.mapper"))
        (.setReducerClass
         (forName "am.ik.cloudure.core.hello.reducer")))))
    {:tag java.lang.Class}))

後者は

(do
 (gen-class
  :name
  am.ik.cloudure.core.hello.mapper
  :extends
  org.apache.hadoop.mapreduce.Mapper
  :prefix
  "hello-mapper-")
 (defn hello-mapper-map [this key value context]
   (.write context (Text. key) (IntWritable. (parseInt value))))
  (gen-class
   :name
   am.ik.cloudure.core.hello.reducer
   :extends
   org.apache.hadoop.mapreduce.Reducer
   :prefix
   "hello-reducer-")
  (defn hello-reducer-reduce [this key values context]
    (.write context key (IntWritable. (reduce + (map #(.get %) values)))))
  (defn get-hello-job
      ([] (get-hello-job true))
    ([set-jar?]
     (let [job (org.apache.hadoop.mapreduce.Job.)]
       (if set-jar?
           (.setJarByClass job (forName "am.ik.cloudure.core")))
       (doto
        job
        (.setMapperClass
         (forName "am.ik.cloudure.core.hello.mapper"))
        (.setReducerClass
         (forName "am.ik.cloudure.core.hello.reducer")))))
    {:tag java.lang.Class}))

前回のサンプルのdefmapreduce版はこんな感じ。

まだまだですね。もっとHadoopを使って慣れながら改良していきたいと思います。

一応Cloudureを使うには、Clojarsに上げてあるので、project.cljに↓を追加すればOK

[am.ik/cloudure "0.1.0-SNAPSHOT"]

コメント・アドバイスがあれば@makingまで。

Created at : 2010-03-01 02:34:55   Updated at : 2010-03-22 05:30:54
Category : Programming::Lisp::Clojure