例で理解するSequence<T>のfilterやmap

  • 4
    いいね
  • 0
    コメント

はじめに

 KotlinのSequenceは、次のようなIterator<T>を返すiterator()関数を持つインターフェースです。

public interface Sequence<out T> {
    public operator fun iterator(): Iterator<T>
}

 kotlin.sequencesパッケージには、Sequenceを生成する関数と多くのSequence拡張関数が定義されています。拡張関数の中にはfiltermaptakeといった物があります。

 Listにもmapfiltertakeがありますね。

 この投稿では、Listのmapやfilter、takeとの違いを通して、Sequenceのmapとfilter、takeの挙動を紹介します。

List<T>のfilter・map・take

 Listfiltermaptakeの挙動を見てみましょう。

 filterは選択を、mapは射影を行う関数です。takeは先頭から指定した要素数のList<T>を新たに生成する関数です。

 実際のコードではfilterやmapに渡す関数リテラル内部で副作用のあるprintlnを行うことは良くありません。今回は挙動を紹介するために関数リテラル内でprintlnを使います。

Listのfilter・mapの例
    val list : List<Int> = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    val converted : List<String> = list
            .filter { // 偶数だけに選択
                println("filter $it")
                it.rem(2) == 0
            }
            .map { // 文字列型に射影
                println("map $it")
                it.toString()
            }
            .take(3) // 戦闘から3個だけのList<T>に

    println("--------")

    for(it in converted) println("for $it")

 上のコードの実行結果は次の通りです。

filter 0
filter 1
filter 2
filter 3
filter 4
filter 5
filter 6
filter 7
filter 8
filter 9
map 0
map 2
map 4
map 6
map 8
--------
for 0
for 2
for 4

 まず、filterが実行されます。すべての要素に対してfilterに渡した関数リテラルが適用されていますね。

 次に、mapが実行されます。filterした結果のListにmapに渡した関数リテラルが適用されていますね。

 そして、takeが実行されます。しかし特に何も表示されません。

 そしてfor文の前のprintlnが実行され、--------が表示されています。

 最後にfor文が実行されます。

 List<T>のfilterはその場で全要素を選択し、mapはその場で全要素を射影します。

Sequence<T>のfilter・map・take

 それでは同様のコードのSequence<T>版を見てみましょう。

    val list : Sequence<Int> = sequenceOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    val converted : Sequence<String> = list
            .filter {
                println("filter $it")
                it.rem(2) == 0
            }
            .map {
                println("map $it")
                it.toString()
            }
            .take(3)

    println("--------")

    for(it in converted) println("for $it")

 実行結果は次の通りです。

--------
filter 0
map 0
for 0
filter 1
filter 2
map 2
for 2
filter 3
filter 4
map 4
for 4

 Listのfilter・map・takeとの実行結果と大きく違うことに気が付きましたか?

 --------がまず表示されていますね。表示された結果から、filterやmapに渡した関数リテラルの適用は--------を表示された後にされていますね。

 Sequence<T>のfilter・map・takeは、すべての要素に対して一気に行われるわけではありません。その要素が必要になったタイミングで実行されるのです。

 それでは、見ていきましょう。

 まず、filterが実行されます。しかし特に表示はされていません。各要素に対しての選択はまだ実行されていません。

 次に、mapが実行されます。しかし特に表示はされていません。各要素に対しての射影はまだ実行されていません。

 そして、takeが実行されます。しかし特に表示はされません。

 そしてfor文の前のprintlnが実行され、--------が表示されています。

 最後にfor文が実行されます。

 for文で要素の列挙を始められ、要素が必要になります。そして先頭からfilter・map・takeが実行されます。

まとめ

 List<T>のmapやfilterはその場で全要素を射影・選択が実行されます。

 それとは異なり、Sequence<T>のmapやfilterは要素が必要になったタイミングで、必要な分の要素に対して射影の処理・選択の処理が行われます。