“身首异处”的序列(Swift)

声明:文章开头部分内容翻译自objc的一篇博客。当然,我并没有逐行翻译原文,只是说个大致意思,顺带阐述一些自己的理解和扩展思考,还有我自己的代码。

分解序列:

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

上面这个代码片段是对Array的一个扩展(对extension不熟悉对同学可以看看我以前的一篇文章)。decompose作为扩展的计算属性,返回一个可空元组(Tuple?),元组包含数组的首元素和一个由剩余元素组成的数组,如果数组为空则返回nil。这个分解操作配合if let和模式匹配将非常好用。譬如对序列数据的累加操作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

在函数式编程的世界中,取序列的首元素和剩余序列是一个很重要的操作,许多高阶的序列操作都可以基于这个操作完成。上面说了累加,稍微扩展一下,累乘呢?当然也可以。甚至我们可以用它定义一个更抽象更一般化的函数,功能与Swift提供的全局函数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接收三个参数:序列list、初始值initValue、操作函数function。我以multiResult为例稍微讲解一下这个函数的过程。这个函数的重点当然是递归,事实上我认为递归可以说是函数式编程这种范式的核心之一。

运行reduce([2, 3, 5], initValue: 1, function: *),先是递归分解:

  • [2, 3, 5]被分解为元组(2, [3, 5])。
  • 2和reduce([3, 5], initValue: 1, function: *)的返回值将作为乘法操作*的两个参数。
  • 执行reduce([3, 5], initValue: 1, function: *),[3, 5]被分解成元组(3, [5])。
  • 3和reduce([5], initValue: 1, function: *)的返回值将作为乘法的左右因数相乘。
  • 执行reduce([5], initValue: 1, function: *),[5]被分解成元组(5, [])。
  • 5和reduce([], initValue: 1, function: *)的返回值将作为乘法的左右因数相乘,而[]是个空数组,它的decompose属性返回nil,所以执行else之后的代码块,即返回initValue1。

至此向下递归结束,接下来就是延递归栈往上计算各个function函数(此例中即乘法)的值。最终得到1 * 5 * 3 * 2 = 30。

当然,递归会消耗栈空间,如果递归很深的话,很有可能会导致栈溢出。有一种常见的优化方式是尾递归(简单来说,即把递归调用放到函数最后),如果编译器支持尾递归优化的话,就会把函数中的一些中间变量舍去甚至直接优化成循环形式。具体内容我就不展开来,大家可以看一下老赵早期的一篇《浅谈尾递归的优化方式》,想必会有所得。

sum函数为例的话,进行尾递归优化我们可以将其改写为如下形式:

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

新的sum函数使用Swift2的新特性guard进行提前返回,guard是我很喜欢的一个语法,哪怕不是为了尾递归优化,我也推荐大家使用guard语句处理边界条件然后提前返回,这也是所谓的防御式编程中所提倡的,我之前的一篇文章也有提到。

最后再说一个用到decompose的快排函数吧:

//快排
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)
}

可以看到这个函数抽象度很高,而且思路非常清晰——选取首元素作为参照点,比参照点小的放参照点左边,大的放右边。函数的大致过程为:递归进行分解排序,最后延递归栈向上连接数组。之前我写过一篇快排的文章,里面的函数远没有上面这个版本简洁优雅。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容