倭マン's BLOG

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

Groovy in Addiction (7) : Collection に追加されたメソッド

今回はコレクション (java.util.Collection) について。 Groovy JDK を参考にしています(一覧)。

Groovyイン・アクション

Groovyイン・アクション

今回は、Groovy で Collection に施された拡張(追加されたメソッド)を見ていきます。 メソッドの振る舞い等は載せてません。 説明に関しては「Sample is Best」 だと思うので、あれこれサンプルコードを書きましたが、書いてたら結構量が増えてシンプルにはなりませんでした・・・

以下、表中にメソッドを Generics にしたものを書いていますが、これらは Collection<E> オブジェクトに対するものです。 ただし、仕様などではなく、正確でもありません(<? extends E> などを単純に <E> と書いたりしています)。

クロージャに関しては

  • it の型が E
  • 返り値が R

のとき、Closuer<E, R> と書いてます。

boolean を返すメソッド

メソッド名 返り値 メソッド名
(Generics)
返り値
(Generics)
asBoolean()** boolean
isCase(Object)** boolean
disjoint(Collection) boolean disjoint(Collection<E>) boolean
any()*
any(Closure)*
boolean any()
any(Closure<E, boolean>)
boolean
every()*
every(Closure)*
boolean every()
every(Closure<E, boolean>)
boolean
  • *付きは Object に定義されているメソッドです。
  • **付きは Object に定義されているメソッドをオーバーライドしています。

any は論理和、every は論理積ですかね。 disjoint は2つの集合が排反かどうかを検証します。

//***** disjoint(Collection) *****
list1 = (0..9) as List;
list2 = ('a'..'z') as List;
assert list1.disjoint(list2);

//***** any(), any(Closure) *****
list = [false, 1, null, '2'];
assert list.any();

result = list.any{ it != null };    // boolean を返すクロージャ
assert result;

//***** every(), every(Closure) *****
assert list.every() == false;

result = list.every{ it != null };    // boolean を返すクロージャ
assert result == false;

値オブジェクトを返すメソッド


数値や文字列を返すメソッド(asType はちょっと違うかな?)。 なんか、Visitor の代わりにクロージャを使って情報をかき集めるような処理。

メソッド名 返り値 メソッド名
(Generics)
返り値
(Generics)
asType(Class)** Object asType(Class<T>) T
count(Object) Number count(E) Number
join(String) String
toListString() String
inject(Object, Closure)** Object inject(R, Closure<R, E, R>) R
sum()
sum(Object)
sum(Closure)
sum(Object, Closure)
Object sum()
sum(E)
sum(Closure<E, R>)
sum(R, Closure<E, R>)
E
E
R
R
  • *付きは Object に定義されているメソッドです。
  • **付きは Object に定義されているメソッドをオーバーライドしています。
//***** asType(Class) *****
list = (1..9) as List;

//***** count(Object) *****
assert list.count(5) == 1;

//***** join(String) *****
assert list.join('-') == '1-2-3-4-5-6-7-8-9';

//***** toListString() *****
assert list.toListString() == '[1, 2, 3, 4, 5, 6, 7, 8, 9]';

//***** inject(Closure) *****
prod = 100;
prod = list.inject(prod) { prod, i -> prod * i };   // Object を返すクロージャ
assert prod == 100*1*2*3*4*5*6*7*8*9;

//***** sum() *****
assert list.sum() == 1+2+3+4+5+6+7+8+9;
assert list.sum(100) == 100+1+2+3+4+5+6+7+8+9;

result = list.sum { it * it };    // Object を返すクロージャ
assert result == 1*1 + 2*2 + 3*3 + 4*4 + 5*5 + 6*6 + 7*7 + 8*8 + 9*9;

要素を返すメソッド


指定した条件を満たすようなコレクションの要素を取得するメソッド。 条件を満たす要素が複数ある場合は、最初に見つかったものを返すと思います。

メソッド名 返り値 メソッド名
(Generics)
返り値
(Generics)
find(Closure) Object find(Closure<E, boolean>) E
max()
max(Comparator)
max(Closure)
 
Object max()
max(Comparator<E>)
max(Closure<E, Comparable<?>>)
max(Closure<E, E, int>)
E
min()
min(Comparator)
min(Closure)
 
Object min()
min(Comparator<E>)
min(Closure<E, Comparable<?>>)
min(Closure<E, E, int>)
E
list = (1..20) as List;

//***** find() *****
result = list.find { it % 7 == 0 }    // boolean を返すクロージャ
assert result == 7;

//***** max(), min() *****
assert list.max() == 20;
assert list.min() == 1;

//***** max(Closure), min(Closure) *****
result = list.max { it % 5};    // Comparable を返すクロージャ
assert result == 4;    // 最初の最大値

result = list.min { a, b -> (a % 5) - (b % 5) };    // int を返すクロージャ
assert result == 5;     // 最初の最小値

Collection 返すメソッド

メソッド名 返り値 メソッド名
(Generics)
返り値
(Generics)
asImmutable() Collection Collection<E>
asSynchronized() Collection Collection<E>
each(Closure)* Object each(Closure<E, ?>) Collection<E>
eachWithIndex(Closure)* Object eachWithIndex(Closure<E, int, ?>) Collection<E>
intersect(Collection) Collection intersect(Collection<E>) Collection<E>
findAll(Closure)** Collection findAll(Closure<E, R>) Collection<R>
collect(Collection, Closure)** Collection collect(Collection<R>, Closure<E, R>) Collection<R>
unique()
unique(Comparator)
unique(Closure)
 
Collection unique()
unique(Comparator<?>)
unique(Closure<E, ?>)
unique(Closure<E, E, int>)
Collection<E>
grep(Object)* Collection grep(Object) Collection<E>
  • *付きは Object に定義されているメソッドです。
  • **付きは Object に定義されているメソッドをオーバーライドしています。

each(), eachWithIndex() は、通常、返り値の Object (実際は Collection)は使わず、Closure の中で処理を行うのに使います。

intersect() は2つの集合の共通部分

unique() はダブった要素を1つにまとめます。 クロージャを引数にとるメソッドは、「ダブる」とは何かを指定します。 数学でいう、商集合を作る手続きと同じですかね。 「ダブる」とは何かを指定するクロージャが商集合を作る際の同値関係を指定してるってことで。

//***** intersect(Collection) *****
list1 = (1..20) as List;
list2 = (18..30) as List;
assert list1.intersect(list2) == [18, 19, 20];

//***** each(Closure), eachWithIndex(Closure) *****
list = ('a'..'e') as LinkedList;
str = '';
result = list.each{ str += it };
assert str == 'abcde';
assert result == ['a', 'b', 'c', 'd', 'e'];    // 通常、返り値は使わない
assert result instanceof LinkedList;

list = ('a'..'e') as List;
str = '';
result = list.eachWithIndex{ it, i -> str += it+i+' ' };
assert str == 'a0 b1 c2 d3 e4 ';
assert result == ['a', 'b', 'c', 'd', 'e'];

//***** findAll(Closure) *****
list = (1..10) as List;
result = list.findAll { it % 3 == 0 };    // boolean を返すクロージャ
assert result == [3, 6, 9]

//***** collect(Collection, Closure) *****
list = [1, 2, 3];
result = [0] as LinkedList;
result = list.collect(result){ it * it };    // Object を返すクロージャ
assert result == [0, 1, 4, 9];
assert result instanceof LinkedList;

//***** unique() *****
assert [1, 3, 3].unique() == [1, 3];

//***** unique(Closure) *****
list = (1..10).toList();
result = list.unique { it % 3 };    // int を返すクロージャ
assert result == [1, 2, 3];

list = (1..10).toList();
result = list.unique { a, b -> (a - b) % 3 };    // Object (Comparable) を返すクロージャ
assert result == [1, 2, 3];

//***** grep() *****
list = (1..10) as List;
filter = 2..5;
assert list.grep(filter) == [2, 3, 4, 5];

List を返すメソッド

メソッド名 返り値 メソッド名
(Generics)
返り値
(Generics)
asList() List asList() List<E>
toList() List toList() List<E>
getAt(String)** List getAt(String) List<E>
multiply(Number) List multiply(Number) List<E>
collect(Closure)** List collect(Closure<E, R>) List<R>
sort()
sort(Comparator)
sort(Closure)
List sort()
sort(Comparator<E>)
sort(Closure<E, R>)
List<E>
List<E>
List<R>
  • *付きは Object に定義されているメソッドです。
  • **付きは Object に定義されているメソッドをオーバーライドしています。
//***** getAt(String) *****
list = [1, 'x', 2.0];
assert list.getAt('class') == [Integer.class, String.class, BigDecimal.class];

//***** multiply(Number) *****
list = [1, 2];
assert list.multiply(3) == [1, 2, 1, 2, 1, 2];
assert list * 3 == [1, 2, 1, 2, 1, 2];    // multiply() は * 演算子の実装
assert (list * 3).size() == list.size() * 3; 

//***** collect(Closure) *****
list = [1, 2, 3];
result = list.collect { it * it };    // Object を返すクロージャ
assert result == [1, 4, 9];

//***** sort(), sort(Closure) *****
assert [3, 1, 2].sort() == [1, 2, 3];

list = ['abc', 'de', 'fghi', 'j'];
result = list.sort { it.size() };    // Comparable を返すクロージャ
result = ['j', 'de', 'abc', 'fghi'];

list = ['abc', 'de', 'fghi', 'j'];
result = list.sort { a, b -> a.size() <=> b.size() };    // int を返すクロージャ
result = ['j', 'de', 'abc', 'fghi'];

メタ・コレクションに関するメソッド


「配列の配列」は多重配列というので、「コレクションのコレクション」は多重コレクションと言うのでしょうか? どのくらい一般的なことかは分かりませんが、「○○の○○」をメタ○○というので*1、「コレクションのコレクション」をメタ・コレクションと勝手に呼ぶことにします*2

ここでは、メタ・コレクションに対してのメソッドか、呼び出した返り値がメタ・コレクションになるメソッドを見ていきます。

メソッド名 返り値 メソッド名
(Generics)
返り値
(Generics)
collectAll(Closure) List collectAll(Closure<E, R>)+ 下記参照++
collectAll(Collection, Closure) Collection collectAll(Collection<R>, Closure<E, R>)+ 下記参照++
split(Closure)** Collection*** split(Closure<E, boolean>) List<Collection<E>>
groupBy(Closure) Map groupBy(Closure<E, R>) Map<R, Collection<E>>
flatten()
flatten(Closure)
Collection flatten()+
flatten(Closure<E, R>)+
Collection<E>
Collection<R>
combinations() List combinations() List<E>
eachPermutation(Closure) Iterator eachPermutation(Closure<E, ?>)
  • *付きは Object に定義されているメソッドです。
  • **付きは Object に定義されているメソッドをオーバーライドしています。
  • ***split(Closure) の返り値は Collection となっていますが、常に(2要素の) List であると思われます(順序が決まっている)。 型階層に矛盾はありませんが。
  • +付きのメソッドは、任意の階層構造のコレクションに対して呼び出せます。
  • ++付き (collectAll) はメソッドを呼び出されたオブジェクトの階層構造を保存します。
  • combinations() メソッドは、Collection に対して呼び出すなら、返り値も Collection でないといけない気がしますが・・・
//***** collectAll(Closure) *****
list = [[1, 2, 3], [4], [5, 6]];
result = list.collectAll { it * it };    // Object を返すクロージャ
assert result == [[1, 4, 9], [16], [25, 36]];

//***** collectAll(Collection, Closure) *****
list = [1, [2, 3], [4], []];
result = ['0'] as LinkedList;
result = list.collectAll(result){ it.toString() };    // Object を返すクロージャ
assert result == ['0', '1', ['2', '3'], ['4'], []];
assert result instanceof LinkedList;

//***** split(Closure) *****
list = (1..6) as List;
result = list.split { it % 3 == 0 };    // boolean を返すクロージャ(真偽で分類)
assert result == [[3, 6], [1, 2, 4, 5]];
assert result.size() == 2;
assert result[0].size() + result[1].size() == list.size();

//***** groupBy() *****
list = (1..6) as List;
map = list.groupBy { it % 3 };    // Number を返すクロージャ(返り値の値で分類)
assert map == [0:[3, 6], 1:[1, 4], 2:[2, 5]];

//***** flatten() *****
list = [[0, 1], [2, 3]];
assert list.flatten() == [0, 1, 2, 3];

list = [['a', 'b'], 'cd', 'efg'];
result = list.flatten { it.toList() };    // Object を返すクロージャ(配列、コレクション以外の要素の処理の仕方)
assert result == ['a', 'b', 'c', 'd', 'e', 'f', 'g'];

//***** combinations() *****
list = [['a', 'b'], ['1', '2']];
assert list.combinations() == [['a', '1'], ['b', '1'], ['a', '2'], ['b', '2']];
assert list.combinations().size() == list[0].size() * list[1].size();
assert list.combinations().size() == list.inject(1){n, inList -> n * inList.size()};

//***** eachPermutation() *****
list = ['a', 'b', 'c'];
result = new LinkedList();
list.eachPermutation { result.add(it) };
assert result == [['a', 'b', 'c'], ['a', 'c', 'b'], ['b', 'a', 'c'], ['b', 'c', 'a'], ['c', 'a', 'b'], ['c', 'b', 'a']];

n = 1; 1.upto(list.size()){ n *= it };
assert result.size() == n;

*1:「情報の情報」をメタ情報、「言語に対する言語」をメタ言語、「コミュニケーションに関するコミュニケーション」をメタ・コミュニケーション、「萌えてる自分に萌える」ことをメタ萌える・・・とか。

*2:数学では、「集合の集合」のことは集合族 (a family of sets) と呼びますが、これもメタ集合 (meta set) と呼ぶ方が分かりやすいと思うんですけど。 誰か広めてww