2012-04-26 61 views
6

Chciałbym napisać prostą funkcję, która iteruje po liniach pliku tekstowego. Wierzę w 2.8 jeden mógłby zrobić:Iterowanie po liniach pliku

def lines(filename: String) : Iterator[String] = { 
    scala.io.Source.fromFile(filename).getLines 
} 

i tyle, ale w 2.9 powyższe nie działa, a zamiast tego muszę zrobić:

def lines(filename: String) : Iterator[String] = { 
    scala.io.Source.fromFile(new File(filename)).getLines() 
} 

Teraz problem jest, chcę komponować powyższe iteratory w for zrozumieniem:

for (l1 <- lines("file1.txt"); l2 <- lines("file2.txt")){ 
    do_stuff(l1, l2) 
} 

To znowu pracował w porządku z 2.8 ale powoduje „zbyt mA ny otwarte pliki " wyjątek, aby uzyskać wyrzucony w 2.9. Jest to zrozumiałe - drugie zrozumienie kończy się otwarciem (i zamknięciem) pliku dla każdej linii w pierwszym.

W moim przypadku, ja wiem, że "file1.txt" jest duży i nie chce ssać go do
pamięci, ale drugi plik jest niewielka, więc mogę napisać inny linesEager jak tak:

def linesEager(filename: String): Iterator[String] = 
    val buf = scala.io.Source.fromFile(new File(filename)) 
    val zs = buf.getLines().toList.toIterator 
    buf.close() 
    zs 

a następnie skręcić w moją For-zrozumieniem na:

for (l1 <- lines("file1.txt"); l2 <- linesEager("file2.txt")){ 
    do_stuff(l1, l2) 
} 

to działa, ale jest wyraźnie brzydkie. Czy ktoś może zaproponować jednolity sposób uzyskania powyższego celu? &. Wygląda na to, że potrzebujesz sposobu na iterator zwrócony przez lines do close pliku, gdy dojdzie do końca, i to musiało się dziać w 2.8, dlaczego właśnie tam działało?

Dzięki!

BTW - tutaj jest minimalna wersja pełnego programu, który pokazuje problem:

import java.io.PrintWriter 
import java.io.File 

object Fail { 

    def lines(filename: String) : Iterator[String] = { 
    val f = new File(filename) 
    scala.io.Source.fromFile(f).getLines() 
    } 

    def main(args: Array[String]) = { 
    val smallFile = args(0) 
    val bigFile = args(1) 

    println("helloworld") 

    for (w1 <- lines(bigFile) 
     ; w2 <- lines(smallFile) 
     ) 
    { 
     if (w2 == w1){ 
     val msg = "%s=%s\n".format(w1, w2) 
     println("found" + msg) 
     } 
    } 

    println("goodbye") 
    } 

} 

Na 2.9.0 skompilować z scalac WordsFail.scala a następnie uzyskać to:

[email protected]:$ scalac WordsFail.scala 
[email protected]:$ scala Fail passwd words 
helloworld 
java.io.FileNotFoundException: passwd (Too many open files) 
    at java.io.FileInputStream.open(Native Method) 
    at java.io.FileInputStream.<init>(FileInputStream.java:120) 
    at scala.io.Source$.fromFile(Source.scala:91) 
    at scala.io.Source$.fromFile(Source.scala:76) 
    at Fail$.lines(WordsFail.scala:8) 
    at Fail$$anonfun$main$1.apply(WordsFail.scala:18) 
    at Fail$$anonfun$main$1.apply(WordsFail.scala:17) 
    at scala.collection.Iterator$class.foreach(Iterator.scala:652) 
    at scala.io.BufferedSource$BufferedLineIterator.foreach(BufferedSource.scala:30) 
    at Fail$.main(WordsFail.scala:17) 
    at Fail.main(WordsFail.scala) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:78) 
    at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:24) 
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:88) 
    at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:78) 
    at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101) 
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:33) 
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:40) 
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:56) 
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:80) 
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:89) 
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala) 
+3

Kod jeden działa dla mnie w REPL (Scala 2.9). –

+0

To nie było ";" niestety. –

+0

@userunknown Działa, ale nie skaluje. (Wyobraź sobie duże pliki/wiele linii.) – Debilski

Odpowiedz

13

scala-arm to świetny mechanizm do automatycznego zamykania zasobów, gdy skończysz.

import resource._ 
import scala.io.Source 

for (file1 <- managed(Source.fromFile("file1.txt")); 
    l1 <- file1.getLines(); 
    file2 <- managed(Source.fromFile("file2.txt")); 
    l2 <- file2.getLines()) { 
    do_stuff(l1, l2) 
} 

Ale jeśli liczysz na zawartości file2.txt zmienić podczas jesteś zapętlenie poprzez file1.txt, byłoby najlepiej, aby przeczytać, że w List przed wami pętli. Nie ma potrzeby przekształcania go w Iterator.

+0

Czy konwertowanie go na listę nie kończy się na przechowywaniu całego pliku w pamięci? Miałem nadzieję, że tego uniknę ... –

+0

Ale 'plik2.txt' jest mały, więc powinien być akceptowalny. Plus, to właśnie robi twój 'linesEager' (' .toList'), z tym wyjątkiem, że tworzysz go w pamięci i wyrzucasz go dla każdej linii w 'file1.txt'. – leedm777

+0

Cześć Dave, tak masz rację. Byłem w niezadowoleniu, że różne działania w zrozumieniu musiały mieć ten sam typ, a więc nieistotny "toList.toIterator", gdy wystarczy "toList" wystarczy ... Dzięki! –

2

Może powinieneś spójrz na scala-arm (https://github.com/jsuereth/scala-arm) i pozwól, aby zamykanie plików (strumienie wejściowe plików) odbywało się automatycznie w tle.

Powiązane problemy