LoginSignup
24
11

More than 3 years have passed since last update.

【Kotlin】Sequenceオブジェクト生成方法7種

Last updated at Posted at 2019-02-03

Kotlin の Sequence オブジェクトの生成方法を7種紹介します。
(2019-04-06 1種追加して6種に。)
(2019-04-13 1種追加して7種に。)

この記事で最もお伝えしたいのは、最も強力でありながら最も知られていないと思われる、sequence 関数 を使う方法です。
ぜひそこをお読みいただいて、活用していただければと思います。

asSequence 拡張関数

Sequence を使ったことがある方は皆さんご存知でしょう。
List インターフェイスなどが継承している Iterable インターフェイスや、 ArrayIntArray などの配列系クラス。それらの拡張関数 asSequence を使うことで、それらを Sequence 化できます。

    val sequence = listOf('A', 'B', 'C').asSequence()
    sequence.forEach { print(it) } // > ABC

なお、関数名は toSequence ではなく asSequence です。
asSequence 関数で生成した Sequence オブジェクトには、元となるオブジェクトへの変更が反映されます。

    val mutableList = mutableListOf('A', 'B')
    val sequence = mutableList.asSequence()
    mutableList += 'C'
    sequence.forEach { print(it) } // > ABC

toList 関数などでは、元となるオブジェクトへの変更が反映されません。
この違いが関数名に現れているのでしょう。

sequenceOf 関数

引数で与えられた要素から Sequence オブジェクトを生成します。

    val sequence = sequenceOf('A', 'B', 'C')
    sequence.forEach { print(it) } // > ABC

emptySequence 関数

空(要素数が 0)の Sequence オブジェクトを生成する関数です。
シングルトンになっているため軽量です。

ですが、引数なしで sequenceOf を使ってしまっても構いません。
要素数が 0 の場合は自動的に emptySequence() が呼ばれるようになっています

Sequence インターフェイスの拡張関数

filtermap などの、Sequence オブジェクトを加工して別の Sequence オブジェクトを生成する、Sequence インターフェイスの拡張関数です。
コレクション系の型の拡張関数と同じ感覚で使えます。

    val sequence = sequenceOf(0, 1, 2)
        .map { 'A' + it }
    sequence.forEach { print(it) } // > ABC

Sequence 関数(フェイクコンストラクター)

Sequence 型はインターフェイスなのでコンストラクターを持ちませんが、このSequence 関数をコンストラクター代わりに使うことができます。

    class CharIterator : Iterator<Char> {
        private var char = 'A'
        override fun hasNext(): Boolean = char <= 'C'
        override fun next(): Char = char++
    }

    val sequence = Sequence { CharIterator() }
    sequence.forEach { print(it) } // > ABC

Sequence インターフェイスはメンバとして、Iterator オブジェクトを返す iterator 関数だけを持っています。
Sequence 関数は引数として受け取った関数を、その iterator 関数の実体として使用します。

generateSequence 関数

要素を生成する関数1から Sequence オブジェクトを生成します。
関数が null を生成すると終端するため、要素に null を含む Sequence オブジェクトは生成できません。
また、生成した Sequence オブジェクトは一度しか使えません。
このような制約があるものの、ある程度自由に手軽に Sequence を作成できます。

    var char = 'A'
    val sequence = generateSequence {
        if (char > 'C') null
        else char++
    }
    sequence.forEach { print(it) } // > ABC
    sequence.forEach { print(it) } // IllegalStateException をスローする。

sequence 関数

本記事で最もお伝えしたい方法です。
任意の Sequence を簡単に作成できます。

    val sequence = sequence {
        var char = 'A'
        while(char <= 'C') {
            yield(char++)
        }
    }
    sequence.forEach { print(it) } // > ABC
    sequence.forEach { print(it) } // > ABC

使い方

sequence 関数の引数である関数(以下、 block と表記)の中で yield 関数を呼び出すごとに、yield 関数に引数として渡した値が、生成した Sequence オブジェクトの要素となります。
block の処理が終了すると終端します。

yield 関数を呼び出して要素を生成すると、次の要素が要求されるまで block の処理は停止します。
そのため、終わりがない数列を表す Sequence オブジェクトを生成するような場合でも、必要な計算のみが行われます。

解説

blockSequenceScope クラスをレシーバーとする拡張関数です。
したがって、block 内では this.yield(もしくは this. を省略して yield)とすることで、SequenceScope#yield メソッドを呼び出せます。

yield 関数は中断関数(suspending function)です。
中断関数は、呼び出されてから処理を返すまでの間に処理を「中断」することができます。
「中断」すると、続きの処理は非同期で行われることになります。
中断関数は Kotlin 1.3 で正式に採用された Coroutines の機能に関連するものです。詳しくはこの辺りをご覧ください。→ Coroutines Overview

例:フィボナッチ数列

    val fibonacci = sequence {
        var a = 0
        var b = 1
        while (true) {
            yield(b)
            val c = a + b
            a = b
            b = c
        }
    }

    fibonacci
        .take(10)
        .forEach { println(it) }


出力

1
1
2
3
5
8
13
21
34
55

おわりに

以上、Sequence オブジェクトの生成方法を6種紹介しました。
一通り網羅したつもりですが、他にもあればご教授ください。


  1. ラムダ式を使う場合がほとんどでしょう。 

24
11
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
11