倭マン's BLOG

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

GDK のList がこんなに可愛いわけがない。 (List 編3) シーケンス

f:id:waman:20130628013258p:plain

今回は GDK が List に追加しているメソッドのうち、関数型言語にある型、シーケンスに関するメソッドを見ていきます。 シーケンスは順序づけられたオブジェクトの列といったイメージで、要素列の走査は1度しか行われない気持ちです*1。 Groovy にはシーケンスという型はありませんが、順序を持つ集合である List インターフェースがその役割を担っていると言っていいでしょう。 まぁ、java.lang.Iterable や java.util.Iterator も今回扱うメソッドと同じものがいくつか定義されているので、それらもシーケンスのように扱うことができますが。

今回見ていくメソッドは以下のもの:

Object head()
List tail()

Object first()
Object last()

List take(int num)
List drop(int num)

List takeWhile(Closure condition)
List dropWhile(Closure condition)

List collate(int size)
List collate(int size, int step)
List collate(int size, boolean keepRemainder)
List collate(int size, int step, boolean keepRemainder)

List reverse()
List reverse(boolean mutate)
List reverseEach(Closure closure)

head(), tail() や take(), drop() などはそのままの名前で関数型言語に定義されていることが多いかと。 同じ名前で挙動がことなる、なんてややこしいことにはなってないはず・・・ 各メソッドをもう少し詳しく見るとこんな感じ

メソッド 返り値 since 説明
head()
tail()
first()
last()
Object
List
Object
Object
1.5.5
1.5.6
1.5.5
1.5.5
シーケンスの先頭、残り、最後の要素などを取得する
take(int)
drop(int)
takeWhile(Closure)
dropWhile(Closure)
List 1.8.1
1.8.1
1.8.7
1.8.7
引数で指定された数の要素、もしくは条件にあう要素を取捨する
collate(int)
collate(int, int)
collate(int, boolean)
collate(int, int, boolean)
List 1.8.6 引数で指定された数とステップ数で要素をグループ化(List オブジェクト)して、そのグループの List を返す
reverse()
reverse(boolean)
reverseEach(Closure)
List 1.0
1.8.1
1.5.0
シーケンスを逆順で返す

ではそれぞれのサンプル・コードを。

head(), tail(), fiirst(), last() メソッド

head(), tail() は先頭とそれを除いた残り。 あわせて元の List になります。 tail() は List オブジェクト(を返す)ことに注意。 first() と last() はまぁそのまま最初と最期の要素。 head() と first() は同じだよね。 使っているコンテキストで head-tail / first-last を使い分けるとコードが読みやすい、って程度のことかと。

def list = 0..9    // 範囲もリスト

// head(), tail()
assert list.head() == 0
assert list.tail() == (1..9)    // List オブジェクトを返すヨ

// first(), last()
assert list.first() == 0
assert list.last() == 9

まぁ、問題なし。

take(), drop(), takeWhile(), dropWhile() メソッド

take(), drop() は指定した数だけ(先頭から)要素を取得、または削除した List を返します。 一方、takeWhile() と dropWhile() は引数で指定した boolean 値を返すクロージャが true を返している間、(先頭から)要素を取得、または削除した List を返します。 同じ引数を指定した場合、take() と drop() または takeWhile() と dropWhile() が元のリストに対して相補的に要素を分けます(head() と tail() みたいに)。

def list = 0..9

// take(), drop()
assert list.take(4) == (0..<4)
assert list.drop(4) == (4..9)

// takeWhile(), dropWhile()
assert list.takeWhile{ it < 4 } == (0..<4)
assert list.dropWhile{ it < 4 } == (4..9)

collate() メソッド

collate」は照合する、順に揃えるという意味の英単語らしいです。 元のリストの要素を、指定した個数ずつグループ(List オブジェクト)にして、それを別に指定したステップ数だけ飛ばして作成していったものを List として返します・・・ うーむ、説明が自分でも分からん、ということでコードを見てちょ:

//***** collate(int) *****
assert list.collate(3) == [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
    // 3個(第1引数)ずつのグループ。 グループの先頭は3個飛ばし

//***** collate(int, int) *****
assert list.collate(3, 2) == [[0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7, 8], [8, 9]]
    // 3個(第1引数)ずつのグループ。 先頭は2個(第2引数)飛ばし

assert list.collate(3, 2).collect{ it.first() } == [0, 2, 4, 6, 8]
    // 各要素のリストは、先頭だけ見ると第2引数の個数だけ飛ばしてる

assert list.collate(3) == list.collate(3, 3)
    // 引数1つは、第1、第2引数に同じ数値を指定したのと同じ

//***** collate(int, bookean) *****
assert list.collate(3, true) == [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
    // 最後に残った要素を1つのリストにする(true)

assert list.collate(3, false) == [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
    // 最後に残った要素は捨てる(false)

assert list.collate(3) == list.collate(3, true)
    // デフォルトでは true を指定したのと同じ

//***** collate(int, int, boolean) *****
assert list.collate(3, 2, false) == [[0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7, 8]]
    // 2つ目と3つ目を合わせたもの

assert list.collate(3, 2, false).collect{ it.first() } == [0, 2, 4, 6]
assert list.collate(3, 2, false).every{ it.size() == 3 }

自分で同じものを実装しようとは思わないので、使うときが来たら便利そう。

reverse(), reverseEach() メソッド

最後は逆順序で要素を返す reverse(), reverseEach() メソッド。 reverse() は List オブジェクトとして逆順序のリストを返すのに対して、reverseEach() は each() メソッド同様にクロージャを引数にとってその処理を要素に適用しますが、その適用順が逆だってだけですね。 reverse() メソッドには元の要素を変更するかどうかを指定する boolean 値を渡せますが、true を渡すと UnsupportedOperationException が投げられよるw(Groovy 2.1.4)

// reverse()
def list1 = (0..9)
def list2 = list1.reverse()    // mutate=false
assert list2 == (9..0)
assert list1 == (0..9)

def list3 = (0..9)
try{
    def list4 = list3.reverse(true)
    assert false
}catch(UnsupportedOperationException e){ assert true }    // サポートされてないんかい!

def list5 = (0..9)
list5.reverseEach{ print it }    // 9876543210
assert list5 == (0..9)    // mutate=false

まぁ、特に問題はないかと。

次回で一応、GDK が List に追加しているメソッドは網羅の予定。 残りのメソッドをやっつけるヨ!(予定)

プログラミングGROOVY

プログラミングGROOVY

*1:今の場合、扱っているのは List なので何度でも走査することができるんですが。 そう言う意味では Iterator とかの方がシーケンスのイメージにあってますね。