web-dev-qa-db-ja.com

Scalaのストリームのユースケース

Scalaには、イテレータに非常によく似たStreamクラスがあります。トピック ScalaでのイテレータとStreamの違いは? は、の類似点と相違点に関する洞察を提供します。二つ。

ストリームの使用方法を確認するのは非常に簡単ですが、他のアーティファクトの代わりにストリームを使用するcommonユースケースはあまりありません。

私が今持っているアイデア:

  • 無限シリーズを利用する必要がある場合。しかし、これは私には一般的なユースケースのようには思えないため、私の基準に適合しません。 (それが一般的で、私が死角になっている場合は修正してください)
  • 各要素を計算する必要がある一連のデータがあるが、それを数回再利用したい場合。これは、開発者集団の大規模なサブセットに対して概念的に追跡しやすいリストにロードできるため、弱いものです。
  • おそらく、大量のデータセットまたは計算コストの高いシリーズがあり、必要なアイテムがすべての要素にアクセスする必要がない可能性が高いです。ただし、この場合、複数の検索を実行する必要がない限り、イテレータは適切に一致します。その場合、効率が少し低下しても、リストを使用することもできます。
  • 再利用する必要のある複雑な一連のデータがあります。ここでもリストを使用できます。この場合、どちらの場合も同じように使用するのは難しく、すべての要素をロードする必要がないため、Streamの方が適しています。しかし、これもそれほど一般的ではありません...それともそうですか?

だから私は何か大きな用途を見逃しましたか?それとも、ほとんどの場合、開発者の好みですか?

ありがとう

50
Jesse Eichar

StreamIteratorの主な違いは、後者は変更可能で、いわば「ワンショット」であるのに対し、前者はそうではないということです。 IteratorStreamよりもメモリフットプリントが優れていますが、is可変であるという事実は不便な場合があります。

この古典的な素数ジェネレータを例にとってみましょう。

def primeStream(s: Stream[Int]): Stream[Int] =
  Stream.cons(s.head, primeStream(s.tail filter { _ % s.head != 0 }))
val primes = primeStream(Stream.from(2))

Iteratorでも簡単に書くことができますが、Iteratorはこれまでに計算された素数をkeepしません。

したがって、Streamの重要な側面の1つは、最初に複製したり、何度も生成したりせずに、他の関数に渡すことができることです。

高価な計算/無限リストに関しては、これらのことはIteratorでも実行できます。無限リストは実際には非常に便利です-持っていなかったので知らないので、強制された有限サイズを処理するためだけに必要なものよりも複雑なアルゴリズムを見てきました。

40

ダニエルの答えに加えて、Streamは評価の短絡に役立つことを覚えておいてください。たとえば、Stringを受け取ってOption[String]を返す関数の膨大なセットがあり、そのうちの1つが機能するまで実行し続けたいとします。

val stringOps = List(
  (s:String) => if (s.length>10) Some(s.length.toString) else None ,
  (s:String) => if (s.length==0) Some("empty") else None ,
  (s:String) => if (s.indexOf(" ")>=0) Some(s.trim) else None
);

まあ、私は確かに全体リストを実行したくありません、そしてListには「これらを関数として扱う」という便利なメソッドはありませんそして、それらのいずれかがNone以外の何かを返すまでそれらを実行します。何をすべきか?おそらくこれは:

def transform(input: String, ops: List[String=>Option[String]]) = {
  ops.toStream.map( _(input) ).find(_ isDefined).getOrElse(None)
}

これはリストを受け取り、それをStream(実際には何も評価しない)として扱い、関数を適用した結果である新しいStreamを定義し(ただし、まだ何も評価しません)、最初のリストを検索します定義されている1つ-そしてここでは、魔法のように振り返って、マップを適用し、元のリストから適切なデータを取得する必要があることを認識してから、それをOption[Option[String]]からOption[String]に展開しますgetOrElseを使用します。

次に例を示します。

scala> transform("This is a really long string",stringOps)
res0: Option[String] = Some(28)

scala> transform("",stringOps)
res1: Option[String] = Some(empty)

scala> transform("  hi ",stringOps)
res2: Option[String] = Some(hi)

scala> transform("no-match",stringOps)
res3: Option[String] = None

しかし、それはうまくいきますか?関数にprintlnを挿入して、それらが呼び出されたかどうかを判断できる場合、

val stringOps = List(
  (s:String) => {println("1"); if (s.length>10) Some(s.length.toString) else None },
  (s:String) => {println("2"); if (s.length==0) Some("empty") else None },
  (s:String) => {println("3"); if (s.indexOf(" ")>=0) Some(s.trim) else None }
);
// (transform is the same)

scala> transform("This is a really long string",stringOps)
1
res0: Option[String] = Some(28)

scala> transform("no-match",stringOps)                    
1
2
3
res1: Option[String] = None

(これはScala 2.8です。2.7の実装では、残念ながら1つオーバーシュートすることがあります。また、do失敗が発生するにつれてNoneの長いリストを蓄積しますが、おそらくこれはここでの実際の計算と比較して安価です。)

18
Rex Kerr

リアルタイムでデバイスをポーリングする場合、ストリームの方が便利だと想像できます。

GPSトラッカーを考えてみてください。GPSトラッカーは、要求すると実際の位置を返します。 5分以内にいる場所を事前に計算することはできません。 OpenStreetMapでパスを実現するために数分間だけ使用することも、砂漠や熱帯雨林で6か月以上の遠征に使用することもできます。

または、ハードウェアが稼働していてオンになっている限り、新しいデータを繰り返し返すデジタル温度計やその他の種類のセンサー-ログファイルフィルターは別の例です。

7
user unknown

StreamIteratorimmutable.Listmutable.List。不変性を優先することで、パフォーマンスを犠牲にして、ある種のバグを防ぐことができます。

scalac自体はこれらの問題の影響を受けません: http://article.gmane.org/gmane.comp.lang.scala.internals/2831

ダニエルが指摘するように、厳密さよりも遅延を優先すると、アルゴリズムが単純化され、アルゴリズムの作成が容易になります。

3
retronym