web-dev-qa-db-ja.com

Scalaの無限ストリーム

たとえば、古いお気に入りの機能があるとします

_def factorial(n:Int) = (BigInt(1) /: (1 to n)) (_*_)
_

ここで、factorial(n)がLongに適合するnの最大値を見つけたいと思います。私はそれをできた

_(1 to 100) takeWhile (factorial(_) <= Long.MaxValue) last
_

これは機能しますが、100は任意の大きな数値です。左側に本当に欲しいのは、takeWhile条件が満たされるまで、より大きな数値を生成し続ける無限ストリームです。

思いついた

_val s = Stream.continually(1).zipWithIndex.map(p => p._1 + p._2)
_

しかし、より良い方法はありますか?

(再帰的に解決策を得ることができることも知っていますが、それは私が探しているものではありません。)

Stream.from(1)

1から始まり1ずつ増加するストリームを作成します。すべてが APIドキュメント にあります。

120
Kim Stebel

イテレータを使用したソリューション

Iteratorの代わりにStreamを使用することもできます。 Streamは、計算されたすべての値の参照を保持します。したがって、各値に一度だけアクセスする場合は、反復子の方が効率的です。ただし、イテレータの欠点はその可変性です。

コンパニオンオブジェクト で定義されたIteratorsを作成するための便利なメソッドがいくつかあります。

編集

残念ながら、私が知っているような短い(ライブラリがサポートされている)方法はありません。

_Stream.from(1) takeWhile (factorial(_) <= Long.MaxValue) last
_

特定の数の要素に対してIteratorを進めるためのアプローチは、drop(n: Int)またはdropWhileです。

_Iterator.from(1).dropWhile( factorial(_) <= Long.MaxValue).next - 1
_

_- 1_はこの特別な目的のために機能しますが、一般的な解決策ではありません。しかし、pimp myライブラリを使用してlastIteratorメソッドを実装しても問題ありません。問題は、無限イテレータの最後の要素を取得することが問題になる可能性があることです。したがって、lastWithを統合するtakeWhileなどのメソッドとして実装する必要があります。

い回避策はslidingに実装されているIteratorを使用して実行できます。

_scala> Iterator.from(1).sliding(2).dropWhile(_.tail.head < 10).next.head
res12: Int = 9
_
28
ziggystar

@ziggystarが指摘したように、Streamsは以前に計算された値のリストをメモリに保持するため、Iteratorを使用することは大きな改善です。

答えをさらに改善するために、「無限ストリーム」は通常、事前に計算された値に基づいて計算される(または計算できる)と主張します。これが当てはまる場合(そしてあなたの階乗ストリームでは間違いなくそうです)、Iterator.iterate代わりに。

おおよそ次のようになります。

scala> val it = Iterator.iterate((1,BigInt(1))){case (i,f) => (i+1,f*(i+1))}
it: Iterator[(Int, scala.math.BigInt)] = non-empty iterator

その後、次のようなことができます:

scala> it.find(_._2 >= Long.MaxValue).map(_._1).get - 1
res0: Int = 22

または@ziggystar slidingソリューションを使用...

思い浮かぶもう1つの簡単な例は、フィボナッチ数です。

scala> val it = Iterator.iterate((1,1)){case (a,b) => (b,a+b)}.map(_._1)
it: Iterator[Int] = non-empty iterator

これらの場合、毎回新しい要素をゼロから計算するのではなく、すべての新しい要素に対してO(1)作業を実行します。これにより、実行時間がさらに改善されます。

4
gilad hoch

階乗は毎回ゼロから計算されるため、元の「階乗」関数は最適ではありません。メモ化を使用した最も単純/不変の実装は次のとおりです。

val f : Stream[BigInt] = 1 #:: (Stream.from(1) Zip f).map { case (x,y) => x * y }

そして今、答えは次のように計算できます:

println( "count: " + (f takeWhile (_<Long.MaxValue)).length )
1
Duckki

次のバリアントは、現在の値をテストしませんが、最後の有効な番号を見つけて返すためにnext整数をテストします。

Iterator.from(1).find(i => factorial(i+1) > Long.MaxValue).get

ここで.getを使用しても問題ありません。無限シーケンスのfindNoneを返さないからです。

0
cubic lettuce