前言
本文翻譯自Swift Functional Programming Tutorial
翻譯的不對(duì)的地方還請(qǐng)多多包涵指正,謝謝~
Swift函數(shù)式編程教程
當(dāng)從Objective-C(文章其余地方將簡(jiǎn)稱OC)編程轉(zhuǎn)移到Swift過程中,將OC中的概念映射到Swfit是非常符合邏輯的。你知道在OC中如何創(chuàng)建類,那在Swift也是一樣。當(dāng)然,Swfit有一些完全新的特性諸如泛型和范圍操作數(shù),但仍然還有你已經(jīng)知道一些小的非常精妙的技術(shù)。(OK,可能也不那么小!)
但,Swfit不僅僅是為應(yīng)用提供了一個(gè)更好的語法。使用這門新語言,你能有機(jī)會(huì)改變解決問題及編碼的思路。結(jié)合Swift,函數(shù)式編程技術(shù)在你的編程武器中成為了一個(gè)可行的重要的部分。
函數(shù)式編程是一個(gè)理論性強(qiáng)的話題,因此這篇教程將通過例子來說明它。你將看到許多的函數(shù)編程的例子,這些例子看起來是熟悉、命令式的編程方式,之后你可以盡所能在解決相同問題的時(shí)考慮使用函數(shù)式編程技術(shù)。
注意:這篇Swfit教程是假定你已有Swift編程的基礎(chǔ)。如果你對(duì)Swfit很陌生,我們建議你先看看其他Swfit的教程
什么是函數(shù)式編程?
簡(jiǎn)潔的說,函數(shù)式編程是是一種編程范式,強(qiáng)調(diào)通過數(shù)學(xué)式的函數(shù)來計(jì)算,函數(shù)具有永恒不可變性及表達(dá)式語法,盡可能少地使用參數(shù)和狀態(tài)位的特性。
因?yàn)闃O少有共享的狀態(tài)且每個(gè)函數(shù)像是應(yīng)用代碼海洋里的一座孤島,所以它更加容易測(cè)試。函數(shù)式編程之所以流行是因?yàn)樗沟卯惒胶筒⑿懈雍?jiǎn)單地協(xié)同工作。為當(dāng)今多核時(shí)代提供了一種提高性能的途徑。
是時(shí)候開始將方法變?yōu)楹瘮?shù)式方式了!
簡(jiǎn)單的數(shù)組篩選
以一個(gè)非常簡(jiǎn)單的事情作為開始:一個(gè)簡(jiǎn)單的數(shù)學(xué)位計(jì)算。你的第一個(gè)任務(wù)是寫一個(gè)簡(jiǎn)單的Swift寫的用于找到1到10中的偶數(shù)的函數(shù)。雖是一個(gè)非常小的任務(wù),確是對(duì)一個(gè)函數(shù)式編程很棒的介紹。
老的篩選方法
創(chuàng)建一個(gè)Swift的工作環(huán)境,保存在你喜歡的任何地方。然后貼下面的代碼到你新創(chuàng)建的Swift文件中:
var evens = [Int]()
for i in 1...10 {
if i % 2 == 0 {
evens.append(i)
}
}
println(evens)
該函數(shù)生成出了我們預(yù)先想要的結(jié)果
[2, 4, 6, 8, 10]
(如果你看不到控制臺(tái)的輸出,記得通過View/Assistant Editor/Show Assistant Editor
選項(xiàng)打開輔助編輯)
這塊小代碼很簡(jiǎn)單,核心算法是這樣的:
- 創(chuàng)建一個(gè)空數(shù)組;
- 1-10的循環(huán)迭代器(記得是1...10是包含1和10的);
- 若符合條件(數(shù)字是偶數(shù)),則添加到數(shù)組內(nèi);
上述代碼是實(shí)質(zhì)上是命令式編程。給出明確的使用一些基本的控制命令諸如if,for-in
指令告訴計(jì)算機(jī)找出偶數(shù)。
代碼運(yùn)行的很好,只是有一點(diǎn)驗(yàn)證數(shù)字是否是偶數(shù)是隱藏在循環(huán)當(dāng)中的。而且存在一些嚴(yán)重耦合,將偶數(shù)添加到數(shù)組的動(dòng)作包含在條件當(dāng)中。如果你希望在App的其他地方打印偶數(shù)數(shù)組,只能通過拷貝粘貼的方式來重用代碼。
讓我們來改成函數(shù)式吧~
函數(shù)式篩選
將以下代碼貼到你的文件中:
func isEven(number: Int) -> Bool {
return number % 2 == 0
}
evens = Array(1...10).filter(isEven)
println(evens)
可以看到,函數(shù)式編程的結(jié)果跟命令式編程一樣:
[2, 4, 6, 8, 10]
讓我們看的更仔細(xì)點(diǎn)。它由兩部分組成:
- 數(shù)組代碼段是一個(gè)簡(jiǎn)單方便生成包含1到10的數(shù)組方式。范圍操作數(shù)
...
創(chuàng)建一個(gè)包含兩端點(diǎn)的范圍; - 篩選聲明處是函數(shù)式編程模仿所在。這個(gè)篩選方法,對(duì)數(shù)組是顯示的(基礎(chǔ)方法),創(chuàng)建并返回了一個(gè)新的數(shù)組,該數(shù)組包含了只有通過篩選器里的函數(shù)返回True的元素。在這個(gè)例子中,
isEven
提供給了filter
。
將isEven
函數(shù)作為參數(shù)傳遞給了filter
函數(shù),但記住函數(shù)僅僅是有名字的閉包。試試添加以下更加簡(jiǎn)明的代碼到你的文件中:
evens = Array(1...10).filter { (number) in number % 2 == 0 }
println(evens)
再一次,確認(rèn)三個(gè)方法的返回結(jié)果是一致的。以上代碼可以說明編譯器從使用上下文推斷出參數(shù)number
的類型并且返回閉包的類型。
如果你想讓你的代碼更加簡(jiǎn)明,再做一步這么來寫:
evens = Array(1...10).filter { $0 % 2 == 0 }
println(evens)
上述代碼使用了參數(shù)簡(jiǎn)寫,隱式返回,類型推薦...且成功了!
使用簡(jiǎn)寫的參數(shù)表達(dá)是一種習(xí)慣或者偏好。個(gè)人來說,覺得像上面簡(jiǎn)單的例子,簡(jiǎn)寫參數(shù)很好。但是,對(duì)于更加復(fù)雜的情況我更傾向于顯示的參數(shù)表達(dá)。編譯器雖然不關(guān)心變量名字,但它們可以創(chuàng)建一個(gè)與人類不同的世界!(原文連續(xù)的意思是說,不寫參數(shù)名計(jì)算機(jī)可以任意翻譯人們寫的代碼,構(gòu)建出不同的含義,這并不是我們想要的。)
函數(shù)式的寫法顯然比命令式的更加簡(jiǎn)潔。這個(gè)簡(jiǎn)單的例子展示出了所有函數(shù)式語言所具有的一些有趣的特性:
- 高階函數(shù):這種函數(shù)的參數(shù)一個(gè)函數(shù),或者返回值是一個(gè)函數(shù)。在這個(gè)例子中,
filter
就是一個(gè)高階函數(shù),它可以接收一個(gè)函數(shù)作為參數(shù); - 一級(jí)函數(shù):你可以將函數(shù)當(dāng)做是任意變量,可以將它們賦值給變量,也可以將它們作為參數(shù)傳給其他函數(shù);
- 閉包:實(shí)際上就是匿名函數(shù);
你可能注意到OC中的block也有一些類似的特性。但swfit在函數(shù)編程上走的更遠(yuǎn),它通過混合使用更加簡(jiǎn)明的語法和內(nèi)建的函數(shù)諸如filter
。
Filter篩選器背后的魔法
Swift有很多功能化方法,例如map, join, reduce...
。那么在這些方法的背后是怎么實(shí)現(xiàn)的呢?
我們來看看filter
背后的魔法并加上我們自己的實(shí)現(xiàn)。
在相同的文件中,添加以下代碼:
func myFilter<T>(source: [T], predicate:(T) -> Bool) -> [T] {
var result = [T]()
for i in source {
if predicate(i) {
result.append(i)
}
}
return result
}
上述代碼是一個(gè)泛型函數(shù),它接受一個(gè)包含類型T的數(shù)組的源(source)和一個(gè)以T類型實(shí)例為入?yún)⒉⒎祷豣ool值得判定函數(shù)(predicate)。
myFilter
函數(shù)看起來更像是我們剛開始寫的命令式函數(shù)。主要區(qū)別在于你可以提供一個(gè)檢查條件的函數(shù)而不是硬編碼在函數(shù)中。
試試新添加的實(shí)現(xiàn),添加以下代碼:
evens = myFilter(Array(1...10)) { $0 % 2 == 0 }
println(evens)
再一次說明,輸出結(jié)果是一樣的!
挑戰(zhàn):上述方法是全局的,看看你是否能讓它變成數(shù)組的一個(gè)方法。
- 可以通過擴(kuò)展Array添加
myFilter
方法; - 可以擴(kuò)展Array,但不是Array[T](泛型擴(kuò)展)。這意味著需要通過self遍歷數(shù)組且需要強(qiáng)制轉(zhuǎn)換類型。
Reducing
之前的是一個(gè)很簡(jiǎn)單的例子,只用了一個(gè)函數(shù)式方法。接下來,在上面的基礎(chǔ)上,運(yùn)用函數(shù)式編程技術(shù)實(shí)現(xiàn)更加復(fù)雜的邏輯。
創(chuàng)建一個(gè)新的工作文件,準(zhǔn)備下一個(gè)任務(wù)吧~
Manual reduction
本階段你的任務(wù)會(huì)復(fù)雜一點(diǎn)點(diǎn):取出1到10的偶數(shù)并求和。這就是熟知的reduce函數(shù),接收一組輸入且返回一個(gè)輸出。
我相信你有能力自己寫完這個(gè)邏輯,但我已經(jīng)寫好了~ 添加以下代碼到工作文件:
var evens = [Int]()
for i in 1...10 {
if i % 2 == 0 {
evens.append(i)
}
}
var evenSum = 0
for i in evens {
evenSum += i
}
println(evenSum)
結(jié)果如下:
30
上面的命令行代碼還是和之前的例子一樣,在for-in
循環(huán)語句中做加法。
讓我們來看看函數(shù)式會(huì)如何編寫吧~
函數(shù)式Reduce(Functional Reduce)
添加如下代碼到你的工作區(qū):
evenSum = Array(1...10)
.filter { (number) in number % 2 == 0 }
.reduce(0) { (total, number) in total + number }
println(evenSum)
你會(huì)看到結(jié)果是:
30
上述代碼段包含了數(shù)組的創(chuàng)建和filter
的使用。這兩個(gè)操作的結(jié)果是五個(gè)數(shù)字的數(shù)組[2, 4, 6, 8, 10]
。最后一步使用的是reduce
。
reduce
是功能極其豐富的數(shù)組方法,可以為數(shù)組的每一個(gè)元素執(zhí)行一個(gè)方法,并累加結(jié)果。
為了理解reduce
是如何工作的,看看它的簽名是有幫助的。
func reduce<U>(initial: U, combine: (U, T) -> U) -> U
第一個(gè)參數(shù)是類型為U的初始值。在目前的代碼中,初始值是0且是Int
型的(這里的U就一直是Int
了)。第二個(gè)參數(shù)是combine
函數(shù),這個(gè)函數(shù)會(huì)對(duì)每個(gè)數(shù)組元素執(zhí)行一遍。
combine
接收連個(gè)參數(shù),第一個(gè)是類型是U,且是上一個(gè)combine
函數(shù)的調(diào)用結(jié)果;第二個(gè)參數(shù)是正在執(zhí)行combine
函數(shù)的數(shù)組元素。reduce
執(zhí)行的返回結(jié)果就是最后一個(gè)combine
函數(shù)的返回結(jié)果。
這里面繁盛了很多事情,我們可以一步步拆解它。
在代碼中,第一個(gè)reduce后的結(jié)果如下:

combine
第一個(gè)入?yún)⒌某跏贾凳?,入?yún)⒌牡诙€(gè)參數(shù)是數(shù)組的第一個(gè)值2。combine
函數(shù)將它們兩想加,返回2。
第二個(gè)循環(huán)如下圖介紹:

在第二個(gè)循環(huán),combine
的入?yún)⑹堑谝粋€(gè)combine
的返回值和下一個(gè)數(shù)組元素值。將他們兩想加即2 + 4 = 6。
繼續(xù)循環(huán)處理所有的數(shù)組元素可以得到下面這張表:

加粗且邊上有星號(hào)的值就是最終的執(zhí)行結(jié)果。
這是一個(gè)簡(jiǎn)單例子;事實(shí)上,使用reduce
你可以執(zhí)行各種各樣有趣的功能強(qiáng)大的轉(zhuǎn)換。下面是幾個(gè)運(yùn)用例子。
添加以下代碼到你的工作區(qū):
let maxNumber = Array(1...10)
.reduce(0) { (total, number) in max(total, number) }
println(maxNumber)
這段代碼用于找出數(shù)組中的最大值。這個(gè)例子中,返回結(jié)果顯而易見~ 在這里要記住,結(jié)果就是最終reduce
執(zhí)行的計(jì)算max數(shù)值的循環(huán)結(jié)果。
如果你還在努力思索為何是這么運(yùn)行的,為什么創(chuàng)建類似于上述表格來記錄每次combine
的入?yún)⒑头祷刂的亍?/p>
至今為止你所見到的reduce
都是將一個(gè)整型數(shù)組轉(zhuǎn)換為一個(gè)整型數(shù)字。顯然,reduce
有兩個(gè)參數(shù),U和T,是不同的,而且必須得他們沒必要是整型。這意味著你能將包含一個(gè)類型的數(shù)組轉(zhuǎn)換成另一種類型的數(shù)。
添加以下代碼到你的工作文件中:
let numbers = Array(1...10)
.reduce("numbers: ") {(total, number) in total + "\(number) "}
println(numbers)
執(zhí)行結(jié)果如下:
numbers: 1 2 3 4 5 6 7 8 9 10
這個(gè)例子展示了reduce
將一個(gè)整型數(shù)組轉(zhuǎn)換成一個(gè)String
數(shù)字。
通過一些練習(xí),你會(huì)發(fā)現(xiàn)可以用各種各樣的有趣且好玩的方式使用reduce
。
挑戰(zhàn):看看你是否能將一個(gè)包含位的數(shù)組轉(zhuǎn)換成一個(gè)整型值。輸入如下:
let digits = ["3", "1", "4", "1"]
你的reduce
結(jié)果需要返回3141的整形值。
Reduce背后的魔法
在之前的段落中,你實(shí)現(xiàn)了自己的簡(jiǎn)單的filter
的方法。現(xiàn)在我們來實(shí)現(xiàn)自己的reduce
方法。
添加如下代碼到工作區(qū):
extension Array {
func myReduce<T, U>(seed:U, combiner:(U, T) -> U) -> U {
var current = seed
for item in self {
current = combiner(current, item as T)
}
return current
}
}
上述代碼添加了myReduce
方法到數(shù)組中,模仿了內(nèi)建的reduce
函數(shù)。這個(gè)函數(shù)簡(jiǎn)單的遍歷每個(gè)數(shù)組元素,每一次遍歷都會(huì)調(diào)用combiner
函數(shù)。
為測(cè)試上述代碼,可以用myReduce
替換掉當(dāng)前工作區(qū)內(nèi)的reduce
函數(shù)。
此時(shí),你也許會(huì)想,“我為什么會(huì)想要自己實(shí)現(xiàn)filter
和reduce
方法?”。回答是,“和很可能不會(huì)~”
但是,你可能會(huì)想要在Swfit中擴(kuò)展當(dāng)前的函數(shù)范式,實(shí)現(xiàn)你自己想要的函數(shù)式方法。看明白并且理解函數(shù)式多么容易實(shí)現(xiàn)對(duì)于你自己去實(shí)現(xiàn)一些強(qiáng)大的諸如reduce
樣的函數(shù)是非常重要且有激勵(lì)性的。
建立索引
是時(shí)候解決一些更復(fù)雜的問題了,意味著你要新建另一個(gè)新的工作區(qū)啦。你明白你想這么做~
在本段中,你將使用函數(shù)式編程技術(shù)把一組單詞通過他們的首字母分成不同的組。
在你新創(chuàng)建的工作區(qū)內(nèi)添加如下代碼:
import Foundation
let words = ["Cat", "Chicken", "fish", "Dog",
"Mouse", "Guinea Pig", "monkey"]
為了完成本階段的任務(wù),你將通過首字母將他們分組(大小寫不敏感)。添加以下代碼做準(zhǔn)備:
typealias Entry = (Character, [String])
func buildIndex(words: [String]) -> [Entry] {
return [Entry]()
}
println(buildIndex(words))
Entry
為每個(gè)entry定義了元祖類型。這個(gè)例子中使用類型重命名是代碼更易讀,全篇不用重復(fù)的說明元祖類型。你將會(huì)在buildIndex
函數(shù)內(nèi)添加建立索引的代碼。
命令式建立索引
命令式索引方法如下:
func buildIndex(words: [String]) -> [Entry] {
var result = [Entry]()
var letters = [Character]()
for word in words {
let firstLetter = Character(word.substringToIndex(
advance(word.startIndex, 1)).uppercaseString)
if !contains(letters, firstLetter) {
letters.append(firstLetter)
}
}
for letter in letters {
var wordsForLetter = [String]()
for word in words {
let firstLetter = Character(word.substringToIndex(
advance(word.startIndex, 1)).uppercaseString)
if firstLetter == letter {
wordsForLetter.append(word)
}
}
result.append((letter, wordsForLetter))
}
return result
}
函數(shù)分為兩個(gè)部分,每一個(gè)部分都是for循環(huán)。前一部分是根據(jù)單詞數(shù)組的第一個(gè)字母建立首字母數(shù)組;后半部分遍歷首字母數(shù)組,把找到以首字母開頭的單詞加入到對(duì)應(yīng)的數(shù)組。
這個(gè)實(shí)現(xiàn)得到我們期待的結(jié)果如下:
[(C, [Cat, Chicken]),
(F, [fish]),
(D, [Dog]),
(M, [Mouse, monkey]),
(G, [Guinea Pig])]
(上面的輸出稍微做了整理)
該命令式實(shí)現(xiàn)花了很多的步數(shù)和嵌套循環(huán),這使得它很難理解。讓我們來看看函數(shù)式編程是如何怎樣的。
函數(shù)式編程建立索引
創(chuàng)建一個(gè)新的工作區(qū)間并添加如下代碼:
import Foundation
let words = ["Cat", "Chicken", "fish", "Dog",
"Mouse", "Guinea Pig", "monkey"]
typealias Entry = (Character, [String])
func buildIndex(words: [String]) -> [Entry] {
return [Entry]();
}
println(buildIndex(words))
這時(shí)輸出是個(gè)空數(shù)組:
[]
第一步是建立索引,將單詞數(shù)組轉(zhuǎn)換成包含單詞首字母的數(shù)組。更新buildIndex
函數(shù)如下:
func buildIndex(words: [String]) -> [Entry] {
let letters = words.map {
(word) -> Character in
Character(word.substringToIndex(advance(word.startIndex, 1)
).uppercaseString)
}
println(letters)
return [Entry]()
}
工作區(qū)會(huì)輸出大寫字母的數(shù)組,每一個(gè)都對(duì)應(yīng)輸入單次數(shù)組的單次的每個(gè)元素。
[C, C, F, D, M, G, M]
在之前的段落中,我們遇到了filter, reduce
等函數(shù)。上面的代碼中,我們來介紹map
,另一個(gè)數(shù)組內(nèi)建API。
map
函數(shù)對(duì)給定的數(shù)組的每個(gè)元素調(diào)用提供的閉包,并用每次調(diào)用的結(jié)果生成一個(gè)新的數(shù)組。你可以使用map
來做轉(zhuǎn)換;在這個(gè)例子中,map
將String
數(shù)組轉(zhuǎn)換成Character
數(shù)組。
目前首字母數(shù)組包含了一些重復(fù)元素,但你期望的數(shù)組是沒有重復(fù)元素的。不幸的是,swift數(shù)組并沒有提供內(nèi)建函數(shù)去重。意味著你將要自己寫一個(gè)~
在之前的段落中,你發(fā)現(xiàn)重新實(shí)現(xiàn)filter, reduce
還是很簡(jiǎn)單的。所以毫無疑問添加一個(gè)去重函數(shù)也是小菜一碟~
添加如下代碼在buildIndex
上邊:
func distinct<T: Equatable>(source: [T]) -> [T] {
var unique = [T]()
for item in source {
if !contains(unique, item) {
unique.append(item)
}
}
return unique
}
distinct
遍歷數(shù)組,建立一個(gè)元素唯一的新數(shù)組。
將distinct
函數(shù)運(yùn)用到buildIndex
中:
func buildIndex(words: [String]) -> [Entry] {
let letters = words.map {
(word) -> Character in
Character(word.substringToIndex(advance(word.startIndex, 1)
).uppercaseString)
}
let distinctLetters = distinct(letters)
println(distinctLetters)
return [Entry]()
}
你的工作區(qū)將會(huì)輸出去重后的字母:
[C, F, D, M, G]
現(xiàn)在你有了首字母去重后的數(shù)組,下一步就是將字母轉(zhuǎn)變成Entry
元祖了。聽起來是不是很像一個(gè)轉(zhuǎn)換?這將是map
的另一個(gè)工作啦~
更新buildIndex
如下:
func buildIndex(words: [String]) -> [Entry] {
let letters = words.map {
(word) -> Character in
Character(word.substringToIndex(advance(word.startIndex, 1)
).uppercaseString)
}
let distinctLetters = distinct(letters)
return distinctLetters.map {
(letter) -> Entry in
return (letter, [])
}
}
第二次調(diào)用map
的目的是將字符數(shù)組裝換成元祖數(shù)組。目前輸出:
[(C, []),
(F, []),
(D, []),
(M, []),
(G, [])]
(再次,上面也是整理過啦~)
就快完成了。最后一步工作用給定字符開頭的單詞生成對(duì)應(yīng)的Entry
。更新函數(shù)并添加嵌套的filter
如下:
func buildIndex(words: [String]) -> [Entry] {
let letters = words.map {
(word) -> Character in
Character(word.substringToIndex(advance(word.startIndex, 1)
).uppercaseString)
}
let distinctLetters = distinct(letters)
return distinctLetters.map {
(letter) -> Entry in
return (letter, words.filter {
(word) -> Bool in
Character(word.substringToIndex(advance(word.startIndex, 1)
).uppercaseString) == letter
})
}
}
輸出結(jié)果如下:
[(C, [Cat, Chicken]),
(F, [fish]),
(D, [Dog]),
(M, [Mouse, monkey]),
(G, [Guinea Pig])]
在第二部分,使用的嵌套調(diào)用是filter
而不是map
。filter
能位不同的字符篩選出對(duì)應(yīng)的單次數(shù)組,并且是根據(jù)首字母來定位的。
以上實(shí)現(xiàn)已經(jīng)比命令式更加簡(jiǎn)潔明了,但是仍有提高空間;上述代碼取出操作和大寫轉(zhuǎn)換操作太多次了。移除這些重復(fù)性是非常好的。
如果這是OC代碼,你可能會(huì)有幾種方式來優(yōu)化:你可以創(chuàng)建公共方法,將方法直接通過category
添加到NSString
類中。但是,如果你僅僅是希望在buildIndex
使用,一個(gè)公共方法顯示不夠清晰且有些過度。
幸運(yùn)的是,使用swift,有更好的方法~
更新代碼如下:
func buildIndex(words: [String]) -> [Entry] {
func firstLetter(str: String) -> Character {
return Character(str.substringToIndex(
advance(str.startIndex, 1)).uppercaseString)
}
let letters = words.map {
(word) -> Character in
firstLetter(word)
}
let distinctLetters = distinct(letters)
return distinctLetters.map {
(letter) -> Entry in
return (letter, words.filter {
(word) -> Bool in
firstLetter(word) == letter
})
}
}
上面的代碼添加了firstLetter
函數(shù),該函數(shù)嵌套在buildIndex
中,而且對(duì)于外部函數(shù)是隱藏的(本地函數(shù))。利用swift一階函數(shù)的特性,你可以想使用變量一樣使用它。
新的代碼合并了重復(fù)邏輯,但還有很多可做的事來整理buildIndex
。
第一步map
是使用(String) 轉(zhuǎn) Character
簽名閉包生成字母數(shù)組。你可能會(huì)發(fā)現(xiàn)這個(gè)函數(shù)跟添加的firstLetter
是一樣的,這意味著你可以直接將它傳給map
。
使用這個(gè)知識(shí)點(diǎn)后,可以將函數(shù)寫成如下:
func buildIndex(words: [String]) -> [Entry] {
func firstLetter(str: String) -> Character {
return Character(str.substringToIndex(
advance(str.startIndex, 1)).uppercaseString)
}
return distinct(words.map(firstLetter))
.map {
(letter) -> Entry in
return (letter, words.filter {
(word) -> Bool in
firstLetter(word) == letter
})
}
}
最終結(jié)果是很簡(jiǎn)潔的,表達(dá)清晰的。
也許現(xiàn)在你注意到函數(shù)式編程有趣的一面~ 命令式解決方案需要依賴于變量(變量關(guān)鍵詞var
),而對(duì)應(yīng)的在函數(shù)式里定義所有值都是常量(通過let
)。
你應(yīng)該更多積極地使用常量,常量易于測(cè)試且方便并行。函數(shù)式編程和不可變類型往往是緊密相連的。結(jié)果,你的代碼會(huì)更加簡(jiǎn)明同時(shí)也不易出錯(cuò)。而且代碼看起來很酷,使你的朋友刮目相看。
挑戰(zhàn):目前,buildIndex
返回了一個(gè)未排序的索引;Entry
的順序取決于輸入單詞數(shù)組的元素順序。你的任務(wù)是將Entry
數(shù)組按照它的字母排序。對(duì)于上面的例子,你需要輸入如下:
[(C, [Cat, Chicken]),
(D, [Dog]),
(F, [fish]),
(G, [Guinea Pig]),
(M, [Mouse, monkey])]
答案:
swift數(shù)組有個(gè)
sort
函數(shù),但是這個(gè)方法會(huì)改變操作的數(shù)組而不是返回一個(gè)新的排序好的實(shí)例,并且它需要操作的數(shù)組是一個(gè)可變數(shù)組。總之,處理不可變數(shù)據(jù)更加安全,因此建議你不要使用該函數(shù)!作為替代,使用sorted
方法會(huì)返回一個(gè)新的數(shù)組。
何去何從
這里是函數(shù)式編程教程所有完整的代碼。
恭喜~ 你已經(jīng)有swift函數(shù)式編程的實(shí)戰(zhàn)經(jīng)驗(yàn)了。你不僅學(xué)會(huì)了如何使用函數(shù)式方法諸如:map, reduce
等,還知道如何自己實(shí)現(xiàn)這些方法,而且學(xué)會(huì)了用函數(shù)式思考。
如果你希望學(xué)到更多函數(shù)式編程的知識(shí),可以查看整個(gè)章節(jié),那里會(huì)講的更加深入也包括部分應(yīng)用功能。
希望看到你自己的App中使用了函數(shù)式編程技術(shù)~~~