Swift化零為整:Reduce方法詳解

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算色、flatMapfilter 等函數(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 中使用 flatMapfilter 來實(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负敏、flatMapfilter 函數(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 是 mapflatMapfilter 的一種擴(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ì) IntOptional<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) flatMapfilter 方法桑腮。

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 方法看起來更像是 mapfilter 的復(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)勢:通常情況下搭伤,mapfilter 所組成的鏈?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)
    }
}
image

正如你所看見的蕾哟,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è)別名 Acctypealias 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)看這篇文章葡秒。


  1. 這么做的原因來看這篇博文

  2. 這篇文章的早期版本中嵌溢,我誤認(rèn)為 Swift 的這種特性是造成這種差異的罪魁禍?zhǔn)住?a target="_blank" rel="nofollow">感謝 Reddit 的這個(gè)討論指出了我的錯(cuò)誤眯牧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市堵腹,隨后出現(xiàn)的幾起案子炸站,更是在濱河造成了極大的恐慌星澳,老刑警劉巖疚顷,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異禁偎,居然都是意外死亡腿堤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門如暖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笆檀,“玉大人,你說我怎么就攤上這事盒至⌒锶鳎” “怎么了士修?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長樱衷。 經(jīng)常有香客問我棋嘲,道長,這世上最難降的妖魔是什么矩桂? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任沸移,我火速辦了婚禮,結(jié)果婚禮上侄榴,老公的妹妹穿的比我還像新娘雹锣。我一直安慰自己,他們只是感情好癞蚕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布蕊爵。 她就那樣靜靜地躺著,像睡著了一般桦山。 火紅的嫁衣襯著肌膚如雪在辆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天度苔,我揣著相機(jī)與錄音匆篓,去河邊找鬼。 笑死寇窑,一個(gè)胖子當(dāng)著我的面吹牛鸦概,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甩骏,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼窗市,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饮笛?” 一聲冷哼從身側(cè)響起咨察,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎福青,沒想到半個(gè)月后摄狱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡无午,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年媒役,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宪迟。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酣衷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出次泽,到底是詐尸還是另有隱情穿仪,我是刑警寧澤席爽,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站啊片,受9級(jí)特大地震影響拳昌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钠龙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一炬藤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碴里,春花似錦沈矿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至根竿,卻和暖如春陵像,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寇壳。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工醒颖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壳炎。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓泞歉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匿辩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腰耙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容