2012-02-16 26 views
6

Sto tentando di modificare un file PostScript di grandi dimensioni in Scala (alcuni hanno dimensioni pari a 1 GB). Il file è un gruppo di lotti, con ogni lotto contenente un codice che rappresenta il numero di lotto, il numero di pagine, eccModifica di un file di grandi dimensioni in Scala

ho bisogno di:

  1. Cercare il file per i codici di lotto (che sempre iniziare con la stessa riga nel file)
  2. Contare il numero di pagine fino al codice batch successivo
  3. Modificare il codice batch per includere il numero di pagine presenti in ogni batch.
  4. Salvare il nuovo file in una posizione diversa.

mia soluzione attuale utilizza due iteratori (iterA e iterB), creata da Source.fromFile("file.ps").getLines. Il primo iteratore (iterA) attraversa in un ciclo while all'inizio di un codice batch (con iterB.next chiamato anche ogni volta). iterB continua quindi la ricerca fino al prossimo codice batch (o alla fine del file), contando il numero di pagine che passa mentre va. Quindi aggiorna il codice batch alla posizione iterA, ripetendo il processo.

Sembra molto simile a Scala e non ho ancora progettato un buon metodo per salvare queste modifiche in un nuovo file.

Qual è un buon approccio a questo problema? Devo abbandonare interamente gli iteratori? Preferirei preferirlo senza dover inserire l'intero input o l'output nella memoria in una sola volta.

Grazie!

risposta

2

Probabilmente si potrebbe implementare questo con classe di Scala Stream. Presumo che non ti dispiaccia con un "batch" in memoria alla volta.

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

La mia ipotesi è che questa soluzione manterrà l'intero file in memoria perché su 2.9.x questo modello 'Source.fromFile (" in.ps "). getLines.toStream' tiene in testa allo stream. Vedi http://stackoverflow.com/a/8640680/257449 e https://issues.scala-lang.org/browse/SI-4835. – huynhjl

+0

huynhjl, ho aggiornato il codice di esempio per correggere il bug (fastidioso) che hai trovato. Grazie. – stephenjudkins

0

Può essere che si può usare span e duplicate efficace. Supponendo che l'iteratore sia posizionato all'inizio di un batch, si prende lo span prima del batch successivo, lo si duplica in modo da poter contare le pagine, scrivere la riga di batch modificata, quindi scrivere le pagine utilizzando l'iteratore duplicato. Poi elaborare lotto successivo in modo ricorsivo ...

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

Supponendo che il seguente testo:

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

si posiziona l'iteratore all'inizio del lotto e quindi di elaborare i lotti:

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

Questa stampa:

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

Se non siete alla ricerca della scala illuminotecnica funzionale, raccomanderei uno stile più imperativo usando java.util.Scanner#findWithinHorizon. Il mio esempio è abbastanza ingenuo, ripetendo due volte l'input.

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