Swift集合类高阶函数

在Swift的集合类型中,有许多十分便捷的函数。相比于Objective-C,这些高阶函数会引起你的极度舒适。因为在Swift的许多函数中引入了闭包元素,这就直接造就了它的灵活性,简直就是极致的便捷。

下面就来对Swift集合类中的这些高阶函数进行总结。

// 全文的基础数据
let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]
1.sort函数

对原集合进行给定条件排序。
无返回值,直接修改原集合,所以这个集合应该是可变类型的。

var sortArr = numbers
numbers.sort { a, b in
    return a < b
}       
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

另外,系统还定义了一个sort()函数,即对集合进行升序排序的函数。但这个函数并不是上面函数不传入缺省值的情况,而是另外一个函数。

var sortArr2 = numbers
numbers.sort()      
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2.sorted函数

sorted函数sort函数对应。
将集合进行给定条件排序,返回一个新的集合,不修改原集合。

let sortedArr = numbers.sorted { a, b in
    return a > b
}
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// sorted()函数
let sortedArr2 = numbers.sorted()
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// 闭包简写
let sortedArr3 = sortedArr2.sorted(by: >)
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
闭包的省略写法

因为在高阶函数中大部分都使用了闭包,所以我认为有必要做一个铺垫,以更好地理解本文。清楚闭包简写的请跳过本段,直奔第3条

由于sort函数使用了闭包,所以自主定义的闭包可以简写为如下格式:

numbers.sort(by: >)     
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

以上述方法为例,一个完整的闭包应该是这样的:

numbers.sorted { (a: Int, b: Int) -> Bool in
    return a > b
}

然后,可以省略闭包中的返回值。

numbers.sorted { (a: Int, b: Int) in
    return a > b
}

然后,再可以省略形参的类型,让编译器去自主推断。

numbers.sorted { a, b in
    return a > b
}

再然后,还可以让$0$1…来代替第一个,第二个形参,以此类推。

numbers.sorted { return $0 > $1 }

再然后,省略return。一般的,到这里也就足够简化了。毕竟在实际开发中我们需要使用闭包中的参数进行一些复杂的判断。

numbers.sorted { $0 > $1 }

如果你不需要复杂的判断,那么还可以写成下面这样,代表降序排序。

numbers.sorted(by: >)
3.map函数

按照闭包中的返回结果,将集合中对应元素进行替代,也就是映射函数。

// 数组数值转换为其各自平方
let mapArr = numbers.map { $0 * $0 }
// [49, 36, 100, 81, 64, 1, 4, 9, 16, 25]
可选类型的 map, flatMap函数

另外,不仅CollectionTypemapflatMap函数,在Optional类型中,也存在这两个函数。
它们的作用是对可选类型就行解包操作,若有值则进入闭包,并返回一个 Optional类型;若为nil,则直接返回当前可选类型的nil

let num1: Int? = 3
let num2: Int? = nil

let numMap1 = num1.map {
    $0 * 2
}
numMap1              // 6
type(of: numMap1)    // Optional<Int>.Type

let numMap2 = num2.map {
    $0 == 0
}
numMap2              // nil
type(of: numMap2)    // Optional<Bool>.Type


let numFlatMap1 = num1.flatMap {
    $0 * $0
}
numFlatMap1              // 9
type(of: numFlatMap1)    // Optional<Int>.Type

let numFlatMap2 = num2.flatMap {
    $0 == 0
}
numFlatMap2              // nil
type(of: numFlatMap2)    // Optional<Bool>.Type

还有一种应用场景,就是解析可选类型的时候,mapflatMap函数会让你的代码更加优雅。

举个例子,当解析并判断可选类型的时候,你可能会经过一堆if或者guard判断,如下所示:

func loadURL(url: URL) {
    print(url.absoluteString)
}

let urlStr: String? = "https://github.com/wangyanchang21"
guard let siteStr = urlStr else {
    assert(false)
}
guard let url = URL(string: siteStr) else {
    assert(false)
}
loadURL(url: url)

如果使用mapflatMap函数的话,就会有十分优雅的感觉。

// 这行优雅的代码代替上面的代码
urlStr.flatMap(URL.init).map(loadURL)

但有一点需要注意,这里 map替换 flatMap会报错, 原因在于 flatMap闭包可以返回 nil, 而 map闭包不可以。就如下面的代码编译不会通过:

// compile error
// urlStr.map(URL.init).map(loadURL)

再举一个例子:

let date: Date? = Date()
let format = date.map(DateFormatter().string)

我在函数的闭包形式中也写过这种优雅的写法具体是怎么回事。有兴趣可以了解一下。

4.flatMap函数

也是一种映射函数,这个函数具有多重功能,所以也就造成了这个函数有一个历史问题,稍后会解释。

第一种情况,解析首层元素,若有nil则过滤,就不会降维

let optLatticeNumbers = [[1, Optional(2), 3], [3, nil, 5], nil]
// 解析首层元素, 若有nil则过滤, 就不会降维
let flatMapArr2 = optLatticeNumbers.flatMap { $0 }
// [[1, 2, 3], [3, nil, 5]]

第二种情况,解析首层元素,若没有nil,则会降维

let latticeNumbers = [[1, Optional(2), 3], [3, nil, 5]]
// 解析首层元素, 若没有nil, 则会降维
let flatMapArr = latticeNumbers.flatMap { $0 }
// [1, 2, 3, 3, nil, 5]

所以flatMap的功能就有两个了,一个功能是解析并过滤首层元素为nil的元素,一个功能是对多维集合进行降维。原因是,其实这是两个功能是两个函数,只是在调用时代码上没有区别。

flatMap和compactMap的关系

但从表面上看,flatMap函数违背了单一功能原则,将过滤nil降维两个功能于隐藏条件中进行判定。这也就是那个历史问题。

因此,为了将过滤nil和降维两个功能于区分开,swift4.1开始,就只保留了降维的flatMap函数,并弃用了过滤nilflatMap函数,又用开放的新函数compactMap来替代弃用的函数。

所以,当需要过滤nil的时候,请使用compactMap函数;当需要进行降维时,请使用flatMap函数。这也就是flatMapcompactMap之间的区别。

5.compactMap函数

Swift4.1开始开放的一种映射函数,会解析并过滤首层元素为nil的元素。

let compactMapArr = optLatticeNumbers.compactMap { $0 }
// [[1, 2, 3], [3, nil, 5]]
let compactMapArr2 = latticeNumbers.compactMap { $0 }
// [[1, 2, 3], [3, nil, 5]]

compactMap函数作为过滤nilflatMap函数的替代函数。当集合中的元素为一个一维集合,他们之间的功能是没有差别的。

let flatNumbers = [1, Optional(2), 3, nil, Optional(5), nil]

let flatMapArr = latticeNumbers.flatMap { $0 }
// [1, 2, 3, 5]
let compactMapArr = optLatticeNumbers.compactMap { $0 }
// [1, 2, 3, 5]
6.filter函数

按照条件进行元素过滤。

let filterArr = numbers.filter { num in
    return num < 3 || num > 8
}
// [10, 9, 1, 2]
7.reduce函数

以指定参数为基础,按照条件进行拼接

let reduceNumber = numbers.reduce(100) { result, num in
    return result + num
}
// 155

let reduceString = ["C", "O", "D", "E"].reduce("word: ") { result, num in
    return result + num
}
// "word: CODE"
8.prefix函数

正向取满足条件的元素,进行新集合创建。一旦出现不满足条件的元素,则跳出循环,不再执行。

let prefixArr = numbers.prefix { $0 < 10 }
// [7, 6]

prefix相关函数:
upTo: 正向取元素创建数组, 包含小于指定index的元素

let prefixUpToArr = numbers.prefix(upTo: 5)
// [7, 6, 10, 9, 8]

through: 正向取元素创建数组, 包含小于等于指定index的元素

let prefixThroughArr = numbers.prefix(through: 2)
// [7, 6, 10]

maxLength: 正向取元素创建数组, 包含指定的元素个数

let prefixMaxLengthArr = numbers.prefix(6)
// [7, 6, 10, 9, 8, 1]
9.drop函数

prefix函数对应。正向跳过满足条件的元素,进行新集合创建。一旦出现不满足条件的元素,则跳出循环,不再执行。

let dropArr = numbers.drop { $0 < 10 }
// [10, 9, 8, 1, 2, 3, 4, 5]

drop相关函数:
dropFirst: 正向跳过元素创建数组, 跳过指定元素个数, 缺省值为1

let dropFirstArr = numbers.dropFirst(3)
// [7, 6, 10, 9, 8]

dropLast: 返向跳过元素创建数组, 跳过指定元素个数, 缺省值为1

let dropLastArr = numbers.dropLast(5)
// [7, 6, 10, 9, 8]
10.first函数

正向找出第一个满足条件的元素。

let first = numbers.first { $0 < 7 }
// 6
11.last函数

first函数对应。反向找出第一个满足条件的元素。

let last = numbers.last { $0 > 5 }
// 8
12.firstIndex函数

正向找出第一个满足条件的元素下标。

let firstIndex = numbers.firstIndex { $0 < 7 }
// 1
13.lastIndex函数

反向找出第一个满足条件的元素下标。

let lastIndex = numbers.lastIndex { $0 > 5 }
// 4
14.partition函数

按照条件进行重新排序,不满足条件的元素在集合前半部分,满足条件的元素后半部分,但不是完整的升序或者降序排列。
返回值为排序完成后集合中第一个满足条件的元素下标。

var partitionNumbers = [20, 50, 30, 10, 40, 20, 60]
let pIndex = partitionNumbers.partition { $0 > 30 }
// partitionNumbers = [20, 20, 30, 10, 40, 50, 60]
// pIndex = 4
15.min函数

按条件排序后取最小元素。

let min = numbers.min { $0 % 5 < $1 % 5 }
// 10

min()函数,自然升序取最小。

let minDefault = numbers.min()
// 1
16.max函数

按条件排序后取最大元素。

let maxDictionary = ["aKey": 33, "bKey": 66, "cKey": 99]
let max = maxDictionary.max { $0.value < $1.value }
// (key "cKey", value 99)

max()函数,自然升序取最大。

let maxDefault = numbers.max()
// 10
17.removeAll函数

移除原集合中所有满足条件的元素。
无返回值,直接修改原集合,所以这个集合应该是可变类型的。

var removeArr = numbers
removeArr.removeAll { $0 > 6 }
// [6, 1, 2, 3, 4, 5]
18.集合遍历

forEach函数:

numbers.forEach { num in
    print(num)
}

for-in函数:

for num in numbers where num < 5 {
    print(num)
}

enumerated()函数配合使用:

for (index, num) in numbers.enumerated() {
    print("\(index)-\(num)")
}

关于集合遍历的性能问题,可以看这里enumerated() 和 enumerateObjectsUsingBlock

19.shuffled函数

shuffled函数,打乱集合中元素的的顺序。

let ascendingNumbers = 0...9
let shuffledArr = ascendingNumbers.shuffled()
// [3, 9, 2, 6, 4, 5, 0, 1, 7, 8]
20.contains函数

contains函数,判断集合中是否包含某元素。

let containsBool = numbers.contains(8)
let containsBool1 = numbers.contains(11)
// true
// false
21.split和joined函数

split函数,字符串的函数,按条件分割字符串,为子字符串创建集合。与Objective-C中的componentsSeparatedByString:方法类似。

let line = "123Hi!123I'm123a123coder.123"
let splitArr = line.split { $0.isNumber }
// ["Hi!", "I'm", "a", "coder."]

// 也可指定字符
let splitArr2 = line.split(separator: "1")
// ["23Hi!", "23I'm", "23a", "23coder.", "23"]

joined函数,数组元素连接指定字符拼接成一个字符串。与Objective-C中的componentsJoinedByString:方法类似。

let joined = splitArr.joined(separator: "_")
// "Hi!_I'm_a_coder."

// 也可以只传入字符
let joined2 = splitArr2.joined(separator: "#")
// "23Hi!#23I'm#23a#23coder.#23"
22.zip函数

将两个数组合并为一个元组组成的数组。

let titles = ["aaa", "bbb", "ccc"]
let numbers = [111, 222, 333]
let zipA = zip(titles, numbers)
for (title, num) in zipA {
    print("\(title)-\(num)")
}

打印结果:

aaa-111
bbb-222
ccc-333

推荐阅读更多精彩内容