2012-02-16 4 views
6

스칼라에서 대형 포스트 스크립트 파일을 수정하려고합니다 (크기가 1GB 정도되는 파일도 있습니다). 이 파일에 등을 배치 번호, 페이지 수를 나타내는 코드, 내가 필요스칼라에서 대용량 파일 수정

를 포함하는 각 배치와 배치의 그룹 :

  1. 배치 코드의 파일 검색 (항상하는 파일에서 같은 줄로 시작하십시오.
  2. 다음 배치 코드까지 페이지 수를 계산하십시오.
  3. 각 배치에 페이지 수를 포함하도록 배치 코드를 수정하십시오.
  4. 새 파일을 다른 위치에 저장하십시오.

나의 현재 용액 Source.fromFile("file.ps").getLines에서 만든 두 반복기 (iterAiterB)를 사용한다. 첫 번째 반복자 (iterA)는 while 루프에서 배치 코드 시작 부분까지 이동합니다 (도 매번 호출됩니다). 그런 다음 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 (".ps "). getLines.toStream' 스트림의 머리 부분을 유지합니다. http://stackoverflow.com/a/8640680/257449 및 https://issues.scala-lang.org/browse/SI-4835를 참조하십시오. – huynhjl

+0

huynhjl, 발견 한 (성가신) 버그를 수정하기 위해 코드 샘플을 업데이트했습니다. 감사합니다. – stephenjudkins

0

은 효과적으로 spanduplicate을 사용할 수 있습니다 될 수 있습니다. 반복기가 배치의 시작 부분에 있다고 가정하면 다음 배치 이전의 기간을 가져 와서 복제하여 페이지를 셀 수 있고 수정 된 배치 라인을 작성한 다음 복제 된 반복자를 사용하여 페이지를 작성할 수 있습니다. 그런 다음 다음과 같은 입력 가정 반복적으로 다음 배치 ...

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