2018-Read-Record 記錄我的2018學(xué)習(xí)歷程
原文鏈接=http://appventure.me/2015/11/30/reduce-all-the-things/
作者=BENEDIKT TERHECHTE
原文日期=30 Nov 2015
譯者=pmst
校對(duì)=Cee
歡迎加入swift技術(shù)群(392436022)共同學(xué)習(xí)進(jìn)步疫向!
已授權(quán)SwiftGG轉(zhuǎn)載锻离,我們是一群熱愛翻譯并且熱愛 Swift 的人页徐,希望通過自己的努力讓不懂英語的程序員也能第一時(shí)間學(xué)習(xí)到國外的先進(jìn)技術(shù)痹筛。
即使早在 Swift 正式發(fā)布之前浮入,iOS / Cocoa 開發(fā)者都可以使用諸如 ObjectiveSugar 或者 ReactiveCocoa 第三方庫烦周,實(shí)現(xiàn)類似 map
算色、flatMap
或 filter
等函數(shù)式編程的構(gòu)建抬伺。而在 Swift 中,這些家伙(map
等幾個(gè)函數(shù))已經(jīng)入駐成為「頭等公民」了灾梦。比起標(biāo)準(zhǔn)的 for
循環(huán)峡钓,使用函數(shù)式編程有很多優(yōu)勢。它們通常能夠更好地表達(dá)你的意圖若河,減少代碼的行數(shù)能岩,以及使用鏈?zhǔn)浇Y(jié)構(gòu)構(gòu)建復(fù)雜的邏輯,更顯清爽萧福。
本文中拉鹃,我將介紹附加于 Swift 中的一個(gè)非常酷的函數(shù):Reduce鲫忍。相對(duì)于 map
/ filter
函數(shù)膏燕,reduce
有時(shí)不失為一個(gè)更好的解決方案。
一個(gè)簡單的問題
思考這么一個(gè)問題:你從 JSON 中獲取到一個(gè) persons 列表悟民,意圖計(jì)算所有來自 California 的居民的平均年齡坝辫。需要解析的數(shù)據(jù)如下所示:
let persons: [[String: AnyObject]] = [["name": "Carl Saxon", "city": "New York, NY", "age": 44],
["name": "Travis Downing", "city": "El Segundo, CA", "age": 34],
["name": "Liz Parker", "city": "San Francisco, CA", "age": 32],
["name": "John Newden", "city": "New Jersey, NY", "age": 21],
["name": "Hector Simons", "city": "San Diego, CA", "age": 37],
["name": "Brian Neo", "age": 27]] //注意這家伙沒有 city 鍵值
注意最后一個(gè)記錄,它遺漏了問題中 person 的居住地 city 射亏。對(duì)于這些情況近忙,默默忽略即可...
本例中,我們期望的結(jié)果是那三位來自 California 的居民智润。讓我們嘗試在 Swift 中使用 flatMap
和 filter
來實(shí)現(xiàn)這個(gè)任務(wù)银锻。使用 flatMap
函數(shù)替代 map
函數(shù)的原因在于前者能夠忽略可選值為 nil 的情況。例如 flatMap([0,nil,1,2,nil])
的結(jié)果是 [0,1,2]
做鹰。處理那些沒有 city 屬性的情況這會(huì)非常有用击纬。
func infoFromState(state state: String, persons: [[String: AnyObject]])
-> Float {
// 先進(jìn)行 flatMap 后進(jìn)行 filter 篩選
// $0["city"] 是一個(gè)可選值,對(duì)于那些沒有 city 屬性的項(xiàng)返回 nil
// componentsSeparatedByString 處理鍵值钾麸,例如 "New York, NY"
// 最后返回的 ["New York","NY"]更振,last 取到最后的 NY
return persons.flatMap( { $0["city"]?.componentsSeparatedByString(", ").last })
.filter({$0 == state})
.count
}
personsFromState(state: "CA", persons: persons)
//#+RESULTS:
//: 3
這非常簡單炕桨。
不過,現(xiàn)在來思考另外一個(gè)難題:你想要獲悉居住在 California 的人口數(shù)肯腕,接著計(jì)算他們的平均年齡献宫。如果我們想要在上面函數(shù)的基礎(chǔ)上嘗試做修改,立馬會(huì)發(fā)現(xiàn)難度不小实撒。解決方法倒是有幾種姊途,不過大都看起來不適用函數(shù)式結(jié)構(gòu)解決方案。倒是通過循環(huán)的方式能簡單的解決這個(gè)問題知态。
這時(shí)候我們要琢磨為啥不適用了捷兰,原因很簡單:數(shù)據(jù)的形式(Shape)改變了。而 map
负敏、flatMap
和 filter
函數(shù)能夠始終保持?jǐn)?shù)據(jù)形式的相似性贡茅。數(shù)組傳入,數(shù)組返回其做。當(dāng)然數(shù)組的元素個(gè)數(shù)和內(nèi)容可以改變顶考,不過始終是數(shù)組形式(Array-shape)。但是妖泄,上面所描述的問題要求我們最后轉(zhuǎn)換成的結(jié)果是個(gè)結(jié)構(gòu)體(Struct)驹沿,或者說是以元組(Tuple)的形式包含一個(gè)整型平均值(平均年齡)和一個(gè)整型總和(人口數(shù))。
對(duì)于這種類型的問題蹈胡,我們可以使用 reduce
來救場甚负。
Reduce
Reduce 是 map
、flatMap
或 filter
的一種擴(kuò)展的形式(譯者注:后三個(gè)函數(shù)能干嘛审残,reduce 就能用另外一種方式實(shí)現(xiàn))梭域。Reduce 的基礎(chǔ)思想是將一個(gè)序列轉(zhuǎn)換為一個(gè)不同類型的數(shù)據(jù),期間通過一個(gè)累加器(Accumulator)來持續(xù)記錄遞增狀態(tài)搅轿。為了實(shí)現(xiàn)這個(gè)方法病涨,我們會(huì)向 reduce 方法中傳入一個(gè)用于處理序列中每個(gè)元素的結(jié)合(Combinator)閉包 / 函數(shù) / 方法。這聽起來有點(diǎn)復(fù)雜璧坟,不過通過幾個(gè)例子練手既穆,你就會(huì)發(fā)現(xiàn)這相當(dāng)簡單。
它是 SequenceType
中的一個(gè)方法雀鹃,看起來是這樣的(簡化版本):
func reduce<T>(initial: T, combine: (T, Self.Generator.Element) -> T) -> T
此刻幻工,我們擁有一個(gè)初始值(Initial value)以及一個(gè)閉包(返回值類型和初始值類型一致)。函數(shù)最后的返回值同樣和初始值類型一致黎茎,為 T
囊颅。
假設(shè)我們現(xiàn)在要實(shí)現(xiàn)一個(gè) reduce 操作——對(duì)一個(gè)整數(shù)列表值做累加運(yùn)算,方案如下:
func combinator(accumulator: Int, current: Int) -> Int {
return accumulator + current
}
[1, 2, 3].reduce(0, combine: combinator)
// 執(zhí)行步驟如下
combinator(0, 1) { return 0 + 1 } = 1
combinator(1, 2) { return 1 + 2 } = 3
combinator(3, 3) { return 3 + 3 } = 6
//= 6
[1,2,3]
中的每個(gè)元素都將調(diào)用一次結(jié)合(Combinator)函數(shù)進(jìn)行處理。同時(shí)我們使用累加器(Accumulator)變量實(shí)時(shí)記錄遞增狀態(tài)(遞增并非是指加法)踢代,這里是一個(gè)整型值盲憎。
接下來,我們重新實(shí)現(xiàn)那些函數(shù)式編程的「伙伴」(自己來寫 map胳挎、flatMap 和 filter 函數(shù))饼疙。簡便起見,所有這些方法都是對(duì) Int
或 Optional<Int>
進(jìn)行操作的慕爬;換言之窑眯,我們此刻不考慮泛型。另外牢記下面的實(shí)現(xiàn)只是為了展示 reduce
的實(shí)現(xiàn)過程医窿。 原生的 Swift 實(shí)現(xiàn)相比較下面 reduce 的版本磅甩,速度要快很多1。不過留搔,reduce 能在不同的問題中表現(xiàn)得很好更胖,之后會(huì)進(jìn)一步地詳述铛铁。
Map
// 重新定義一個(gè) map 函數(shù)
func rmap(elements: [Int], transform: (Int) -> Int) -> [Int] {
return elements.reduce([Int](), combine: { (var acc: [Int], obj: Int) -> [Int] in
acc.append(transform(obj))
return acc
})
}
print(rmap([1, 2, 3, 4], transform: { $0 * 2}))
// [2, 4, 6, 8]
這個(gè)例子能夠很好地幫助你理解 reduce
的基礎(chǔ)知識(shí)隔显。
- 首先,elements 序列調(diào)用 reduce 方法:
elements.reduce...
饵逐。 - 然后括眠,我們傳入初始值給累加器(Accumulator),即一個(gè) Int 類型空數(shù)組(
[Int]()
)倍权。 - 接著掷豺,我們傳入
combinator
閉包,它接收兩個(gè)參數(shù):第一個(gè)參數(shù)為 accumulator薄声,即acc: [Int]
当船;第二個(gè)參數(shù)為從序列中取得的當(dāng)前對(duì)象obj: Int
(譯者注:對(duì)序列進(jìn)行遍歷,每次取到其中的一個(gè)對(duì)象 obj)默辨。 -
combinator
閉包體中的實(shí)現(xiàn)代碼非常簡單德频。我們對(duì) obj 做變換處理,然后添加到累加器 accumulator 中缩幸。最后返回 accumulator 對(duì)象壹置。
相比較調(diào)用 map
方法,這種實(shí)現(xiàn)代碼看起來有點(diǎn)冗余表谊。的確如此钞护!但是,上面這個(gè)版本相當(dāng)詳細(xì)地解釋了 reduce
方法是怎么工作的爆办。我們可以對(duì)此進(jìn)行簡化难咕。
func rmap(elements: [Int], transform: (Int) -> Int) -> [Int] {
// $0 表示第一個(gè)傳入?yún)?shù),$1 表示第二個(gè)傳入?yún)?shù),依次類推...
return elements.reduce([Int](), combine: {$0 + [transform($1)]})
}
print(rmap([1, 2, 3, 4], transform: { $0 * 2}))
// [2, 4, 6, 8]
依舊能夠正常運(yùn)行步藕。這個(gè)版本都有哪些不同呢惦界?實(shí)際上,我們使用了 Swift 中的小技巧咙冗,+
運(yùn)算符能夠?qū)蓚€(gè)序列進(jìn)行加法操作沾歪。因此 [0,1,2] + [transform(4)]
表達(dá)式將左序列和右序列進(jìn)行相加,其中右序列由轉(zhuǎn)換后的元素構(gòu)成雾消。
這里有個(gè)地方需要引起注意:[1,2,3] + [4]
執(zhí)行速度要慢于 [1,2,3].append(4)
灾搏。倘若你正在處理龐大的列表,應(yīng)取代集合 + 集合的方式立润,轉(zhuǎn)而使用一個(gè)可變的 accumulator 變量進(jìn)行遞增:
func rmap(elements: [Int], transform: (Int) -> Int) -> [Int] {
return elements.reduce([Int](), combine: { (var ac: [Int], b: Int) -> [Int] in
// 作者提倡使用這種狂窑,因?yàn)閳?zhí)行速度更快
ac.append(transform(b))
return ac
})
}
為了進(jìn)一步加深對(duì) reduce
的理解,我們將繼續(xù)重新實(shí)現(xiàn) flatMap
和 filter
方法桑腮。
func rflatMap(elements: [Int], transform: (Int) -> Int?) -> [Int] {
return elements.reduce([Int](),
combine: { guard let m = transform($1) else { return $0 }
return $0 + [m]})
}
print(rflatMap([1, 3, 4], transform: { guard $0 != 3 else { return nil }; return $0 * 2}))
// [2, 8]
這里 rflatMap 和 rmap 主要差異在于泉哈,前者增加了一個(gè) guard
表達(dá)式確保可選類型始終有值(換言之破讨,摒棄那些 nil 的情況)丛晦。
Filter
func rFilter(elements: [Int], filter: (Int) -> Bool) -> [Int] {
return elements.reduce([Int](),
combine: { guard filter($1) else { return $0 } // 確保滿足篩選條件
return $0 + [$1]})
}
print(rFilter([1, 3, 4, 6], filter: { $0 % 2 == 0}))
// [4, 6]
依舊難度不大。我們?cè)俅问褂?guard 表達(dá)式確保滿足篩選條件提陶。
到目前為止烫沙,reduce
方法看起來更像是 map
或 filter
的復(fù)雜版本,除此之外然并卵隙笆。不過锌蓄,所結(jié)合的內(nèi)容不需要是一個(gè)數(shù)組,它可以是其他任何類型撑柔。這使得我們依靠一種簡單的方式瘸爽,就可以輕松地實(shí)現(xiàn)各種 reduction 操作。
Reduce 范例
首先介紹我最喜歡的數(shù)組元素求和范例:
// 初始值 initial 為 0铅忿,每次遍歷數(shù)組元素剪决,執(zhí)行 + 操作
[0, 1, 2, 3, 4].reduce(0, combine: +)
// 10
僅傳入 +
作為一個(gè) combinator
函數(shù)是有效的,它僅僅是對(duì) lhs(Left-hand side辆沦,等式左側(cè))
和 rhs(Right-hand side昼捍,等式右側(cè))
做加法處理,最后返回結(jié)果值肢扯,這完全滿足 reduce
函數(shù)的要求妒茬。
另外一個(gè)范例:通過一組數(shù)字計(jì)算他們的乘積:
// 初始值 initial 為 1,每次遍歷數(shù)組元素蔚晨,執(zhí)行 * 操作
[1, 2, 3, 4].reduce(1, combine: *)
// 24
甚至我們可以反轉(zhuǎn)數(shù)組:
// $0 指累加器(accumulator)乍钻,$1 指遍歷數(shù)組得到的一個(gè)元素
[1, 2, 3, 4, 5].reduce([Int](), combine: { [$1] + $0 })
// 5, 4, 3, 2, 1
最后肛循,來點(diǎn)有難度的任務(wù)。我們想要基于某個(gè)標(biāo)準(zhǔn)對(duì)列表做劃分(Partition)處理:
// 為元組定義個(gè)別名银择,此外 Acc 也是閉包傳入的 accumulator 的類型
typealias Acc = (l: [Int], r: [Int])
func partition(lst: [Int], criteria: (Int) -> Bool) -> Acc {
return lst.reduce((l: [Int](), r: [Int]()), combine: { (ac: Acc, o: Int) -> Acc in
if criteria(o) {
return (l: ac.l + [o], r: ac.r)
} else {
return (r: ac.r + [o], l: ac.l)
}
})
}
partition([1, 2, 3, 4, 5, 6, 7, 8, 9], criteria: { $0 % 2 == 0 })
//: ([2, 4, 6, 8], [1, 3, 5, 7, 9])
上面實(shí)現(xiàn)中最有意思的莫過于我們使用 tuple
作為 accumulator多糠。你會(huì)漸漸發(fā)現(xiàn),一旦你嘗試將 reduce
進(jìn)入到日常工作流中浩考,tuple
是一個(gè)不錯(cuò)的選擇夹孔,它能夠?qū)?shù)據(jù)與 reduce 操作快速掛鉤起來。
執(zhí)行效率對(duì)比:Reduce vs. 鏈?zhǔn)浇Y(jié)構(gòu)
reduce
除了較強(qiáng)的靈活性之外析孽,還具有另一個(gè)優(yōu)勢:通常情況下搭伤,map
和 filter
所組成的鏈?zhǔn)浇Y(jié)構(gòu)會(huì)引入性能上的問題,因?yàn)樗鼈冃枰啻伪闅v你的集合才能最終得到結(jié)果值袜瞬,這種操作往往伴隨性能損失怜俐,比如以下代碼:
[0, 1, 2, 3, 4].map({ $0 + 3}).filter({ $0 % 2 == 0}).reduce(0, combine: +)
除了毫無意義之外,它還浪費(fèi)了 CPU 周期邓尤。初始序列(即 [0,1,2,3,4])被重復(fù)訪問了三次之多拍鲤。首先是 map,接著 filter汞扎,最后對(duì)數(shù)組內(nèi)容求和季稳。其實(shí),所有這一切操作我們能夠使用 reduce
完全替換實(shí)現(xiàn)佩捞,極大提高執(zhí)行效率:
// 這里只需要遍歷 1 次序列足矣
[0, 1, 2, 3, 4].reduce(0, combine: { (ac: Int, r: Int) -> Int in
if (r + 3) % 2 == 0 {
return ac + r + 3
} else {
return ac
}
})
這里給出一個(gè)快速的基準(zhǔn)運(yùn)行測試绞幌,使用以上兩個(gè)版本以及 for-loop 方式對(duì)一個(gè)容量為 100000 的列表做處理操作:
// for-loop 版本
var ux = 0
for i in Array(0...100000) {
if (i + 3) % 2 == 0 {
ux += (i + 3)
}
}
正如你所看見的蕾哟,reduce
版本的執(zhí)行效率和 for-loop
操作非常相近一忱,且是鏈?zhǔn)讲僮鞯囊话霑r(shí)間。
不過谭确,在某些情況中帘营,鏈?zhǔn)讲僮魇莾?yōu)于 reduce
的。思考如下范例:
Array(0...100000).map({ $0 + 3}).reverse().prefix(3)
// 0.027 Seconds
Array(0...100000).reduce([], combine: { (var ac: [Int], r: Int) -> [Int] in
ac.insert(r + 3, atIndex: 0)
return ac
}).prefix(3)
// 2.927 Seconds
這里逐哈,注意到使用鏈?zhǔn)讲僮骰ㄙM(fèi) 0.027s芬迄,這與 reduce 操作的 2.927s 形成了鮮明的反差,這究竟是怎么回事呢昂秃?2
Reddit 網(wǎng)站的搜索結(jié)果指出禀梳,從 reduce 的語義上來說,傳入閉包的參數(shù)(如果可變的話肠骆,即 mutated)算途,會(huì)對(duì)底層序列的每個(gè)元素都產(chǎn)生一份 copy 。在我們的案例中蚀腿,這意味著 accumulator 參數(shù) ac
將為 0...100000 范圍內(nèi)的每個(gè)元素都執(zhí)行一次復(fù)制操作嘴瓤。有關(guān)對(duì)此更好扫外、更詳細(xì)的解釋請(qǐng)看這篇 Airspeedvelocity 博客文章。
因此廓脆,當(dāng)我們?cè)噲D使用 reduce
來替換掉一組操作時(shí)筛谚,請(qǐng)時(shí)刻保持清醒,問問自己:reduction 在問題中的情形下是否確實(shí)是最合適的方式停忿。
現(xiàn)在驾讲,我們回到我們初始問題:計(jì)算人口總數(shù)和平均年齡。請(qǐng)?jiān)囍?reduce
來解決吧席赂。
再一次嘗試來寫 infoFromState 函數(shù)
func infoFromState(state state: String, persons: [[String: AnyObject]])
-> (count: Int, age: Float) {
// 在函數(shù)內(nèi)定義別名讓函數(shù)更加簡潔
typealias Acc = (count: Int, age: Float)
// reduce 結(jié)果暫存為臨時(shí)的變量
let u = persons.reduce((count: 0, age: 0.0)) {
(ac: Acc, p) -> Acc in
// 獲取地區(qū)和年齡
guard let personState = (p["city"] as? String)?.componentsSeparatedByString(", ").last,
personAge = p["age"] as? Int
// 確保選出來的是來自正確的洲
where personState == state
// 如果缺失年齡或者地區(qū)蝎毡,又或者上者比較結(jié)果不等,返回
else { return ac }
// 最終累加計(jì)算人數(shù)和年齡
return (count: ac.count + 1, age: ac.age + Float(personAge))
}
// 我們的結(jié)果就是上面的人數(shù)和除以人數(shù)后的平均年齡
return (age: u.age / Float(u.count), count: u.count)
}
print(infoFromState(state: "CA", persons: persons))
// prints: (count: 3, age: 34.3333)
和早前的范例一樣氧枣,我們?cè)俅问褂昧?tuple
作為 accumulator 記錄狀態(tài)值沐兵。除此之外,代碼讀起來簡明易懂便监。
同時(shí)扎谎,我們?cè)诤瘮?shù)體中定義了一個(gè)別名 Acc:typealias Acc = (count: Int, age: Float)
,起到了簡化類型注釋的作用。
總結(jié)
本文是對(duì) reduce
方法的一個(gè)簡短概述烧董。倘若你不想將過多函數(shù)式方法通過鏈?zhǔn)浇Y(jié)構(gòu)串聯(lián)起來調(diào)用毁靶,亦或是數(shù)據(jù)的輸出形式與傳入數(shù)據(jù)的形式不一致時(shí),reduce 就相當(dāng)有用了逊移。最后预吆,我將向你展示通過使用 reduce 的各種范例來結(jié)束本文,希望能為你帶來些許靈感胳泉。
更多范例
以下范例展示了 reduce
的其他使用案例拐叉。請(qǐng)記住例子只作為展示教學(xué)使用,即它們更多地強(qiáng)調(diào) reduce 的使用方式扇商,而非為你的代碼庫提供通用的解決方法凤瘦。大多數(shù)范例都可以通過其他更好、更快的方式來編寫(即通過 extension 或 generics)案铺。并且這些實(shí)現(xiàn)方式已經(jīng)在許多 Swift 庫中都有實(shí)現(xiàn)蔬芥,諸如 SwiftSequence 以及 Dollar.swift
Minimum
返回列表中的最小項(xiàng)。顯然控汉,[1,5,2,9,4].minElement()
方法更勝一籌笔诵。
// 初始值為 Int.max,傳入閉包為 min:求兩個(gè)數(shù)的最小值
// min 閉包傳入兩個(gè)參數(shù):1. 初始值 2. 遍歷列表時(shí)的當(dāng)前元素
// 倘若當(dāng)前元素小于初始值姑子,初始值就會(huì)替換成當(dāng)前元素
// 示意寫法: initial = min(initial, elem)
[1, 5, 2, 9, 4].reduce(Int.max, combine: min)
Unique
剔除列表中重復(fù)的元素乎婿。當(dāng)然,最好的解決方式是使用集合(Set)
壁酬。
[1, 2, 5, 1, 7].reduce([], combine: { (a: [Int], b: Int) -> [Int] in
if a.contains(b) {
return a
} else {
return a + [b]
}
})
// prints: 1, 2, 5, 7
Group By
遍歷整個(gè)列表次酌,通過一個(gè)鑒別函數(shù)對(duì)列表中元素進(jìn)行分組恨课,將分組后的列表作為結(jié)果值返回。問題中的鑒別函數(shù)返回值類型需要遵循 Hashable
協(xié)議岳服,這樣我們才能擁有不同的鍵值剂公。此外保留元素的排序,而組內(nèi)元素排序則不需要保留吊宋。
func groupby<T, H: Hashable>(items: [T], f: (T) -> H) -> [H: [T]] {
return items.reduce([:], combine: { (var ac: [H: [T]], o: T) -> [H: [T]] in
// o 為遍歷序列的當(dāng)前元素
let h = f(o) // 通過 f 函數(shù)得到 o 對(duì)應(yīng)的鍵值
if var c = ac[h] { // 說明 o 對(duì)應(yīng)的鍵值已經(jīng)存在纲辽,只需要更新鍵值對(duì)應(yīng)的數(shù)組元素即可
c.append(o)
ac.updateValue(c, forKey: h)
} else { // 說明 o 對(duì)應(yīng)的鍵值不存在,需要為字典新增一個(gè)鍵值璃搜,對(duì)應(yīng)值為 [o]
ac.updateValue([o], forKey: h)
}
return ac
})
}
print(groupby([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], f: { $0 % 3 }))
// prints: [2: [2, 5, 8, 11], 0: [3, 6, 9, 12], 1: [1, 4, 7, 10]]
print(groupby(["Carl", "Cozy", "Bethlehem", "Belem", "Brand", "Zara"], f: { $0.characters.first! }))
// prints: ["C" : ["Carl" , "Cozy"] , "B" : ["Bethlehem" , "Belem" , "Brand"] , "Z" : ["Zara"]]
Interpose
函數(shù)給定一個(gè) items
數(shù)組拖吼,每隔 count
個(gè)元素插入 element
元素,返回結(jié)果值这吻。下面的實(shí)現(xiàn)確保了 element 僅在中間插入吊档,而不會(huì)添加到數(shù)組尾部。
func interpose<T>(items: [T], element: T, count: Int = 1) -> [T] {
// cur 為當(dāng)前遍歷元素的索引值 cnt 為計(jì)數(shù)器唾糯,當(dāng)值等于 count 時(shí)又重新置 1
typealias Acc = (ac: [T], cur: Int, cnt: Int)
return items.reduce((ac: [], cur: 0, cnt: 1), combine: { (a: Acc, o: T) -> Acc in
switch a {
// 此時(shí)遍歷的當(dāng)前元素為序列中的最后一個(gè)元素
case let (ac, cur, _) where (cur+1) == items.count: return (ac + [o], 0, 0)
// 滿足插入條件
case let (ac, cur, c) where c == count:
return (ac + [o, element], cur + 1, 1)
// 執(zhí)行下一步
case let (ac, cur, c):
return (ac + [o], cur + 1, c + 1)
}
}).ac
}
print(interpose([1, 2, 3, 4, 5], element: 9))
// : [1, 9, 2, 9, 3, 9, 4, 9, 5]
print(interpose([1, 2, 3, 4, 5], element: 9, count: 2))
// : [1, 2, 9, 3, 4, 9, 5]
Interdig
該函數(shù)允許你有選擇從兩個(gè)序列中挑選元素合并成為一個(gè)新序列返回怠硼。
func interdig<T>(list1: [T], list2: [T]) -> [T] {
// Zip2Sequence 返回 [(list1, list2)] 是一個(gè)數(shù)組,類型為元組
// 也就解釋了為什么 combinator 閉包的類型是 (ac: [T], o: (T, T)) -> [T]
return Zip2Sequence(list1, list2).reduce([], combine: { (ac: [T], o: (T, T)) -> [T] in
return ac + [o.0, o.1]
})
}
print(interdig([1, 3, 5], list2: [2, 4, 6]))
// : [1, 2, 3, 4, 5, 6]
Chunk
該函數(shù)返回原數(shù)組分解成長度為 n
后的多個(gè)數(shù)組:
func chunk<T>(list: [T], length: Int) -> [[T]] {
typealias Acc = (stack: [[T]], cur: [T], cnt: Int)
let l = list.reduce((stack: [], cur: [], cnt: 0), combine: { (ac: Acc, o: T) -> Acc in
if ac.cnt == length {
return (stack: ac.stack + [ac.cur], cur: [o], cnt: 1)
} else {
return (stack: ac.stack, cur: ac.cur + [o], cnt: ac.cnt + 1)
}
})
return l.stack + [l.cur]
}
print(chunk([1, 2, 3, 4, 5, 6, 7], length: 2))
// : [[1, 2], [3, 4], [5, 6], [7]]
函數(shù)中使用一個(gè)更為復(fù)雜的 accumulator
移怯,包含了 stack香璃、current list 以及 count 。
譯者注:有關(guān) Reduce 底層實(shí)現(xiàn)舟误,請(qǐng)看這篇文章葡秒。
這么做的原因來看這篇博文。
這篇文章的早期版本中嵌溢,我誤認(rèn)為 Swift 的這種特性是造成這種差異的罪魁禍?zhǔn)住?a target="_blank" rel="nofollow">感謝 Reddit 的這個(gè)討論指出了我的錯(cuò)誤眯牧。