メモリ使用量を一定に保つために、結果をメモ化せずに、一度に1つの要素で作業できる、無限の非厳密な系列x 1、x 2、x3 ...が必要です。特異性のために、それが一連の整数(たとえば、自然数、奇数、素数)であるとしましょう。ただし、この質問は、より一般的なデータ型に当てはまる場合があります。
無限リストを操作する最も簡単な方法は、ScalaのStream
オブジェクトを使用することです。一般的なイディオムは、を返し、演算子をStream
使用して系列に項を追加し、それ自体を再帰的に呼び出す関数を作成することです。#::
たとえば、次の例では、開始値と後続関数が指定された整数の無限ストリームが生成されます。
def infiniteList(n: Int, f: Int => Int): Stream[Int] = {
n #:: infiniteList(f(n), f)
}
infiniteList(2, _*2+3).take(10) print
// returns 2, 7, 17, 37, 77, 157, 317, 637, 1277, 2557, empty
(上記はライブラリ呼び出しと同等であることに気付きました。この無限のイディオムStream.iterate(2)(_*2+3)
の例としてここに書きました。)Stream
ただし、ストリームは結果をメモ化するため、メモリ要件が一定ではなく、非常に大きくなる可能性があります。ドキュメントには、の頭を保持しないとメモ化が回避されると記載されていますStream
が、実際にはこれは注意が必要な場合があります。ストリームヘッドを保持しているとは思わない無限リストコードを実装することもできますが、それでも無制限のメモリ要件がある場合は、ストリームを何らかの方法で処理していることが問題かどうかを判断する必要があります。それはメモ化を引き起こします、またはそれが何か他のものである場合。これは難しいデバッグタスクになる可能性があり、明示的にメモ化されたデータ構造をだましてメモ化されていない結果を返すようにしようとしているため、コードの臭いがあります。
私が欲しいのは、Stream
メモ化せずに期待するというセマンティクスを持つものです。そのようなオブジェクトがScalaに存在するようには見えません。私はイテレータを使用して無限の数値系列を実装することを試みてきましたが、イテレータの可変性により、イテレータに対して理解操作を実行したい場合、これは注意が必要です。また、独自のコードを最初から作成しようとしましたが、どこから始めればよいのか(サブクラス化するのか?)、、、などの機能の再実装を回避する方法が明確ではありませんTraversable
。map
fold
誰かが、厳密ではなく、不変で、メモ化されていない無限リストの実装の良い例のScalaコードを持っていますか?
より一般的には、トラバース可能、反復可能、シーケンス、ストリーム、およびビューのセマンティクスを理解していますが、この質問を非常に厄介に感じているという事実は、私が何かを誤解しているように感じさせます。非厳密性と非メモ化は完全に直交する特性であるように私には思えますが、Scalaはそれらを統合する設計上の決定を下し、それらをStream
簡単に切り離す方法を与えていないようです。これはScala側の見落としですか、それとも私が見落としている非厳密性と非メモ化の間に深い関係がありますか?
質問はかなり抽象的なものだと思います。これは、それを特定の問題に結び付けるいくつかの追加のコンテキストです。
MeissaO'Niellの論文「TheGenuineSieveof Eratosthenes 」で説明されているように、素数ジェネレーターを実装する過程でこの問題が発生します。Iterator
オブジェクトが不十分である場合の簡単な例を、多くのその論文からの詳細。これは、非常にエレガントであるが、疑わしいほど大量のメモリを消費するストリームを使用した完全な実装 です。
これは、コンパイルされないが、私が何を望んでいるかについてのアイデアを提供するイテレーターを使用した単純化された実装です。
import scala.collection.mutable
object ONeillSieve {
class NumericSeries extends BufferedIterator[Int] with Ordered[NumericSeries] {
def hasNext = true
def compare(that: NumericSeries) = that.head.compare(head)
override def toString() = head + "..."
var head = 3
def next() = {
val r = head
head += 2
r
}
}
def main(args: Array[String]) {
val q = mutable.PriorityQueue[NumericSeries]()
val odds = new NumericSeries
q += odds.map(odds.head * _)
odds.next()
q += odds.map(odds.head * _)
println("Sieve = %s\nInput = %s".format(q, odds))
}
}
PriorityQueue
最小の要素をキーとする無限の数値級数を作成する必要があります。(したがってBufferedIterator
、単純なものではなく、を使用しIterator
ます。)また、ここで無限級数の基礎は奇数の整数ですが、最も一般的な解決策には、より複雑な級数が含まれることに注意してください。main関数の最後に、キューに2つの無限級数を含める必要があります。
- 3x(3から始まるオッズ)(つまり、9,12,15 ...)
- 5x(5から始まるオッズ)(つまり、25、30、35 ...)
上記はコンパイルされません。これは、をodds.map(...)
返すのIterator
ではなく、NumericSeries
を返すため、優先キューに追加できないためです。
この時点で、私はコレクションクラスの拡張機能に取り組んでいるように見えますが、それは注意が必要なので、どうしても必要な場合を除いて、それを行う必要がないことを確認したいと思います。