TL;DR

  • Clojure のマップは、clojure.lang.IPersistentMap インタフェースを実装したインスタンスである。
  • clojure.core には、マップを作る関数、要素を追加・更新・削除・取得する関数、マップをマージする関数がそろっている。ネストしたマップには assoc-in、get-in、update-in を使う。

内容

マップの種類と作成

マップには、配列マップ(PersistentArrayMap)、ハッシュマップ(PersistentHashMap)、ソート済みマップ(PersistentTreeMap)がある。{...} のリテラルと array-map は配列マップを作る。hash-map はキーと値のペアからハッシュマップを作り、キーが重複すると最後の値が使われる。

(type {:a 1 :b 2})         ; clojure.lang.PersistentArrayMap
(array-map :a 1 :b 2 :c 3) ; {:a 1, :b 2, :c 3}
(hash-map :a 1 :b 2 :a 3)  ; {:b 2, :a 3}

sorted-map はキーの昇順でソートしたマップを作り、sorted-map-by は比較関数でソートしたマップを作る。map? は、引数が IPersistentMap を実装しているかを判定する。

(sorted-map :a 1 :c 3 :b 2)                 ; {:a 1, :b 2, :c 3}
(sorted-map-by > 3 "three" 1 "one" 2 "two") ; {3 "three", 2 "two", 1 "one"}
 
(map map? ['() [] #{} {}]) ; (false false false true)

追加・更新・削除

assoc は、マップでは指定したキーの値を置き換えた新しいマップを返す。ベクタにも使え、指定したインデックスに値を入れた新しいベクタを返す。範囲外のインデックスを指定すると IndexOutOfBoundsException になる。dissoc は、指定したキーを削除した新しいマップを返し、キーがなければ何もしない。

(assoc {:a 1 :b 2} :a 3 :c 4) ; {:a 3, :b 2, :c 4}
(assoc [1 2 3] 1 4)           ; [1 4 3]
(assoc [1 2 3] 3 4)           ; [1 2 3 4]
 
(dissoc {:a 1 :b 2 :c 3} :b) ; {:a 1, :c 3}
(dissoc {} :a)               ; {}

update は、キーの値に関数を適用した結果を新しい値とするマップを返す。キーがなければ、関数には nil が渡される。update-vals はすべての値に、update-keys はすべてのキーに関数を適用する。

(update {:a 1 :b 2} :b inc)               ; {:a 1, :b 3}
(update {:a 1 :b 2} :a + 10)              ; {:a 11, :b 2}
(update {:a 1 :b 2} :c (constantly 42))   ; {:a 1, :b 2, :c 42}
 
(update-vals {:a 1 :b 2 :c 3} inc)        ; {:a 2, :b 3, :c 4}
(update-keys {:one 1 :two 2 :three 3} name)
;; {"one" 1, "two" 2, "three" 3}

ネストしたマップ

assoc-in と update-in は、assoc と update のネスト対応版である。キーのパスに沿って値を更新し、途中のキーがなければ作る。get-in はキーのパスに沿って値を取り出し、見つからなければ nil か第 3 引数の値を返す。

(assoc-in {:a {:b 2}} [:a :b] 3) ; {:a {:b 3}}
(assoc-in {} [:a :b :c] 1)       ; {:a {:b {:c 1}}}
 
(def m {:a {:b 1 :c 2} :x 5})
(update-in m [:a] assoc :b 3 :d 4)
;; {:a {:b 3, :c 2, :d 4}, :x 5}
(update-in m [:y :z] (fn [x] (if x (inc x) 1)))
;; {:a {:b 1, :c 2}, :x 5, :y {:z 1}}
 
(get-in {:a {:b 1 :c 2}} [:a :b])             ; 1
(get-in {:a {:b 1 :c 2}} [:a :d] "not-found") ; "not-found"

取り出しとマージ

select-keys は、指定したキーとその値だけを持つ新しいマップを返す。存在しないキーは無視される。

(select-keys {:a 1 :b 2 :c 3} [:a :c :d]) ; {:a 1, :c 3}

merge は複数のマップを 1 つにまとめる。同じキーがあれば、後ろのマップの値が使われる。引数がなければ nil を返す。merge-with は、同じキーの値を指定した関数で結合する。

(merge {:a 1 :b 2} {:b 3 :c 4})            ; {:a 1, :b 3, :c 4}
(merge)                                     ; nil
(merge-with + {:a 1 :b 2} {:a 3 :c 4})      ; {:a 4, :b 2, :c 4}
(merge-with str {:a "A"} {:a "a" :b "B"})   ; {:a "Aa", :b "B"}

マップエントリ

find は、キーに対応するマップエントリ(キーと値のペア、clojure.lang.MapEntry)を返し、見つからなければ nil を返す。map-entry? はマップエントリかを判定する。key と val は、マップエントリのキーと値を返す。

(find {:a 1 :b 2 :c 3} :a)       ; [:a 1]
(map-entry? (first {:a 1 :b 2})) ; true
(map key {:a 1 :b 2 :c 3})       ; (:a :b :c)
(map val {:a 1 :b 2 :c 3})       ; (1 2 3)

構造体

defstruct マクロは構造体の定義を作り、Var に束縛する。create-struct は定義を作るだけで、Var には束縛しない。struct は定義の順に値を渡してインスタンス(clojure.lang.PersistentStructMap)を作り、struct-map はキーを指定して値を渡す。

(defstruct person :name :age)
(struct person "Bob" 20)                ; {:name "Bob", :age 20}
(struct-map person :age 20 :name "Bob") ; {:name "Bob", :age 20}

キーワード