clojureのreduceの使いどころ その2

Posted by TAKAIY On 2011年8月28日日曜日 0 コメント
前に書いたreduceのポストのトラフィックが意外に高くて気をよくしたのでもうちょっと書いてみる。


reduceの使いかたは

その1 (reduce f coll)
その2 (reduce f val coll)

fは2つの引数を採って1番目の引数と同じ形の値を返す関数。どちらの場合でも、collの要素を一つずつ2番目の引数にしながら、collが無くなるまで、fを呼び出していくけれど、一番最初に違いがある。

その1の書式では、最初の引数は、collの1番目と2番目の要素になる。ということは、reduceの結果は要素と同じ形のものしか作れないことになる。

しかし、その2では、最初の引数はvalと1番目の引数になる。これはreduceの結果がvalと同じ形になることを意味しているので、好きな結果を得ることができるのだ。


さて、こんなデータがあるとする。

(def sample-data [:a :b 11 22 :c :d 33 :e :f])

まずは、この中の数字の数を数える関数を作ってみる。

(reduce f val coll)
collはsample-dataですね。
欲しいデータは数字の個数で、その初期値だからvalに入れるのは0にします。
fは2つの引数(count tgt)を受け取って、tgtが数字ならcountを1増やした値を返す関数ということになります。
こんな感じ

(reduce <数字だったら1増やす> 0 sample-data)


作るとこうなります。
(defn count-num [lst]

(reduce (fn [count tgt] ;; 2つの引数を採って

(if (number? tgt) ;; 数字だったら

(inc count) ;; 1増やしたもの

count)) ;; そうでなかったら、そのまま

0 lst))



(count-num sample-data)

3


この例は、filter と countを使えば簡単にできてしまうけれど、数字だけじゃなくて、それ以外のものの個数も数えたかったらちょっと面倒じゃないかな。 でも、reduceを使うと簡単。
(reduce f val coll)

valに入れるのは、やっぱり欲しいデータの初期値。こんどは2つの値が必要です。それをまとめるのであれば、ベクタを使うのがいいですね。数字もそれ以外のものも0個なので、0が2つだから[0 0] とします。

関数はこう。

(reduce <数字だったら左を+1、そうでなかったら右を+1> [0 0] sample-data)


作ると、

(defn count-num-other [lst]

(reduce (fn [count tgt]

(if (number? tgt)

[(inc (first count)) (last count)] ;; 数字だったら左を+1

[(first count) (inc (last count))])) ;; そうでなかったら右を+1

[0 0] lst))



(count-num-other sample-data)

[3 6]


ここまでは、使ったデータを全部捨てていましたが、当然、使うこともできます。こんどは分類してみます。
結果として、数字とそれ意外のものそれぞれリストが欲しい。、初期値はからなので、valは [[] []].

関数はこう。

(reduce <数字だったら左に入れる、そうでなかったら右に入れる> [[] []] sample-data)


(defn num-or-other [lst]

(reduce (fn [[nlist olist] tgt]

(if (number? tgt)

[(conj nlist tgt) olist] ;;数字だったら左に追加

[nlist (conj olist tgt)])) ;;それでなかったら右に追加

[[] []] lst))



(num-or-other sample-data)

[[11 22 33] [:a :b :c :d :e :f]]




さて、最後は、ちょっと毛色を替えて、数字を出現順の番号に入れかえてみる。google groupのclojure-jaにあった問題。

[:a :b 11 22 :c :d 33 :e :f] を [:a :b 1 2 :c :d 3 :e :f]) のようにしたい。
数字は、11 22 33 の順に出てくるので、11 を 1、22 を 2、33 を 3に置き替えるということ。

カウンターとしてmutableな変数を使ってしまう手もあるけれど、それはだめ。つまらない。でも、カウンターが無いと、番号を振れない。そこで、最初にやった個数を数える問題を改めて見てみると、立派にカウントしているではないですか。リストを順に処理して、33が来たときにちゃんと3を生成していて、これが最終的な答えになっている。今回、このカウンターは最終結果には不要だけれども、途中では必要。最後に捨ててしまうことにする。

(reduce f val coll)
valは、カウンターと結果の初期値 [] を入れるので、 [1 []] になる。 1じゃなくて0でもOKだけれども、処理がちょっと変わる。
関数はこう。

(reduce

<数字だったらカウンタを+1して、リストにカウンタを入れる、

そうでなかったら、データをリストに入れる>

[1 []] sample-data)


(defn seq-num [lst]

(second ;; 結果のリストだけ抜きだす。

(reduce (fn [[counter lst] tgt]

(if (number? tgt)

[(inc counter) (conj lst counter)] ;; 数字だったら、カウンタを+1して、

;; カウンタをリストに入れる

[counter (conj lst tgt)])) ;; そうでなかったら、カウンタはそのまま

;; データをリストに入れる

[1 []] data)))





(seq-num data)

[:a :b 1 2 :c :d 3 :e :f]

このときの second に入る前のデータは、
[3 [:a :b 1 2 :c :d 3 :e :f]]
のようになっています。

0 コメント:

コメントを投稿