uehaj's blog

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

1.8.1以降をキャッチアップするシリーズその2、複数クロージャによる多段groupBy

あけましておめでとうございます。書き初めならぬ、エントリ初め。今年もよろしくお願いします。みなさまにとって飛躍の年になりますように。

さて、Groovy 1.8.1以降で追加されたGDKメソッド(主にコレクション関係)を解説するシリーズ第2弾、「複数クロージャによる多段groupBy」です。

通常のgroupBy

「多段groupBy」を説明するまえに、従来の(単段)groupByの動作について説明しておきましょう。groupByは「コレクションを分類する」ものです。「要素がどんな分類に所属するか」を定義するクロージャ(ここでは「分類クロージャ」とでも呼んでおきます)を渡してやると、「分類」と、「その分類に所属する要素」をマップのキー、バリューで表現して返してくれます。図にするとこうです。

f:id:uehaj:20120101055150p:image
バリュー部分はリストになります。
たとえば、文字列のコレクションを「先頭文字のアルファベット(大文字)」で分類したいのであれば、文字列の先頭文字を返すようなクロージャを与えるだけです。

collection = ['dog','doctor', 'cat', 'ash', 'april']
result = collection.groupBy{ it[0].toUpperCase() }
assert [D:['dog', 'doctor'], C:['cat'], A:['ash', 'april']] == result

groupByは、一言で言ってSQLのSELECT文でGROUP BY句を指定することに相当するような機能です。

複数クロージャによる多段groupBy

さて本題ですが、上のようなgroupByに対してgroovy 1.8.1で追加されたのは以下のバリエーションです。

  • public Map groupBy(Object closures)
  • public Map groupBy(List closures)

1つめのObjectを渡す方は、クロージャの配列、もしくは複数のクロージャを可変個数引数で渡すことができます。2番目のリストを渡す方でも、いずれも複数のクロージャを渡すことができます。

複数のクロージャを渡す場合、2番目以降のクロージャは、1番目の分類結果のバリュー部分をさらに2番目以降のクロージャで分類することができます。ネストしたマップが返ってきます。
例えば、以下のようなPersonクラスがあったとします。

class Person { String name, String gender }

以下のようにPersonのリストpeopleに対して、まず性別で分類し、次に名前で分類する、ということができます。

class Person { String name; String gender; String toString(){ name } }

people = [ new Person(name:'Yamada Tarou', gender:'male'),
           new Person(name:'Yamada Hanako', gender:'female'),
           new Person(name:'Mine Fujiko', gender:'female'),
           new Person(name:'Jigen Daisuke', gender:'male'),
           new Person(name:'Lupin III', gender:'male') ]

result = people.groupBy { it.gender } { it.name[0] }
print result

//以下が表示される
//  [male:[Y:[Yamada Tarou], J:[Jigen Daisuke], L:[Lupin III]], female:[Y:[Yamada Hanako], M:[Mine Fujiko]]] 

3段以上のネストも可能です。