迭代器並非集合,而是一種逐一存取集合元素的方法。迭代器 it
的兩個基本操作為 next
和 hasNext
。呼叫 it.next()
會傳回迭代器的下一個元素並推進迭代器的狀態。再次對同一個迭代器呼叫 next
,會產生比先前傳回元素再下一個的元素。如果沒有更多元素可以傳回,呼叫 next
會擲出 NoSuchElementException
。您可以使用 Iterator 的 hasNext
方法來找出是否還有更多元素可以傳回。
「逐步執行」迭代器 it
傳回的所有元素最直接的方法是使用 while 迴圈
while (it.hasNext)
println(it.next())
while it.hasNext do
println(it.next())
Scala 中的迭代器也提供類似於 Iterable
和 Seq
類別中大多數方法的類比。例如,它們提供一個 foreach
方法,用於對迭代器傳回的每個元素執行特定程序。使用 foreach
,上面的迴圈可以縮寫為
it.foreach(println)
一如往常,for-expression 可以用作涉及 foreach
、map
、withFilter
和 flatMap
的 expression 的替代語法,因此列印迭代器傳回的所有元素的另一種方法是
for (elem <- it) println(elem)
for elem <- it do println(elem)
迭代器上的 foreach 方法和可迭代集合上的相同方法之間有一個重要的差異:當在迭代器上呼叫時,foreach
會在完成時將迭代器保留在其結尾。因此,再次在同一個迭代器上呼叫 next
會因 NoSuchElementException
而失敗。相反,當在集合上呼叫時,foreach
會讓集合中的元素數量保持不變(除非傳遞的函式新增或移除元素,但這並不建議,因為這可能會導致令人驚訝的結果)。
Iterator
與 Iterable
共有的其他運算具有相同的屬性。例如,迭代器提供 map
方法,它會傳回一個新的迭代器
scala> val it = Iterator("a", "number", "of", "words")
val it: Iterator[java.lang.String] = <iterator>
scala> it.map(_.length)
val res1: Iterator[Int] = <iterator>
scala> it.hasNext
val res2: Boolean = true
scala> res1.foreach(println)
1
6
2
5
scala> it.hasNext
val res4: Boolean = false
如你所見,在呼叫 it.map
之後,it
迭代器並未進展到其結尾,但遍歷呼叫 res1.foreach
所產生的迭代器也會遍歷 it
並將其進展到其結尾。
另一個範例是 dropWhile
方法,它可用於尋找具有特定屬性的迭代器的第一個元素。例如,若要尋找上面迭代器中第一個至少有兩個字元的字詞,你可以寫
scala> val it = Iterator("a", "number", "of", "words")
val it: Iterator[java.lang.String] = <iterator>
scala> it.dropWhile(_.length < 2)
val res4: Iterator[java.lang.String] = <iterator>
scala> res4.next()
val res5: java.lang.String = number
再次注意,it
已由呼叫 dropWhile
而變更:它現在指向清單中的第二個字詞「number」。事實上,it
和 dropWhile
傳回的結果 res4
將會傳回完全相同的元素順序。
要迴避此行為的一種方法是 duplicate
基礎反覆運算器,而不是直接呼叫其方法。產生的兩個反覆運算器將會各傳回與基礎反覆運算器 it
完全相同的元素
scala> val (words, ns) = Iterator("a", "number", "of", "words").duplicate
val words: Iterator[String] = <iterator>
val ns: Iterator[String] = <iterator>
scala> val shorts = words.filter(_.length < 3).toList
val shorts: List[String] = List(a, of)
scala> val count = ns.map(_.length).sum
val count: Int = 14
這兩個反覆運算器獨立運作:推進一個不會影響另一個,因此每個反覆運算器都可以透過呼叫任意方法而被破壞性修改。這會產生反覆運算元素兩次的錯覺,但效果是透過內部緩衝來達成的。如同往常,基礎反覆運算器 it
無法直接使用,且必須捨棄。
總之,如果在呼叫迭代器上的方法後,不再存取迭代器,則迭代器會像集合一樣運作。Scala 集合函式庫透過抽象 IterableOnce 明確表達這一點,它是 Iterable 和 Iterator 的共同超類別。IterableOnce[A]
只有兩個方法:iterator: Iterator[A]
和 knownSize: Int
。如果 IterableOnce
物件實際上是 Iterator
,則其 iterator
作業會在目前狀態下,永遠回傳它自己,但如果它是 Iterable
,則其 iterator
作業會永遠回傳新的 Iterator
。IterableOnce
的常見使用案例,是作為方法的引數類型,這些方法可以將迭代器或集合作為引數。一個範例是 Iterable
類別中的追加方法 concat
。它採用 IterableOnce
參數,因此您可以追加來自迭代器或集合的元素。
以下是迭代器上所有作業的摘要。
Iterator 類別中的作業
作業內容 | 作業功能 |
---|---|
抽象方法 | |
it.next() |
回傳迭代器上的下一個元素,並進到下一個元素。 |
it.hasNext |
如果 it 可以傳回另一個元素,則傳回 true 。 |
變異 | |
it.buffered |
一個緩衝迭代器,傳回 it 的所有元素。 |
it.grouped(size) |
一個迭代器,以固定大小的序列「區塊」產生 it 傳回的元素。 |
it.sliding(size) |
一個迭代器,以表示滑動固定大小視窗的序列產生 it 傳回的元素。 |
複製 | |
it.duplicate |
一對迭代器,各自獨立傳回 it 的所有元素。 |
新增 | |
it.concat(jt) 或 it ++ jt |
一個迭代器,傳回迭代器 it 傳回的所有元素,接著傳回迭代器 jt 傳回的所有元素。 |
it.padTo(len, x) |
一個迭代器,先傳回 it 的所有元素,然後接著傳回 x 的複本,直到總共傳回 len 個元素。 |
對應 | |
it.map(f) |
一個迭代器,從 it 傳回的每個元素套用函數 f 而取得。 |
it.flatMap(f) |
一個迭代器,從 it 中的每個元素套用迭代器值函數 f 而取得,並附加結果。 |
it.collect(f) |
將部分函數 f 套用至 it 中每個已定義的元素,並收集結果而取得的迭代器。 |
轉換 | |
it.toArray |
將 it 傳回的元素收集至陣列中。 |
it.toList |
將 it 傳回的元素收集至清單中。 |
it.toIterable |
將 it 傳回的元素收集至可迭代物件中。 |
it.toSeq |
將 it 傳回的元素收集至序列中。 |
it.toIndexedSeq |
將 it 傳回的元素收集至索引序列中。 |
it.toLazyList |
將 it 傳回的元素收集至延遲清單中。 |
it.toSet |
將 it 傳回的元素收集至集合中。 |
it.toMap |
將 it 傳回的鍵/值對收集至映射中。 |
複製 | |
it.copyToArray(arr, s, n) |
將 it 傳回的最多 n 個元素複製至陣列 arr ,從索引 s 開始。最後兩個參數為選用參數。 |
大小資訊 | |
it.isEmpty |
測試迭代器是否為空(與 hasNext 相反)。 |
it.nonEmpty |
測試集合是否包含元素(hasNext 的別名)。 |
it.size |
it 傳回的元素數量。注意:此操作後 it 將會在結尾處! |
it.length |
與 it.size 相同。 |
it.knownSize |
元素數量,如果已知且不修改迭代器的狀態,否則為 -1 。 |
元素檢索索引搜尋 | |
it.find(p) |
包含 it 回傳的第一個符合 p 的元素,或如果沒有符合的元素則為 None 。注意:迭代器會進展到該元素之後,或如果找不到元素則進展到最後。 |
it.indexOf(x) |
it 回傳的第一個等於 x 的元素的索引。注意:迭代器會進展到該元素的位置之後。 |
it.indexWhere(p) |
it 回傳的第一個符合 p 的元素的索引。注意:迭代器會進展到該元素的位置之後。 |
子迭代器 | |
it.take(n) |
回傳 it 的前 n 個元素的迭代器。注意:它會進展到 n 個元素之後的位置,或如果包含的元素少於 n 個元素則進展到最後。 |
it.drop(n) |
從 it 的第 (n+1) 個元素開始的迭代器。注意:it 會進展到相同的位置。 |
it.slice(m,n) |
回傳從 it 回傳的元素的切片,從第 m 個元素開始,在第 n 個元素之前結束。 |
it.takeWhile(p) |
只要條件 p 為真,就會從 it 回傳元素的迭代器。 |
it.dropWhile(p) |
一個迭代器跳過 it 中的元素,只要條件 p 為 true ,並傳回剩餘的元素。 |
it.filter(p) |
一個迭代器傳回 it 中滿足條件 p 的所有元素。 |
it.withFilter(p) |
與 it filter p 相同。需要這樣做才能在 for-expressions 中使用迭代器。 |
it.filterNot(p) |
一個迭代器傳回 it 中不滿足條件 p 的所有元素。 |
it.distinct |
一個迭代器傳回 it 中不重複的元素。 |
細分 | |
it.partition(p) |
將 it 分割成一對兩個迭代器:一個傳回 it 中滿足謂詞 p 的所有元素,另一個傳回 it 中不滿足謂詞 p 的所有元素。 |
it.span(p) |
將 it 分割成一對兩個迭代器:一個傳回 it 的前綴中滿足謂詞 p 的所有元素,另一個傳回 it 的所有剩餘元素。 |
元素條件 | |
it.forall(p) |
一個布林值,表示謂詞 p 是否對 it 傳回的所有元素成立。 |
it.exists(p) |
布林值,指示謂詞 p 是否對 it 中的某些元素成立。 |
it.count(p) |
滿足謂詞 p 的 it 中的元素數量。 |
折疊 | |
it.foldLeft(z)(op) |
在 it 傳回的連續元素之間套用二元運算 op ,由左至右,並從 z 開始。 |
it.foldRight(z)(op) |
在 it 傳回的連續元素之間套用二元運算 op ,由右至左,並從 z 開始。 |
it.reduceLeft(op) |
在非空反覆運算 it 傳回的連續元素之間套用二元運算 op ,由左至右。 |
it.reduceRight(op) |
在非空反覆運算 it 傳回的連續元素之間套用二元運算 op ,由右至左。 |
特定折疊 | |
it.sum |
反覆運算 it 傳回的數字元素值的總和。 |
it.product |
反覆運算 it 傳回的數字元素值的乘積。 |
it.min |
反覆運算 it 傳回的有序元素值的最小值。 |
it.max |
反覆運算 it 傳回的有序元素值的最大值。 |
拉鍊 | |
it.zip(jt) |
從反覆運算 it 和 jt 傳回的對應元素組成的對反覆運算。 |
it.zipAll(jt, x, y) |
一個迭代器,由迭代器 it 和 jt 傳回的對應元素組成,其中較短的迭代器會附加元素 x 或 y 來與較長的迭代器相符。 |
it.zipWithIndex |
一個迭代器,由 it 傳回的元素與其索引組成。 |
更新 | |
it.patch(i, jt, r) |
由 it 產生的迭代器,它從 i 開始,用修補程式迭代器 jt 取代 r 個元素。 |
比較 | |
it.sameElements(jt) |
一個測試,用來檢查迭代器 it 和 jt 是否以相同的順序傳回相同的元素。注意:此操作後使用迭代器是不確定的,且可能會變更。 |
字串 | |
it.addString(b, start, sep, end) |
將一個字串新增到 StringBuilder b ,其中會顯示 it 傳回的所有元素,它們位於分隔符號 sep 之間,並用字串 start 和 end 包圍。 start 、sep 、end 都是選用的。 |
it.mkString(start, sep, end) |
將集合轉換成一個字串,其中會顯示 it 傳回的所有元素,它們位於分隔符號 sep 之間,並用字串 start 和 end 包圍。 start 、sep 、end 都是選用的。 |
惰性
與直接對具體集合(例如 List
)進行的操作不同,對 Iterator
進行的操作是延遲的。
延遲操作不會立即計算其所有結果。相反,它會在個別請求時計算結果。
因此,表達式 (1 to 10).iterator.map(println)
不會在螢幕上列印任何內容。在此情況下,map
方法並未將其參數函式套用至範圍中的值,而是會傳回一個新的 Iterator
,而這個 Iterator
會在個別請求時執行此動作。將 .toList
新增至該表達式的結尾會實際列印元素。
這會導致像 map
或 filter
之類的方法不一定會將其參數函式套用至所有輸入元素。例如,表達式 (1 to 10).iterator.map(println).take(5).toList
僅會列印值 1
至 5
,因為這些值是唯一會從 map
傳回的 Iterator
中請求的。
這就是為什麼僅將純函式用作 map
、filter
、fold
和類似方法的參數非常重要的原因之一。請記住,純函式沒有副作用,因此通常不會在 map
中使用 println
。使用 println
是為了展示延遲,因為它通常不會在純函式中顯示。
儘管懶惰通常不可見,但它仍然有價值,因為它可以防止不必要的計算發生,並且可以處理無窮序列,如下所示
def zipWithIndex[A](i: Iterator[A]): Iterator[(Int, A)] =
Iterator.from(0).zip(i)
緩衝迭代器
有時你想要一個可以「向前看」的迭代器,以便你可以檢查要傳回的下一個元素,而不用跳過該元素。例如,考慮從傳回字串序列的迭代器中跳過前導空字串的任務。你可能會想寫以下內容
def skipEmptyWordsNOT(it: Iterator[String]) =
while (it.next().isEmpty) {}
def skipEmptyWordsNOT(it: Iterator[String]) =
while it.next().isEmpty do ()
但是仔細查看這段程式碼,很明顯這是錯誤的:程式碼確實會跳過前導空字串,但它也會推進it
超過第一個非空字串!
解決此問題的方法是使用緩衝迭代器。類別 BufferedIterator 是 Iterator 的子類別,它提供了一個額外的函數 head
。在緩衝迭代器上呼叫 head
會傳回它的第一個元素,但不會推進迭代器。使用緩衝迭代器,可以寫成如下方式來跳過空字詞。
def skipEmptyWords(it: BufferedIterator[String]) =
while (it.head.isEmpty) { it.next() }
def skipEmptyWords(it: BufferedIterator[String]) =
while it.head.isEmpty do it.next()
每個迭代器都可以透過呼叫其 buffered
函數來轉換為緩衝迭代器。以下是一個範例
scala> val it = Iterator(1, 2, 3, 4)
val it: Iterator[Int] = <iterator>
scala> val bit = it.buffered
val bit: scala.collection.BufferedIterator[Int] = <iterator>
scala> bit.head
val res10: Int = 1
scala> bit.next()
val res11: Int = 1
scala> bit.next()
val res12: Int = 2
scala> bit.headOption
val res13: Option[Int] = Some(3)
請注意,在緩衝迭代器 bit
上呼叫 head
不會推進它。因此,後續呼叫 bit.next()
傳回的值與 bit.head
相同。
與往常一樣,基礎迭代器不得直接使用,並且必須捨棄。
緩衝迭代器僅在呼叫 head
時緩衝下一個元素。其他衍生迭代器,例如由 duplicate
和 partition
產生的迭代器,可能會緩衝基礎迭代器的任意子序列。但是迭代器可以透過使用 ++
將它們加在一起來有效率地串接。
scala> def collapse(it: Iterator[Int]) = if (!it.hasNext) Iterator.empty else {
| var head = it.next
| val rest = if (head == 0) it.dropWhile(_ == 0) else it
| Iterator.single(head) ++ rest
|}
def collapse(it: Iterator[Int]): Iterator[Int]
scala> def collapse(it: Iterator[Int]) = {
| val (zeros, rest) = it.span(_ == 0)
| zeros.take(1) ++ rest
|}
def collapse(it: Iterator[Int]): Iterator[Int]
scala> collapse(Iterator(0, 0, 0, 1, 2, 3, 4)).toList
val res14: List[Int] = List(0, 1, 2, 3, 4)
scala> def collapse(it: Iterator[Int]) = if !it.hasNext then Iterator.empty else
| var head = it.next
| val rest = if head == 0 then it.dropWhile(_ == 0) else it
| Iterator.single(head) ++ rest
|
def collapse(it: Iterator[Int]): Iterator[Int]
scala> def collapse(it: Iterator[Int]) =
| val (zeros, rest) = it.span(_ == 0)
| zeros.take(1) ++ rest
|
def collapse(it: Iterator[Int]): Iterator[Int]
scala> collapse(Iterator(0, 0, 0, 1, 2, 3, 4)).toList
val res14: List[Int] = List(0, 1, 2, 3, 4)
在 collapse
的第二個版本中,未消耗的零會在內部進行緩衝。在第一個版本中,任何前導零都會被捨棄,而所需結果會建構為一個串聯的迭代器,它只需依序呼叫其兩個組成迭代器即可。