clojureのreduceの使いどころ

Posted by YpsilonTAKAI On 2011年7月3日日曜日 0 コメント
reduceの使いどころ

(その2も書きました)

reduceって全部足すとかそんな使い方しかしてなかったんだけど、コレクションを全部なめて何かをつくる。という捉えかたをするツールであると捉えると、かなり強力なツールであることがちょっとわかってきた。

たとえば、下の2つの処理は同じことをしている。

  (reduce #(conj (* 2 %1) %2) [] [1 2 3])
(map #(* 2 %) [1 2 3])

これならmapでやればいいわけだけれども、
でも、逆に言えば、reduceの方がやりたいことを細かく指定できるということなわけ。ここでは、「2倍したものをベクターにつっこむ」ということを指定している。ここは好きにいじることができるので、いろいろなことができる。
特にmapを作るような場合に有効なのではないかと思う。

たとえば、下のような処理。
ポーカーの問題のときに作ったんだけど、個数の多い順に数字を並べたいわけ。
単目的ならもう少しすっきりできたと思うけど、group-sameがあったからこうなってる。

(defn poker-sort [hand]
"Sort cand num. set letf according to card count.
ex. [2 2 3 7 7 8] -> (7 2 8 3) : 7 and 2 are two cards."
(map first
(sort (fn [a b]
(if (= (count a) (count b))
(> (first a) (first b))
(> (count a) (count b))))
(group-same (map first hand)))))

(defn group-same
"Split into same value group
ex. (2 3 1 3 1 3 3) -> [(1 1) (2) (3 3 3 3)]"
([col] (group-same (sort col) []))
([col res]
(if (empty? col)
res
(let [[head tail] (split-with #(= (first col) %) col)]
(recur tail (conj res head ))))))

これをreduceをつかってやってみると、

(let [col [2 3 1 3 1 3 3]]
(map first
(sort-by second >
(reduce #(assoc %1 %2 (inc (get %1 %2 0))) {} col)))

こんな風に書けてしまいます。

reduceの初期値に空のmapを食わせるところがミソ。(常識?)
reduceで1つずつ取り出し数字をキーにして、配列に入っている数を1増やすことでカウントしてるわけ。
最初に出現しとときのために、get関数には無かった場合に0を返すように指定してある。
すると、こんなmapが返ってくる。
   {1 2, 3 4, 2 1}
これをsortで2番目の数字で大きい順に並び替えると、
   ([3 4] [1 2] [2 1])
こうなる。で、これの先頭だけ取り出すと、
   (3 1 2)

どうですかね?

0 コメント:

コメントを投稿