ネストしたvector or hashmap

機械学習とかで隠れ変数を扱っているときに、「隠れ変数zに割り当てたサンプルの数を保存しておきたい」みたいな要望に答えるにはvectorやhashmapを使うのが上等手段だと思うが、隠れ変数が2段、3段となっていくとそれらの構造もネストしていってしまうわけで、インクリメントやデクリメントなどのアップデートが結構面倒なことになる(自分の場合3つくらい隠れ変数があった)。Clojureは注意しないと(注意しても、かもしれない)どんどん横に長くなってしまうが、ネストしたケースだとどうしようもなく長くなってしまいがち。

「うひょー、なんか逃げる方法ないの?」と思っていたらまさにどんぴしゃな関数があったのでメモ。

"assoc-in"、"update-in"、"get-in"。覚えましたし。適当に試してみたところ、mapがnestした場合だけでなく、vectorが混じってても大丈夫らしい。素晴らしい。

(assoc-in {0 {1 2}} [0 1] 100) ; {0 {1 100}}
(assoc-in {0 [0 1 2]} [0 1] 100) ; {0 [0 100 2]}

この付近のvector、list、map、set付近のデータ構造に関するユーティリティ関数はまさに生産性に関わってくるところなので早めに抑えておきたいところですね。語彙力っぽい感じ。ちなみに自分は下のcheatsheetをiPadに入れて気が向いたら眺めたりしています。

walk系は全然使ったことがないので抑えておきたいところ。

その他知らない関数メモ

sequence関係のユーティリティ関数で自分がよく知らないものをまとめておく。

reductions

reduceしていくときの中間値みたいなのも戻り値の要素に加えておいてくれる。Rでいうところにcumsumっぽいことが簡単にできて便利。reduce likeな関数なので初期値ももちろん取れる。

(reductions + [1 2 3]) ; (1 3 6)

zipmap

intoも似たようなことしなかったっけ...と思って違いを押さえる。intoはvectorをつなげていく。zipmapは最初のvectorをkeyにして二つ目をvalueにしたmapを返す。keyが被ってたら後ろのが生き残るらしい。intoとconcatは何が違うねん、、、と思ったけど、concatのほうはlazylistを返すそうなので、lazyなのが欲しいかそうでないかでintoとconcatを使い分けるとよいのかな。

(zipmap [:a :b :c :d :e] [1 2 3 4 5]) ; {:e 5, :d 4, :c 3, :b 2, :a 1}
(zipmap [:a :a :b :b :c] [1 2 3 4 5]) ; {:c 5, :b 4, :a 2}
(into [:a :a :b :b :c] [1 2 3 4 5]) ; [:a :a :b :b :c 1 2 3 4 5]
(concat [:a :a :b :b :c] [1 2 3 4 5]) ; (:a :a :b :b :c 1 2 3 4 5)

group-by

第二引数に渡したcollectionを第一引数fに渡したに適用した結果で分類してmapで返す。キーは当然ながらfを適用した結果。

(group-by count ["a" "as" "asd" "aa" "asdf" "qwer"]) ; {1 ["a"], 2 ["as" "aa"], 3 ["asd"], 4 ["asdf" "qwer"]}
(group-by odd? (range 10)) ; {false [0 2 4 6 8], true [1 3 5 7 9]}
(group-by :user_id [{:user_id 1 :uri "/"}
		    {:user_id 2 :uri "/foo"}
		    {:user_id 1 :uri "/account"}]) ; {1 [{:user_id 1, :uri "/"} {:user_id 1, :uri "/account"}], 2 [{:user_id 2, :uri "/foo"}]}

keep

exampleを見たら「それfilterと何が違うん...?」って感じだったけど、keepのほうはtrue falseでfilteringするのではなくnilかどうかでやる関数らしい。ちなみに似た関数にkeep-indexedというのがあって、これは無名関数のほうにindexも使えるようにした感じのものらしい。

(keep #(if (odd? %) %) (range 10)) ; (1 3 5 7 9)
(filter #(if (odd? %) %) (range 10)) ; (1 3 5 7 9)


(keep-indexed #(if (odd? %1) %2) (range 0 10)) ; (1 3 5 7 9)
(keep-indexed (fn [idx v]
		(if (pos? v) idx)) [-9 0 29 -7 45 3 -8]) ; (2 4 5)

split-at

指定した位置でcollectionをぶったぎる。split-withだと指定した条件を初めて満たすところでぶったぎる。take nとdrop nの組み合わせみたいなもの。

(split-at 2 [1 2 3 4 5]) ; [(1 2) (3 4 5)]
(split-with (partial >= 3) [1 2 3 4 5]) 

iterate

いかにもclojureらしい関数。無限リストを生み出すのだが、関数fと初期値xに関してx、(f x)、(f (f x))、(f (f (f x)))...というようなsequenceを作る。使うときにはtakeとかと合わせて、という感じかな。

(take 3 (iterate inc 0)) ; (0 1 2)

mapcat

複数のcollectionを引数に取り、それぞれをmapした結果を連結する。下のmapしてapply concatと等価、だと思う。

(mapcat reverse [[3 2 1 0] [6 5 4] [9 8 7]]) ; (0 1 2 3 4 5 6 7 8 9)
(apply concat (map reverse [[3 2 1 0] [6 5 4] [9 8 7]])) ; (0 1 2 3 4 5 6 7 8 9)

interleave

2つ、またはそれ以上のsequenceを受け取り、それらの要素を交互に持つようなlazy listを返す。これは例を見ればすぐ分かる。

(interleave '[1 3 5] '[2 4 6]) ; (1 2 3 4 5 6)
(interleave '[1 4 7] '[2 5 8] '[3 6 9]) ; (1 2 3 4 5 6 7 8 9)

interpose

リストの間に一個置きに何か要素を差し挟む関数。my-stringsの例は別途他の関数があるのでそちらを使えばいいが、まぁ例ということで。

(interpose 3 '[1 1 1 1]) ; (1 3 1 3 1 3 1)

(def my-strings ["one" "two" "three"])
(interpose ", " my-strings) ; ("one" ", " "two" ", " "three")
(apply str (interpose ", " my-strings)) "one, two, three"