Why Coding Like this -------map函數揭祕

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的實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章