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がSymbol
かKeyword
のときだけだと思う。多分。
この場合、keyを関数のように適用できる利点がある。変な例だけど
user> (map :b [m m m]) ("a" "a" "a")
関数ではget
が使える。
user> (get m :a) 1
keyやvalueのシーケンスがほしいときはkeys
、vals
で。
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!
もあるけど、使わないほうが良いと思う。)
まあまあ使えると思いませんか?