【问题标题】:Scala: Read some data of an Enumerator[T] and return the remaining Enumerator[T]Scala:读取 Enumerator[T] 的一些数据并返回剩余的 Enumerator[T]
【发布时间】:2012-06-12 14:19:30
【问题描述】:

我正在使用使用 Iteratees 和 Enumerators 的 playframework 的异步 I/O 库。我现在有一个 Iterator[T] 作为数据接收器(为简单起见,它是一个将其内容存储到文件中的 Iterator[Byte])。这个 Iterator[Byte] 被传递给处理写入的函数。

但在写之前我想在文件开头添加一些统计信息(为了简化说它是一个字节),所以我在将迭代器传递给写函数之前按以下方式传输它:

def write(value: Byte, output: Iteratee[Byte]): Iteratee[Byte] =
    Iteratee.flatten(output.feed(Input.El(value)))

当我现在从磁盘读取存储的文件时,我得到了一个 Enumerator[Byte]。 首先,我想读取并删除附加数据,然后我想将 Enumerator[Byte] 的其余部分传递给处理读取的函数。 所以我还需要对枚举器进行改造:

def read(input: Enumerator[Byte]): (Byte, Enumerator[Byte]) = {
   val firstEnumeratorEntry = ...
   val remainingEnumerator = ...
   (firstEnumeratorEntry, remainingEnumerator)
}

但我不知道该怎么做。如何从 Enumerator 读取一些字节并获取剩余的 Enumerator?

用OutputStream替换Iteratee[Byte],用InputStream替换Enumerator[Byte],这很容易:

def write(value: Byte, output: OutputStream) = {
    output.write(value)
    output
}
def read(input: InputStream) = (input.read,input)

但我需要播放框架的异步 I/O。

【问题讨论】:

  • mmmmh,我在想(但需要检查一下)是让read 方法返回(Byte, Enumerator[Byte]) 会破坏Async。因为一切都基于 Iteratee、Enumeratee、Enumerator,它们就像“嘿,我会承诺给予或映射或生产一些东西”。但是,当您说 Byte... 时,您已经有了一些东西。因此,我的想法是……我们处于两个不同的时间线。
  • 我会满足于 (Promise[Byte],Enumerator[Byte])
  • 我认为应该使用fold 伴侣的fold 方法来完成。所以创建一个 Iteratee 持有一个可能类似于(Option[Byte], Array[Byte]) 的状态。你从(None, [])开始
  • 其实我在使用Byte的时候并不是你也有。您可能必须使用转换后的类型,例如 (Option[Int], List[Entry]) 或其他类型。

标签: scala playframework playframework-2.0 enumerator iterate


【解决方案1】:

我想知道你是否可以从另一个角度解决你的目标。

那个将使用剩余枚举数的函数,我们称它为remaining,大概它适用于一个迭代器来处理余数:remaining |>> iteratee 产生另一个迭代器。让我们称这个结果为 iteratee iteratee2... 你能检查一下你是否可以获得对 iteratee2 的引用吗?如果是这种情况,那么您可以使用第一个迭代对象head 获取并处理第一个字节,然后通过 flatMap 组合headiteratee2

val head = Enumeratee.take[Byte](1) &>> Iteratee.foreach[Byte](println)
val processing = for { h <- head; i <- iteratee2 } yield (h, i)
Iteratee.flatten(processing).run

如果您无法获得iteratee2 - 如果您的枚举器与您未实现的枚举器组合时就是这种情况 - 那么这种方法将不起作用。

【讨论】:

  • 你的意思是val head = Enumeratee.take[Byte](1) &amp;&gt;&gt; Iteratee.getChunks[Byte]
  • @Sadache,这看起来不错。 getChunks 是在 2.0 还是 2.0.1 中还是只在 master 中?
  • 它在master上。 def getChunks[E]: Iteratee[E,List[E]] = fold[E, List[E]](Nil) { (els, chunk) =&gt; els :+ chunk }
  • 这是 head 的直接定义(我添加的)(不假设 E 是类似列表的)def head[E]:Iteratee[E,Option[E]] = { def step:K[E,Option[E]] = { case Input.Empty =&gt; Cont(step) case Input.EOF =&gt; Done(None,Input.EOF) case Input.El(e) =&gt; Done(Some(e), Input.Empty) } Cont(step) }
  • 另一个假设 E 是类列表: def head[E,A](implicit p: E => scala.collection.TraversableLike[A, E]):Iteratee[E,Option[A ]] = { def step:K[E,Option[A]] = { case Input.Empty => Cont(step) case Input.EOF => Done(None,Input.EOF) case Input.El(xs) if ! xs.isEmpty => Done(Some(xs.head), Input.El(xs.tail)) case Input.El(empty) => Cont(step) } Cont(step) }
【解决方案2】:

这是通过在 Iteratee 和适当的(某种)状态累加器(此处为元组)内折叠来实现此目的的一种方法

我去读取routes 文件,第一个字节将作为Char 读取,另一个将作为UTF-8 字节串附加到String

  def index = Action {
    /*let's do everything asyncly*/
    Async {
      /*for comprehension for read-friendly*/
      for (
        i <- read; /*read the file */
        (r:(Option[Char], String)) <- i.run /*"create" the related Promise and run it*/
      ) yield Ok("first : " + r._1.get + "\n" + "rest" + r._2) /* map the Promised result in a correct Request's Result*/
    }
  }


  def read = {
    //get the routes file in an Enumerator
    val file: Enumerator[Array[Byte]] = Enumerator.fromFile(Play.getFile("/conf/routes"))

    //apply the enumerator with an Iteratee that folds the data as wished
    file(Iteratee.fold((None, ""):(Option[Char], String)) { (acc, b) =>
       acc._1 match {
         /*on the first chunk*/ case None => (Some(b(0).toChar), acc._2 + new String(b.tail, Charset.forName("utf-8")))
         /*on other chunks*/ case x => (x, acc._2 + new String(b, Charset.forName("utf-8")))
       }
    })

  }

编辑

我发现了另一种使用 Enumeratee 的方法,但它需要创建 2 个 Enumerator s(一个短暂的)。但是,它是否更优雅一些。我们使用“一种” Enumeratee,但 Traversal 的工作水平比 Enumeratee(块级)更好。 我们使用 take 1 ,它只占用 1 个字节,然后关闭流。另一方面,我们使用drop,它只是删除第一个字节(因为我们使用的是 Enumerator[Array[Byte]])

此外,现在read2 的签名比您希望的更接近,因为它返回 2 个枚举器(距离 Promise 和枚举器不远)

def index = Action {
  Async {
    val (first, rest) = read2
    val enee = Enumeratee.map[Array[Byte]] {bs => new String(bs, Charset.forName("utf-8"))}

    def useEnee(enumor:Enumerator[Array[Byte]]) = Iteratee.flatten(enumor &> enee |>> Iteratee.consume[String]()).run.asInstanceOf[Promise[String]]

    for {
      f <- useEnee(first);
      r <- useEnee(rest)
    } yield Ok("first : " + f + "\n" + "rest" + r)
  }
}

def read2 = {
  def create = Enumerator.fromFile(Play.getFile("/conf/routes"))

  val file: Enumerator[Array[Byte]] = create
  val file2: Enumerator[Array[Byte]] = create

  (file &> Traversable.take[Array[Byte]](1), file2 &> Traversable.drop[Array[Byte]](1))

}

【讨论】:

  • 为什么需要第二个文件枚举器?枚举器不是不可变的功能对象吗?所以我可以对两个被枚举者使用相同的枚举器?
  • 是的,但这可能是因为底层 InputStream 不是... 进一步检查
  • 你是对的。 Enumerator.fromFile 使用 fromStream 和 Enumerator.fromStream 只是通过 Enumerator.fromCallback 实现的,它存储一个(可变的!)FileInputStream。为了在函数上下文中可用,这应该实现为不可变的,或者至少通过存储文件指针来实现,但事实并非如此。
  • 没错,因此我创建了两个枚举器。第一个非常短暂,但由于其构造而引入了开销......
【解决方案3】:

实际上我们喜欢Iteratees,因为他们会作曲。因此,与其从原来的创建多个 Enumerators,不如按顺序组合两个 Iteratee(先读和再读),然后用您的单个 Enumerator 提供它。

为此,您需要一个顺序组合方法,现在我称之为andThen。这是一个粗略的实现。请注意,返回未使用的输入有点苛刻,也许可以使用基于 Input 类型的类型类自定义行为。它也不处理将剩余的东西从第一个迭代器传递给第二个迭代器(练习:)。

object Iteratees {
  def andThen[E, A, B](a: Iteratee[E, A], b: Iteratee[E, B]): Iteratee[E, (A,B)] = new Iteratee[E, (A,B)] {
    def fold[C](
        done: ((A, B), Input[E]) => Promise[C],
        cont: ((Input[E]) => Iteratee[E, (A, B)]) => Promise[C],
        error: (String, Input[E]) => Promise[C]): Promise[C] = {

      a.fold(
        (ra, aleft) => b.fold(
          (rb, bleft) => done((ra, rb), aleft /* could be magicop(aleft, bleft)*/),
          (bcont) => cont(e => bcont(e) map (rb => (ra, rb))),
          (s, err) => error(s, err)
        ),
        (acont) => cont(e => andThen[E, A, B](acont(e), b)),
        (s, err) => error(s, err)
      )
    }
  }
}

现在您可以使用以下内容:

object Application extends Controller {

  def index = Action { Async {

    val strings: Enumerator[String] = Enumerator("1","2","3","4")
    val takeOne = Cont[String, String](e => e match {
      case Input.El(e) => Done(e, Input.Empty)
      case x => Error("not enough", x)
    })
    val takeRest = Iteratee.consume[String]()
    val firstAndRest = Iteratees.andThen(takeOne, takeRest)

    val futureRes = strings(firstAndRest) flatMap (_.run)

    futureRes.map(x => Ok(x.toString)) // prints (1,234)
  } }

}

【讨论】:

  • 查看@huynhji 的回答,在这里您可以看到实际上您不必自己实现andThen,因为它可以使用flatMap 进行组合。
猜你喜欢
  • 2014-10-04
  • 1970-01-01
  • 2013-10-06
  • 2014-06-02
  • 2021-08-23
  • 2012-05-26
  • 1970-01-01
  • 1970-01-01
  • 2011-11-03
相关资源
最近更新 更多