map、fitter、reduce、flatMap

在 Swift 高阶使用中,map、fitter、reduce 是很常见的操作,能使代码干净整洁。在理解其概念之前,先要明白泛型的概念。

泛型

定义 computeIntArray 函数,接收一个数组,并返回其元素的2倍组成的另一个新的数组。但此函数只支持 Int 类型。如果换成其他类型则不适用,所以这里考虑用泛型来解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func computeIntArray(xs: [Int], transform: (Int) -> Int) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(transform(x))
}
return result
}

func doubleArray(xs: [Int]) -> [Int] {
return computeIntArray(xs: xs) { x in x * 2 }
}

doubleArray(xs: [1, 2, 3])


// 换成 bool,则会出现编译错误
func isEvenArray(xs: [Int]) -> [Bool] {
return computeIntArray(xs: xs) { x in x % 2 == 0 }
}

// 使用泛型支持各种类型,进行抽象,对于任何 Element 的数组和 transform: Element -> T 函数,它都会生成一个 T 类型数组
func map<Element, T>(xs: [Element], transform: (Element) -> T) -> [T] {
var result: [T] = []
for x in xs {
result.append(transform(x))
}
return result
}

func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
return map(xs: xs, transform: transform)
}

genericComputeArray(xs: [2, 3, 4]) { x in x * 2 }

map

map函数能够被数组调用,还接收一个闭包参数,将数组中的每一个元素依次作用于该闭包,并返回一个新的数组。

上面的例子中,将泛型函数写成全局函数固然能够完成任务,但是为了避免写入顶层函数实现。将此函数定义为 Array 的扩展更合适。

1
2
3
4
5
6
7
8
9
10
11
extension Array {
func map<T>(tranform: (Element) -> T) -> [T] {
var result: [T] = []
for x in self {
result.append(tranform(x))
}
return result
}
}

[2, 3, 4].map { x in x * x }

fitter

map 函数类似,fitter 函数也可以接收一个闭包作为参数,同样可以避免函数为顶层实现:

1
2
3
4
5
6
7
8
9
10
11
12
extension Array {
func filter(includeElement: (Element) -> Bool) -> [Element] {
var result: [Element] = []
for x in self where includeElement(x) {
result.append(x)
}
return result
}
}

// ["HelloWorld.swift"]
["HelloWorld.swift", "HelloWorld.md"].filter {x in x.hasSuffix("swift")}

reduce

reduce 函数将变量初始化为某个值,对数组中的每一项进行遍历,最后一某种方式更新结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extension Array {
func reduce<T>(initial: T, combine: (T, Element) -> T) -> T {
var result = initial
for x in self {
result = combine(result, x)
}
return result
}
}

// 输出:1
[1, 2, 3].reduce(initial: 0) { result, x in result + x }

// 也可以用运算符作为最后一个参数。这里初始化值为:1
// 输出:7
[1, 2, 3].reduce(initial: 1, combine: +)

flatMap

flatMapmap 类似,区别是若元素值不为 nil 的情况下,flatMap 能将可选类型转换为非可选类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let array = ["123", "", "4567"]
let anotherArray = array.map { (string: String) -> Int? in
let length = string.characters.count
guard length > 0 else {
return nil
}

return string.characters.count
}

// [Optional(3), nil, Optional(4)]
print(anotherArray)


let anotherArray2 = array.flatMap{ (string: String) -> Int? in
let length = string.characters.count
guard length > 0 else {
return nil
}

return string.characters.count
}

// [3, 4]
print(anotherArray2)

Any 与 泛型

尽量避免使用 Any 类型,因为使用 Any 类型会避开 swift 的类型系统。比如将 noOp 函数返回值设为 0 会导致类型错误。此外,任何调用 noOpAny 的函数都不知道返回值会被转换为何种类型。而结果就是可能导致各种各样的运行时错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func noOp<T>(x: T) -> T {
return x
}

func noOpAny(x: Any) -> Any {
return x
}

let result = noOp(x: 3)
let ret = noOpAny(x: "3")

// oOp中,我们可以清楚返回值和输入值完全一样,而 noOpAny 则不一样,返回是任意类型。甚至和原来输入值不同的类型。
func noOpWrong(x: Any) -> Any {
return 0
}

let s = noOpWrong(x: "string")