一柏蘑、Map , FlatMap , Filter , Reduce 指南
Swift是支持一門函數(shù)式編程的語言幸冻,擁有Map
,FlatMap
,Filter
,Reduce
針對集合類型的操作咳焚。在使用Objective-C開發(fā)時洽损,如果你沒接觸過函數(shù)式編程,那你可能沒聽說過這些名詞革半,希望此篇文章可以幫助你了解Swift中的Map
碑定,FlatMap
,Filter
,Reduce
流码。
Map
首先我們來看一下map
在Swift
中的的定義,我們看到它可以用在 Optionals 和 SequenceType 上(如:數(shù)組延刘、詞典等)漫试。
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}
extension CollectionType {
/// Returns an `Array` containing the results of mapping `transform`
/// over `self`.
///
/// - Complexity: O(N).
@warn_unused_result
public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
}
@warn_unused_result:表示如果沒有檢查或者使用該方法的返回值,編譯器就會報警告碘赖。
@noescape:表示transform
這個閉包是非逃逸閉包驾荣,它只能在當前函數(shù)map
中執(zhí)行,不能脫離當前函數(shù)執(zhí)行普泡。這使得編譯器可以明確的知道運行時的上下文環(huán)境(因此播掷,在非逃逸閉包中可以不用寫self
),進而進行一些優(yōu)化撼班。
對 Optionals進行map
操作
簡要的說就是歧匈,如果這個可選值有值,那就解包权烧,調(diào)用這個函數(shù)眯亦,之后返回一個可選值,需要注意的是般码,返回的可選值類型可以與原可選值類型不一致:
///原來類型: Int?,返回值類型:String?
var value:Int? = 1
var result = value.map { String("result = \($0)") }
/// "Optional("result = 1")"
print(result)
var value:Int? = nil
var result = value.map { String("result = \($0)") }
/// "nil"
print(result)
對SequenceType進行map
操作
我們可以使用map
方法遍歷數(shù)組中的所有元素妻率,并對這些元素一一進行一樣的操作(函數(shù)方法)。map
方法返回完成操作后的數(shù)組板祝。
我們可以用For-in
完成類似的操作:
var values = [1,3,5,7]
var results = [Int]()
for var value in values {
value *= 2
results.append(value)
}
//"[2, 6, 10, 14]"
print(results)
這看起來有點麻煩宫静,我們得先定義一個變量var results
然后將values
里面的元素遍歷,進行我們的操作以后券时,將其添加進results
孤里,我們比較下使用map
又會怎么樣:
let results = values.map ({ (element) -> Int in
return element * 2
})
//"[2, 6, 10, 14]"
我們向map
傳入了一個閉包,對數(shù)組中的所有元素都 乘以2
橘洞,將返回的新的數(shù)組賦值為results
捌袜,是不是精簡了許多?還能更精簡炸枣!
精簡寫法
let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"
what the fuck...沉住氣虏等,讓我們一步步來解析怎么就精簡成這樣了,保證讓你神清氣爽适肠。翻開The Swift Programming Language中對于閉包的定義你就能找到線索霍衫。
第一步:
由于閉包的函數(shù)體很短,所以我們將其改寫成一行:
let results = values.map ({ (element) -> Int in return element * 2 })
//"[2, 6, 10, 14]"
第二步:
由于我們的閉包是作為map
的參數(shù)傳入的侯养,系統(tǒng)可以推斷出其參數(shù)與返回值敦跌,因為其參數(shù)必須是(Element) -> Int類型的函數(shù)。因此逛揩,返回值類型柠傍,->
及圍繞在參數(shù)周圍的括號都可以被忽略:
let results = values.map ({ element in return element * 2 })
//"[2, 6, 10, 14]"
第三步:
單行表達式閉包可以通過省略return
來隱式返回閉包的結(jié)果:
let results = values.map ({ element in element * 2 })
//"[2, 6, 10, 14]"
由于閉包函數(shù)體只含有element * 2
這單一的表達式麸俘,該表達式返回Int
類型,與我們例子中map
所需的閉包的返回值類型一致(其實是泛型)携兵,所以疾掰,可以省略return
。
第四步:
參數(shù)名稱縮寫(Shorthand Argument Names)徐紧,由于Swift
自動為內(nèi)聯(lián)閉包提供了參數(shù)縮寫功能静檬,你可以直接使用$0
,$1
,$2
...依次獲取閉包的第1,2并级,3...個參數(shù)拂檩。
如果您在閉包表達式中使用參數(shù)名稱縮寫,您可以在閉包參數(shù)列表中省略對其的定義嘲碧,并且對應(yīng)參數(shù)名稱縮寫的類型會通過函數(shù)類型進行推斷稻励。in
關(guān)鍵字也同樣可以被省略:
let results = values.map ({ $0 * 2 })
//"[2, 6, 10, 14]"
例子中的$0即代表閉包中的第一個參數(shù)。
最后一步:
尾隨閉包愈涩,由于我們的閉包是作為最后一個參數(shù)傳遞給map
函數(shù)的望抽,所以我們可以將閉包表達式尾隨:
let results = values.map (){ $0 * 2 }
//"[2, 6, 10, 14]"
如果函數(shù)只需要閉包表達式一個參數(shù),當您使用尾隨閉包時履婉,您甚至可以把()省略掉:
let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"
如果還有不明白的煤篙,可以多翻閱翻閱The Swift Programming Language。
FlatMap
與map一樣毁腿,它可以用在 Optionals和 SequenceType 上(如:數(shù)組辑奈、詞典等)。我們先來看看針對Optional的定義:
對 Optionals進行flatMap
操作
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
/// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
}
就閉包而言已烤,這里有一個明顯的不同鸠窗,這次flatMap
期望一個 (Wrapped) -> U?)
閉包。對于可選值胯究, flatMap 對于輸入一個可選值時應(yīng)用閉包返回一個可選值稍计,之后這個結(jié)果會被壓平,也就是返回一個解包后的結(jié)果裕循。本質(zhì)上臣嚣,相比 map
,flatMap
也就是在可選值層做了一個解包。
var value:String? = "1"
var result = value.map { Int($0)}
/// "Optional(Optional(1))"
print(result)
var value:String? = "1"
var result = value.flatMap { Int($0)}
/// ""Optional(1)"
print(result)
使用flatMap就可以在鏈式調(diào)用時费韭,不用做額外的解包工作:
var value:String? = "1"
var result = value.flatMap { Int($0)}.map { $0 * 2 }
/// ""Optional(2)"
print(result)
對SequenceType進行flatMap
操作
我們先來看看Swift中的定義
extension SequenceType {
/// 返回一個將變換結(jié)果連接起來的數(shù)組
/// `transform` over `self`.
/// s.flatMap(transform)
/// is equivalent to
/// Array(s.map(transform).flatten())
@warn_unused_result
public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
}
extension SequenceType {
/// 返回一個包含非空值的映射變換結(jié)果
@warn_unused_result
public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}
通過這兩個描述,就提現(xiàn)了flatMap
對SequenceType的兩個作用:
一:壓平
var values = [[1,3,5,7],[9]]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]
二:空值過濾
var values:[Int?] = [1,3,5,7,9,nil]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]
Filter
同樣庭瑰,我先來看看Swift
中的定義:
extension SequenceType {
/// 返回包含原數(shù)組中符合條件的元素的數(shù)組
/// Returns an `Array` containing the elements of `self`,
/// in order, that satisfy the predicate `includeElement`.
@warn_unused_result
public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]
}
filter
函數(shù)接受一個(Element) -> Bool)
的閉包星持,來判斷原數(shù)組中的元素是否符合條件,這個方法用來過濾數(shù)組中的一些元素再好不過了:
var values = [1,3,5,7,9]
let flattenResults = values.filter{ $0 % 3 == 0}
//[3, 9]
我們向flatMap傳入了一個閉包弹灭,篩選出了能被3整除的數(shù)據(jù)督暂。
Reduce
我們先來看下Swift
中的定義:
extension SequenceType {
/// Returns the result of repeatedly calling `combine` with an
/// accumulated value initialized to `initial` and each element of
/// `self`, in turn, i.e. return
/// `combine(combine(...combine(combine(initial, self[0]),
/// self[1]),...self[count-2]), self[count-1])`.
@warn_unused_result
public func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T
}
給定一個初始化的combine結(jié)果揪垄,假設(shè)為result,從數(shù)組的第一個元素開始,不斷地調(diào)用combine
閉包逻翁,參數(shù)為:(result饥努,數(shù)組中的元素),返回的結(jié)果值繼續(xù)調(diào)用combine函數(shù)
八回,直至元素最后一個元素酷愧,返回最終的result值。來看下面的代碼(為了更方便你理解這個過程缠诅,代碼就不簡寫了):
var values = [1,3,5]
let initialResult = 0
var reduceResult = values.reduce(initialResult, combine: { (tempResult, element) -> Int in
return tempResult + element
})
print(reduceResult)
//9
我們存在一個數(shù)組[1,3,5]
溶浴,給定了一個初始化的結(jié)果 initialResult = 0
,向reduce
函數(shù)傳了 (tempResult, element) -> Int
的閉包管引,tempResut
便是每次閉包返回的結(jié)果值士败,并且其初始值為我們之前設(shè)置的initialResult
為0
,element
即為我們數(shù)組中的元素(可能為1
,3
,5
)褥伴。reduce會一直調(diào)用combine
閉包谅将,直至數(shù)組最后一個元素。下面的代碼更形象地描述了整個過程重慢,這其實跟reduce
所做的操作是等價的:
func combine(tempResult: Int, element: Int) -> Int {
return tempResult + element
}
reduceResult = combine(combine(combine(initialResult, element: 1), element: 3), element: 5)
print(reduceResult)
//9
作者:rayjuneWu
鏈接:http://www.reibang.com/p/87b97dfbf17b
二饥臂、map,filter伤锚,reduce 實踐
map:轉(zhuǎn)換擅笔,可以對數(shù)組中的元素格式進行轉(zhuǎn)換
//將Int數(shù)組轉(zhuǎn)換為String數(shù)組
//$0代表數(shù)組的元素
let array = [1, 2, 3, 4, 5 , 6, 7]
let result = array.map{
String($0)
}
filter:過濾,可以對數(shù)組中的元素按照某種規(guī)則進行過濾
//在array中過濾出偶數(shù)
let result2 = array.filter{
$0 % 2 == 0
}
reduce:計算 屯援,可以對數(shù)組中的元素進行計算
//計算數(shù)組array元素的和
//在這里$0和$1的意義不同猛们,$0代表元素計算后的結(jié)果,$1代表元素
//10代表初始化值狞洋,在這里可以理解為 $0初始值 = 10
let result3 = array.reduce(10){
$0 + $1
}
這三個函數(shù)介紹完了弯淘,可以看到這三個方法使用起來非常的便利,接下來我會寫一個計算文件夾大小的Demo
之前我已經(jīng)在沙盒中創(chuàng)建了log文件夾吉懊,里邊存放了四個文件,我們要做的是計算出log文件夾下.pdf格式的文件大小庐橙。
先寫兩個方法分別獲取文件夾的路徑和計算一個文件的大小
//獲取文件夾路徑
func getFolderPath(folderName: String) -> String{
let path: NSString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
return path.stringByAppendingPathComponent(folderName)
}
//計算一個文件的大小
func caculateFileSize(path: String) -> UInt64{
let fileManager = NSFileManager.defaultManager()
let dic: NSDictionary = try! fileManager.attributesOfItemAtPath(path)
let size = dic.fileSize()
return size
}
使用map,filter借嗽,reduce計算文件夾下.pdf格式文件大小
let folderPath = getFolderPath("log")
let childFiles = NSFileManager.defaultManager().subpathsAtPath(folderPath)
//使用filter過濾出.pdf格式的文件
//在map方法體中态鳖,將文件數(shù)組轉(zhuǎn)換為size的數(shù)組
//使用reduce計算size數(shù)組的和
//最終返回reduce的計算結(jié)果
let result = childFiles?.filter{
($0.componentsSeparatedByString(".")).last == "pdf"
}.map({ (fileName) -> UInt64 in
let filePath = folderPath + "/" + fileName
return caculateFileSize(filePath)
}) .reduce(0){
$0 + $1
}
print(".pdf文件大小總和為----\(result)")
計算結(jié)果:
在代碼中使用filter方法后直接調(diào)用了map方法,這是因為高階函數(shù)支持鏈式調(diào)用恶导,高階函數(shù)的特性就是可以以一個函數(shù)或多個函數(shù)當參數(shù)浆竭,返回值也可以是一個函數(shù),如果你使用過AutoLayout庫 Masonry的話會很習(xí)慣這種寫法。
以上僅代表我的個人觀點邦泄,有不足的地方希望大家隨時與我溝通
參考:
Swift 燒腦體操(三) - 高階函數(shù)
Swift函數(shù)式編程實踐
作者:Lilin_Coder
鏈接:http://www.reibang.com/p/32c009fcb13d