Scala причудливый в этом коде цикла - PullRequest
7 голосов
/ 21 июля 2011

Вчера этот кусок кода вызвал у меня головную боль. Я исправил это, читая файл построчно. Есть идеи?

Цикл while никогда не выполняется, хотя число строк в файле больше 1.

 val lines = Source.fromFile( new File("file.txt") ).getLines;

 println( "total lines:"+lines.size );

 var starti = 1;
 while( starti < lines.size ){
   val nexti = Math.min( starti + 10, lines.size  );

   println( "batch ("+starti+", "+nexti+") total:" + lines.size )
   val linesSub = lines.slice(starti, nexti)
   //do something with linesSub
   starti = nexti
 }

Ответы [ 4 ]

14 голосов
/ 21 июля 2011

Это действительно сложно, и я бы даже сказал, что это ошибка в Iterator. getLines возвращает Iterator, что происходит лениво. Таким образом, кажется, что если вы запрашиваете lines.size, итератор просматривает весь файл для подсчета строк. После этого он "исчерпан":

scala> val lines = io.Source.fromFile(new java.io.File("....txt")).getLines
lines: Iterator[String] = non-empty iterator

scala> lines.size
res4: Int = 15

scala> lines.size
res5: Int = 0

scala> lines.hasNext
res6: Boolean = false

Видите ли, когда вы выполняете size дважды, результат равен нулю.

Есть два решения, либо вы заставляете итератор превращаться во что-то «стабильное», например lines.toSeq. Или вы забудете о size и выполните «обычную» итерацию:

while(lines.hasNext) {
  val linesSub = lines.take(10)
  println("batch:" + linesSub.size)
  // do something with linesSub
}
5 голосов
/ 21 июля 2011

Ни один из приведенных выше ответов совершенно не касается гвоздя на голове.

Есть веская причина, по которой Iterator возвращается сюда.Будучи ленивым, он снимает нагрузку с кучи, и строка, представляющая каждую строку, может быть собрана мусором, как только вы закончите с ней.В случае больших файлов это может существенно помочь избежать исключения OutOfMemoryException.

В идеале вы должны работать непосредственно с итератором, а не принудительно устанавливать его в строгий тип коллекции.

Используя grouped, тогда, как в ответе om-nom-nom:

for (linesSub <- lines grouped 10) {
  //do something with linesSub
}

И если вы хотите сохранить счетчик println, индексируйте индекс:

for ( (linesSub, batchIdx) <- (lines grouped 10).zipWithIndex ) {
  println("batch " + batchIdx)
  //do something with linesSub
}

Если вам действительно нужно общее количество, вызовите getLines дважды.Один раз для подсчета, а второй раз для фактической обработки строк.

4 голосов
/ 21 июля 2011

Я переписал ваш код Seq способом, который был предложен в ответе @ 0__:

val batchSize = 10;
val lines = Source.fromFile("file.txt").getLines.toSeq;

 println( "total lines:"+lines.length);

 var processed = 0;
 lines.grouped(batchSize).foreach( batch => {
      println( "batch ("+processed+","+(processed+Math.min(lines.length-processed,batchSize))+")
               total:"+lines.length
      );
      processed = processed + batchSize;
      //do something with batch
   }
 )
4 голосов
/ 21 июля 2011

При втором вызове lines.size возвращается 0. Это потому, что lines является итератором, а не массивом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...