89-Swift之map和flatMap函数的介绍
引言
在使用 Swift 语言开发中对集合类型的数据操作也是频频使之。Swift 是一种支持函数式编程的语言,它提给开发者许多对集合类型操作的函数如: map 、 flatMap 、Filter 、Reduce 等等。今天我们主要介绍 map 和 flatMap 两各函数。
map 的介绍
map 的作用
下面我将举出一个例子来阐述 map 的作用,例子如下:
// TODO: 创建一个数组
let number = [9 ,99 ,999 , 9999]
// TODO: 我们要将 ‘number’ 数组中每一个对象都加 ‘1’
// 古老的方法
var handleNumber:[Int] = []
for item in number {
handleNumber.append(item + 1)
}
print("古老方法结果:")
print(handleNumber)
// map 方法一
handleNumber = number.map({ (item) -> Int in
return item + 1
})
print("方法一结果:")
print(handleNumber)
// map 方法二
handleNumber = number.map({$0+1})
print("方法二结果:")
print(handleNumber)
// map 方法三
handleNumber = number.map(){$0 + 1}
print("方法三结果:")
print(handleNumber)
// MARK: 输出结果
/*
古老方法结果:
[10, 100, 1000, 10000]
方法一结果:
[10, 100, 1000, 10000]
方法二结果:
[10, 100, 1000, 10000]
方法三结果:
[10, 100, 1000, 10000]
*/
测试实例图如下:
5B9B1370-F360-4813-BD37-4E012CEB1E82.png
从上面代码来看 map 函数接受一个闭包作为参数来对数组中的每一个元素做闭包函数处理,类似对数组中每一个元素做一个映射。总结: map 函数的作用是可以对集合类型数据元素进行遍历并可执行闭包函数操作。
map 函数的定义
// TODO: Map 的定义
public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
// TODO: 定义函数中参数的解释
// T : 代表的是数据类型; 数据泛型类型的标示符。
// <T> : map 函数后面的 <T> 是函数定义的一个占位类型名。不会查找 T 的类型。
// Element : 是要被操作集合中的元素。
// [T] : 是代表的是存储 T 类型的数组。
// throws : 是表示调用这个闭包函数可能会抛出异常。
// rethrows : 是表示引用这个闭包函数可能会抛出异常。
// (Element)->T :是一个闭包函数。
// (Element)->T)-> [T] : 是一个引用闭包函数。
闭包的写法
- 闭包的基本写法
{ (Element)-> T in
// code
}
// TODO: 基本闭包的写法解释
// Element: Element 可以是输入参数和输出参数,但不可为默认值。如果 Element 是可变参数,那必须将其放到参数的最后一位,否则编辑报错。注释: 元组可以是参数也可以是返回值。
// in : in 关键字是将闭包的参数引入到函数。
- 尾随闭包
如果将闭包作为函数的最后一个参数,那可以省略参数标签。然后将闭包表达式写在调用函数的括号后面。例如下面函数:
func map(_ transform: (Element) -> T) -> [T]
其中 _ transform: (Element) -> T 就是一个闭包。而这个 _ transform: (Element) -> T 闭包它作为 map 函数的最后一个参数。闭包 _ transform: (Element) -> T 的表达式是 Element + 1 。那按尾随闭包的写法如下:
[9,99,999].map(){$0 + 1}
// TODO: 函数解释
// map : 是调用函数。
// $0 + 1 : 是闭包表达式。
由于闭包是函数的最后一个参数也无其他参数。则上面表达式也可以去掉函数的调用括号,直接在大括号中写闭包表达式。这种写法也是推荐写法,其形式如下:
[9,99,999].map {$0 + 1}
$0 的理解
Swift 自动对行内闭包提供简写实际参数名,你也可以通过 $0 , $1 , $2 等名字来引用闭包的实际参数值。使用规则如下:用$0、$1、$2来表示调用闭包中参数,$0指代第一个参数,$1指代第二个参数,$2指代第三个参数,以此类推$n+1指代第n个参数。$后的数字代表参数在闭包中的位置。
map 函数的几种使用情况
- 遍历集合类型数据中的每一个元素
// TODO: 创建一个数组
let number = [9 ,99 ,999 , 9999]
// TODO: 我们要将 ‘number’ 数组中每一个对象都加 ‘1’
let handleNumber = number.map({$0+1})
print(handleNumber)
// TODO: 输出结果
// [10, 100, 1000, 10000]
- map 可以是集合中元素类型发生改变
let number = [1 ,2 ,3 , 4]
let handleString = number.map {"NO.\($0)"}
print(handleString)
// TODO: 输出结果
// ["NO.1", "NO.2", "NO.3", "NO.4"]
- map 可以遍历二位数组中的每一个对象
let twoDimensionalArray = [[1,2,3],[5,65, 665],[9,99,999]]
let handleArray = twoDimensionalArray.map {$0.map { $0 + 1 }}
print(handleArray)
// TODO: 输出结果
// [[2, 3, 4], [6, 66, 666], [10, 100, 1000]]
flatMap 的介绍
flatMap 也是对集合数据类型可以操作函数,那么它与 map 又有什么异同和练习呢?我们看下面的一个例子:
// MARK: 创建数据类型
let flatNumber = [9 ,99 ,999 ,9999 ]
var handleFlatNumber:[Int] = []
// TODO: 第一种写法
handleFlatNumber = flatNumber.flatMap { (item) -> Int in
return item + 1
}
print("第一种写法")
print(handleFlatNumber)
// TODO: 第二种写法
handleFlatNumber = flatNumber.flatMap(){$0 + 1}
print("第二种写法")
print(handleFlatNumber)
// TODO: 第三种写法
handleFlatNumber = flatNumber.flatMap {$0 + 1}
print("第三种写法")
print(handleFlatNumber)
// TODO: 输出结果
// 第一种写法
// [10, 100, 1000, 10000]
// 第二种写法
// [10, 100, 1000, 10000]
// 第三种写法
// [10, 100, 1000, 10000]
测试视图如下:
4EB50D1A-498D-46C3-B1F3-100068664247.png
经过上面对 flatMap 的测试可看到 flatMap 和 map 的作用相同。如果它们作用真的相同,那么为什么还会在出一个 flatMap 函数呢? 那么它们两个的真正区别是什么? 我们继续看下面这个例子:
// MARK: 多维数组的处理
let numbersArray = [[5 ,65 ,665 ],[9, 99 ,999 ]]
// TODO: map 的处理
let mapHandleArray = numbersArray.map {$0.map{$0 + 1}}
print("map 处理结果")
print(mapHandleArray)
// TODO: flatMap 的处理
let flatMapHandleArray = numbersArray.flatMap{$0.flatMap{$0 + 1}}
print("flatMap 处理结果")
print(flatMapHandleArray)
// TODO: 输出结果
// map 处理结果
// [[6, 66, 666], [10, 100, 1000]]
// flatMap 处理结果
// [6, 66, 666, 10, 100, 1000]
what ? why ? 不是说 map 和 flatMap 的作用相同吗? 这怎么出现不同的结果?其实,这就能看出 flatMap 和 map 对二位数组的处理不同的差异了。从上面的测试结果可以看到 flatMap 函数对二位数组进行了降维操作。那为什么 flatMap 函数可以使数组降维操作?我们来看看 flatMap 的定义:
// 定义一
public func flatMap(_ transform: (Element) throws -> String?) rethrows -> [String]
// 定义二
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
// 定义三
public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element]
// 去除一些修饰词,变换为下面的样式:
// 定义一
public func flatMap((Element) -> String?) -> [String]
// 定义二
public func flatMap<ElementOfResult>((Element) -> ElementOfResult?) -> [ElementOfResult]
// 定义三
public func flatMap<SegmentOfResult : Sequence>( (Element) -> SegmentOfResult) -> [SegmentOfResult.Iterator.Element]
从上面的定义可以看出 定义一 和定义二是相同的。看下面的例子:
let stringArray = ["100" ,"" ,"200" ,"300" ,""]
let stringHandleArray = stringArray.flatMap { (item) -> Int? in
Int(item)
}
print(stringHandleArray)
// TODO: 输出结果
// [100, 200, 300]
从上面的输出结果我们可以看到 可选类型的数据被解包。其中 Int(item) 这个是可选类型,它有可能不成功,为nil 。上面的例子是调用 flatMap 的第二种定义。我们继续再看下面的一个例子:
let exampleArray = [ 1 , "2" , 3 , 9] as [Any]
let handleExpArray = exampleArray.flatMap {(item) in
Array(repeatElement(item, count: 2))
}
print(handleExpArray)
// TODO: 输出结果
// [1, 1, "2", "2", 3, 3, 9, 9]
从上面返回结果可以看出 flatMap 函数使 ** exampleArray** 中的数据重复2次生成一个子数组(SegmentOfResult),而flatMap 函数要输出 [SegmentOfResult.Iterator.Element] 类型的一维数组。所以才有上面的输出结果。
flatMap 的用途
- 可以遍历集合类型对象的每一个元素
let flatNumberArray = [9 , 99 , 999 ,9999]
let handleFlatMapNumberArray = flatNumberArray.flatMap({$0 + 1 })
print(handleFlatMapNumberArray)
// TODO: 输出结果
// [10, 100, 1000, 10000]
- 使二维数组降为一维数组
// MARK: 多维数组的处理
let numbersArray = [[5 ,65 ,665 ],[9, 99 ,999 ]]
let mapHandleArray = numbersArray.map {$0.map{$0 + 1}}
print(mapHandleArray)
// TODO: 输出结果
// [6, 66, 666, 10, 100, 1000]
- 可以移除集合数据中元素不存在元素
let stringArray = ["100" ,"" ,"200" ,"300" ,""]
let stringHandleArray = stringArray.flatMap { (item) -> Int? in
Int(item)
}
print(stringHandleArray)
// TODO: 输出结果
// [100, 200, 300]