1. 序章

Scalaには、レイジー操作をサポートするためのいくつかのデータ構造があります。 ストリームイテレータ、 と 意見これらのコレクションはすべての計算が延期されるため、厳密ではありません。。 このチュートリアルでは、これらのコレクションのユースケースと機能について説明します。

2. 怠惰で厳格なコレクション

厳密なコレクションを扱っている場合( リスト設定地図)、要素のすべての変換は一度に計算され、アサーションによって検証できます。

val list = List(1, 2, 3)
list.map(_ * 2) shouldBe List(2, 4, 6))

一方、怠惰なコレクションがあります(など ストリーム)即時計算なしで要素の変換を可能にします:

val stream = Stream(1, 2, 3)
stream.map(_ * 2) // scala.collection.immutable.Stream[Int] = Stream(2, )

この場合、 stream のテールは、参照するまでまだ計算されていません。

3. イテレータ

イテレータ は、Scalaの特性であり、順次要素に1つずつアクセスします。 すべてのScalaコレクションが拡張されるため、任意のコレクションのイテレーターを取得できます IterableOnce 抽象メソッドを定義するトレイト イテレータ:イテレータ[A]:

val list = List(1, 2, 3)
val it: Iterator[Int] = list.iterator

の主な方法イテレータそれは hasNextこれは、イテレータの値を次の値にシフトします。 のために利用可能なコレクションのいくつかの方法もありますイテレータ 、 そのような foreach 地図 flatMap フィルター。 ただし、場合によっては、 イテレータ動作が異なります。 たとえば、 foreachイテレータそれを変更し、itr.nextを呼び出すと NoSuchElementException なぜならイテレータ空です

val itr = Iterator(1, 2, 3)
itr foreach println
itr.next

その間、私たちは変更しません イテレータ 私たちがそれを変換するとき:

val itr = Iterator(1, 2, 3)
val itrUpdated = itr.map(_ * 5)
itr.next shouldBe 5

の値を取得します イテレータ 上記を参照する場合のみ(遅延実行):

val itrUpdated: Iterator[Int] = <iterator>

後続の値にのみアクセスできます イテレータ 経由 次、 値にアクセスできません イテレータ すでにトラバースしています。

4. ストリーム LazyList

ようではない イテレータストリーム (2.13.0以降廃止され、 LazyList)はコレクションです。 実際、 ストリーム は リスト だれの しっぽ は 怠惰なヴァル:

val stream: Stream[Int] = Stream(1, 2, 3)
stream.head shouldBe 1

上記の例では、 ストリーム 計算されます。

とは異なり イテレータ ストリーム 以前に計算された値と次の値にインデックスを介してアクセスできます。

stream(0) shouldBe 1
stream(1) shouldBe 2

また、構築することができます ストリーム 経由 #:: の構築と同様の方法で演算子 リスト 経由 :: オペレーター:

val stream = 1 #:: 2 #:: 3 #:: Stream.empty

反復しない限り ストリーム、その要素 ストリーム 計算されることはありません。 たとえば、階乗の例のように、厳密なコレクションの場合にスタックオーバーフローを引き起こす再帰的アルゴリズムを安全に実装できます。

def factorial(a: Int, b: Int): Stream[Int] = a #:: factorial(a*(b+1), b+1)
val factorials7: Stream[Int] = factorial(1, 1).take(7)
val factorialsList = factorials7.toList // List(1, 2, 6, 24, 120, 720, 5040)

List など、厳密な収集に同じ factorial メソッドを実装すると、スタックオーバーフローエラーが発生します。  重要なのは、 ストリームの のすべての要素にアクセスする必要がある場合、怠惰は問題ではありません ストリーム すぐに(の適用とは異なり 地図flatMapフィルター メソッド):

stream.size shouldBe 3

5 。 意見

The 意見はScalaの特別な種類のコレクションであり、基本コレクションを取得し、そのコレクションに対してトランスフォーマーメソッドを遅延実行します。 すべてのScalaコレクションを怠惰な表現に変えて、 見る方法。

ビューリストに適用してscala.collection.SeqView[Int]を取得する方法は次のとおりです。 ]:

val list = List(1, 2, 3)
val listView = list.view

この場合、 リストビュー タイプの新しいコレクションを作成します SeqView[Int]。 私たちは通常使用します ビュー パフォーマンスを向上させるために中間コレクションでオーバーヘッドを回避する必要がある場合。 収集操作を一度に組み合わせることができない場合は特に重要です。 上記の例では、コレクションを次のようにマッピングした結果には関心がありません。 (_ * 2)、 文字列の最終コレクションのみが必要です。

(list.view.map(_ * 2).map(_.toString)).force

の呼び出し  からコレクションを作成します 意見.

大量のデータを扱う場合は、コレクションビューの使用を検討できます。 たとえば、文字列のデータセットに単語が含まれているかどうかを確認します。

val listOfWords = loadListOfWords("myList.txt")
val occurence = "Scala"
val hasOccurence = (listOfWords: Iterable[String]) => listOfWords.exists(_ == occurrence)

それから私達が呼ぶなら hasOccurence に listOfWords.view、単語のリスト全体を操作するのではなく、出現が見つかったときに停止します。

val res = hasOccurence(listOfWords.view)

副作用のあるビューを使用する場合は、強制するまで副作用が計算されないため、注意が必要であることに注意することも重要です。

def printer = println(System.currentTimeMillis())
val printView: SeqView[Unit, Seq[_]] = List.range(0, 10).view.map(_ => printer)

6. 結論

この記事では、私たちは会った イテレータストリーム、 と 意見 Scalaで怠惰な計算を処理します。

いつものように、これらの例は利用可能です GitHubで.