2010-01-19 29 views
47

在Scala中有一個Stream類,它是非常像一個迭代器。主題Difference between Iterator and Stream in Scala?提供了一些有關這兩者之間的相似之處和差異的見解。使用情況的數據流在斯卡拉

看到如何使用流很簡單,但我沒有很多常見使用情況下,我會使用流而不是其他工件。

我現在的想法:

  • 如果你需要利用無限系列。但是這對我來說似乎不是一個常見的用例,所以它不符合我的標準。 (請糾正我,如果它是常見的,我只是有一個盲點)
  • 如果你有一個系列,其中每個元素需要計算的數據,但你可能要重複使用多次。這很弱,因爲我可以將它加載到一個列表中,這個列表對於大部分開發人員來說在概念上更容易遵循。
  • 也許有一個大的數據集或計算昂貴的系列,有一個高的概率,你需要的項目將不需要訪問所有的元素。但在這種情況下,迭代器將是一個很好的匹配,除非您需要進行多次搜索,在這種情況下,即使效率稍低,也可以使用列表。
  • 有一系列需要重複使用的複雜數據。這裏也可以使用一個列表。雖然在這種情況下,兩種情況都會同樣難以使用,並且由於並非所有元素都需要加載,所以Stream會更合適。但是,這又不是那麼普通......還是這樣?

所以我錯過了什麼大的用途?或者大部分是開發者偏好?

感謝

回答

40

一個StreamIterator之間的主要區別是,後者是可變的,「一次性」的,可以這麼說,而前者則不是。 Iterator具有更好的內存佔用比Stream,但事實證明這是可變是不方便。

把這個經典的素數生成器,例如:

def primeStream(s: Stream[Int]): Stream[Int] = 
    Stream.cons(s.head, primeStream(s.tail filter { _ % s.head != 0 })) 
val primes = primeStream(Stream.from(2)) 

它可以很容易地與Iterator以及編寫的,但一個Iterator不會保持到目前爲止計算出的質數。

因此,Stream的一個重要方面是,您可以將它傳遞給其他函數,而不必先複製它,或者必須一次又一次生成它。

至於昂貴的計算/無限列表,這些東西都可以用Iterator的形式完成。無限列表實際上是非常有用的 - 你只是不知道它,因爲你沒有它,所以你看到的算法比嚴格必要的更復雜,只是爲了處理強制的有限大小。

+2

我想補充這另一個區別是,'Stream'從來沒有在它的頭部元素懶惰。 「流」的頭部以評估形式存儲。如果需要一個沒有元素(包括頭部)直到被請求的序列,那麼'Iterator'是唯一的選擇。 – Lii 2016-08-07 08:37:55

+0

除了頭元素不懶惰之外,它還評估你想要放下的每個元素。例如:' 「一個」 #:: 「B」 #:: 「C」 #:: 「d」 #:: Stream.empy [字符串] .drop(3)'將評價 「一」, 「B」,「 c「和」d「。 「d」因爲它變成了頭。 – r90t 2016-08-13 13:21:23

+0

素數生成器的有趣簡潔示例。有趣的是,如果我在一個簡單的Scala控制檯中創建它,然後請求4000個素數(在實踐中沒有那麼多,我有一個可以在不到2秒內創建100K的替代定義),它會導致Scala出現「GC overhead overhead exceeded」錯誤。 – 2018-02-06 20:24:53

17

除了丹尼爾的回答之外,請記住Stream對於短路評估很有用。例如,假設我有一個巨大的一套採取String並返回Option[String]功能,我想繼續執行,直到其中的一個作品:

val stringOps = List(
    (s:String) => if (s.length>10) Some(s.length.toString) else None , 
    (s:String) => if (s.length==0) Some("empty") else None , 
    (s:String) => if (s.indexOf(" ")>=0) Some(s.trim) else None 
); 

嘛,我當然不希望執行整個列表,並且在List上沒有任何方便的方法,它說:「將它們作爲函數處理並執行它們,直到它們中的一個返回除None以外的其他值」。該怎麼辦?也許這:

def transform(input: String, ops: List[String=>Option[String]]) = { 
    ops.toStream.map(_(input)).find(_ isDefined).getOrElse(None) 
} 

這需要一個列表,並把它當作一個Stream(實際上不評價任何東西),然後定義一個新的Stream作爲應用功能的結果(但不評估任何東西),然後搜索第一個被定義的 - 在這裏,神奇地,它回顧並且意識到它必須應用該地圖,並從原始列表中獲得正確的數據 - 然後從Option[Option[String]]中解開它到Option[String]使用getOrElse

下面是一個例子:

scala> transform("This is a really long string",stringOps) 
res0: Option[String] = Some(28) 

scala> transform("",stringOps) 
res1: Option[String] = Some(empty) 

scala> transform(" hi ",stringOps) 
res2: Option[String] = Some(hi) 

scala> transform("no-match",stringOps) 
res3: Option[String] = None 

但它的工作原理?如果我們把一個println到我們的功能,所以我們可以告訴我們,如果他們是所謂的,我們得到

val stringOps = List(
    (s:String) => {println("1"); if (s.length>10) Some(s.length.toString) else None }, 
    (s:String) => {println("2"); if (s.length==0) Some("empty") else None }, 
    (s:String) => {println("3"); if (s.indexOf(" ")>=0) Some(s.trim) else None } 
); 
// (transform is the same) 

scala> transform("This is a really long string",stringOps) 
1 
res0: Option[String] = Some(28) 

scala> transform("no-match",stringOps)      
1 
2 
3 
res1: Option[String] = None 

(這是斯卡拉2.8; 2.7的實現有時會通過一個超調,遺憾的是並注意您。 積累的None很長的列表,你的失敗累積,但據推測相比,這裏你真正的計算,這是廉價的。)

+0

我其實就是這樣的一個例子,但這可以用'Iterator'輕鬆完成,所以我認爲它並不重要。 – 2010-01-20 10:45:00

+2

授予。我應該澄清這不是Stream特定的,或者選擇了對同一個Stream使用多個調用的示例。 – 2010-01-20 11:34:34

2

StreamIteratorimmutable.Listmutable.List。有利於不變性防止了一類錯誤,偶爾以性能爲代價。

scalac本身也不能倖免於這些問題:http://article.gmane.org/gmane.comp.lang.scala.internals/2831

由於丹尼爾指出,這有利於在懶惰嚴格簡化算法,並使其更容易撰寫他們。

+1

當然,對於那些懶惰的新手來說,主要的注意事項是它降低了代碼的可預測性,可能導致海森堡,並且可能對某些類別的算法有嚴重的性能問題。 – 2010-01-21 03:26:36

7

我可以想象,如果您實時輪詢某個設備,Stream更方便。

想象一下GPS追蹤器,如果你問它,它會返回實際位置。您無法預先計算您在5分鐘內的位置。您可以使用它了幾分鐘才具體化在OpenStreetMap的路徑或者你可能在沙漠或熱帶雨林用它來征戰超過六個月。

或數字溫度計或其他類型的重複地返回新的數據,只要硬件是活的,打開傳感器 - 日誌文件過濾器可以是另一個例子。

+1

Upvoted爲Stream的良好使用案例。 – nilskp 2012-10-26 13:24:41