第三章——集合(集合協議)

bestswifter發表於2017-12-21

只要仔細觀察一下陣列、字典、集合(Set)和字串檢視,我們可以發現他們都實現了CollectionType協議。如果再深入一些,CollectionType協議實現的是SequenceType協議,同時序列(Sequence)使用了GeneratorType來提供元素。為了瞭解這其中的詳細原理,我們從最底層往上看。簡單來說,一個生成器(Generator)負責封裝如何生成新的值的細節,序列(Sequence)封裝了建立生成器(Generator)的細節,而集合型別(Collection)可以隨機地訪問序列(Sequence)。

###生成器(Generators)

GeneratorType協議由兩部分組成。首先,它要求實現這個協議的型別有一個關聯的Element型別。比如考慮String.CharacterView,它的元素型別(element type)就是Character,對於字典來說,它的元素型別被定義為(Key, Value),也就是我們所說的鍵值對。

GeneratorType協議還定義了next()函式,它返回的是元素的可選型別值。所以只要你有一個實現了GeneratorType協議的值,你就可以呼叫它的next()方法,直到這個方法返回nil為止。把剛剛所說的GeneratorType協議的兩部分合並起來,我們就得到了它簡單的定義:

protocol GeneratorType {
typealias Element
mutating func next() -> Element?
}
複製程式碼

Swift文件告訴我們我們可以隨便賦值生成器型別的值,但由於生成器只能單向訪問:我們只能遍歷它一次。因此生成器沒有值語義,所以我們需要把它實現為一個類而不是結構體。我們可以想到的最簡單的生成器是一個在next()方法中總是返回常量的物件:

class ConstantGenerator: GeneratorType {
typealias Element = Int
func next() -> Element? {
return 1
}
}
複製程式碼

或者我們也可以利用型別推斷,隱式地標註出Element的型別:

class ConstantGenerator: GeneratorType {
func next() -> Int? {
return 1
}
}
複製程式碼

這個生成器的使用很簡單,只要先建立它的例項物件,用一個while迴圈,就可以獲得無數個1:

var generator = ConstantGenerator()
while let x = generator.next() {
// 使用x
}
複製程式碼

如果要生成一個斐波那契數列(從0開始),我們需要額外儲存一個狀態,也就是當前數字的前兩個數。這時next()函式返回的是前兩個數之和,同時還要更新這個狀態:

class FibsGenerator: GeneratorType {
var state = (0,1)
func next() -> Int? {
let upcommingNumber = state.0
state = (state.1, state.0 + state.1)
return upcommingNumber
}
}
複製程式碼

剛剛我們舉的兩個生成器的例子,都是可以生成無限多值的。再來看一個有限的生成器PrefixGenerator,它可以生成字串的所有字首子串(包括字串自己),它內部記錄一個每次自增的下標,返回的就是從0到這個下標之間的子字串:

class PrefixGenerator: GeneratorType {
var string: String
var offset: String.Index

init (string: String) {
self.string = string
offset = string.startIndex
}

func next() -> String? {
if offset < string.endIndex {
offset++
return string[string.startIndex..<offset]
}
return nil
}
}
複製程式碼

以上所示的這三個生成器都只能遍歷一次。如果還想從頭開始再遍歷一次,就得重新建立一個生成器。因為我們知道生成器不是結構體而是類,它們被另一個變數共享時,不會複製一個新的而是直接傳遞引用。

###序列(Sequences)

我們知道生成器只能遍歷一次,但多次遍歷又是一個很常見的需求,所以就有了SequenceType這個協議。它是基於GeneratorType的。在這個協議中,我們需要指定生成器的型別,並且提供一個可以建立生成器的函式。定義在這個協議中的typealias關鍵字就表示需要指定的生成器的型別:

protocol SequenceType {
typealias Generator: GeneratorType
func generate() -> Generator
}
複製程式碼

這樣一來,如果想要多次遍歷字串的所有字首,我們就可以把生成器包裝在序列中:

struct PrefixSequence: SequenceType {
let string: String

func generate() -> PrefixGenerator {
return PrefixGenerator(string: string)
}
}
複製程式碼

現在,我們已經把建立生成器的過程封裝在序列中,由PrefixSequence負責,於是我們就可以在for迴圈裡遍歷所有的字串字首了:

for prefix in PrefixSequence(string: "Hello") {
print(prefix)
}

複製程式碼

隱藏在簡單的for迴圈背後的是編譯器建立了一個全新的生成器,然後不斷呼叫生成器的next方法直到得到返回值為nil

如果我們去看看Swift對陣列的實現,就更容易想象生成器和序列具體是什麼,其他的資料結構如字典、集合、字串也是如此。如果我們想要實現自己的資料結構並想讓它可以在for迴圈中使用,就需要保證它實現了SequenceeType協議。

###基於函式的生成器和序列

生成序列和生成器還有一種更簡單的方式,這種方式不用建立一個自定義的類,而是使用內建的AnyGeneratorAnySequence型別。這兩種型別需要接受一個函式作為引數。比如我們可以這樣實現之前的斐波那契數列生成器:

func fibGenerator() -> AnyGenerator<Int> {
var state = (0,1)
return anyGenerator {
let result = state.0
state = (state.1, state.1 + state.0)
return result
}
}
複製程式碼

anyGenerator方法把另一個函式作為引數(也就是定義中的body函式)。在這個例子中,我們把state移到函式之外,然後每次函式呼叫時都該state。因為函式會截獲自動變數,所以這麼做是沒有問題的。

現在,建立序列就更簡單了:

let fibSequence = AnySequence(fibGenerator)
for number in fibSequence {
print(number)
}
複製程式碼

相關文章