KotlinのIterableとSequenceはまったく同じように見えます。なぜ2つのタイプが必要なのですか?


88

これらのインターフェースは両方とも1つのメソッドのみを定義します

public operator fun iterator(): Iterator<T>

ドキュメントによるSequenceと、怠惰になることを意図しています。しかし、Iterable怠惰ではありませんCollectionか(に裏打ちされていない限り)?

回答:


138

主な違いは、とのstdlib拡張関数のセマンティクスと実装にIterable<T>ありSequence<T>ます。

  • の場合Sequence<T>、拡張関数は、Java Streamsの中間操作と同様に、可能な場合は遅延して実行されます。たとえば、Sequence<T>.map { ... }別のものSequence<R>を返し、またはのような端末操作が呼び出されるまで、実際にはアイテムを処理しません。toListfold

    このコードを検討してください:

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    印刷します:

    before sum 1 2
    

    Sequence<T>Java Streamsと同様に、ターミナル操作で行われる作業を可能な限り減らしたい場合の怠惰な使用法と効率的なパイプライン化を目的としています。ただし、怠惰はオーバーヘッドをもたらします。これは、小さなコレクションの一般的な単純な変換には望ましくなく、パフォーマンスが低下します。

    一般に、いつ必要かを判断する良い方法はありません。そのため、Kotlinではstdlibの怠惰が明示的になり、デフォルトSequence<T>ですべてので使用されないようにインターフェイスに抽出さIterableれます。

  • 以下のためにIterable<T>逆に、と拡張機能、中間演算のセマンティクスは、熱心に働く、すぐにアイテムを処理し、別のものを返しますIterable。たとえば、マッピング結果をIterable<T>.map { ... }含むList<R>を返します。

    Iterableの同等のコード:

    val lst = listOf(1, 2)
    val lstMapped: List<Int> = lst.map { print("$it "); it * it }
    print("before sum ")
    val sum = lstMapped.sum()
    

    これは印刷されます:

    1 2 before sum
    

    上記のように、Iterable<T>デフォルトでは遅延がなく、このソリューションはそれ自体がうまく機能します。ほとんどの場合、参照の局所性が良好であるため、CPUキャッシュ、予測、プリフェッチなどを利用して、コレクションの複数のコピーでも正常に機能します。十分であり、コレクションが少ない単純なケースでパフォーマンスが向上します。

    評価パイプラインをさらに制御する必要がある場合は、Iterable<T>.asSequence()関数を使用して遅延シーケンスに明示的に変換します。


3
Java(ほとんどGuava)ファンにとっておそらく大きな驚き
Venkata Raju 2016

機能的な人々のための@VenkataRajuは、デフォルトで怠惰な選択肢に驚かれるかもしれません。
ジェイソンミナード2016

9
デフォルトでは、Lazyは通常、より小さく、より一般的に使用されるコレクションのパフォーマンスが低下します。CPUキャッシュなどを利用する場合、コピーは遅延評価よりも高速になる可能性があります。したがって、一般的なユースケースでは、怠惰にならない方がよいでしょう。そして、残念ながらのような機能のための一般的な契約mapfilterおよびそれがあるので、「怠惰なこと」のための良好なマーカーではないこと、他の人がソースコレクション型以外から決定するのに十分な情報を有していない、とほとんどのコレクションはまた、反復処理可能であるため、一般的にどこでも。怠惰は安全であるために明示的でなければなりません。
ジェイソンミナード2016

1
@naki最近のApacheSparkの発表からの一例として、彼らは明らかにこれ について心配しています。databricks.com/ blog / 2015/04/28 /の「キャッシュ対応の計算」セクションを参照してください…...しかし彼らは何十億ものことを心配しています物事が繰り返されるので、彼らは完全に極端に行く必要があります。
ジェイソンミナード2016年

3
さらに、遅延評価の一般的な落とし穴は、コンテキストをキャプチャし、キャプチャされたすべてのローカルとそれらが保持するものとともに、結果の遅延計算をフィールドに保存することです。したがって、メモリリークをデバッグするのは困難です。
Ilya Ryzhenkov 2017年

50

ホットキーの回答を完了する:

シーケンスと反復可能要素が要素全体でどのように反復されるかに注意することが重要です。

シーケンス例:

list.asSequence().filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

ログ結果:

フィルタ-マップ-それぞれ; フィルタ-マップ-それぞれ

反復可能な例:

list.filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

フィルタ-フィルタ-マップ-マップ-それぞれ-それぞれ


5
これは、2つの違いの優れた例です。
Alexey

これは良い例です。
frye3k 2018

2

Iterablejava.lang.Iterableインターフェイスに マップされ、JVMListやSetなどの一般的に使用されるコレクションによって実装されます。これらのコレクション拡張関数は熱心に評価されます。つまり、入力内のすべての要素を即座に処理し、結果を含む新しいコレクションを返します。

これは、コレクション関数を使用して、21歳以上のリストの最初の5人の名前を取得する簡単な例です。

val people: List<Person> = getPeople()
val allowedEntrance = people
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)

ターゲットプラットフォーム:kotlinv。1.3.61でのJVMRunning最初に、リスト内のすべての個人に対して年齢チェックが実行され、結果が新しいリストに入れられます。次に、フィルター演算子の後に残ったすべてのPersonに対して名前へのマッピングが行われ、最終的にさらに別の新しいリストになります(これは現在List<String>)です。最後に、前のリストの最初の5つの要素を含むように作成された最後の新しいリストが1つあります。

対照的に、Sequenceは、遅延評価された値のコレクションを表すKotlinの新しい概念です。同じコレクション拡張機能をSequenceインターフェイスで使用できますが、これらは日付の処理済み状態を表すシーケンスインスタンスをすぐに返しますが、実際には要素を処理しません。処理を開始するに Sequenceは、端末オペレーターで終了する必要があります。これらは基本的に、シーケンスが表すデータを具体的な形式で具体化するためのシーケンスへの要求です。例としては、、、、toListなどtoSetがありsumます。これらが呼び出されると、必要な最小数の要素のみが処理され、要求された結果が生成されます。

既存のコレクションをシーケンスに変換するのは非常に簡単ですasSequence。拡張機能を使用する必要があります。上記のように、ターミナル演算子も追加する必要があります。そうしないと、シーケンスは処理を実行しません(繰り返しますが、怠惰です!)。

val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)
    .toList()

ターゲットプラットフォーム:kotlinv。1.3.61でのJVMRunningこの場合、シーケンス内のPersonインスタンスはそれぞれ年齢がチェックされ、合格した場合は名前が抽出され、結果リストに追加されます。これは、5人が見つかるまで、元のリストの各人に対して繰り返されます。この時点で、toList関数はリストを返し、の残りのユーザーSequenceは処理されません。

シーケンスに追加できる機能もあります。無限の数のアイテムを含めることができます。これを考慮すると、オペレーターが自分のやり方で作業することは理にかなっています。無限のシーケンスのオペレーターは、熱心に作業を行った場合、戻ることはできません。

例として、ターミナルオペレーターが必要とする数の2の累乗を生成するシーケンスを次に示します(これがすぐにオーバーフローするという事実を無視します)。

generateSequence(1) { n -> n * 2 }
    .take(20)
    .forEach(::println)

詳細については、こちらをご覧ください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.