使用Swift的高阶函数简化5种复杂算法
hudson 译 原文
作为一名开发人员,我们经常需要处理需要数小时甚至数天才能开发的复杂算法。多亏了Swift的高阶函数,如map、reduce、filter等,其中一些复杂的算法现在只需几行代码即可轻松解决。
在本文中,我想向您展示5种过去难以实现的算法现在非常容易实现,这要归功于Swift中的高阶函数。
在本文中,我将使用以下Students学生数组作为模型对象,以便您可以更好地了解这些高阶函数的工作原理。
// The model object of upcoming examples
let students = [
Student(id: ”001“, name: ”Jessica“, gender: .female, age: 20),
Student(id: ”002“, name: ”James“, gender: .male, age: 25),
Student(id: ”003“, name: ”Mary“, gender: .female, age: 19),
Student(id: ”004“, name: ”Edwin“, gender: .male, age: 27),
Student(id: ”005“, name: ”Stacy“, gender: .female, age: 18),
Student(id: ”006“, name: ”Emma“, gender: .female, age: 22),
]
enum Gender {
case male
case female
}
struct Student {
let id: String
let name: String
let gender: Gender
let age: Int
}
不多说,让我们直接进入主题!
对数组元素进行分组
假设我们想用他们名字的第一个字母对学生进行分组。传统上,我们必须手动循环遍历数组中的每个元素,并相应地对其进行分组。
现在,在Dictinary(grouping:by:)初始化器的帮助下,我们可以在不使用for-in循环的情况下实现这一点。方法如下:
let groupByFirstLetter = Dictionary(grouping: students) { student in
return student.name.first!
}
/*
Output:
[
”E“: [Edwin, Emma],
”M“: [Mary],
”J“: [Jessica, James],
”S“: [Stacy]
]
*/
正如您从上述示例代码中看到的,初始化器将生成一个[KeyType: Student]类型的字典。如果我们想按某些规则对学生进行分组,并在多节表格视图中显示他们,这特别有用。
我们甚至可以通过在Swift中使用速记参数名称或Key Path语法来进一步简化上述代码:
// Using shorthand argument names
let groupByFirstLetter = Dictionary(grouping: students, by: { $0.name.first! })
// Using key path syntax
let groupByFirstLetter = Dictionary(grouping: students, by: \.name.first!)
注意:
想知道如何按自定义对象对数组元素进行分组吗?查看我之前的文章“[在Swift中用字典对数组元素进行分组] (swiftsenpai.com/swift/group…
计算数组元素的出现次数
计算数组中的元素总数很容易,但如果我们想根据某些标准计算元素的出现情况呢?例如,假设我们想知道Students学生数组中有多少男女学生。
一种方法是使用我们刚刚看到的Dictionary (grouping:by:)初始化器:
let groupByGender = Dictionary(grouping: students, by: \.gender)
let femaleCount = groupByGender[.female]!.count // Output: 4
let maleCount = groupByGender[.male]!.count // Output: 2
上述方法可能会给我们带来预期的结果,但是,它确实有一些内存开销。如您所见,初始化器将生成我们并不真正需要的男女学生阵列,而我们需要的只是男女学生的出现次数。
为了克服内存开销,我们可以利用数组的reduce(into:)函数。让我们看看以下示例代码:
let genderCount = students.reduce(into: [Gender: Int]()) { result, student in
guard var count = result[student.gender] else {
// Set initial value to `result`
result[student.gender] = 1
return
}
// Increase counter by 1
count += 1
result[student.gender] = count
}
let femaleCount = genderCount[.female]! // Output: 4
let maleCount = genderCount[.male]! // Output: 2
上述示例代码的作用是将Students学生数组归约到[Geder:Int]类型的字典中。在闭包中,我们通过计算男女学生的出现次数来累积,并将最终结果输出到词典。
现在您已经了解了如何使用reduce(into:)函数计算出现次数,让我们通过给 result字典一个默认值0来进一步简化示例代码,就像这样:
let genderCount = students.reduce(into: [Gender: Int]()) { result, student in
result[student.gender, default: 0] += 1
}
let femaleCount = genderCount[.female]! // Output: 4
let maleCount = genderCount[.male]! // Output: 2
有了这一点,我们避免了内存开销,同时保持代码简单和干净。
获取数组的总和
接下来,我想向您展示如何获得只有1行代码的数组的总和。假设我们想得到学生年龄的总和。为此,我们可以像这样使用数组的reduce(_:_:)函数:
let sum = students.reduce(0) { result, student in
return result + student.age
}
// Output: 131
正如您可能已经猜到的,我们可以通过使用速记参数名称来进一步改进上述示例代码:
Let sum = students.reduce(0, { $0 + $1.age })
对于数组元素类型是支持加法运算符(+)的类型,我们可以通过省略速记参数名称来进一步简化它:
let sum1 = [2, 3, 4].reduce(0, +) // Output: 9
let sum2 = [5.5, 10.7, 9.43].reduce(0, +) // Output: 44.435
let sum3 = [”a“,”b“,”c“].reduce(”“, +) // Output: ”abc“
很酷,不是吗?
通过ID访问数组元素
在处理数组时,我们需要执行的最常见的操作之一是使用对象ID找到特定的数组元素。最直接的方法是使用for-in循环或数组的filter(_:)函数来循环每个数组元素。
这两种方法在大多数情况下都足够好。然而,它们都有O(n)时间复杂度,这意味着当数组变大时,它们将需要更多时间来找到特定的元素。对于努力提高速度和响应速度的应用程序,这些方法肯定会造成性能瓶颈。
为了使这个操作具有O(1)复杂性,我们可以将Students学生数组转换为字典,其中键是学生ID,值是Student学生对象。
为此,我们将首先利用数组的map(_:)函数将数组转换为带有学生ID和Student学生对象的元组数组。之后,我们将使用Dictionary(uniqueKeysWithValues:)初始化器将元组数组转换为字典。
// Transform [Student] --> [(String, Student)]
let studentsTuple = students.map { ($0.id, $0) }
// Transform [(String, Student)] --> [String: Student]
let studentsDictionary = Dictionary(uniqueKeysWithValues: studentsTuple)
// Read from dictionary (this is O(1) operation)
let emma = studentsDictionary[”006“]!
请注意,将数组转换为字典的过程仍然是一个O(n)操作。然而,我们只需要做一次。字典准备就绪后,在字典上执行的任何读取操作都是O(1)操作。
从数组中获取一些随机元素
最后一个例子不涉及任何高阶函数,但我认为它仍然值得分享。从数组中获取一些随机元素曾经是一个难以实现的算法,因为我们需要处理各种边缘情况。
现在,在Swift数组中的shuffled()和prefix(_:)函数的帮助下,这个操作变得非常容易实现。
以下是从Students数组中随机挑选3名学生的方法:
// Randomize all elements within the array
let randomized = students.shuffled()
// Get the first 3 elements in the array
let selected = randomized.prefix(3)
这种方法的一个好处是,即使我们试图获取的元素数量超过数组的总元素个数,它也不会触发索引超出范围异常。
小结
上面显示的所有示例肯定可以通过使用传统的for-in循环来解决。然而,这将需要我们手动处理各种边缘情况,因此它非常容易出错。
通过使用高阶函数,我们可以大大降低代码的复杂性,从而使其不那么容易出错。最重要的是,它使我们的代码更容易维护。
感谢您的阅读。🧑🏻💻