Swift中的map 和 flatMap 原理及用法
之前對(duì)這兩個(gè)概念有點(diǎn)糊,今天正好遇到一個(gè)相關(guān)需求,才深入了解了下。
需求如下:
大概就是對(duì)一個(gè)數(shù)組的model,重構(gòu)成一個(gè)新model,返回得到一個(gè)新數(shù)組
用map很容易實(shí)現(xiàn),不過后來我需要對(duì)其中進(jìn)行一些過濾處理,這樣,用map就不行了,幸好,flatMap可以滿足我的需要。
其中原因歸納如下:
map是對(duì)原對(duì)象所有元素進(jìn)行一對(duì)一轉(zhuǎn)換處理,中間不會(huì)跳過或遺漏,包括nil元素
flatMap更靈活,可變換維度,也能夠自動(dòng)解包,所以當(dāng)我們對(duì)不符合元素,返回nil,最終的結(jié)果是過濾掉nil的,從而能夠?qū)崿F(xiàn)過濾。
以下是我網(wǎng)上看到的一篇描述比較詳細(xì)的文章,我就直接轉(zhuǎn)載過來了,想了解具體的童鞋可往下翻看。
原文地址:http://blog.csdn.net/fish_yan_/article/details/51785441
版權(quán)歸原作者所有
map 和 flatMap 是Swift中兩個(gè)常用的函數(shù),它們體現(xiàn)了 Swift 中很多的特性。對(duì)于簡(jiǎn)單的使用來說,它們的接口并不復(fù)雜,但它們內(nèi)部的機(jī)制還是非常值得研究的,能夠幫助我們夠好的理解 Swift 語(yǔ)言。
map 簡(jiǎn)介
首先,咱們說說 map 函數(shù)如何使用。
let numbers = [1,2,3,4]
let result = numbers.map { $0 + 2 }
print(result) // [3,4,5,6]
map 方法接受一個(gè)閉包作為參數(shù), 然后它會(huì)遍歷整個(gè) numbers 數(shù)組,并對(duì)數(shù)組中每一個(gè)元素執(zhí)行閉包中定義的操作。 相當(dāng)于對(duì)數(shù)組中的所有元素做了一個(gè)映射。 比如咱們這個(gè)例子里面的閉包是講所有元素都加 2 。 這樣它產(chǎn)生的結(jié)果數(shù)據(jù)就是 [3,4,5,6]。
初步了解之后,我們來看一下 map 的定義:
func map (@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
咱們拋開一些和關(guān)鍵邏輯無關(guān)的修飾符 @noescape,throws 這些,在整理一下就是這樣:
func map (transform: (Self.Generator.Element) -> T) rethrows -> [T] ``` map 函數(shù)接受一個(gè)閉包, 這個(gè)閉包的定義是這樣的:
(Self.Generator.Element) -> T
“`
它接受 Self.Generator.Element 類型的參數(shù), 這個(gè)類型代表數(shù)組中當(dāng)前元素的類型。 而這個(gè)閉包的返回值,是可以和傳遞進(jìn)來的值不同的。 比如我們可以這樣:
let stringResult = numbers.map { "No. ($0)" }
// ["No. 1", "No. 2", "No. 3", "No. 4"]
這次我們?cè)陂]包裝把傳遞進(jìn)來的數(shù)字拼接到一個(gè)字符串中, 然后返回一個(gè)組數(shù), 這個(gè)數(shù)組中包含的數(shù)據(jù)類型,就是我們拼接好的字符串。
這就是關(guān)于 map 的初步了解, 我們繼續(xù)來看 flatMap。
flatMap
map 可以對(duì)一個(gè)集合類型的所有元素做一個(gè)映射操作。 那么 flatMap 呢?
讓我們來看一個(gè) flatMap 的例子:
result = numbers.flatMap { $0 + 2 }
// [3,4,5,6]
我們對(duì)同樣的數(shù)組使用 flatMap 進(jìn)行處理, 得到了同樣的結(jié)果。 那 flatMap 和 map 到底有什么區(qū)別呢?
咱們?cè)賮砜戳硪粋€(gè)例子:
let numbersCompound = [[1,2,3],[4,5,6]];
var res = numbersCompound.map { $0.map{ $0 + 2 } }
// [[3, 4, 5], [6, 7, 8]]
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]
這里就看出差別了。 對(duì)于二維數(shù)組, map 和 flatMap 的結(jié)果就不同了。 我們先來看第一個(gè)調(diào)用:
var res = numbersCompound.map { $0.map{ $0 + 2 } }
// [[3, 4, 5], [6, 7, 8]]
numbersCompound.map { … } 這個(gè)調(diào)用實(shí)際上是遍歷了這里兩個(gè)數(shù)組元素 [1,2,3] 和 [4,5,6]。 因?yàn)檫@兩個(gè)元素依然是數(shù)組,所以我們可以對(duì)他們?cè)俅握{(diào)用 map 函數(shù):$0.map{ $0 + 2 }。 這個(gè)內(nèi)部的調(diào)用最終將數(shù)組中所有的元素加 2。
再來看看 flatMap 的調(diào)用:
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]
flatMap 依然會(huì)遍歷數(shù)組的元素,并對(duì)這些元素執(zhí)行閉包中定義的操作。 但唯一不同的是,它對(duì)最終的結(jié)果進(jìn)行了所謂的 “降維” 操作。 本來原始數(shù)組是一個(gè)二維的, 但經(jīng)過 flatMap 之后,它變成一維的了。
flatMap 是如何做到的呢,它的原理是什么,為什么會(huì)存在這樣一個(gè)函數(shù)呢? 相信此時(shí)你腦海中肯定會(huì)浮現(xiàn)出類似的問題。
下面咱們?cè)賮砜匆幌?flatMap 的定義, 還是拋去 @noescape, rethrows 這些無關(guān)邏輯的關(guān)鍵字:
func flatMap(transform: (Self.Generator.Element) throws -> T?) -> [T] func flatMap(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]
和 map 不同, flatMap 有兩個(gè)重載。 參照我們剛才的示例, 我們調(diào)用的其實(shí)是第二個(gè)重載:
func flatMa p(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]
flatMap 的閉包接受的是數(shù)組的元素,但返回的是一個(gè) SequenceType 類型,也就是另外一個(gè)數(shù)組。 這從我們剛才這個(gè)調(diào)用中不難看出:
numbersCompound.flatMap { $0.map{ $0 + 2 } }
我們傳入給 flatMap 一個(gè)閉包$0.map{ $0 + 2 }, 這個(gè)閉包中,又對(duì) $0 調(diào)用了 map 方法, 從 map 方法的定義中我們能夠知道,它返回的還是一個(gè)集合類型,也就是 SequenceType。 所以我們這個(gè) flatMap 的調(diào)用對(duì)應(yīng)的就是第二個(gè)重載形式。
那么為什么 flatMap 調(diào)用后會(huì)對(duì)數(shù)組降維呢? 我們可以從它的源碼中窺探一二(Swift 不是開源了嗎~)。
文件位置: swift/stdlib/public/core/SequenceAlgorithms.swift.gyb
extension Sequence {
//...
public func flatMap(
@noescape transform: (${GElement}) throws -> S
) rethrows -> [S.${GElement}] {
var result: [S.${GElement}] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}
//...
}
這就是 flatMap 的完整源碼了, 它的源碼也很簡(jiǎn)單, 對(duì)遍歷的每一個(gè)元素調(diào)用 try transform(element)。 transform 函數(shù)就是我們傳遞進(jìn)來的閉包。
然后將閉包的返回值通過 result.append(contentsOf:) 函數(shù)添加到 result 數(shù)組中。
那我們?cè)賮砜匆幌?result.append(contentsOf:) 都做了什么, 它的文檔定義是這樣:
Append the elements of newElements to self.
簡(jiǎn)單說就是將一個(gè)集合中的所有元素,添加到另一個(gè)集合。 還以我們剛才這個(gè)二維數(shù)組為例:
let numbersCompound = [[1,2,3],[4,5,6]];
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]
flatMap 首先會(huì)遍歷這個(gè)數(shù)組的兩個(gè)元素 [1,2,3] 和 [4,5,6], 因?yàn)檫@兩個(gè)元素依然是數(shù)組, 所以我們可以對(duì)他們?cè)龠M(jìn)行 map 操作:$0.map{ $0 + 2 }。
這樣, 內(nèi)部的$0.map{ $0 + 2 }調(diào)用返回值類型還是數(shù)組, 它會(huì)返回 [3,4,5] 和 [6,7,8]。
然后, flatMap 接收到內(nèi)部閉包的這兩個(gè)返回結(jié)果, 進(jìn)而調(diào)用 result.append(contentsOf:) 將它們的數(shù)組中的內(nèi)容添加到結(jié)果集中,而不是數(shù)組本身。
那么我們最終的調(diào)用結(jié)果理所當(dāng)然就應(yīng)該是 [3, 4, 5, 6, 7, 8] 了。
仔細(xì)想想是不是這樣呢~
flatMap 的另一個(gè)重載
我們剛才分析了半天, 其實(shí)只分析到 flatMap 的一種重載情況, 那么另外一種重載又是怎么回事呢:
func flatMap (transform: (Self.Generator.Element) -> T?) -> [T]
從定義中我們看出, 它的閉包接收的是 Self.Generator.Element 類型, 返回的是一個(gè) T? 。 我們都知道,在 Swift 中類型后面跟隨一個(gè) ?, 代表的是 Optional 值。 也就是說這個(gè)重載中接收的閉包返回的是一個(gè) Optional 值。 更進(jìn)一步來說,就是閉包可以返回 nil。
我們來看一個(gè)例子:
let optionalArray: [String?] = ["AA", nil, "BB", "CC"];
var optionalResult = optionalArray.flatMap{ $0 }
// ["AA", "BB", "CC"]
這樣竟然沒有報(bào)錯(cuò), 并且 flatMap 的返回結(jié)果中, 成功的將原數(shù)組中的 nil 值過濾掉了。 再仔細(xì)觀察,你會(huì)發(fā)現(xiàn)更多。 使用 flatMap 調(diào)用之后, 數(shù)組中的所有元素都被解包了, 如果同樣使用 print 函數(shù)輸出原始數(shù)組的話, 大概會(huì)得到這樣的結(jié)果:
[Optional("AA"), nil, Optional("BB"), Optional("CC")]
而使用print函數(shù)輸出 flatMap 的結(jié)果集時(shí),會(huì)得到這樣的輸出:
["AA", "BB", "CC"]
也就是說原始數(shù)組的類型是 [String?] 而 flatMap 調(diào)用后變成了 [String]。 這也是 flatMap 和 map 的一個(gè)重大區(qū)別。 如果同樣的數(shù)組,我們使用 map 來調(diào)用, 得到的是這樣的輸出:
[Optional("AA"), nil, Optional("BB"), Optional("CC")]
這就和原始數(shù)組一樣了。 這兩者的區(qū)別就是這樣。 map 函數(shù)值對(duì)元素進(jìn)行變換操作。 但不會(huì)對(duì)數(shù)組的結(jié)構(gòu)造成影響。 而 flatMap 會(huì)影響數(shù)組的結(jié)構(gòu)。再進(jìn)一步分析之前,我們暫且這樣理解。
flatMap 的這種機(jī)制,而已幫助我們方便的對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證,比如我們有一組圖片文件名, 我們可以使用 flatMap 將無效的圖片過濾掉:
var imageNames = ["test.png", "aa.png", "icon.png"];
imageNames.flatMap{ UIImage(named: $0) }
那么 flatMap 是如何實(shí)現(xiàn)過濾掉 nil 值的呢? 我們還是來看一下源碼:
extension Sequence {
// ...
public func flatMap(
@noescape transform: (${GElement}) throws -> T?
) rethrows -> [T] {
var result: [T] = []
for element in self {
if let newElement = try transform(element) {
result.append(newElement)
}
}
return result
}
// ...
}
依然是遍歷所有元素,并應(yīng)用 try transform(element) 閉包的調(diào)用, 但關(guān)鍵一點(diǎn)是,這里面用到了 if let 語(yǔ)句, 對(duì)那些只有解包成功的元素,才會(huì)添加到結(jié)果集中:
if let newElement = try transform(element) {
result.append(newElement)
}
這樣, 就實(shí)現(xiàn)了我們剛才看到的自動(dòng)去掉 nil 值的效果了。
總結(jié)
以上是生活随笔為你收集整理的Swift中的map 和 flatMap 原理及用法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开源IOT平台
- 下一篇: python机器学习实战 getA()函