7

我正在尝试在 Scala 中修改一个大的 PostScript 文件(有些大到 1GB)。该文件是一组批次,每个批次包含一个代码,代表批次号、页数等。

我需要:

  1. 在文件中搜索批处理代码(始终以文件中的同一行开头)
  2. 计算页数直到下一个批处理代码
  3. 修改批次代码以包括每个批次中有多少页。
  4. 将新文件保存在其他位置。

我当前的解决方案使用两个迭代器(iterAiterB),从Source.fromFile("file.ps").getLines. 第一个迭代器 ( iterA) 在 while 循环中遍历到批处理代码的开头(iterB.next每次也被调用)。iterB然后继续搜索直到下一个批处理代码(或文件末尾),计算它通过的页数。iterA然后,它会更新' 位置的批处理代码,并重复该过程。

这看起来非常不像 Scala,而且我还没有设计出一种将这些更改保存到新文件中的好方法。

什么是解决这个问题的好方法?我应该完全放弃迭代器吗?我宁愿这样做,而不必一次将整个输入或输出输入内存。

谢谢!

4

3 回答 3

3

您可能可以使用 Scala 的Stream类来实现这一点。我假设您不介意一次在内存中保存一个“批次”。

import scala.annotation.tailrec
import scala.io._

def isBatchLine(line:String):Boolean = ...

def batchLine(size: Int):String = ...

val it = Source.fromFile("in.ps").getLines
// cannot use it.toStream here because of SI-4835
def inLines = Stream.continually(i).takeWhile(_.hasNext).map(_.next)

// Note: using `def` instead of `val` here means we don't hold
// the entire stream in memory
def batchedLinesFrom(stream: Stream[String]):Stream[String] = {
  val (batch, remainder) = stream span { !isBatchLine(_) }
  if (batch.isEmpty && remainder.isEmpty) { 
    Stream.empty
  } else {
    batchLine(batch.size) #:: batch #::: batchedLinesFrom(remainder.drop(1))
  }
}

def newLines = batchedLinesFrom(inLines dropWhile isBatchLine)

val ps = new java.io.PrintStream(new java.io.File("out.ps"))

newLines foreach ps.println

ps.close()
于 2012-02-17T01:10:55.887 回答
1

如果您不追求功能性的 scala 启蒙,我会推荐使用java.util.Scanner#findWithinHorizo​​n的更具命令性的样式。我的例子很天真,遍历输入两次。

val scanner = new Scanner(inFile)

val writer = new BufferedWriter(...)

def loop() = {
  // you might want to limit the horizon to prevent OutOfMemoryError
  Option(scanner.findWithinHorizon(".*YOUR-BATCH-MARKER", 0)) match {
    case Some(batch) =>
      val pageCount = countPages(batch)
      writePageCount(writer, pageCount)
      writer.write(batch)        
      loop()

    case None =>
  }
}

loop()
scanner.close()
writer.close()
于 2012-02-17T11:15:38.757 回答
0

可能是您可以有效span地使用duplicate。假设迭代器位于批处理的开头,则在下一个批处理之前获取跨度,复制它以便您可以计算页面,写入修改后的批处理行,然后使用复制的迭代器写入页面。然后递归处理下一批......

def batch(i: Iterator[String]) {
  if (i.hasNext) {
    assert(i.next() == "batch")
    val (current, next) = i.span(_ != "batch")
    val (forCounting, forWriting) = current.duplicate
    val count = forCounting.filter(_ == "p").size
    println("batch " + count)
    forWriting.foreach(println)
    batch(next)
  }
}

假设以下输入:

val src = Source.fromString("head\nbatch\np\np\nbatch\np\nbatch\np\np\np\n")

您将迭代器定位在批处理的开头,然后处理批处理:

val (head, next) = src.getLines.span(_ != "batch")
head.foreach(println)
batch(next)

这打印:

head
batch 2
p
p
batch 1
p
batch 3
p
p
p
于 2012-02-17T07:47:41.017 回答