14

iteratee は怠け者だと聞いたことがありますが、具体的にどのくらい怠けているのでしょう? あるいは、中間データ構造を構築する必要がないように、反復対象を後処理関数と融合できますか?

たとえば、 iteratee で aStream[Option[String]] からjava.io.BufferedReader100 万個のアイテムを作成し、その後、NoneStream 全体をメモリに保持する必要なく、構成的な方法で s を除外できますか? 同時に、スタックを爆破しないことを保証しますか? またはそのようなもの - を使用する必要はありませんStream

私は現在 Scalaz 6 を使用していますが、他の iteratee 実装がより良い方法でこれを行うことができれば、知りたいです。

該当する場合は、 を閉じてBufferedReaderを呼び出すなど、完全な解決策を提供してください。unsafePerformIO

4

2 に答える 2

12

これは、Scalaz 7 ライブラリを使用した iteratee の簡単な例で、関心のあるプロパティを示しています: 一定のメモリとスタックの使用。

問題

まず、各行に 10 進数の文字列を含む大きなテキスト ファイルがあり、少なくとも 20 個のゼロを含むすべての行を見つけたいとします。次のようなサンプル データを生成できます。

val w = new java.io.PrintWriter("numbers.txt")
val r = new scala.util.Random(0)

(1 to 1000000).foreach(_ =>
  w.println((1 to 100).map(_ => r.nextInt(10)).mkString)
)

w.close()

という名前のファイルができましnumbers.txtた。で開きましょうBufferedReader:

val reader = new java.io.BufferedReader(new java.io.FileReader("numbers.txt"))

大きすぎるわけではありませんが (~97 メガバイト)、処理中にメモリ使用量が実際に一定に保たれているかどうかを簡単に確認するのに十分な大きさです。

列挙子の設定

まず、いくつかのインポートについて:

import scalaz._, Scalaz._, effect.IO, iteratee.{ Iteratee => I }

そして列挙子 (便宜上、IoExceptionOrs をs に変更していることに注意してください):Option

val enum = I.enumReader(reader).map(_.toOption)

Scalaz 7 は現在、ファイルの行を列挙する適切な方法を提供していないため、一度に 1 文字ずつファイルをチャンクしています。もちろん、これは非常に遅くなりますが、ここではそれについて心配するつもりはありません。なぜなら、このデモの目的は、この大きなファイルを一定のメモリ内で、スタックを破壊することなく処理できることを示すことだからです。この回答の最後のセクションでは、パフォーマンスが向上したアプローチを提供しますが、ここでは改行で分割します。

val split = I.splitOn[Option[Char], List, IO](_.cata(_ != '\n', false))

そして、分割しないsplitOn場所を指定する述語を取るという事実があなたを混乱させるなら、あなたは一人ではありません. 列挙型の最初の例です。先に進み、列挙子をラップします。split

val lines = split.run(enum).map(_.sequence.map(_.mkString))

これで、モナドにOption[String]s の列挙子ができました。IO

列挙型でファイルをフィルタリングする

次は述語です。少なくとも 20 個のゼロを含む行が必要だと言ったことを思い出してください。

val pred = (_: String).count(_ == '0') >= 20

これをフィルタリング列挙型に変換し、列挙子をその中にラップできます。

val filtered = I.filter[Option[String], IO](_.cata(pred, true)).run(lines)

このフィルターを通過するすべてのものを出力するだけの単純なアクションを設定します。

val printAction = (I.putStrTo[Option[String]](System.out) &= filtered).run

もちろん、実際にはまだ何も読んでいません。これを行うには、次を使用しますunsafePerformIO

printAction.unsafePerformIO()

Some("0946943140969200621607610...")これで、メモリ使用量が一定のままである間、s がゆっくりとスクロールするのを見ることができます。これは遅く、エラー処理と出力は少しぎこちないですが、9 行ほどのコードだと思いますが、それほど悪くはありません。

iteratee からの出力の取得

それはforeach- っぽい使い方でした。折り畳みのように機能する iteratee を作成することもできます。たとえば、フィルターを通過した要素を集めてリストに返します。printAction定義まで上記のすべてを繰り返し、代わりに次のように記述します。

val gatherAction = (I.consume[Option[String], IO, List] &= filtered).run

そのアクションを開始します。

val xs: Option[List[String]] = gatherAction.unsafePerformIO().sequence

コーヒーを飲みに行きましょう (かなり遠くにある必要があるかもしれません)。戻ってくると、None(IOException途中の の場合) またはSome1,943 個の文字列のリストを含む があります。

ファイルを自動的に閉じる完全な (より高速な) 例

リーダーのクローズに関する質問に答えるために、上記の 2 番目のプログラムとほぼ同等の完全な動作例を次に示しますが、リーダーのオープンとクローズを担当する列挙子を使用します。また、文字ではなく行を読み取るため、はるかに高速です。まず、インポートといくつかのヘルパー メソッドについて:

import java.io.{ BufferedReader, File, FileReader }
import scalaz._, Scalaz._, effect._, iteratee.{ Iteratee => I, _ }

def tryIO[A, B](action: IO[B]) = I.iterateeT[A, IO, Either[Throwable, B]](
  action.catchLeft.map(
    r => I.sdone(r, r.fold(_ => I.eofInput, _ => I.emptyInput))
  )
)

def enumBuffered(r: => BufferedReader) =
  new EnumeratorT[Either[Throwable, String], IO] {
    lazy val reader = r
    def apply[A] = (s: StepT[Either[Throwable, String], IO, A]) => s.mapCont(
      k =>
        tryIO(IO(reader.readLine())).flatMap {
          case Right(null) => s.pointI
          case Right(line) => k(I.elInput(Right(line))) >>== apply[A]
          case e => k(I.elInput(e))
        }
    )
  }

そして今、列挙子:

def enumFile(f: File): EnumeratorT[Either[Throwable, String], IO] =
  new EnumeratorT[Either[Throwable, String], IO] {
    def apply[A] = (s: StepT[Either[Throwable, String], IO, A]) => s.mapCont(
      k =>
        tryIO(IO(new BufferedReader(new FileReader(f)))).flatMap {
          case Right(reader) => I.iterateeT(
            enumBuffered(reader).apply(s).value.ensuring(IO(reader.close()))
          )
          case Left(e) => k(I.elInput(Left(e)))
        }
      )
  }

これで準備完了です:

val action = (
  I.consume[Either[Throwable, String], IO, List] %=
  I.filter(_.fold(_ => true, _.count(_ == '0') >= 20)) &=
  enumFile(new File("numbers.txt"))
).run

これで、処理が完了するとリーダーが閉じられます。

于 2012-11-15T00:01:38.290 に答える
0

もう少し読むべきでした...これはまさに列挙型の目的です。列挙型は Scalaz 7 と Play 2 で定義されていますが、Scalaz 6 では定義されていません。

列挙型は「垂直」構成 (「垂直統合産業」の意味で) 用であり、通常の反復型は「水平」な方法で単項的に構成します。

于 2012-11-14T21:52:45.003 に答える