博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第六章——函数(函数的便捷性)
阅读量:6584 次
发布时间:2019-06-24

本文共 4910 字,大约阅读时间需要 16 分钟。

本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。

柯里化函数(Curried Function)

函数的柯里化通常被用于创建一组函数,并作为参数传入到更高阶的函数中。这个概念不太好理解,举个实际例子来说明问题:假设我们需要判断一个整数i是不是另一个整数n的整数倍。虽然n是一个变量,它的值不确定,但是判断逻辑总是相同的:i % n == 0。所以判断函数可以这样写:

func isMultipleOf(n n: Int, i: Int) -> Bool {return i % n == 0}isMultipleOf(n: 2, i: 3) //false,3不是2的整数倍isMultipleOf(n: 2, i: 4) //true,4是2的整数倍复制代码

如果把isMultipleOf作为参数传入到高阶函数,比如filter中,代码会这样写:

let nums = 1...10let evens = nums.filter { isMultipleOf(n: 2, i: $0) }  // evens = [2,4,6,8,10]复制代码

这种写法的可读性并不高,更好的解决方案是我们创建一个作为过渡的函数isEven

isEven = { isMultipleOf(n: 2, i: $0) }let evens = nums.filter(isEven)复制代码

这种写法稍稍改进了可读性,不过最好的方法还是定义一个柯里化函数:

func isMultipleOf(n n: Int)(_ i: Int) -> Bool {return i % n == 0}复制代码

这个函数首先接收一个参数n,然后返回一个函数。被返回的函数的类型是Int -> Bool,它会判断这个参数是否是n的整数倍。所以isEven函数可以这样定义:

let isEven = isMultipleOf(n: 2)复制代码

也可以省略这一步,直接写filter方法:

let evens = nums.filter(isMultipleOf(n: 2))  // evens = [2,4,6,8,10]复制代码

最直观的来看,相比于定义一个普通的函数,柯里化的isMultipleOf在被传入filter函数中时,省略了第二个参数i。回顾一下柯里化函数的定义就可以理解了:isMultipleOf(n: 2)其实是原柯里化函数的返回值,这个值本身也是一个函数。

这里我们用的是柯里化函数的简单声明方法,它把多个参数分别写在多个括号中。柯里化函数还有一种完整的声明方法:

func isMultipleOf(n n: Int) -> Int -> Bool {return { i ini % n == 0}}复制代码

这里我们显式的声明了isMultipleOf方法的返回值类型。这两种声明方式是完全等价的。

排序问题

我们暂且把柯里化函数放在一边,待会儿还有他大显身手的机会。现在来看一个很简单的数组排序问题。我们知道数组实现了sort方法,默认是从小到大排序,如果想要指定排序规则,需要向sort方法中传入一个排序函数作为参数。这也正是Swift的强大和灵活之处。不过考虑一个稍复杂的问题:一个数组中有多个字典,每个字典都有两个键,lastNamefirstName,现在我们对数组按照lastName的值进行排序,如果值相同就按照firstName的值进行排序:

let last = "lastName", first = "firstName"let people = [[first: "Jo",       last: "Smith"],[first: "Joe",      last: "Smith"],[first: "Joe",      last: "Smyth"],[first: "Joanne",   last: "Smith"],[first: "Robert",   last: "Jones"],]复制代码

如果我们使用OC中NSArray的sortedArrayUsingDescriptors方法,问题就比较容易解决:

let lastDescriptor = NSSortDescriptor(key: last, ascending: true, selector: "localizedCaseInsensitiveCompare")let firstDescriptor = NSSortDescriptor(key: first, ascending: true, selector: "localizedCaseInsensitiveCompare")let descriptors = [lastDescriptor, firstDescriptor]let sortedArray = (people as NSArray).sortedArrayUsingDescriptors(descriptors)复制代码

这种做法的一大优势在于descriptors是排序函数的集合,它可以在运行时动态的创建。那么怎么用纯Swift代码解决相同问题呢。首先来看一下只根据lastName排序的解决方案:

let sortedArray = people.sort {$0[last] < $1[last]}复制代码

但是如果一旦使用localizedCaseInsensitiveCompare,这种写法很快就变得非常丑。因为数组的下标脚本返回值是可选类型,无法直接使用localizedCaseInsensitiveCompare方法:

let sortedArray = people.sort { lhs, rhs inreturn rhs[first].flatMap {lhs[first]?.localizedCaseInsensitiveCompare($0)} == .OrderedAscending}复制代码

为了能在lastName相同时比较firstName,我们可以使用标准库的lexicographicalCompare方法。这个方法逐一比较两个序列中的元素,直到比较出大小为止:

let sortedArray = people.sort { p0, p1 inlet left = [p0[last], p0[first]]let right = [p1[last], p1[first]]return left.lexicographicalCompare(right) {guard let l = $0 else { return false }guard let r = $1 else { return true }return l.localizedCaseInsensitiveCompare(r) == .OrderedAscending}}复制代码

虽然这样可以实现排序功能,但依然有一些可以优化的地方。首先,在每一次排序时都新建数组是很低效的。其次,比较方法是写死的,不能动态的修改,而且对可选类型的处理导致代码不是很简洁。我们首先优化一下可选类型的比较,这里就用到了我们之前讲的柯里化函数:

extension Optional {func compare(rhs: Wrapped?, _ comparator: Wrapped -> Wrapped -> NSComparisonResult) -> Bool {switch (self, rhs) {case (nil, nil), (_?, nil): return falsecase (nil, _?): return truecase let (l?, r?): return comparator(l)(r) == .OrderedAscending}}}复制代码

我们模仿可选类型的==运算符(详见),实现了可选类型的compare方法。其中的参数comparator就是一个柯里化函数。于是,原来的sort方法可以简化成这样:

let sortedArray = people.sort { p0, p1 inlet left = [p0[last], p0[first]]let right = [p1[last], p1[first]]return left.lexicographicalCompare(right) {return $0.compare($1, String.localizedCaseInsensitiveCompare)}}复制代码

函数作为数据

现在的sort比之前简洁了很多,不过比较的逻辑依然是hard-code的,我们需要模仿OC的sortedArrayUsingDescriptors方法。其实我们用的lexicographicalCompare是有问题的,它原本用于通过一个比较方法,依次比较两组的元素,直到比较出顺序为止。而我们现在的实际情况恰好完全相反:我们需要多个比较方法,比较固定的两个元素,直到比较出顺序为止,所以我们需要实现自己的lexicographicalCompare方法:

func lexicographicalCompare
(comparators: [(T,T) -> Bool])(lhs: T, _ rhs: T) -> Bool {for isOrderedBefore in comparators {if isOrderedBefore(lhs, rhs) { return true }if isOrderedBefore(rhs, lhs) { return false }}return false}复制代码

我们用每一个比较方法去比较这两个元素,如果能比较出顺序则返回true,否则就互换元素位置。如果两次都无法比较则说明这两个元素是相等的(这需要保证每一个比较方法都是的),那么就使用下一个比较方法。如果所有比较方法都无法比较出顺序,则返回false

我们自定义的lexicographicalCompare方法也是柯里化的,第一个参数是比较方法的数组,接下来是待比较的两个参数。于是sort函数可以=被简化成一行代码:

// 首先定义比较方法的数组,先按照lastName排序,再按照firstName排序let comparators: [([String: String], [String: String]) -> Bool] = [{ $0[last].compare($1[last], String.localizedCaseInsensitiveCompare)},{ $0[first].compare($1[first], String.localizedCaseInsensitiveCompare)},]let sortedArray = people.sort(lexicographicalCompare(comparators))复制代码

这种方法几乎与使用OC的sortedArrayUsingDescriptors方法一样简单。这种方法不再把比较的逻辑写死,因此具有很高的灵活性,比如如果要升序排列lastName,但是降序排列firstName,代码可以这样写:

let sortedArray = people.sort(lexicographicalCompare([{ $0[last] < $1[last] },{ $0[first] > $1[first] },]))复制代码

通过把函数作为数据,Swift这种静态的、面向编译的语言,也像OC、Ruby这样的语言一样,拥有了很强大的动态特性。

转载地址:http://auano.baihongyu.com/

你可能感兴趣的文章
mac上安装consolas字体
查看>>
对向量、矩阵求导
查看>>
各版本linux下载地址大全
查看>>
CentOS 6.X 关闭不需要的 TTY 方法
查看>>
我的友情链接
查看>>
分区技术学习一
查看>>
Juniper 高级选项
查看>>
编程能力的四种境界
查看>>
编译安装mysql
查看>>
在windows上秒开应用程序
查看>>
【20180611】MySQL OOM
查看>>
Python面向对象编程(一)
查看>>
决心书
查看>>
如何把图片上的文字转换成word?
查看>>
7z命令行
查看>>
C语言编程实现 输入一个非负整数,返回组成它的数字之和(递归方法)
查看>>
c3p0
查看>>
我的友情链接
查看>>
引号-下划线,连接多个变量
查看>>
我的友情链接
查看>>