[はじめようApache Groovy] #06 - コレクションその1(リスト編)

こんにちは!なるーらぼです。GroovyでもJavaと同様にオブジェクトの集まりであるコレクションを操作することができます。パッと見たところは配列ですので、気楽に勉強していってください。今回は配列のようなもの、リストです。


リスト

リストは配列のようなもので、実際のところ「java.util.ArrayList」インターフェースを実装したものになっています。

def arr = [1,2,3]

このように角かっこでリテラルを記述します。これはJavaなんかと同じですよね。リストの要素数を取得するには「size()」メソッドを使います。要素数ゼロ、つまり空のリストを作成するには角かっこのみを使うと作成することができます。

def emptyList = []

さらに、既存のリストから新たにリストを作成することもできます。方法は2つあって、リストクラスのコンストラクタへ既存のリストを渡して生成する方法と「clone()」メソッドを利用する方法があります。

def arr2 = new ArrayList<Integer>(arr)
def arr3 = arr.clone()

こうして作成されたオブジェクトは各要素の値が同じになります。さて、各要素へのアクセスの仕方は古典的な配列アクセスと同じようにもできますし、Javaのように「getAt()」メソッドを利用することもできます。Groovyではさらに「get()」メソッドを利用することができて、これは「getAt()」の別名のようなものです。

arr[1]
arr.getAt(1)
arr.get(1)

なお、配列要素へのアクセスをするときに指定するインデックスは負の整数を利用することができます。この場合はリストの末尾からアクセスすることになります。

assert arr[-1] == 3
assert arr[-3] == 1

ただし、1つ注意が必要なことがあります。「get」メソッドを利用するときは負のインデックスを指定することができません。これは例外が発生します。

さらに各要素への値のセットも同様で、3種類の方法から用途に応じて使い分けてください。セットのときは「get」だった部分を「set」にしたメソッドを使うようになります。2つ目の引数にセットする値を入れましょう。

arr[1] = 2
arr.setAt(1, 2)
arr.set(1, 2)

ちょっと変わったところでは、1つのリストに型がまったく異なるものを混在させてつくることができるというものがあります。気持ち悪いかもしれませんが。

def arr = [1, 2.5f, "hello", null, false]


リストを反復しよう

よくあるリスト中の要素に繰り返し何かをするというようなケースがめちゃくちゃありますよね。こうした場合、リストのもつ「each」メソッドか「eachWithIndex」メソッドを使います

それぞれの違いはeachは各要素の値のみが、eachWithIndexは名前のとおりインデックス付きで利用することができます。

[1,2,3].each {
 println it
}

上記の例では暗黙のパラメータ「it」へリストの各要素がセットされるので順に「1、2、3」と出力されていきます。

[1,2,3].eachWithIndex { it, i ->
 println "$i -> $it"
}

上記の例ではインデックスを明示的に「i」というパラメータで受け取って「0 -> 1」のような形式で出力します。さらに、こうした反復をショートカットした書き方もすることができて、「*」記号を使います。これを展開演算子といいます。

[1,2,3]*.plus(2) // 3,4,5となる

これを先ほどのように波括弧つきで書き直すとこのようになります。

[1,2,3].collect { it.plus(2) }

この「collect」というメソッドは引数に別のリストを渡すと新たに結合したリストを作成します。

def list1 = [1,2,3]
def list2 = [4,5,6].collect(list1) { it }
// list2 == [1,2,3,4,5,6]

もちろん、波括弧の中で各要素に加工を施せば結合してかつ編集されたリストを得ることができます。


リスト操作

リストは便利なメソッドがたくさんあります。たとえば各要素のうちから何か特定のものを探したいときは「find」メソッドを使います。

[1,2,3,4,5].find { it < 3 }

このメソッドは条件に当てはまる要素のうち、最も小さい要素を返します

上記の例では「1」が返されます。しかし、条件に当てはまる要素すべてがほしかったーということもあるでしょう。そうしたときには「findAll」を使ってみてください。

[1,2,3,4,5].findAll { it < 3 }

上記の例では[1, 2]という新たなリストが返されます。さらに、要素ではなくインデックスがほしかったーということもあるでしょう。そうしたときには「findIndexOf」を使います。

ただ、このメソッドは最初に見つかった要素のインデックスを返します

[1,2,3,4,5].findIndexOf { it < 3 }

上記の例ではゼロが返されることになります。負等号の向きを逆にすれば3が返されます。


次はインデックスのみを調べたい場合です。ある値がリストに含まれているかどうかを調べるには「indexOf」メソッドを使います。これも最初に見つかったインデックスを返します。また、「lastIndexOf」メソッドを使えば逆順からも検索することができます。

[1,2,3,4,5].indexOf(3) // 2
['a','b','c','d','c'].lastIndexOf("c") // 4

いずれもリストに存在しない場合は「-1」を返します。また、リストの各要素をチェックすることもできます。リストのすべての要素が指定された条件に合致する場合に真(true)を返すには「every」メソッドを使います。

[1,2,3].every { it < 5 }

一部が条件に合致していればよいという場合は「any」メソッドを使います。

[1,2,3].any { it < 3 }

リストの内容をすべて足しこみたいなーというニーズに応えるには「sum」メソッドを使います。これは数値でもいいですし文字列でも足しこんでくれます。

[1,2,3].sum() // 6
['a','b','c'].sum() // abc

また、その動作をカスタマイズすることができます。まずは波括弧のなかでカスタマイズする方法です。各要素は暗黙のパラメータ「it」に渡されるのでそれを利用してカスタマイズします。

[1,2,3,4,5].sum { it * 2 }

上記では普通に合計すると15になりますが、各要素を2倍しているので30が返されます。数値も文字列も足しこまれましたが、リストも足しこむことができます。

[[1,2,3],[4,5,6],[7,8,9]].sum() // [1,2,3,4,5,6,7,8,9]

さらに、要素を足しこむときにリストとは全く無関係の値も併せて足しこむことができます。sumメソッドの引数に値を渡してやります。

[1,2,3,4,5].sum(6000) // 6015

これを利用すると、空のリストでも特定の値を返させることができます。


配列の各要素を特定の文字列で結合していくことも多いですよね。例えば簡易的にCSVをつくったりとか…そういう場合は「join」メソッドを使います。もしも要素は要素だけで結合して、付加的に何かを追加したい場合は「inject」メソッドを使います。

[1,2,3].join("-") // 1-2-3
[1,2,3].inject("get wild and ") { str + item } // get wild and 123
[1,2,3].inject(6) { num + item } // 12

ところで、配列の中でどの要素が一番大きいか、小さいか、というのを取り出さなければならないこともあるでしょう。そういったときに便利なのが最大の要素を取り出す「max()」メソッド、最小の要素を取り出す「min()」メソッドです。

[5,2,7,1,9].max() // 9
[5,2,7,1,9].min() // 1

上記のような数値のリストであればわかりやすいのですが、文字の場合は・・・?

['a','g','A','z'].max() // z
['a','g','A','z'].min() // A

文字のコード順ということですね。では文字列の場合はどうでしょう?こういったときは事前に「何を大きいとするか」というルールを決める必要があります。ルールもsumメソッドのときのように波括弧の中へ記述してやることができます

たとえば、「文字列の長さ」というのが大きさを表すとすると、次のようになります。

["nalulabo", "m0t0k1m0t0k1","yes", "no"].max { it.size() } // m0t0k1m0t0k1
["nalulabo", "m0t0k1m0t0k1","yes", "no"].min { it.size() } // no

この波括弧部分はComparatorオブジェクトとして別に定義することもできます。


リストへの要素追加

空のリストは先ほどお話ししたとおり、角かっこを使うことで作成することができます。

では、このリストへ要素を追加するにはどうしたらいいでしょうか?Groovyでは左シフト演算子「<<」を使うとリストへ要素を追加することができます

list = []
list << 5
list.size() // 0

list.empty // true

list << 5

list.size() // 1

list.empty // false

さらに左シフト演算子をつないでチェーン化していくことができます。なぜなら、新たに要素が追加されたリストが返されるからです。

list << 6 << 7 << 'aaa'

もちろんリストへリストを要素として追加することもできます。

list << [true, false] // [5, 6, 7, aaa, [false, true]]

なお、「<<」は演算子ですので実体となるメソッドが存在します。「leftShift()」メソッドです。この左シフト演算子以外にも要素を追加する方法はたくさんあります。

もっともわかりやすいのは「+」演算子です。メソッドは「plus()」になります。

リストに要素を足しこんでいくことができます。

[1,2] + 6 + [12, 15] // [1,2,6,12,15]

とすると、代入演算子も利用することができます。

list = [1,2,3]
list += 4 // [1,2,3,4]
list += [5,6,7] // [1,2,3,4,5,6,7]

さらにJavaのように「add」メソッドを使うことができます。

list.add(8) // [1,2,3,4,5,6,7,8]

リストに要素としてリストを追加するには「addAll()」メソッドを使うことができます。

list.add([9, 10]) // [1,2,3,4,5,6,7,8,9,10]

なお、「add」メソッド、「addAll」メソッドの1つ目の引数には追加したいインデックスを指定することができます。便利ではありますが注意が必要な追加方法がありまして、それは存在しないインデックスを指定して要素をセットするという方法です。

たとえば、次のリストは4つしか要素がありませんが7つ目のインデックス(6になります)に要素の値をセットしています。

list = [1,2,3,4]
list[6] = 7

すると飛んでしまったインデックス4と5の箇所にはnullがセットされます。誤ったコードでこのような意図しない結果になった場合はnullが登場することがありますので注意してください。


リストへの要素削除

Groovyでリストから要素を削除する方法もいくつもあります。「+」で追加することができたのだから、ということで削除は「-」でできるのでは?と思った方はお察しのとおりです。代入演算子を使うこともできますし、リストで削除することもできます。

list = [1,2,3,6,5,4]
list -= 6 // [1,2,3,5,4]
list - [5,4] // [1,2,3]

「add」の反対、というわけではありませんが「remove」メソッドで要素を削除することもできます。引数に削除したいインデックスを指定します。削除ができると、指定したインデックスにあった要素が返されます

list = [1,2,3,5,4]
list.remove(3) // 5

リストが文字列の場合は対象の文字列を削除することができます。もし同じ値が複数存在する場合は、最初の要素が削除されます。そして削除が成功したらremoveメソッドはtrueを返します

list = ["abc","def", "hij"]
list.remove("hij") // true

もうリストの内容がすべて不要になった!という場合には「clear」メソッドを使うときれいさっぱり削除してくれます。

list = [1,2,3,4,5,6,7,8,9,10]
list.clear()
list.empty // true

list.size() // 0


リスト中にあるかどうか

特定の値が要素中に含まれているかどうかをチェックする方法として、findメソッドなどをお話ししました。今度はその値が要素中にあるかどうか、あるいはいくつあるかをチェックするメソッドについてお話しします。リスト内に要素が存在するかどうかは「in」演算子を利用します。これは「contains」メソッドの別名です。もしも複数の値が存在するかであれば「containsAll」メソッドへリストとして渡します。

list = [1,2,3,4,5]
3 in list // true
list.contains(3) // true

list.containsAll([2, 4]) // true

リスト内に含まれている場合、いくつ含まれているか、含まれている要素の個数を調べるには「count」メソッドを利用します。このメソッドは値の指定だけでなく、チェックのためのコードを波括弧のなかへ記述することができます

list = [6,2,3,7,1,2,3,4]
list.count(3) // 2
list.count { it % 2 == 0 } // 4、偶数のものを探している

それから、リスト同士で同じ要素のものだけを使って新たなリストを作成する「intersect」というメソッドがあります。

list = [1,3,5,7,9]
list.intersect([1,2,4,7]) // [1,7]

逆にまったく一致しなければ真(true)を返す「disjoint」メソッドというものもあります。

list = [1,3,5,7,9]
list.disjoint([2,4,6,8]) // true

ちょっとしたデータベース操作のような感覚でリストを扱うことができますね。


並び替え

これも必要になることが多いのではないでしょうか?「sort」メソッドを使います。

list = [5,6,3,4,7,9,1,2,7]
list.sort // [1, 2, 3, 4, 5, 6, 7, 7, 9]

インデックスの小さいほうから昇順に並び変わります。逆順にする場合は「reverse」メソッドを使います。

list.reverse() // [9, 7, 7, 6, 5, 4, 3, 2, 1]

また「sort」メソッドは「sum」メソッドや「max」「min」メソッドなどのように波括弧の中にどういうルールで並び替えをするかを記述することができます

list = ["nalulabo", "m0t0k1m0t0k1","blog","no"]
list.sort { it.size() } // [no, blog, nalulabo, m0t0k1m0t0k1]

ただし「reverse」メソッドはこの方法をとることができません。

list.reverse { it.size() }
ERROR groovy.lang.MissingMethodException:
No signature of method: java.util.ArrayList.reverse() is applicable for argument types: (groovysh_evaluate$_run_closure1) values: [groovysh_evaluate$_run_closure1@a4ca3f6]Possible solutions: reverse(), reverse(boolean), every(), every(groovy.lang.Closure), every(groovy.lang.Closure), remove(java.lang.Object)

ルールを適用したsortを行った後にreverseするようにしてください。

(list.sort { it.size() }).reverse() // [m0t0k1m0t0k1, nalulabo, blog, no]


リストの複製

長かったですね…これで最後です。リストは「*」演算子を使って、指定された回数の要素を繰り返したリストを作成することができます。

[1,2,3] * 3 // [1,2,3,1,2,3,1,2,3]

これは「multiply」メソッドによるものです。同じことはJavaのCollections.nCopiesメソッドを使うこともできますが、こちらのほうがわかりやすいと思います。


最後に

今回は長々とリストについてお話ししてきました。

しかしコレクションの括りとしてはまだマップとレンジがあります。

次回はマップについてみていきたいと思います。

もんが(なるーらぼ)

個人でプログラミングの学習サイト「なるーらぼ」を運営しています。
https://nalu-labo.amebaownd.com
PowerShell入門の電子書籍2冊も出版しています。
http://www.amazon.co.jp/dp/B017LJOCJ2

なるーらぼ

ごく自然にプログラミングを楽しむことができる世界をつくるお手伝いをしています

0コメント

  • 1000 / 1000