Swift中的高阶函数

前言

Swift的标准数组支持三个高阶函数:map,filter和reduce,以及map的扩展flatMap。Objective-C的NSArray没有实现这些方法,但是开源社区弥补了这个不足。

极大地体现了Swift的高大功能和优点。


了解闭包

  • 阐述

    Swift一大特性便是使用简洁的头等函数(闭包)语法代替了复杂的blocks语法。头等函数-即可将函数当作参数传递给其他的函数,或从其他的函数里返回出值,并且可以将他们设定为变量,或者将他们存储在数据结构中。

    闭包的形式: ((参数) -> 返回值)

  • 我们先写一个求立方的函数:

    1
    2
    3
    4
     //MARK: -- 立方
    func cube(_ a: Float) -> Float{
    return a*a*a
    }
  • 求两数进行f函数运算后的平均值的函数

    1
    2
    3
    func averageOfFunction(_ a: Float ,_ b: Float ,_ f: ((Float) -> Float)) -> Float{   
    return (f(a) + f(b))/2
    }
  • 进行调用,简化闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //直接传参数和函数名
    let sum1 = averageOfFunction(2, 3, cube)

    //直接把闭包做为它的函数方法参数
    let sum2 = averageOfFunction(2, 3, {(x : Float) -> Float in x*x*x })

    //隐式推断输入值为一个Float
    let sum3 = averageOfFunction(2, 3, {x -> Float in x*x*x })

    //把retuen一起忽略
    let sum4 = averageOfFunction(2, 3, {x in x*x*x })

    //使用默认参数名,最简化地写法
    let sum5 = averageOfFunction(2, 3, { $0*$0*$0 })
  • 默认参数名补充

    我们还可以忽略指定参数名,使用默认参数名$0(如果函数接收多个参数,使用$K作为第K-1个参数,如$0,$1,$2……)

    在高阶函数中也会根据闭包的的简化来进行简化代码。


map函数

  • map需求

    map用于将每个数组元素通过某个方法进行转换。
    我们用一个Int类型数组,想把每个数后面添加一个字符“元”,把数组转成字符串数组

    1
    2
    let moneyArray = [12,34,8,17,91,2,82]
    var stringArray : [String] = []//结果数组
  • 一般的思维

    1
    2
    3
    4
    for money in moneyArray {
    stringArray.append("\(money)元")
    }
    print(stringArray)
  • map的函数介绍

    public func map(_ transform: (Element) throws -> T) rethrows -> [T]

    以一个命名函数transform作为参数,transform负责把元素Element转成类型T并返回一个类型T的数组。

    在我们的事例中,T为Int,T为String,作为转换函数传给map的是一个把Int转成String

  • map的方法

    1
    2
    3
    stringArray = moneyArray.map({ (a: Int) -> String in
    return "\(a)元"
    })
1
2
3
4
5
6
7
//MARK: -- 可以直接把代码换成一个简化的简约闭包
stringArray = moneyArray.map({money in "\(money)元"})
print(stringArray)

//MARK: -- 最简约的写法,采用默认参数
stringArray = moneyArray.map({"\($0)元"})
print(stringArray)

filter

  • filter需求

    filter用于选择数组元素中满足某种条件的元素。使用上面的例子,筛选出大于20的元素,结果应该是[34,91,82]

    1
    var  filterArray : [Int] =[]//结果数组
  • 一般的思维

    1
    2
    3
    4
    5
    6
    7
     for money in moneyArray {
    if money > 20{
    filterArray.append(money)
    }
    }

    print(filterArray)
  • filter的函数介绍

    public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]

    以一个命名函数isIncluded作为参数,isIncluded返回true或者false,对原数组元素Element调用isIncluded时,只有返回true的元素会通过筛选,从而放入返回数组 [Element]中

  • filter的方法

    1
    2
    3
    4
     filterArray = moneyArray.filter({ (a: Int) -> Bool in
    return a > 20
    })
    print(filterArray)
1
2
3
4
5
6
7
 //MARK: — 一个简化的简约闭包
filterArray = moneyArray.filter({money in money > 30})
print(filterArray)

//MARK: -- 最简约的写法,采用默认参数
filterArray = moneyArray.filter({$0 > 30})
print(filterArray)

reduce

  • reduce需求

    reduce方法把数组元素组合计算为一个值。我们使用上面例子计算数组元素的和,结果应该为246(12+34+8+17+91+2+82) 以及: 828102912(12 X 34 X 8 X 17 X 91 X 2 X 82)

    1
    var  filterArray : [Int] =[]//结果数组
  • 一般的思维

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //MARK: — 求和
    var sum = 0
    for money in moneyArray {
    sum += money
    }
    print(sum)

    //MARK: — 求乘积
    var product = 1
    for money in moneyArray {
    product = product * money
    }
    print(product)
  • reduce的函数介绍

    Reduce便可用于快速完成这类操作,通过指定一个初始值和一个组合元素的方法.

    public func reduce( initialResult: Result, nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

    接收两个参数,一个为initialResult的命名参数,类型跟Result一样的的初始值参数,另一个是命名函数nextPartialResult,把Result类型和数组元素Element进行运算,得到一个Result,逐个运行算成后得到最终的Result,将其返回。

  • reduce的方法

    1
    2
    3
    4
    5
    6
    7
    8
    //MARK: --一个简化的简约闭包 
    let sum = moneyArray.reduce(0) { (a : Int, b : Int) -> Int in
    return a + b
    }
    let product = moneyArray.reduce(1) { (a : Int, b: Int) -> Int in
    return a * b
    }
    print("数组总和==\(result),数组乘积==\(product)”)
1
2
3
4
//MARK: -- 最简约的写法,采用默认参数
let sum = moneyArray.reduce(0) { $0 + $1}
let product = moneyArray.reduce(1) { $0 * $1}
print("数组总和==\(result),数组乘积==\(product)”)
  • 特殊情况下可再简化

    上面的都不是最简化的,其实最简化地写法是这样的:

    1
    2
    3
    4
    //MARK:— Swift中操作符可用着函数,可简化成:
    let sum = moneyArray.reduce(0, +)
    let product = moneyArray.reduce(1, *)
    print("数组总和==\(sum),数组乘积==\(product)")

reduce可能是三个高阶函数中最难理解的一个。 需要注意的是nextPartialResult函数的两参数类型不同,Result为计算结果类型,Element为数组元素类型。


flatMap

  • flatMap介绍

    比map更深一样,在自定义函数时,可能把元素生成更多的元素,然后会把这些元素都加到新的元素数组中去。

  • 代码示例

    1
    2
    3
    4
    5
    6
    7
      let oldArray = [20,10,30]

    let newArray = oldArray.flatMap { (a : Int) -> [String] in
    return ["\(a)元","\(a)分"]
    }
    print(newArray)
    // 结果: ["20元", "20分", "10元ƒ", "10分", "30元", "30分”]
    1
    2
    3
    4
     //MARK: -- 简约写法
    let newArray = oldArray.flatMap({["\($0)元","\($0)分"]})
    print(newArray)
    // 结果: ["20元", "20分", "10元", "10分", "30元", "30分”]
  • 对比map

    1
    2
    3
       let newArr = oldArray.map({["\($0)元","\($0)分"]})     
    print (newArr)
    // 结果: [["20元", "20分"], ["10元", "10分"], ["30元", "30分"]]

    对比一下结果就看出运算的不同之处了。map是由一个元素生成object,返回后替代原来的元素,数组的这个元素就成了是object,数组的这个元素就是object。而flatMap可以由一个元素生成一个数组newArr,结果数组在这个元素的位置插入newArr.count个数的newArr,结果数组的个数就增加了newArr.count元素了。

    所以flatmap是map的扩展功能,因为它生成的不是单个元素,而可以是数组。


总结

  1. 数据比较大的时候,高阶函数会比传统实现更快,因为它可以并行执行(如运行在多核上),除非真的需要更高定制版本的map,reduce和filter,否则可以一直使用它们以获得更快的执行速度。

  2. 当使用map,filter,reduce的代码质量会更好。

    小毛的博客