“身首異處”的序列(Swift)

聲明:文章開(kāi)頭部分內(nèi)容翻譯自objc的一篇博客米酬。當(dāng)然赃额,我并沒(méi)有逐行翻譯原文阁簸,只是說(shuō)個(gè)大致意思启妹,順帶闡述一些自己的理解和擴(kuò)展思考醉旦,還有我自己的代碼车胡。

分解序列:

//分解成首元素和剩余數(shù)組
extension Array {
    var decompose : (head: Element, tail: [Element])? {
        return (count > 0) ? (self[0], Array(self[1..<count])) : nil
    }
}

上面這個(gè)代碼片段是對(duì)Array的一個(gè)擴(kuò)展(對(duì)extension不熟悉對(duì)同學(xué)可以看看我以前的一篇文章)。decompose作為擴(kuò)展的計(jì)算屬性丧慈,返回一個(gè)可空元組(Tuple?)逃默,元組包含數(shù)組的首元素和一個(gè)由剩余元素組成的數(shù)組簇搅,如果數(shù)組為空則返回nil。這個(gè)分解操作配合if let和模式匹配將非常好用吟税。譬如對(duì)序列數(shù)據(jù)的累加操作sum

//累加
func sum(list: [Int]) -> Int {
    if let (head, tail) = list.decompose {
        return head + sum(tail)
    } else {
        return 0
    }
}

let sumResult = sum([1, 2, 3, 4])   //10

在函數(shù)式編程的世界中,取序列的首元素和剩余序列是一個(gè)很重要的操作肖抱,許多高階的序列操作都可以基于這個(gè)操作完成虐沥。上面說(shuō)了累加,稍微擴(kuò)展一下欲险,累乘呢天试?當(dāng)然也可以喜每。甚至我們可以用它定義一個(gè)更抽象更一般化的函數(shù)雳攘,功能與Swift提供的全局函數(shù)reduce相同:

//山寨reduce
func reduce<T>(list: [T], initValue: T, function: (lhs: T, rhs: T) -> T) -> T {
    if let (head, tail) = list.decompose {
        return function(lhs: head, rhs: reduce(tail, initValue: initValue, function: function))
    } else {
        return initValue
    }
}

let multiResult = reduce([2, 3, 5], initValue: 1, function: *)  //30

reduce接收三個(gè)參數(shù):序列list、初始值initValue刚照、操作函數(shù)function无畔。我以multiResult為例稍微講解一下這個(gè)函數(shù)的過(guò)程吠冤。這個(gè)函數(shù)的重點(diǎn)當(dāng)然是遞歸,事實(shí)上我認(rèn)為遞歸可以說(shuō)是函數(shù)式編程這種范式的核心之一郭变。

運(yùn)行reduce([2, 3, 5], initValue: 1, function: *)诉濒,先是遞歸分解:

  • [2, 3, 5]被分解為元組(2, [3, 5])遭赂。
  • 2和reduce([3, 5], initValue: 1, function: *)的返回值將作為乘法操作*的兩個(gè)參數(shù)。
  • 執(zhí)行reduce([3, 5], initValue: 1, function: *)茄猫,[3, 5]被分解成元組(3, [5])。
  • 3和reduce([5], initValue: 1, function: *)的返回值將作為乘法的左右因數(shù)相乘划纽。
  • 執(zhí)行reduce([5], initValue: 1, function: *)勇劣,[5]被分解成元組(5, [])比默。
  • 5和reduce([], initValue: 1, function: *)的返回值將作為乘法的左右因數(shù)相乘,而[]是個(gè)空數(shù)組篡九,它的decompose屬性返回nil榛臼,所以執(zhí)行else之后的代碼塊沛善,即返回initValue1。

至此向下遞歸結(jié)束金刁,接下來(lái)就是延遞歸棧往上計(jì)算各個(gè)function函數(shù)(此例中即乘法)的值胀葱。最終得到1 * 5 * 3 * 2 = 30笙蒙。

當(dāng)然捅位,遞歸會(huì)消耗椡Р螅空間焰雕,如果遞歸很深的話芳杏,很有可能會(huì)導(dǎo)致棧溢出辟宗。有一種常見(jiàn)的優(yōu)化方式是尾遞歸(簡(jiǎn)單來(lái)說(shuō)泊脐,即把遞歸調(diào)用放到函數(shù)最后)容客,如果編譯器支持尾遞歸優(yōu)化的話缩挑,就會(huì)把函數(shù)中的一些中間變量舍去甚至直接優(yōu)化成循環(huán)形式鬓梅。具體內(nèi)容我就不展開(kāi)來(lái),大家可以看一下老趙早期的一篇《淺談尾遞歸的優(yōu)化方式》士袄,想必會(huì)有所得娄柳。

sum函數(shù)為例的話艘绍,進(jìn)行尾遞歸優(yōu)化我們可以將其改寫(xiě)為如下形式:

//累加
func sum(list: [Int]) -> Int {
    guard let (head, tail) = list.decompose else {
        return 0
    }
    return head + sum(tail)
}

新的sum函數(shù)使用Swift2的新特性guard進(jìn)行提前返回诱鞠,guard是我很喜歡的一個(gè)語(yǔ)法航夺,哪怕不是為了尾遞歸優(yōu)化阳掐,我也推薦大家使用guard語(yǔ)句處理邊界條件然后提前返回,這也是所謂的防御式編程中所提倡的缭保,我之前的一篇文章也有提到诸老。

最后再說(shuō)一個(gè)用到decompose的快排函數(shù)吧:

//快排
func quickSort(list: [Int]) -> [Int] {
    guard let (pivot, rest) = list.decompose else {
        return []
    }
    
    let lesser = rest.filter { $0 < pivot }
    let greater = rest.filter { $0 >= pivot }
    return quickSort(lesser) + [pivot] + quickSort(greater)
}

可以看到這個(gè)函數(shù)抽象度很高钳恕,而且思路非常清晰——選取首元素作為參照點(diǎn),比參照點(diǎn)小的放參照點(diǎn)左邊畸肆,大的放右邊宦芦。函數(shù)的大致過(guò)程為:遞歸進(jìn)行分解排序,最后延遞歸棧向上連接數(shù)組轴脐。之前我寫(xiě)過(guò)一篇快排的文章调卑,里面的函數(shù)遠(yuǎn)沒(méi)有上面這個(gè)版本簡(jiǎn)潔優(yōu)雅。

快把decompose加入你的Code Snippet中吧^ ^大咱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恬涧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碴巾,更是在濱河造成了極大的恐慌溯捆,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厦瓢,死亡現(xiàn)場(chǎng)離奇詭異提揍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)劳跃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)杉武,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)圾亏,“玉大人曹铃,你說(shuō)我怎么就攤上這事秘血。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵旬薯,是天一觀的道長(zhǎng)骤公。 經(jīng)常有香客問(wèn)我趁猴,道長(zhǎng),這世上最難降的妖魔是什么跷坝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任贴届,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布蕴侧。 她就那樣靜靜地躺著择葡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天返奉,我揣著相機(jī)與錄音,去河邊找鬼膀哲。 笑死兴喂,一個(gè)胖子當(dāng)著我的面吹牛酱酬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呵俏,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼动猬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體略号,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铐伴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挂洛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礼预。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖虏劲,靈堂內(nèi)的尸體忽然破棺而出托酸,到底是詐尸還是另有隱情褒颈,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布励堡,位于F島的核電站谷丸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏念秧。R本人自食惡果不足惜淤井,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一布疼、第九天 我趴在偏房一處隱蔽的房頂上張望摊趾。 院中可真熱鬧,春花似錦游两、人聲如沸砾层。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肛炮。三九已至,卻和暖如春宝踪,著一層夾襖步出監(jiān)牢的瞬間侨糟,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工瘩燥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秕重,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓厉膀,卻偏偏與公主長(zhǎng)得像溶耘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子服鹅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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