如何用 Swift 的高阶函数来简化一些复杂代码

1,445 阅读3分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

在我们的日常开发中,经常会碰到一些复杂的数据操作的需求。这些复杂的需求可能会花费我们半天甚至几天的时间趋势线。

但是如果我们能利用 Swift 本身提供的高阶函数来去实现的话,不仅能降低项目的复杂度,还能使代码更加易读高效。

如果你对 Swift 的高阶函数的使用不太了解的话,推荐先去看一下我之前写的一篇文章:高阶函数的使用

在本篇文章中,将会介绍 5 个通过高阶函数来进行简化的复杂的数据操作需求。Let's go!

Prepare

假设,我们有一个 Person 的结构体如下:

struct Person {
    enum Gender {
        case male
        case female
    }
    
    var name = ""
    var age = 0
    var gender = Gender.female
}

下面是我们需要操作的数据源,后面的需求都在此数据源上操作:

let dataSource = [Person(name: "鸡大宝", age: 38, gender: .male),
                  Person(name: "江主任", age: 50, gender: .female),
                  Person(name: "可乐", age: 10, gender: .female),
                  Person(name: "伍六七", age: 16, gender: .male),
                  Person(name: "梅花十三", age: 20, gender: .female)]

获取数据源中男女各多少人

如果不适用高阶函数的话,我们可能会按照下面的思路来去获取:

  • 创建两个变量 maleCount/femaleCount 来代表男女的人数。
  • 遍历数组元素,在 for-loop 中添加 if-else 语句,如果男则 maleCount +1,反之则 femaleCount +1.

上面的代码虽然写起来也不复杂,但还是有 2 个小瑕疵:

  • 代码不能一目了然地去知道代码的实际用途。
  • maleCount/femaleCount 必须声明为 var,但其实这两个变量后续我们是不需要改动的,按道理应该声明为 let。但局限于后面的变量修改,只能声明为 var,这又会给程序带来不可控性。

这个需求可以使用高阶函数的 reduce(into:) 来实现:

let genderCount = dataSource.reduce(into: [Person.Gender: Int]()) { (result, person) in
    result[person.gender, default: 0] += 1
}

let maleCount = genderCount[Person.Gender.male] // 2
let femaleCount = genderCount[Person.Gender.female] // 3

代码解释:通过 reduce(into:) 函数生成一个 [Gender: Int] 类型的字典,在 reduce 内部的进行 male 和 female 的计数。

这样也很好的解决了上面的两个问题。代码可读性很高;maleCount 与 femaleCount 也被声明为 let 。

随机获取两个元素

对于这个需求,我们可以拆分成下面的两个步骤来实现:

  • 通过数据源生成一个随机数组 - randomDataSource。
  • 获取 randomDataSource 的前两个元素。 转换为代码如下:
let randomDataSource = dataSource.shuffled()
let randomElement = randomDataSource.prefix(2)

查找某个特定的元素

在项目开发中,我们会经常遇到在一个数组中查找某个特定元素的需求。如果,目标数组的元素较少, for - loop 或者 filter 是最方便的。但如果目标数组很大,而我们有经常需要进行查找操作的话,上述的实现就不太满足了。

这时候,我们可以使用空间换时间的思路来解决查找时间过长的问题。主要步骤分为下面的两步:

  • 现将数据源转为一个 tuple 的数组 - personTuple。0 的位置为 key,1 的位置为 person 对象。
  • 再通过 Dictionary 的初始化函数:init<S>(uniqueKeysWithValues keysAndValues: S) 将 personTuple 转为字典。 这样,通过 key 再去查找就是 O(1) 的时间了。需要注意的是将数组转为字典的过程仍然是 O(n)。
let personTuple = dataSource.map { ($0.name, $0)}
let personDict = Dictionary(uniqueKeysWithValues: personTuple)
let target = personDict["伍六七"]