1

への呼び出しを介して、データベース API から一度に 1 ページずつ (ページあたりのレコード数が不明な状態で) 非常に多数のレコードを順番に読み取っています。def readPage(pageNumber: Int): Iterator[Record]

私は、この APIをページの無限ストリームStream[Iterator[Record]]またはIterator[Iterator[Record]]イテレータのシーケンスとして扱い、抽象化して、クライアントからのページネーション。クライアントは next() を呼び出すことで結果を繰り返すことができ、次のページ (Iterator[Record]) を取得します。

これを Scala で実装する最も慣用的で効率的な方法は何ですか。

編集:一度に1ページずつレコードをフェッチして処理する必要があり、メモリ内のすべてのページからすべてのレコードを維持することはできません。1 つのページが失敗した場合は、例外をスローします。多数のページ/レコードは、すべての実用的な目的において無限であることを意味します。ページの無限ストリーム (またはイテレータ) として扱いたいと思います。各ページは有限数のレコードのイテレータです (たとえば、<1000 未満ですが、時間がある場合は正確な数は不明です)。

Monixで BatchCursorを見ましたが、別の目的を果たします。

編集 2: これは、以下の Tomer の回答を出発点として使用する現在のバージョンですが、Iterator の代わりに Stream を使用しています。これにより、 https://stackoverflow.com/a/10525539/165130に従って末尾再帰の必要性を排除し、ストリームのプリペンド#::操作に O(1) 時間を持つことができます (操作を介してイテレータを連結した場合は++O になります) (n))

注: ストリームは遅延評価されますが、ストリームのメモ化は依然としてメモリの爆発を引き起こす可能性があり、メモリ管理はトリッキーになります。以下のストリームを定義するためにからvalに変更しても、何の効果もないようですdefdef pages = readAllPages

def readAllPages(pageNumber: Int = 0): Stream[Iterator[Record]] = {
   val iter: Iterator[Record] = readPage(pageNumber)
   if (iter.isEmpty)
     Stream.empty
   else
    iter #:: readAllPages(pageNumber + 1)
} 
      
//usage
val pages = readAllPages
for{
    page<-pages
    record<-page
    if(isValid(record))
}
process(record)
 

編集 3: Tomer による 2 番目の提案が最良のようです。その実行時間とメモリ フットプリントは上記のソリューションに似ていますが、はるかに簡潔でエラーが発生しやすくなっています。

val pages = Stream.from(1).map(readPage).takeWhile(_.nonEmpty)

注: Stream.from(1)1 から開始して 1 ずつ増加するストリームを作成します。これはAPI ドキュメントにあります。

4

1 に答える 1