前言
本文翻譯自Swift Functional Programming Tutorial
翻譯的不對的地方還請多多包涵指正美莫,謝謝~
Swift函數(shù)式編程教程
當(dāng)從Objective-C(文章其余地方將簡稱OC)編程轉(zhuǎn)移到Swift過程中,將OC中的概念映射到Swfit是非常符合邏輯的慎陵。你知道在OC中如何創(chuàng)建類示血,那在Swift也是一樣。當(dāng)然,Swfit有一些完全新的特性諸如泛型和范圍操作數(shù)祠墅,但仍然還有你已經(jīng)知道一些小的非常精妙的技術(shù)。(OK歌径,可能也不那么谢汆隆!)
但回铛,Swfit不僅僅是為應(yīng)用提供了一個(gè)更好的語法狗准。使用這門新語言,你能有機(jī)會改變解決問題及編碼的思路茵肃。結(jié)合Swift腔长,函數(shù)式編程技術(shù)在你的編程武器中成為了一個(gè)可行的重要的部分。
函數(shù)式編程是一個(gè)理論性強(qiáng)的話題验残,因此這篇教程將通過例子來說明它捞附。你將看到許多的函數(shù)編程的例子,這些例子看起來是熟悉胚膊、命令式的編程方式故俐,之后你可以盡所能在解決相同問題的時(shí)考慮使用函數(shù)式編程技術(shù)。
注意:這篇Swfit教程是假定你已有Swift編程的基礎(chǔ)紊婉。如果你對Swfit很陌生药版,我們建議你先看看其他Swfit的教程
什么是函數(shù)式編程?
簡潔的說喻犁,函數(shù)式編程是是一種編程范式槽片,強(qiáng)調(diào)通過數(shù)學(xué)式的函數(shù)來計(jì)算,函數(shù)具有永恒不可變性及表達(dá)式語法肢础,盡可能少地使用參數(shù)和狀態(tài)位的特性还栓。
因?yàn)闃O少有共享的狀態(tài)且每個(gè)函數(shù)像是應(yīng)用代碼海洋里的一座孤島,所以它更加容易測試传轰。函數(shù)式編程之所以流行是因?yàn)樗沟卯惒胶筒⑿懈雍唵蔚貐f(xié)同工作动猬。為當(dāng)今多核時(shí)代提供了一種提高性能的途徑作箍。
是時(shí)候開始將方法變?yōu)楹瘮?shù)式方式了案狠!
簡單的數(shù)組篩選
以一個(gè)非常簡單的事情作為開始:一個(gè)簡單的數(shù)學(xué)位計(jì)算哈扮。你的第一個(gè)任務(wù)是寫一個(gè)簡單的Swift寫的用于找到1到10中的偶數(shù)的函數(shù)链瓦。雖是一個(gè)非常小的任務(wù),確是對一個(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]
(如果你看不到控制臺的輸出,記得通過View/Assistant Editor/Show Assistant Editor
選項(xiàng)打開輔助編輯)
這塊小代碼很簡單玛臂,核心算法是這樣的:
- 創(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)中。如果你希望在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è)簡單方便生成包含1到10的數(shù)組方式苏章。范圍操作數(shù)
...
創(chuàng)建一個(gè)包含兩端點(diǎn)的范圍; - 篩選聲明處是函數(shù)式編程模仿所在奏瞬。這個(gè)篩選方法枫绅,對數(shù)組是顯示的(基礎(chǔ)方法),創(chuàng)建并返回了一個(gè)新的數(shù)組硼端,該數(shù)組包含了只有通過篩選器里的函數(shù)返回True的元素并淋。在這個(gè)例子中,
isEven
提供給了filter
珍昨。
將isEven
函數(shù)作為參數(shù)傳遞給了filter
函數(shù)县耽,但記住函數(shù)僅僅是有名字的閉包。試試添加以下更加簡明的代碼到你的文件中:
evens = Array(1...10).filter { (number) in number % 2 == 0 }
println(evens)
再一次镣典,確認(rèn)三個(gè)方法的返回結(jié)果是一致的兔毙。以上代碼可以說明編譯器從使用上下文推斷出參數(shù)number
的類型并且返回閉包的類型。
如果你想讓你的代碼更加簡明兄春,再做一步這么來寫:
evens = Array(1...10).filter { $0 % 2 == 0 }
println(evens)
上述代碼使用了參數(shù)簡寫澎剥,隱式返回,類型推薦...且成功了赶舆!
使用簡寫的參數(shù)表達(dá)是一種習(xí)慣或者偏好哑姚。個(gè)人來說趾唱,覺得像上面簡單的例子,簡寫參數(shù)很好蜻懦。但是甜癞,對于更加復(fù)雜的情況我更傾向于顯示的參數(shù)表達(dá)。編譯器雖然不關(guān)心變量名字宛乃,但它們可以創(chuàng)建一個(gè)與人類不同的世界S圃邸(原文連續(xù)的意思是說,不寫參數(shù)名計(jì)算機(jī)可以任意翻譯人們寫的代碼征炼,構(gòu)建出不同的含義析既,這并不是我們想要的。)
函數(shù)式的寫法顯然比命令式的更加簡潔谆奥。這個(gè)簡單的例子展示出了所有函數(shù)式語言所具有的一些有趣的特性:
- 高階函數(shù):這種函數(shù)的參數(shù)一個(gè)函數(shù)眼坏,或者返回值是一個(gè)函數(shù)。在這個(gè)例子中酸些,
filter
就是一個(gè)高階函數(shù)宰译,它可以接收一個(gè)函數(shù)作為參數(shù); - 一級函數(shù):你可以將函數(shù)當(dāng)做是任意變量魄懂,可以將它們賦值給變量沿侈,也可以將它們作為參數(shù)傳給其他函數(shù);
- 閉包:實(shí)際上就是匿名函數(shù)市栗;
你可能注意到OC中的block也有一些類似的特性缀拭。但swfit在函數(shù)編程上走的更遠(yuǎ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è)很簡單的例子辩涝,只用了一個(gè)函數(shù)式方法贸伐。接下來,在上面的基礎(chǔ)上怔揩,運(yùn)用函數(shù)式編程技術(shù)實(shí)現(xiàn)更加復(fù)雜的邏輯捉邢。
創(chuàng)建一個(gè)新的工作文件,準(zhǔn)備下一個(gè)任務(wù)吧~
Manual reduction
本階段你的任務(wù)會復(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ù)式會如何編寫吧~
函數(shù)式Reduce(Functional Reduce)
添加如下代碼到你的工作區(qū):
evenSum = Array(1...10)
.filter { (number) in number % 2 == 0 }
.reduce(0) { (total, number) in total + number }
println(evenSum)
你會看到結(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ù)會對每個(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ù)組元素可以得到下面這張表:

加粗且邊上有星號的值就是最終的執(zhí)行結(jié)果蓬推。
這是一個(gè)簡單例子;事實(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í)贺喝,你會發(fā)現(xiàn)可以用各種各樣的有趣且好玩的方式使用reduce
。
挑戰(zhàn):看看你是否能將一個(gè)包含位的數(shù)組轉(zhuǎn)換成一個(gè)整型值宗兼。輸入如下:
let digits = ["3", "1", "4", "1"]
你的reduce
結(jié)果需要返回3141的整形值躏鱼。
Reduce背后的魔法
在之前的段落中,你實(shí)現(xià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ù)簡單的遍歷每個(gè)數(shù)組元素茶行,每一次遍歷都會調(diào)用combiner
函數(shù)。
為測試上述代碼登钥,可以用myReduce
替換掉當(dāng)前工作區(qū)內(nèi)的reduce
函數(shù)畔师。
此時(shí),你也許會想怔鳖,“我為什么會想要自己實(shí)現(xiàn)filter
和reduce
方法茉唉?”固蛾。回答是度陆,“和很可能不會~”
但是艾凯,你可能會想要在Swfit中擴(kuò)展當(dāng)前的函數(shù)范式,實(shí)現(xiàn)你自己想要的函數(shù)式方法懂傀≈菏看明白并且理解函數(shù)式多么容易實(shí)現(xiàn)對于你自己去實(shí)現(xiàn)一些強(qiáng)大的諸如reduce
樣的函數(shù)是非常重要且有激勵性的。
建立索引
是時(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ù)的說明元祖類型。你將會在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ù)組但校,把找到以首字母開頭的單詞加入到對應(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ū)會輸出大寫字母的數(shù)組,每一個(gè)都對應(yīng)輸入單次數(shù)組的單次的每個(gè)元素辨绊。
[C, C, F, D, M, G, M]
在之前的段落中,我們遇到了filter, reduce
等函數(shù)匹表。上面的代碼中门坷,我們來介紹map
,另一個(gè)數(shù)組內(nèi)建API袍镀。
map
函數(shù)對給定的數(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
還是很簡單的度帮。所以毫無疑問添加一個(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ū)將會輸出去重后的字母:
[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, [])]
(再次,上面也是整理過啦~)
就快完成了袖迎。最后一步工作用給定字符開頭的單詞生成對應(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
能位不同的字符篩選出對應(yīng)的單次數(shù)組浴韭,并且是根據(jù)首字母來定位的。
以上實(shí)現(xiàn)已經(jīng)比命令式更加簡潔明了脯宿,但是仍有提高空間念颈;上述代碼取出操作和大寫轉(zhuǎn)換操作太多次了。移除這些重復(fù)性是非常好的连霉。
如果這是OC代碼榴芳,你可能會有幾種方式來優(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
中躏嚎,而且對于外部函數(shù)是隱藏的(本地函數(shù))。利用swift一階函數(shù)的特性菩貌,你可以想使用變量一樣使用它卢佣。
新的代碼合并了重復(fù)邏輯,但還有很多可做的事來整理buildIndex
箭阶。
第一步map
是使用(String) 轉(zhuǎn) Character
簽名閉包生成字母數(shù)組虚茶。你可能會發(fā)現(xiàn)這個(gè)函數(shù)跟添加的firstLetter
是一樣的戈鲁,這意味著你可以直接將它傳給map
。
使用這個(gè)知識點(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é)果是很簡潔的婆殿,表達(dá)清晰的。
也許現(xiàn)在你注意到函數(shù)式編程有趣的一面~ 命令式解決方案需要依賴于變量(變量關(guān)鍵詞var
)待笑,而對應(yīng)的在函數(shù)式里定義所有值都是常量(通過let
)鸣皂。
你應(yīng)該更多積極地使用常量,常量易于測試且方便并行暮蹂。函數(shù)式編程和不可變類型往往是緊密相連的寞缝。結(jié)果,你的代碼會更加簡明同時(shí)也不易出錯仰泻。而且代碼看起來很酷荆陆,使你的朋友刮目相看。
挑戰(zhàn):目前集侯,buildIndex
返回了一個(gè)未排序的索引被啼;Entry
的順序取決于輸入單詞數(shù)組的元素順序。你的任務(wù)是將Entry
數(shù)組按照它的字母排序棠枉。對于上面的例子浓体,你需要輸入如下:
[(C, [Cat, Chicken]),
(D, [Dog]),
(F, [fish]),
(G, [Guinea Pig]),
(M, [Mouse, monkey])]
答案:
swift數(shù)組有個(gè)
sort
函數(shù),但是這個(gè)方法會改變操作的數(shù)組而不是返回一個(gè)新的排序好的實(shí)例辈讶,并且它需要操作的數(shù)組是一個(gè)可變數(shù)組命浴。總之贱除,處理不可變數(shù)據(jù)更加安全生闲,因此建議你不要使用該函數(shù)!作為替代月幌,使用sorted
方法會返回一個(gè)新的數(shù)組碍讯。
何去何從
這里是函數(shù)式編程教程所有完整的代碼。
恭喜~ 你已經(jīng)有swift函數(shù)式編程的實(shí)戰(zhàn)經(jīng)驗(yàn)了扯躺。你不僅學(xué)會了如何使用函數(shù)式方法諸如:map, reduce
等捉兴,還知道如何自己實(shí)現(xiàn)這些方法,而且學(xué)會了用函數(shù)式思考录语。
如果你希望學(xué)到更多函數(shù)式編程的知識轴术,可以查看整個(gè)章節(jié),那里會講的更加深入也包括部分應(yīng)用功能钦无。
希望看到你自己的App中使用了函數(shù)式編程技術(shù)~~~