2012-02-16 11 views
6

में एक बड़ी फ़ाइल को संशोधित करना मैं स्कैला में एक बड़ी पोस्टस्क्रिप्ट फ़ाइल को संशोधित करने की कोशिश कर रहा हूं (कुछ आकार में 1 जीबी जितना बड़ा है)। फ़ाइल, बैच का एक समूह है एक कोड है कि बैच नंबर, पृष्ठों की संख्या का प्रतिनिधित्व करता है, आदिस्कैला

मैं जरूरत से युक्त प्रत्येक बैच के साथ करने के लिए:

  1. बैच कोड के लिए फ़ाइल खोजें (हमेशा जो फ़ाइल में एक ही पंक्ति के साथ शुरू करें)
  2. अगले बैच कोड
  3. प्रत्येक बैच में कितने पेज हैं, शामिल करने के लिए बैच कोड संशोधित करें तक पृष्ठों की संख्या की गणना करें।
  4. नई फ़ाइल को किसी अन्य स्थान पर सहेजें।

मेरे वर्तमान समाधान दो iterators (iterA और iterB), Source.fromFile("file.ps").getLines से बनाए उपयोग करता है। पहला इटरेटर (iterA) बैच कोड की शुरुआत में थोड़ी देर के लूप में ट्रैवर्स (iterB.next प्रत्येक बार भी कहा जाता है)। iterB फिर अगले बैच कोड (या फ़ाइल का अंत) तक खोज जारी रखता है, जो इसे गुजरने वाले पृष्ठों की संख्या को गिनता है। फिर, यह iterA की स्थिति पर बैच कोड अपडेट करता है, प्रक्रिया दोहराती है।

यह बहुत गैर-स्कैला जैसा लगता है और मैंने अभी भी इन परिवर्तनों को एक नई फ़ाइल में सहेजने के लिए एक अच्छा तरीका नहीं बनाया है।

इस समस्या का अच्छा दृष्टिकोण क्या है? क्या मुझे पूरी तरह से इटरेटर को कुचलना चाहिए? मैं अधिमानतः एक ही समय में पूरे इनपुट या आउटपुट में स्मृति के बिना ऐसा करना पसंद करूंगा।

धन्यवाद!

उत्तर

2

आप शायद इसे स्कैला के 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() 
+1

मेरा अनुमान है कि यह समाधान पूरी फ़ाइल को स्मृति में रखेगा क्योंकि 2.9.x इस पैटर्न 'Source.fromFile ("में।पीएस ")। getLines.toStream' स्ट्रीम के सिर पर है। http://stackoverflow.com/a/8640680/257449 और https://issues.scala-lang.org/browse/SI-4835 देखें। – huynhjl

+0

huynhjl, मैंने आपके द्वारा पाई गई (परेशान) बग को ठीक करने के लिए कोड नमूना अपडेट किया है। धन्यवाद। – stephenjudkins

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 
1

यदि आप कार्यात्मक स्कैला ज्ञान की खोज में नहीं हैं, तो मैं java.util.Scanner#findWithinHorizon का उपयोग करके एक और अनिवार्य शैली की अनुशंसा करता हूं। मेरा उदाहरण काफी मूर्ख है, इनपुट के माध्यम से दो बार पुनरावृत्ति।

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