Scalaでのストリームvsビューvsイテレータ
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 と次
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.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で.