加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

使用懒惰评估或融合的Scala中的迭代?

发布时间:2020-12-16 09:18:17 所属栏目:安全 来源:网络整理
导读:我听说迭代是懒惰的,但他们究竟是多么懒惰?或者,可以迭代与后处理函数进行融合,以便不必构建中间数据结构? 我可以在我的迭代中,例如从java.io.BufferedReader中构建一个100万个项目Stream [Option [String]],然后以组合方式过滤掉Nones,而不需要将整个流保
我听说迭代是懒惰的,但他们究竟是多么懒惰?或者,可以迭代与后处理函数进行融合,以便不必构建中间数据结构?

我可以在我的迭代中,例如从java.io.BufferedReader中构建一个100万个项目Stream [Option [String]],然后以组合方式过滤掉Nones,而不需要将整个流保存在内存中?同时保证我不要吹堆?或者类似的东西 – 它不必使用流.

我正在使用Scalaz 6,但如果其他迭代实现能够以更好的方式执行此操作,我有兴趣知道.

请提供一个完整的解决方案,包括关闭BufferedReader并调用unsafePerformIO(如果适用).

解决方法

以下是使用Scalaz 7库演示您感兴趣的属性的快速迭代示例:常量内存和堆栈使用情况.

问题

首先假设我们有一个大文本文件,每一行都有一个十进制数字的字符串,我们要查找包含至少二十个零的所有行.我们可以生成一些这样的示例数据:

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更改为Options):

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

Scalaz 7目前没有提供一个很好的方法来枚举文件的行,所以我们一次通过该文件来分块一个字符.这当然会很慢,但是我不会在这里担心,因为这个演示的目的是表明我们可以在恒定的内存中处理这个大型的ish文件,而不会吹动堆栈.这个答案的最后一部分给出了一个更好的表现的方法,但是这里我们只需要换行符:

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

而如果splitOn采用指定不分割的位置的谓词混淆了你的事实,那么你并不孤单.分裂是我们的枚举的第一个例子.我们将继续包装我们的枚举器:

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

现在我们在IO monad中有一个Option [String]的枚举器.

使用枚举过滤文件

接下来我们的谓语 – 记住我们说我们想要至少二十个零的行:

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 …”)慢慢滚动,而我们的内存使用率保持不变.这很慢,错误处理和输出有点笨拙,但不是太糟糕,我认为大约九行代码.

从迭代中获取输出

这是foreach-ish的用法.我们还可以创建一个更像迭代的迭代,例如收集通过过滤器并将其返回到列表中的元素.只需重复上面的所有内容直到printAction定义,然后再写:

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

踢那个动作:

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

现在去喝咖啡(可能需要很远).当你回来的时候,你会有一个None(在某个地方有一个IOException的情况),或者一个包含1,943个字符串的列表.

自动关闭文件的完整(更快)示例

为了回答关于关闭读者的问题,这里是一个完整的工作示例,大致相当于上面的第二个程序,但是使用一个枚举器来负责打开和关闭读者.它也是更快,更快,因为它读取行,而不是字符.首先为进口和一些辅助方法:

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

def tryIO[A,B](action: IO[B]) = I.iterateeT[A,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,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,IO] =
  new EnumeratorT[Either[Throwable,IO] {
    def apply[A] = (s: StepT[Either[Throwable,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,List] %=
  I.filter(_.fold(_ => true,_.count(_ == '0') >= 20)) &=
  enumFile(new File("numbers.txt"))
).run

现在读者将在处理完成时关闭.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读