uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

1.8.1以降をキャッチアップするシリーズその5、collectAllのcollectNestedへのリネーム、collectManyの新規追加

Groovy 1.8.1以降で追加されたGDKメソッド(主にコレクション関係)を解説するシリーズ第5段、「collectAllのcollectNestedへのリネーム、collectManyの新規追加」です。とりあえずこれでシリーズ打ち止め予定です。

collect系とfind系の概括的な違い

まずは、自明かもしれませんが、collect系とfind系との違いを示します。

  • find系は、抽出条件を表すクロージャを与えて、コレクションの要素それぞれにクロージャを適用し、要素を抽出します。クロージャの返り値が要素を抽出するかどうかを決定します。一般に、findの返り値がコレクションならばそのコレクションの要素数は元のコレクションの要素数と比べて減少します*1
  • collect系は、要素の変換を行うクロージャを与えて、コレクションの全ての要素についてクロージャで変換した値からなるコレクションを返します。クロージャの返り値は要素を抽出するかどうかにはかかわりません。基本的に元のコレクションと同じ要素数のコレクションが返ります*2

collect系GDKメソッド一覧

以下に、Groovy 1.8.1でのcollect系のメソッドを列挙しておきます。

  1. List collect(Closure closure)★
  2. Collection collect(Collection collection, Closure closure) →追加的な1
  3. List collectAll(Closure closure) → 5の別名
  4. Collection collectAll(Collection collection, Closure closure) →追加的な3。6の別名
  5. List collectNested(Closure closure)
  6. Collection collectNested(Collection collection, Closure closure) →追加的な5
  7. Collection collectMany(Closure closure)
  8. Map collectEntries(Closure closure) ★
  9. Map collectEntries(Map result, Closure closure) →追加的な8

太字の5,6,7が1.8.1で追加されたメソッドです。

いっぱいあるように見えますが、2、4、6、9の意味はそれぞれ1、3、5、8と同じで、ただし変換した結果を格納する初期値としてのコレクションを引数に与えるバージョンであり、処理結果を引数のコレクションに追加していきます(上の列挙では「追加的なx」と記述しました)。また、後述のようにcollectAll(3,4)はcollectNested(5,6)の別名という扱いになったので、本質的には1、5、7、8の4つ(★)の意味を押さえておけば良いわけです。

collectAllのcollectNestedへのリネーム

Groovyでは1.8.1では以下のメソッドが追加されました。(前述の5と6)

  • public List collectNested(Closure closure)
  • public Collection collectNested(Collection collection, Closure closure)

とはいうもの、従来の「collectAll」が「collectNested」にリネームされた*3というものなので、collectNetedの意味は、従来のcollectAllと同じです。

collectAll改めcollectNestedの意味は、おさらいになりますが、コレクションのすべての要素(コレクションがコレクションを含んでいるなら、再帰的にコレクション内の要素をたどって)について、引数に指定したクロージャで変換した値を、もとの再帰構造を保存したコレクションの形で返却するというものです。
collectNestedとcollectの違いは、collectは要素であるコレクションを再帰的にたどることはしないということです。collectはトップレベルのコレクションの要素数に等しい回数しかクロージャが呼ばれません。

assert [1,2,[3,4],5].collectNested{it*2} == [2,4,[6,8],10]
assert [1,2,[3,4],5].collectAll{it*2} == [2,4,[6,8],10]  // collectNestedと同じ
assert [1,2,[3,4],5].collect{it*2} == [2,4,[3,4,3,4],10] // [3,4]*2 = [3,4,3,4]。
// リスト[3,4]に再帰的に適用されるのではなく、[3,4]という1つの要素としてあつかわれている

collectManyの新規追加

さらに、Groovyでは1.8.1では以下のメソッドが追加されました。(前述の7)

  • public Collection collectMany(Closure closure)

結局のところ、Groovy 1.8.1でcollect系に本当に新しく追加されたメソッドはこのcollectManyのみです。

collectManyは、collectと同様に、変換元のコレクションの要素にコレクションが含まれていても再帰的にたどることはしません。
collectManyの特徴は、変換クロージャの返り値がリスト(など)なので、複数もしくは0個の値を返せる、という点です。リストに含まれる値は結果のコレクションにフラットに追加されます。

なので、コレクションからコレクションの変換に際して、要素が1対1対応にはならない変換を行うことができます。

assert [1,2,3].collectMany{[it*3, it*3+1]} == [3,4,6,7,9,10]

追記。collectManyはflatMapです。

まとめ

collect系をまとめてみました。

項番 GDKメソッド 説明 入れ子になったコレクションの処理 結果
1 List collect(Closure closure) 再帰的にたどらない 要素に対してclosureを適用した結果からなるリスト
2 Collection collect(Collection collection, Closure closure) 追加的な1 1と同じ 要素に対してclosureを適用した結果からなるコレクション
3 List collectAll(Closure closure) 5の別名 5と同じ 5と同じ
4 Collection collectAll(Collection collection, Closure closure) 追加的な3。6の別名 6と同じ 6と同じ
5 List collectNested(Closure closure) 再帰的にたどる 要素に対してclosureを適用した結果からなる、入れ子構造を保ったリスト
6 Collection collectNested(Collection collection, Closure closure) 追加的な5 5と同じ 要素に対してclosureを適用した結果からなる、入れ子構造を保ったコレクション
7 Collection collectMany(Closure closure) 再帰的にたどらない 要素に対してclosureを適用した結果をフラット化して接続したコレクション
8 Map collectEntries(Closure closure) マップを対象 再帰的にたどらない マップに含まれるエントリ(Key,Value)に対してclosureを適用した結果からなるマップ
9 Map collectEntries(Map result, Closure closure) 追加的な8 8と同じ 8と同じ

ここでも太字が1.8.1で追加されたメソッドです。
Collection collectMany(Collection collection, Closure closure)が無いのが気になるな…。

以上でGroovy 1.8.1におけるコレクションGDKメソッドに対する変更の主要部分の説明は終りです。あと何個か残ってた気もするので、つづくかも。読んで下さった方、おつかれさまでした、ありがとー。

プログラミングGROOVY
プログラミングGROOVY
posted with amazlet at 12.01.04
関谷 和愛 上原 潤二 須江 信洋 中野 靖治
技術評論社
売り上げランキング: 75693

*1:この表現は少々正確ではない。findResultsでクロージャがリストを返す場合などがあるし。

*2:これも正確ではない。collectManyでは要素数が増減する。

*3:collectAllも互換性のために(?)collectNestedを呼び出す形に変更されてメソッドとしてはそのまま残されている。