通過closure參數(shù)化對數(shù)組元素的變形操作

當你要對Array做一些處理的時候,像C語言中類似的循環(huán)和下標,都不是理想的選擇。Swift有一套自己的“現(xiàn)代化”手段摇展。簡單來說,就是用closure來參數(shù)化對數(shù)組的操作行為溺忧。這聽著有點兒抽象咏连,從一個最簡單的例子開始來試著理解一下。

從循環(huán)到map

假設(shè)我們有一個簡單的Fibonacci序列:[0, 1, 1, 2, 3, 5]鲁森。如果我們要計算每個元素的平方祟滴,怎么辦呢?

一個最樸素的做法是for循環(huán):

var fibonacci = [0, 1, 1, 2, 3, 5]
var squares = [Int]()

for value in fibonacci {
    squares.append(value * value)
}

也許刀森,現(xiàn)在你還覺得這樣沒什么不好理解踱启,但是,想象一下這段代碼在幾十行代碼中間的時候研底,或者當這樣類似的邏輯反復(fù)出現(xiàn)的時候埠偿,整體代碼的可讀性就不那么強了。

如果你覺得這還不是個足夠引起你注意的問題榜晦,那么冠蒋,當我們要定義一個常量squares的時候,上面的代碼就完全無法勝任了乾胶。怎么辦呢抖剿?先來看解決方案:

// [0, 1, 1, 4, 9, 25]
let constSquares = fibonacci.map { $0 * $0 }

上面這行代碼,和之前那段for循環(huán)執(zhí)行的結(jié)果是相同的识窿。顯然斩郎,它比for循環(huán)更具表現(xiàn)力,并且也能把我們期望的結(jié)果定義成常量喻频。當然缩宜,map并不是什么魔法,無非就是把for循環(huán)執(zhí)行的邏輯,封裝在了函數(shù)里锻煌,這樣我們就可以把函數(shù)的返回值賦值給常量了妓布。我們可以通過extension很簡單的自己來實現(xiàn)map

extension Array {
    func myMap<T>(_ transform: (Element) -> T) -> [T] {
        var tmp: [T] = []
        tmp.reserveCapacity(count)

        for value in self {
            tmp.append(transform(value))
        }

        return tmp
    }
}

雖然和Swift標準庫相比,myMap的實現(xiàn)中去掉了和異常聲明相關(guān)的部分宋梧。但它已經(jīng)足以表現(xiàn)map的核心實現(xiàn)過程了匣沼。除了在append之前使用了reserveCapacity給新數(shù)組預(yù)留了空間之外,它的實現(xiàn)過程和一開始我們使用的for循環(huán)沒有任何差別捂龄。

如果你還不了解Element也沒關(guān)系释涛,把它理解為Array中元素類型的替代符就好了。在后面我們講到Sequence類型的時候倦沧,會專門提到它枢贿。

完成后,當我們在playground里測試的時候:

// [0, 1, 1, 4, 9, 25]
let constSequence1 = fibonacci.myMap { $0 * $0 }

就會發(fā)現(xiàn)執(zhí)行結(jié)果和之前的constSequence是一樣的了刀脏。

參數(shù)化數(shù)組元素的執(zhí)行動作

其實,仔細觀察myMap的實現(xiàn)超凳,就會發(fā)現(xiàn)它最大的意義愈污,就是保留了遍歷Array的過程,而把要執(zhí)行的動作留給了myMap的調(diào)用者通過參數(shù)去定制轮傍。而這暂雹,就是我們一開始提到的用closure來參數(shù)化對數(shù)組的操作行為的含義。

有了這種思路之后创夜,我們就可以把各種常用的帶有遍歷行為的操作杭跪,定制成多種不同的遍歷“套路”,而把對數(shù)組中每一個元素的處理動作留給函數(shù)的調(diào)用者驰吓。但是別急涧尿,在開始自動動手造輪子之前,Swift library已經(jīng)為我們準備了一些檬贰,例如:

首先姑廉,是找到最小、最大值翁涤,對于這類操作來說桥言,只要數(shù)組中的元素實現(xiàn)了Equatable protocol,我們甚至無需定義對元素的具體操作:

fibonacci.min() // 0
fibonacci.max() // 5

使用minmax很安全葵礼,因為當數(shù)組為空時号阿,這兩個方法將返回nil

其次鸳粉,過濾出滿足特定條件的元素扔涧,我們只要通過參數(shù)指定篩選規(guī)則就好了:

fibonacci.filter { $0 % 2 == 0 }

第三,比較數(shù)組相等或以特定元素開始赁严。對這類操作扰柠,我們需要提供兩個內(nèi)容粉铐,一個是要比較的數(shù)組,另一個則是比較的規(guī)則:

// false
fibonacci.elementsEqual([0, 1, 1], by: { $0 == $1 })
// true
fibonacci.starts(with: [0, 1, 1], by: { $0 == $1 })

第四卤档,最原始的for循環(huán)的替代品:

fibonacci.forEach { print($0) }
// 0
// 1
// ...

要注意它和map的一個重要區(qū)別:forEach并不處理closure參數(shù)的返回值蝙泼。因此它只適合用來對數(shù)組中的元素進行一些操作,而不能用來產(chǎn)生返回結(jié)果劝枣。

第五汤踏、對數(shù)組進行排序,這時舔腾,我們需要通過參數(shù)指定的是排序規(guī)則:

// [0, 1, 1, 2, 3, 5]
fibonacci.sorted()
// [5, 3, 2, 1, 1, 0]
fibonacci.sorted(by: >)

let pivot = fibonacci.partition(by: { $0 < 1 })
fibonacci[0 ..< pivot] // [5, 1溪胶,1,2, 3]
fibonacci[pivot ..< fibonacci.endIndex] // [0]

其中稳诚,sorted(by:)的用法是很直接的哗脖,它默認采用升序排列。同時扳还,也允許我們通過by自定義排序規(guī)則才避。在這里>{ $0 > $1 }的簡寫形式。Swift中有很多在不影響語義的情況下的簡寫形式氨距。

partition(by:)則會先對傳遞給它的數(shù)組進行重排桑逝,然后根據(jù)指定的條件在重排的結(jié)果中返回一個分界點位置。這個分界點分開的兩部分中俏让,前半部分的元素都不滿足指定條件楞遏;后半部分都滿足指定條件。而后首昔,我們就可以使用range operator來訪問這兩個區(qū)間形成的Array對象寡喝。大家可以根據(jù)例子中注釋的結(jié)果,來理解partition的用法沙廉。

第六拘荡,是把數(shù)組的所有內(nèi)容,“合并”成某種形式的值撬陵,對這類操作珊皿,我們需要指定的,是合并前的初始值巨税,以及“合并”的規(guī)則蟋定。例如,我們計算fibonacci中所有元素的和:

fibonacci.reduce(0, +) // 12

在這里草添,初始值是0驶兜,和第二個參數(shù)+,則是{ $0 + $1 }的縮寫。

通過這些例子抄淑,你應(yīng)該能感受到了屠凶,這些通過各種形式封裝了遍歷動作的方法,它們之中的任何一個肆资,都比直接通過for循環(huán)實現(xiàn)具有更強的表現(xiàn)力矗愧。這些API,開始讓我們的代碼從面向機器的郑原,轉(zhuǎn)變成面向業(yè)務(wù)需求的唉韭。因此,在Swift里犯犁,你應(yīng)該試著讓自己轉(zhuǎn)變觀念属愤,當你面對一個Array時,你真的幾乎可以忘記下標和循環(huán)了酸役。

區(qū)分修改外部變量和保存內(nèi)部狀態(tài)

當我們使用上面提到的這些帶有closure參數(shù)的Array方法時住诸,一個不好的做法就是通過closure去修改外部變量,并依賴這種副作用產(chǎn)生的結(jié)果涣澡。來看一個例子:

var sum = 0
let constSquares2 = fibonacci.map { (fib: Int) -> Int in
    sum += fib
    return fib * fib
}

在這個例子里只壳,map的執(zhí)行產(chǎn)生了一個副作用,就是對fibonacci中所有的元素求和暑塑。這不是一個好的方法,我們應(yīng)該避免這樣锅必。你應(yīng)該單獨使用reduce來完成這個操作事格,或者如果一定要在closure參數(shù)里修改外部變量,哪怕用forEach也是比map更好的方案搞隐。

但是驹愚,在函數(shù)實現(xiàn)內(nèi)部,專門用一個外部變量來保存closure參數(shù)的執(zhí)行狀態(tài)劣纲,則是一個常用的實現(xiàn)技法逢捺。例如,我們要創(chuàng)建一個新的數(shù)組癞季,其中每個值劫瞳,都是數(shù)組當前位置和之前所有元素的和,可以這樣:

extension Array {
    func accumulate<T>(_ initial: T,
                       _ nextSum: (T, Element) -> T) -> [T] {
        var sum = initial

        return map { next in
            sum = nextSum(sum, next)
            return sum
        }
    }
}

在上面這個例子里绷柒,我們利用map的closure參數(shù)捕獲了sum志于,這樣就保存了每一次執(zhí)行map時,之前所有元素的和废睦。

// [0, 1, 2, 4, 7, 12]
fibonacci.accumulate(0, +)

總結(jié)

Swift中伺绽,使用Array最重要的一個思想:通過closure來參數(shù)化對數(shù)組的操作行為。在Swift標準庫中,基于這個思想奈应,為我們提供了在各種常用數(shù)組操作場景中的API澜掩。因此,當你下意識的開始用一個循環(huán)處理數(shù)組時杖挣,讓自己停一下肩榕,去看看Array的官方文檔,你一定可以找到更現(xiàn)代化的處理方法程梦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末点把,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屿附,更是在濱河造成了極大的恐慌郎逃,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挺份,死亡現(xiàn)場離奇詭異褒翰,居然都是意外死亡,警方通過查閱死者的電腦和手機匀泊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門优训,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人各聘,你說我怎么就攤上這事揣非。” “怎么了躲因?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵早敬,是天一觀的道長。 經(jīng)常有香客問我大脉,道長搞监,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任镰矿,我火速辦了婚禮琐驴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秤标。我一直安慰自己绝淡,他們只是感情好,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布苍姜。 她就那樣靜靜地躺著够委,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怖现。 梳的紋絲不亂的頭發(fā)上茁帽,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天玉罐,我揣著相機與錄音,去河邊找鬼潘拨。 笑死吊输,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的铁追。 我是一名探鬼主播季蚂,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼琅束!你這毒婦竟也來了扭屁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涩禀,失蹤者是張志新(化名)和其女友劉穎料滥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體艾船,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡葵腹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了屿岂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片践宴。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爷怀,靈堂內(nèi)的尸體忽然破棺而出阻肩,到底是詐尸還是另有隱情,我是刑警寧澤运授,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布磺浙,位于F島的核電站,受9級特大地震影響徒坡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘤缩,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一喇完、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剥啤,春花似錦锦溪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至牺丙,卻和暖如春则涯,著一層夾襖步出監(jiān)牢的瞬間复局,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工粟判, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亿昏,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓档礁,卻偏偏與公主長得像角钩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子呻澜,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

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

  • Array [TOC] 如何創(chuàng)建Array 定義空數(shù)組 定義空數(shù)組并指定初始值 兩個常用的Array屬性 .cou...
    Jesmine陽閱讀 10,093評論 0 10
  • 放得下回憶羹幸,可是放得下自己嗎脊髓,留在心底的回憶或許可以隨著時間物是人非,可是自己呢睹欲,自己的心供炼,是不是也能物是人非呢...
    羽落如夢閱讀 261評論 0 0
  • 聲明 我對Flume的研究并不深,這一篇文章來源于2016年3月的某一個下午對Flume的調(diào)研窘疮,僅有一個下午袋哼,所以...
    AlbertCheng閱讀 3,183評論 0 4
  • 年少時,只想著讀書能改變命運闸衫,于是好好念書涛贯,后來考上了大學(xué),離開了農(nóng)村蔚出。 喜歡文學(xué)弟翘,卻被分到了歷史系,至...
    簡遠山人閱讀 721評論 9 36
  • 難得周末骄酗,想清閑一下稀余,卻發(fā)現(xiàn)沒有什么可放松的。找了網(wǎng)游排行榜趋翻,想再次癡迷游戲睛琳,發(fā)現(xiàn)這些游戲都好無聊。不是打打殺殺的...
    Airing閱讀 1,101評論 2 1