uehaj's blog

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

findAllとgrepの違い

自分が曖昧に理解していたのでメモ。

findAllとgrepの違い

Collection#findAll(Closure clos)
引数で指定した1引数のクロージャclosが真を返すもののコレクションを返す。たとえば
clos.call(a)==true,
clos.call(b)==false,
clos.call(c)==true
のとき、[a,b,c].findAll(clos)は[a,c]が返る。マップに対して2引数のクロージャを引数として渡してfindAllを呼び出したときの動作については後述。
Collection#grep(Object x)
引数で指定したオブジェクトxに対して、x.isCase()が真を返すもののコレクションを返す。たとえば
x.isCase(a)==true,
x.isCase(b)==false,
x.isCase(c)==true
のとき、[a,b,c].grep(x)は[a,c]が返る。

少しだけややこしいのは、isCase()は、クロージャに対しては、クロージャを呼び出した結果の値を返すように定義されていることです。

Closure x
assert x.isCase(y) == x.call(y)

このため、grepにクロージャを渡すとfindAllと結果的に同じ動作になります。

さらに、findAllにはもう一つ特徴がありまして、それは、マップに対し、2引数のクロージャを引数にfindAllを呼び出すと、それぞれのマップエントリのキーとバリューを引数として呼び出してくれるということです。

まとめ

まとめますと、まず使い分けに関しては曖昧さがあるのは1引数のクロージャの場合だけです。例えば

(1..100).grep(2..5)
('A'..'Z').grep(~/[XYZ]/)

のように非クロージャを渡すときはgrepを使うしかありませんし、マップエントリに対して

[a:1, b:2, c:3].findAll{key, value-> value == 3}

の様に2引数のクロージャで抽出する際にはfindAllを使うしかありません*1

リストに対して、もしくはマップに対して1引数クロージャで抽出する場合に限ってgrepでもfindAllでもどっちでもいいわけですが、性能に関して言えば、ほんの少しだけ(isCaseを呼ばなくて済む分だけ)、findAllの方が速いかもしれません。

*1:上は1引数のクロージャでもfindAll{entry-> entry.value == 3}のように同じことができる。