2014-05-20 50 views
3

例如,考慮一個小文件。如何將迭代器中的項目T分組爲迭代器[Seq [T]]

one 
two 
three 

four 
five 

six 
seven 
eight 
nine 

我想編寫的代碼,將採取一行迭代it: Iterator[String]並進行迭代sectionIt: Iterator[Seq[String]],超過的部分迭代。

在C#和Ruby中,使用yield關鍵字很容易實現。有talk of how to add that keyword to scala,但它取決於編譯器插件。

創建sectionIt的一種方法是直接創建Iterator[Seq[String]]並覆蓋nexthasNext。對於像Scala這樣的更高級語言來說,這種方法看起來很乏味且需要大量國家資源。

我意識到還有其他的流式數據抽象,如迭代,這可能會使這更容易,但這不是一個容易賣給正在學習一種新語言的人。

在Scala中編寫上述代碼的好方法是什麼?

+0

爲什麼你想要這些特定類型?爲什麼不'Seq [Seq [String]]'? – Bill

+0

我不想一次把所有內容都放在內存中。 – schmmd

+2

我知道你說過「沒有迭代」,但是考慮給[Scalaz Stream](https://github.com/scalaz/scalaz-stream)一試!它沒有比'io.linesR(「data.txt」)更好。split(_。isEmpty)'。 –

回答

2

稍微不同版本的其他答案:

def section(it: Iterator[String]): Iterator[Seq[String]] = { 
    def spanned(it: Iterator[String]): Stream[Seq[String]] = 
     if (!it.hasNext) Stream.empty 
     else { val (a, b) = it span (_ != "") ; a.toSeq #:: spanned(b drop 1) } 
    spanned(it).iterator 
    } 

這是一個有點懶惰和周圍之間的空白閱讀行爲線條不同:

scala> lazysplit.Test.splitOnBlankLines(f"%n%n%n%n%n".lines).size 
res0: Int = 6 

scala> lazysplit.Test.section(f"%n%n%n%n%n".lines).size 
res1: Int = 5 
+0

太棒了。使用span對於這個問題很有意義。我一直想念,可以構建使用流的迭代器。 – schmmd

2

你可以完成大部分的你會用Ruby想要的東西或C#的yield使用Stream

def splitOnBlankLines(iter: Iterator[String]): Iterator[Seq[String]] = { 
    def asStream(list: List[String]): Stream[List[String]] = { 
    if (iter.hasNext) { 
     val line = iter.next() 
     if (line == "") 
     list.reverse #:: asStream(Nil) 
     else 
     asStream(line :: list) 
    } else { 
     list.reverse #:: Stream.empty 
    } 
    } 
    asStream(Nil).iterator 
} 

每當我們會想yield,我們使用#::與價值,我們希望在這種情況下返回(list.reverse )和表示流的其餘部分的表達式。 #::將此表達式作爲一個名稱參數,所以它不會執行,直到需要其餘的Stream。當返回最後一個值時,我們使用Stream.empty來表示不會生成更多值。

這是可能的的Stream這種行爲結合與延續插件得到的東西語法上等同於Ruby或的yield(在所有二十行代碼)C#,但延續插件是不可能永遠成爲穩定。

然而,手工編寫的Iterator幾乎是一樣簡單:

import scala.annotation.tailrec 

class BlankLineSplittingIterator(iter: Iterator[String]) extends Iterator[Seq[String]] { 
    def hasNext = iter.hasNext 
    def next = { 
    if (!iter.hasNext) 
     Iterator.empty.next 
    @tailrec def untilBlank(list: List[String]): List[String] = { 
     val line = iter.next() 
     if (line == "" || !iter.hasNext) 
     list.reverse 
     else 
     untilBlank(line :: list) 
    } 
    untilBlank(Nil) 
    } 
} 
+0

遍歷迭代器時,您的流代碼會將整個數據結構保存在內存中嗎? – schmmd

+0

否。返回的「Iterator」只保留對尚未返回的「Stream」部分的引用。 – wingedsubmariner