2012-02-16 48 views
6

Estoy intentando modificar un archivo PostScript grande en Scala (algunos tienen un tamaño de 1 GB). El archivo es un grupo de lotes, cada lote que contiene un código que representa el número de lote, número de páginas, etc.Modificar un archivo grande en Scala

necesito:

  1. Buscar en el archivo para los códigos de lote (que siempre comience con la misma línea en el archivo)
  2. Cuente el número de páginas hasta el siguiente código de lote
  3. Modifique el código del lote para incluir cuántas páginas hay en cada lote.
  4. Guarde el nuevo archivo en una ubicación diferente.

Mi solución actual utiliza dos iteradores (iterA y iterB), creados a partir de Source.fromFile("file.ps").getLines. El primer iterador (iterA) se desplaza en un ciclo while al comienzo de un código de lote (también se llama a iterB.next cada vez). iterB luego continúa buscando hasta el próximo código de lote (o el final del archivo), contando el número de páginas que pasa a medida que avanza. Luego, actualiza el código del lote en la posición iterA, y el proceso se repite.

Esto parece muy similar a Scala y todavía no he diseñado una buena manera de guardar estos cambios en un nuevo archivo.

¿Cuál es un buen enfoque para este problema? ¿Debo deshacerme de los iteradores por completo? Preferiría hacerlo sin tener que tener toda la entrada o salida en la memoria a la vez.

Gracias!

Respuesta

2

Probablemente podría implementar esto con la clase Stream de Scala. Estoy asumiendo que no te importa sosteniendo un "lote" en la memoria a la vez.

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() 
+1

Supongo que esta solución mantendrá todo el archivo en la memoria porque en 2.9.x este patrón 'Source.fromFile (" in.ps "). getLines.toStream' se mantiene en el encabezado de la secuencia. Consulte http://stackoverflow.com/a/8640680/257449 y https://issues.scala-lang.org/browse/SI-4835. – huynhjl

+0

huynhjl, he actualizado el ejemplo de código para corregir el error (molesto) que has encontrado. Gracias. – stephenjudkins

0

puede ser usted puede utilizar span y duplicate eficacia. Suponiendo que el iterador está posicionado al comienzo de un lote, tome el lapso antes del siguiente lote, duplíquelo para que pueda contar las páginas, escriba la línea del lote modificado y luego escriba las páginas utilizando el repetidor duplicado. A continuación, procesar el siguiente lote de forma recursiva ...

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) 
    } 
} 

Suponiendo que la siguiente entrada:

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

sitúa el iterador en el inicio del lote y después de procesar los lotes:

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

Esto imprime:

head 
batch 2 
p 
p 
batch 1 
p 
batch 3 
p 
p 
p 
1

Si no persigue la iluminación funcional scala, recomendaría un estilo más imperativo usando java.util.Scanner#findWithinHorizon. Mi ejemplo es bastante ingenuo, iterando a través de la entrada dos veces.

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()