Swift高阶函数使用技巧与部分场景 (`reduce`等)

2,504 阅读6分钟

前言

作为开发人员,我们经常需要处理需要数小时甚至数天复杂业务算法(各种for循环)。由于swift的高阶函数功能,如mapreducefilter,等,其中的一些复杂的算法,现在可以很容易地通过短短的几行代码解决。

在本文中,我想向您展示 4 种以前难以实现的算法,现在非常容易实现,这要归功于 Swift 中的高阶函数

在本文中,我将使用以下students数组作为模型对象,以便您更好地了解这些高阶函数的工作原理。

let students = [
    Student(id: "991", name: "Jessica", gender: .female, age: 20),
    Student(id: "992", name: "James", gender: .male, age: 25),
    Student(id: "993", name: "Mary", gender: .female, age: 19),
    Student(id: "994", name: "Edwin", gender: .male, age: 27),
    Student(id: "995", name: "Stacy", gender: .female, age: 18),
    Student(id: "996", 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
}

计算数组元素的出现次数

计算数组中元素的总数很容易,但是如果我们想根据某些条件计算元素的出现次数怎么办?例如,假设我们想统计students数组中有多少男学生女学生(Gender属性)。

我们可以利用数组的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数组缩减为一个类型为 的字典``[Gender: Int]。在闭包中,我们通过计算男女学生的出现次数来累积填充最终字典。

复习Swift reduce 函数

我们先来复习一下 reduce 函数 , reduce函数是,一个可以设置一个初始值的函数,并且可以返回两个结果变量,我们一般称为result,currentCase

result: 一般是指上次得到的结果之和

currentCase: 一般指本次遍历的对象

举例:

let prices = [20,30,40]
let sum = prices.reduce(0) { $0 + $1 }
print(sum)

//90

注意: reduce(0) 这里reduce(0)是什么初始值,我们函数就会返回什么结果 好,返回上面的函数,我们要统计students数组中有多少男学生女学生

[Gender: Int]() : 是一个以 GenderKey, int的的字典
所以我们得以 key Gender 来统计累加count,男生女生总数,来分别计算出男女生的总数.
我们打印的log po genderCount

 2 elements
   0 : 2 elements
    - key : SwiftUnitTest.Gender.female
    - value : 4
   1 : 2 elements
    - key : SwiftUnitTest.Gender.male
    - value : 2

获取数组内某个model属性的总和

接下来,我想向您展示如何仅用 1 行代码获得数组的总和。假设我们想要获得学生年龄的总和。为此,我们可以reduce(_:_:)像这样使用数组的函数:

let sum = students.reduce(0) { result, student in
    return result + student.age
}

可能你已经猜到了,我们可以通过使用简写参数名称来进一步简化上述示例代码:

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"

reduce 是很好用的,在计算数值,和拼接字符串的时候.

从数组中获取一些随机元素

从数组中获取一些随机元素曾经是一种难以实现的算法,因为我们需要处理各种边缘情况。

现在借助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)
let selected2 = randomized.prefix(13)
print(selected)
print(selected2)

这种方法的一个好处是,即使我们尝试获取的元素数量超过数组的元素,它也不会触发索引超出范围异常。

结果

[SwiftUnitTest.Student(id: "996", name: "Emma", gender: SwiftUnitTest.Gender.female, age: 22), 
SwiftUnitTest.Student(id: "991", name: "Jessica", gender: SwiftUnitTest.Gender.female, age: 20), 
SwiftUnitTest.Student(id: "992", name: "James", gender: SwiftUnitTest.Gender.male, age: 25)]


[SwiftUnitTest.Student(id: "996", name: "Emma", gender: SwiftUnitTest.Gender.female, age: 22), 
SwiftUnitTest.Student(id: "991", name: "Jessica", gender: SwiftUnitTest.Gender.female, age: 20),
SwiftUnitTest.Student(id: "992", name: "James", gender: SwiftUnitTest.Gender.male, age: 25), 
SwiftUnitTest.Student(id: "995", name: "Stacy", gender: SwiftUnitTest.Gender.female, age: 18), 
SwiftUnitTest.Student(id: "993", name: "Mary", gender: SwiftUnitTest.Gender.female, age: 19), 
SwiftUnitTest.Student(id: "994", name: "Edwin", gender: SwiftUnitTest.Gender.male, age: 27)]

我们可以看到随机可以随到最大的数6就结束了,并没有一定要完成到 13

按条件对数组元素进行分组

假设我们想按student name的第一个字母对学生进行分组。传统上,我们必须手动遍历数组中的每个元素, 并相应地对它们进行分组。

struct Student {
    let id: String
    let name: String
    let gender: Gender
    let age: Int
}

现在,在Dictionary(grouping:by:)初始化程序的帮助下,我们可以在不使用for-in循环的情况下实现这一点。就是这样:

let groupByFirstLetter = Dictionary(grouping: students) { student in
    return student.name.first!
}

/*
 PrintLog:
 [
    ["J": [SwiftUnitTest.Student(id: "991", name: "Jessica", gender: SwiftUnitTest.Gender.female, age: 20), SwiftUnitTest.Student(id: "992", name: "James", gender: SwiftUnitTest.Gender.male, age: 25)],
    "M": [SwiftUnitTest.Student(id: "993", name: "Mary", gender: SwiftUnitTest.Gender.female, age: 19)], 
    "E": [SwiftUnitTest.Student(id: "994", name: "Edwin", gender: SwiftUnitTest.Gender.male, age: 27), SwiftUnitTest.Student(id: "996", name: "Emma", gender: SwiftUnitTest.Gender.female, age: 22)], 
    "S": [SwiftUnitTest.Student(id: "995", name: "Stacy", gender: SwiftUnitTest.Gender.female, age: 18)]]

 ]
 */

从上面的示例代码中可以看出,初始化程序将生成一个类型为 的字典[KeyType: Student]。 KEY = 每个Studentname 的第一个字母 ,

如果我们想按标准对学生进行分组, 并在多部分表格视图中显示他们,这将特别有用。 我们甚至可以通过Swift 中使用速记参数名称或者健路语法来进一步简化

// 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!)

总结

我们本次说了4种使用高阶函数的场景 计算数组元素的出现次数,获取数组内某个model属性的总和,从数组中获取一些随机元素,按条件对数组元素进行分组

上面显示的所有示例绝对可以通过使用传统for-in循环来解决。但是,这需要我们手动处理各种边缘情况,因此非常容易出错。

通过使用高阶函数,我们可以大大降低代码的复杂性,从而减少出错的可能性。最重要的是,它使我们的代码更易于维护。 因为这个部分比较多,我后面应该也还会分享一些, 其他的高阶函数使用方法,或者如果你有什么好的使用场景,也可以留言,我们互相学习,哈哈哈~ 周末愉快!