欢迎阅读 Swift 中高阶函数系列的第三部分!在第一部分中,我大致介绍了什么是高阶函数,以及如何创建自己的函数。在第二篇文章中,我专注于三个相关的高阶函数;map, compactMap, flatMap. 这篇文章继续介绍 Swift 中最重要和最常用的高阶函数;在这里,您将了解forEach,filter和sorted功能。
forEach 高阶函数
forEach可以使用高阶函数代替for-in循环来遍历集合的元素。与大多数其他高阶函数不同,forEach 不返回新集合;相反,它只是通过原始集合并允许在重复操作和任务中使用其项目。
让我们看一个非常简单的例子。考虑以下带有正数和负数的数组:
var numbers = [5, -2, 11, 18, -28, 17, 21, -19]
假设我们要遍历数组并打印每个数字的绝对值。我们可以在这样的for-in循环中做到这一点:
for number in numbers {
print("Absolute value of \(number) is \(abs(number))")
}
这种传统方式可以被forEach高阶函数取代。我们所要做的就是通过numbers数组访问它:
numbers.forEach { number in
print("Absolute value of \(number) is \(abs(number))")
}
上述闭包中的参数表示执行循环时源集合中的每一项。大多数时候我们可以省略它并使用速记参数代替:
numbers.forEach { print("Absolute value of \($0) is \(abs($0))") }
以上所有片段将打印完全相同的结果。
当然,也可以在forEach. 例如,以下代码打印原始数组中的所有正数:
numbers.forEach {
if $0 > 0 {
print($0)
}
}
有些事情我们不能使用forEach. 也就是说,在迭代原始项目的同时对它们进行操作。如果您想使用高阶函数来更新原始集合,那么您应该考虑使用map,而不是forEach; 后者旨在为我们提供循环内的每个项目,保持源集合不可变。
要理解这一点,请参阅下一个片段:
for i in 0..<numbers.count {
numbers[i] *= 2
}
它所做的只是将numbers数组中每个数字的值加倍。即使上述内容可以完美地完成工作,以下内容也不起作用:
numbers.forEach { $0 *= 2 }
尝试这样做会使 Xcode 显示以下错误:
Left side of mutating operator isn't mutable: '$0' is immutable
请记住这一点,不要尝试使用forEach.
字典和集合也可以与 一起使用forEach,因为它们也是集合类型。举个例子,让我们考虑以下保留一些欧洲国家及其首都的字典:
let capitals = ["France": "Paris", "Italy": "Rome", "Greece": "Athens", "Spain": "Madrid"]
我们可以forEach像这样分别打印每个键值对:
capitals.forEach { print($0) }
每个循环打印一个包含键和实际值的元组:
(key: "Italy", value: "Rome")
(key: "France", value: "Paris")
(key: "Greece", value: "Athens")
(key: "Spain", value: "Madrid")
也可以只访问键或值:
capitals.forEach { print($0.value) }
所以,forEach简而言之,这是高阶函数。尽可能使用它代替传统的循环,因为它有助于编写更短、更清晰的代码。
滤波器高阶函数
filter高阶函数的名称几乎解释了它的用途。当应用于一个集合时,它会在根据条件过滤原始元素后返回另一个集合。
让我们再次考虑numbers上一部分中的示例数组,假设我们只想使用该filter方法获取负数。在最扩展的形式中,这可以按如下方式完成:
let negatives = numbers.filter { number -> Bool in
return number < 0
}
闭包的返回类型是一个 Bool 值;结果数组中的项目是那些满足主体内部条件的项目;在这种情况下,数字小于零。number闭包的参数代表原始集合中的每一项。
与其他高阶函数一样,return如果它是闭包中的单行语句,我们可以省略显式设置返回类型以及关键字。这意味着我们可以将上面的代码写得更简单:
let negatives = numbers.filter { number in
number < 0
}
如果我们使用速记参数并将所有内容仅写在一行中,它会变得更加简单:
let negatives = numbers.filter { $0 < 0 }
以上都是相同的,但是最后一个片段是过滤原始数字的最清晰和最短的方法。
同样的逻辑,我们也可以得到所有大于 10 的数字:
let positivesOver10 = numbers.filter { $0 > 10 }
filter与任何其他高级函数一样,不限于其项目为基本数据类型的集合。当项目是自定义类型时也可以使用它。考虑以下描述俱乐部成员的结构:
struct Member {
enum Gender { case male, female }
var name: String
var age: Int
var gender: Gender
}
假设我们有以下成员:
let members = [Member(name: "John", age: 35, gender: .male),
Member(name: "Bob", age: 32, gender: .male),
Member(name: "Helen", age: 33, gender: .female),
Member(name: "Kate", age: 28, gender: .female),
Member(name: "Sean", age: 27, gender: .male),
Member(name: "Peter", age: 39, gender: .male),
Member(name: "Susan", age: 41, gender: .female),
Member(name: "Jim", age: 43, gender: .male)]
让我们看一些场景和示例。首先,假设我们要过滤上述内容并仅获取男性成员。我们使用filter如下所示的扩展形式来做到这一点:
let males = members.filter { member -> Bool in
return member.gender == .male
}
或者,我们可以用它的较短版本做得更好:
let males = members.filter { $0.gender == .male }
只需一行代码即可返回一个新数组,该数组仅包含满足闭包内条件的那些元素。看到我们可以通过速记参数访问自定义类型的属性,就像我们有一个普通对象一样;$0是本例中的对象。
继续另一个示例,以下返回所有 30 多岁的女性成员:
let femalesIn30s = members.filter { $0.gender == .female && $0.age >= 30 && $0.age < 40 }
接下来是30岁以下的所有男性成员:
let malesUnder30 = members.filter { $0.gender == .male && $0.age < 30 }
通常,我们可以在上下文中有效的闭包中编写任何可能的条件。例如,以下过滤所有成员并仅返回名称以“S”开头的成员:
let membersWithS = members.filter { $0.name.starts(with: "S") }
为了快速打印并查看每个结果数组的内容,我们可以使用forEach前面介绍的高阶函数。这就是上面最后一个数组接下来发生的事情:
membersWithS.forEach { print($0.name) }
Sean Susan
filter高阶函数没有什么特别困难的。您唯一应该特别注意的是您在其中应用的条件,以便它返回正确和预期的结果。
排序后的高阶函数
该sorted高阶函数可以以按升序或降序排列的集合中。它返回的是一个新的集合,其中的项目已排序。
要查看示例,请再次考虑numbers本文第一部分中介绍的数组。假设我们要按升序对它进行排序并从最低值到最高值,sorted可以像这样使用:
let sorted = numbers.sorted { (num1, num2) -> Bool in
return num1 < num2
}
闭包中的两个参数是排序过程中任何给定时刻正在比较的两个项目。比较被确定为闭包主体中的条件。
与所有其他高阶函数类似,参数、返回类型以及return关键字可以省略并使用速记参数代替:
let sorted = numbers.sorted { $0 < $1 }
这比前一个片段短得多,并且看到第二个元素是用$1速记参数表示的。
sorted也为我们提供了一个更快的选项来执行比较;仅指示顺序,根本不使用任何参数:
let sorted = numbers.sorted(by: <)
此外sorted,还有sort高阶函数。尽管它的目的也是将集合的项目按升序或降序排列,但两者之间存在很大差异;sorted返回一个包含已排序项的新数组,同时sort对原始集合中的项进行排序。
以下代码按numbers降序对数组中的所有数字进行排序,但不返回新数组;更改发生在同一个数组上:
numbers.sort(by: >)
以上结果为:
[21, 18, 17, 11, 5, -2, -19, -28]
作为建议,sort如果您希望在排序后保持原始集合完好无损,请不要使用。另一方面,sorted如果您有一个非常大的集合要排序,请不要使用;这将需要大量额外的内存,因此最好使用它sort来进行适当的排序。
您应该注意的最后一条有用信息是sorted应用于字典时返回的内容。让我们以这篇文章第一部分中介绍的几个国家和首都的字典为例:
let capitals = ["France": "Paris", "Italy": "Rome", "Greece": "Athens", "Spain": "Madrid"]
要根据从 A 到 Z 的城市名称对其进行排序,我们可以执行以下操作:
let sortedCapitals = capitals.sorted { $0.value < $1.value }
有趣的部分在这里;什么sorted返回并不是一个新的字典,但包含所有键-值对元组的数组排序。上面的代码结果如下:
[(key: "Greece", value: "Athens"), (key: "Spain", value: "Madrid"), (key: "France", value: "Paris"), (key: "Italy", value: "Rome")]
概括
今天我介绍了 Swift 中三个非常常见的高阶函数,它们是编程中的好工具;forEach,filter和sorted。当它们代替使用循环实现的传统解决方案时,代码可以变得更短、更清晰、可读和可维护。
- 底层相关的面试文章(github.com/iOS-Mayday/…
- 简历指导和常见算法(github.com/iOS-Mayday/…