title: “Why coding like This —— Map 函數揭祕”
date: 2015-08-02 23:24:16
categories: “why coding like this”
tags: [swift進階]
1.Map函數揭祕
Topic 1:
請用Map函數實現對一個Int類型數組的元素進行2倍放大。
Example:
//例一
let intArray = [1,2,3]
var result = intArray.map{ $0 * 2} //輸出[2,4,6]
why coding like this?
假設讓你寫一個函數,傳入參數爲Int類型數組,對數組內每一個元素進行放大2倍操作,然後將處理後的數組作爲結果返回。
這不難實現,只需要使用for-in
語句遍歷數組元素,進行放大處理並保存結果值,最後返回處理後的數組。代碼如下:
//例二:
func doubleArrayByTwo(xs:[Int])->[Int]{
var result :[Int] = []
for x in xs{
result.append(x * 2)
}
return result
}
不妨來試試寫的函數:
result = doubleArrayByTwo(intArray) //返回[2,4,6]
看來我們確實實現了對一個數組元素進行放大兩倍的函數,那麼接下來請實現對每一個數組元素進行一個線性變換(y = ax + b),其中 a = 2 b = 3 。
難度不大,重寫一個函數:
//例三:線性變換 y = 2x + 3
func linealMeasureArray(xs:[Int])->[Int]{
var result :[Int] = []
for x in xs{
result.append(x * 2 + 3) //僅僅只是括號中的代碼改變了下而已
}
return result
}
那麼接下來 a = 3 b = 4呢,你開始抓狂,Oh,No!顯然每一次a,b值的改變就要重寫一個函數絕非明智,考慮到所給命題我們均可以通過for-in
語句遍歷數組每一個元素,進行f(x)
變換即可,因此上述代碼我們更改爲:
//例四:
func handleIntArray(xs:[Int],f:Int->Int)->[Int]{
var result :[Int] = []
for x in xs {
result.append(f(x))
}
return result
}
值得注意得是,我們除了傳入一個xs數組外,還傳入了一個f
閉包,而這個閉包類型爲(Int->Int)
,接受Int
類型數據,經過閉包體內處理後再返回一個Int
結果值。更多閉包內容,請點擊這裏。
現在來測試下所寫的這個函數是否滿足我們要求:
//輸出 3 5 7
result = handleIntArray(intArray){
x in
return 2 * x + 1 //2 * x + 1 就是閉包的處理體
}
可能如此寫法仍然讓人迷惑,因此我決定再簡單分解下。首先聲明一個函數名位handleClosure
的函數,主要作用是對傳入的x元素進行2*x+1
的線性變換,最後將處理後的結果值返回。現在來調用handleIntArray
函數,首先傳入intArray
數組,接着將handleClosure
函數作爲一個參數傳入,那麼在什麼時候調用呢? 請看result.append(f(x))
f(x)此時即爲handleClosure(x:)函數,而傳入的x參數爲遍歷數組中的元素。一切問題引刃而解!代碼如下。
func handleClosure(x:Int)->Int{
return 2 * x + 1
}
result = handleIntArray(intArray, handleClosure)
新命題:對Int類型數組的每一個元素進行判斷,偶數爲true
,奇數爲false
,結果數組爲[False]
分析:奇偶判斷我們通過 x % 2 == 0 語句輕鬆實現,然後調用handleIntArray即可,遺憾的是閉包的返回參數與我們的不匹配,我們所期望的是返回Bool類型,而閉包中爲Int,不得已我們需要重新構建一個
//例五:
func handleBoolArray(xs:[Int],f:Int->Bool)->[Bool]{
var result :[Bool] = []
for x in xs {
result.append(f(x))
}
return result
}
現在我們能夠使用func handleIntArray(xs:[Int],f:Int->Int)->[Int]
來處理Int類型的數組,使用func handleBoolArray(xs:[Int],f:Int->Bool)->[Bool]
來處理Int類型 返回Bool類型,但這都均有侷限性。假如下一次是要處理String類型的數組,亦或是Double類型的數組呢?
仔細分析兩個handle函數,它們看起來非常相似,唯一的區別就是在傳入參數上。這時候我們就要試想了,如何聲明一個函數能表示不同類型的參數輸入呢? 感謝Swift提供了generics
爲我們很好的解決了這一難題。現在我們來構建一個函數:
//例六:
func genericComputeArray<U>(xs:[Int],f:Int->U)->[U]{
var result :[U] = []
for x in xs {
result.append(f(x))
}
return result
}
泛型並不難,注意幾點,尖括號<>
中的U
爲類型,而非類,其次類型U
是未定的,直到你調用該函數,傳入參數爲Int
類型,U
便替換成Int
;傳入參數爲String
類型,U
便替換成String
。
更多時候我建議你把
genericComputerArray<U>
看作是該函數的大家庭family,一個具體的類型U
對應一個新函數!該函數傳入一個Int類型數組,以及一個閉包(類型爲Int->U
),返回一個U
類型的數組[U]
。
爲了使得函數更通用,嘗試稍微修改以上函數來構建我們的map
函數:
//例七:
func myMap<T,U>(xs:[T],f:T->U)->[U]{
var result:[U] = []
for x in xs{
result.append(f(x))
}
return result
}
myMap(intArray){
x in x * 2
}//輸出 [2,4,6]
至於爲什麼系統的調用是xxx.map{},其實map函數是作爲數組的實例方法存在,遵循了協議實現罷了。好奇的你可以一試。
總結:恭喜你自定義了一個myMap函數,可見系統自帶的map函數也並不是那麼神祕。 下次帶來
why coding like this --- filter的實現