倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

GDK のコレクションがこんなに便利なわけがない。 (Collection 編6) Reduce 処理

今回は GDK が Collection インターフェースに追加しているメソッドの内、Reduce 処理を行うものを見ていきます。 Reduce 処理とは各要素もしくはそれから得た値などをある意味で「足し上げる」メソッドです。 今回扱うメソッドはは以下のもの:

Number count(Object value)
Number count(Closure closure)
Map countBy(Closure closure)

Object sum()
Object sum(Object initialValue)
Object sum(Closure closure)
Object sum(Object initialValue, Closure closure)

Object max()
Object max(Closure closure)
Object max(Comparator comparator)
Object min()
Object min(Closure closure)
Object min(Comparator comparator)

Object inject(Closure closure)
Object inject(Object initialValue, Closure closure)

Reduce 処理のメソッドは(おそらく)すべて inject() メソッドで書くことができます。 以下のサンプルでいくつかそういったコードを試してます。

メソッド 返り値 since 説明
count(Object)
count(Closure)
Number 1.0
1.8.0
条件にあう要素の数え上げ
countBy(Closure) Map 1.8.0 クロージャの処理結果でグループ化して各グループの要素数を数え上げ
sum()
sum(Object)
sum(Closure)
sum(Object, Closure)
Object 1.0
1.5.0
1.0
1.5.0
各要素の足し上げ
max()
max(Closure)
max(Comparator)
Object 1.0 最大値を取得
min()
min(Closure)
min(Comparator)
Object 1.0 最小値を取得
inject(Closure)
inject(Object, Closure)
Object 1.8.7
1.0
汎用的な Reduce 処理

では各メソッドのサンプルを。

count(), countBy() メソッド

まずは要素の数を数え上げる count(), countBy() メソッド。 count() は引数のオブジェクトと等価な要素、もしくは引数のクロージャが true を返す元の要素の数を数えて返します。 countBy() は各要素をクロージャで変換し、それを元に要素をグループ化してそのグループの要素数を返します。 次回やる予定の groupBy() で値のリストをそのサイズに置き換えたような処理です。

def langsDup = ['Java', 'Groovy', 'Groovy', 'Scala', 'Java', 'Clojure']

// count(), countBy()
assert langsDup.count('Java') == 2
assert langsDup.count{ it.contains 'a' } == 3
assert langsDup.countBy{ it.size() } == [4:2, 6:2, 5:1, 7:1]

// countBy() はこんな感じの処理かな? もちろん実装は違うだろうけど
assert langsDup.countBy{ it.size() } == 
        langsDup.groupBy{ it.size() }.collectEntries{ key, value -> [key, value.size()] }

countBy() は最初「いるか?こんなの?」と思ったけど(笑)、他のメソッドで書こうとするとちょびっと面倒かもね。 上記の最後のサンプルも、Map#withDefault() とか使うともっとエレガントに書けそう。

sum() メソッド

sum() メソッドはその名の通り、全ての要素を足し上げるメソッドです。 足し上げ処理に使われるメソッドは plus() メソッド、すなわち「+」演算子です。 数値の場合はそのままですが、文字列のように「+」演算子が定義されているオブジェクトのコレクションに対しても問題なく実行できます。 クロージャをとるメソッドは、クロージャによって+演算子が定義されているオブジェクト(数値など)に変換してから足し上げを実行します:

assert (0..10).sum() == 55

def langs = ['Java', 'Groovy', 'Scala', 'Clojure']
assert langs.sum() == 'JavaGroovyScalaClojure'    // String の和
assert langs.sum{ it.size() } == 4 + 6 + 5 + 7    // 文字列長の和
assert langs.sum(8){ it.size() } == 8 + 4 + 6 + 5 + 7    // 初期値指定

// sum(Closure) の書き換え
assert langs.sum{ it.size() } == langs.collect{ it.size() }.sum()

// count(Object) の書き換え
assert langs.count('Java') == langs.sum{ it == 'Java' ? 1 : 0 }

max(), min() メソッド

max(), min() メソッドはコレクション中の最大値、最小値となる要素を返すメソッド。 引数がない場合は要素のクラスが持っている自然順序で比較が行われます。 自然順序以外の比較を行いたい場合はクロージャや Comparator を引数にとるメソッドを使えます:

def langs = ['Java', 'Groovy', 'Scala', 'Clojure']

// 自然順序(String の場合は辞書順序)で比較
assert langs.max() == 'Scala'
assert langs.min() == 'Clojure'

// 文字サイズで比較(クロージャ使用)
assert langs.max{ it.size() } == 'Clojure'
assert langs.min{ it.size() } == 'Java'

// 最後の文字から見る辞書順序で比較(Comparator 使用)
assert langs.max{ s0, s1 -> s0[-1] <=> s1[-1] ?: s0[-2] <=> s1[-2] } == 'Groovy'
assert langs.min{ s0, s1 -> s0[-1] <=> s1[-1] ?: s0[-2] <=> s1[-2] } == 'Scala'

そう言えば、比較を行うときは「<=>」演算子ってのがあったんですね。 最後のサンプルでは、最後の文字の辞書順序(同じならその前の文字の辞書順序)を使って比較しています。

inject() メソッド

最後は Reduce 処理の汎用型、inject() メソッド。 これを使うと大抵の Reduce 処理は書けます。 クロージャの引数は、第1引数が初期値もしくはそれまで「足し上げられた結果」、第2引数が次の要素です。 結果として、前の足し上げ結果に次の要素を足し上げたものを返します。 初期値が指定されていない場合は第1引数が初期値として渡されます:

def langs = ['Java', 'Groovy', 'Scala', 'Clojure']

// sum() の再実装
assert langs.inject(''){ sum, s -> sum + s } == 'JavaGroovyScalaClojure'

// 要素の長さの積
assert langs.inject(1){ prod, s -> prod * s.size() } == 4 * 6 * 5 * 7

// 初期値を指定しない場合は第1引数が初期値
assert langs.inject{ sum, s -> sum + s } == 'JavaGroovyScalaClojure'
assert langs.inject{ prod, s -> prod * s.size() } == 'Java' * 6 * 5 * 7
        // 初期値は文字列 'Java' なので、整数をかけると文字列の繰り返しが返されるヨ

// min() の再実装
assert langs.inject{ s0, s1 -> s0.size() <=> s1.size() ? s0 : s1 } == 'Java'

要素を変換して「足し上げ」を行う場合は、初期値を指定しないと変な結果になる場合があるので注意。

次回は多重コレクションに関するメソッドを扱う予定。

プログラミングGROOVY

プログラミングGROOVY