跳转至

高阶函数

高阶函数的本质也是函数,有两个特点:

  • 接受函数或者闭包作为参数
  • 返回值是一个函数或者闭包

这些函数我们常常用来作用于ArraySetDictionary中的每一个元素

Map函数

Map函数作用于Collection中的每一个元素,然后返回一个新的Collection

map 方法接受一个闭包作为参数, 然后它会遍历整个数组,并对数组中每一个元素执行闭包中定义的操作。

    let strings = ["AAA","BBB","CCC","DDD"]
    //转小写 map作用于字符串每一个元素,转小写
    let newStrings = strings.map { (element) -> String in
        return element.lowercased()
    }
    print(newStrings)

    ///map函数是由字符串组调用的,所以默认的形式参数就是 (string) -> String 类型,系统能够自动推断类型,所以  -> String 我们就可以省略,同样的return也可以省略
    newStrings = strings.map({ element in element.lowercased()})

    ///swift自动对行内闭包提供简写实际参数名,可以通过$0、$1、$2 等名字来引用闭包的实际参数值。
    ///如果在闭包表达式中使用这些简写实际参数名,那么可以在闭包的实际参数列表中忽略对其的定义,并且简写实际参数名的数字和类型将会从期望的函数类型中推断出来。
    ///in 关键字也能被省略,因为闭包表达式完全由它的函数体组成
    newStrings = strings.map({$0.lowercased()})

例:模型数组 拼接字符串

//adminPhones是PhoneModel模型数组
struct PhoneModel: Codable {
    let UserSn: Int
    let Mobile: String
}

usersString = adminPhones.map {"\($0.UserSn)"}.joined(separator: ",")

我们先来看一下Map的函数原型,这里注意Map函数其实是Sequence协议的拓展,所以这里我们找到Sequence的文件。

    /// Returns an array containing the results of mapping the given closure
    /// over the sequence's elements.
    ///
    /// In this example, `map` is used first to convert the names in the array
    /// to lowercase strings and then to count their characters.
    ///
    ///     let cast = ["Vivien", "Marlon", "Kim", "Karl"]
    ///     let lowercaseNames = cast.map { $0.lowercased() }
    ///     // 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
    ///     let letterCounts = cast.map { $0.count }
    ///     // 'letterCounts' == [6, 6, 3, 4]
    ///
    /// - Parameter transform: A mapping closure. `transform` accepts an
    ///   element of this sequence as its parameter and returns a transformed
    ///   value of the same or of a different type.
    /// - Returns: An array containing the transformed elements of this
    ///   sequence.

/**
map<T> 表示是一个泛型函数
_ transform: (Element) throws -> T 接受一个闭包作为参数
把当前结果返回
*/
    @inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

接下来我们看一下源码到底是如何作用于每一个元素的:

对每一个元素执行闭包表达式,然后拼接返回集合。

  @inlinable
  public func map<T>(
    _ transform: (Element) throws -> T
  ) rethrows -> [T] {
    let initialCapacity = underestimatedCount
    var result = ContiguousArray<T>()
    result.reserveCapacity(initialCapacity)

    var iterator = self.makeIterator()

    // Add elements up to the initial capacity without checking for regrowth.
    for _ in 0..<initialCapacity {
      result.append(try transform(iterator.next()!))
    }
    // Add remaining elements, if any.
    while let element = iterator.next() {
      result.append(try transform(element))
    }
    return Array(result)
  }

flatMap函数

flatMap 的定义

public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element]

flatMap 中的闭包的参数同样是 Sequence 中的元素类型,但其返回类型为 SegmentOfResult

在函数体的范型定义中,SegmentOfResult 的类型其实就是 Sequence

flatMap 函数返回的类型是 SegmentOfResult.Element 的数组。

从函数的返回值来看,与 map 的区别在于 flatMap 会将 Sequence 中的元素进行”压平”,返回的类型会是 Sequence 中元素类型的数组,而 map 返回的这是闭包返回类型T的数组。

源码:

  @inlinable
  public func flatMap<SegmentOfResult: Sequence>(
    _ transform: (Element) throws -> SegmentOfResult
  ) rethrows -> [SegmentOfResult.Element] {
    var result: [SegmentOfResult.Element] = []
    for element in self {
      result.append(contentsOf: try transform(element))
    }
    return result
  }

相比较 map 来说, flatMap 最主要的两个作用一个是压平,一个是过滤空值。

let numbers = [[1,2,3,4],[5,6]]

// MARK: map
let result1 = numbers.map{$0}
print(result1)//打印:[[1, 2, 3, 4], [5, 6]]

// MARK: flatMap
let result2 = numbers.flatMap{$0}
print(result2)//打印:[1, 2, 3, 4, 5, 6]

这里我们可以通过下面的这种方式来区别一下 map  & flatMap

let numbers = [1, 2, 3, 4]
let mapped = numbers.map { Array(repeating: $0, count: $0) }
let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }

再看一个列子:

let number:String? = String(20)
let mapResult = number.map{ Int($0)}
let flatMapResult = number.flatMap{ Int($0)}
print("map和flatMap:\(mapResult)\(flatMapResult)")//Optional(Optional(20)) 和 Optional(20)

可以看到这里我们使用 map 做集合操作之后,得到的 mapResult一个可选的可选,那么这里其实我们在使用 mapResult 的过程中考虑的情况就比较多

通过 flatMap 我们就可以得到一个可选值而不是可选的可选 我们来看一下源码

image.png

flatMap 对于输入一个可选值时应用闭包返回一个可选值,之后这个结果会被压平,也就是返回一个解包后的结果。本质上,相比 map,flatMap也就是在可选值层做了一个解包。 image.png

使用 flatMap 就可以在链式调用时,不用做额外的解包工作,什么意思那?我们先来看我们使用 map 来进行链式调用

let number:String? = String(20)
let result = number.map{Int($0)}?.map{$0 * 2}

这里我们得到的 result是一个可选的可选,而且在调用的过程中如果有必要我们依然需要进行解包的操作

let number:String? = String(20)
let result = number.flatMap{Int($0)}.map{$0 * 2}

CompactMap函数

当前对有可选值的集合进行操作的时候,可以选择 compactMap 。

image.png

什么时候使用 compactMap

当转换闭包返回可选值并且你期望得到的结果为非可选值的序列时,使用 compactMap

let arr = [[1, 2, 3], [4, 5]]

let result = arr.map { $0 }
// [[1, 2, 3], [4, 5]]

let result = arr.flatMap { $0 }
// [1, 2, 3, 4, 5]


let arr = [1, 2, 3, nil, nil, 4, 5, nil]
//过滤nil可选值
let result = arr.compactMap { $0 } // [1, 2, 3, 4, 5]

let array2 = ["1", "2", "3", "four"]
let result2 = array2.compactMap{ Int($0) } // [1, 2, 3]

什么时候使用 flatMap

当对于序列中元素,转换闭包返回的是序列或者集合时,而你期望得到的结果是一维数组时,使用 flatMap

let scoresByName = ["Henk": [0, 5, 8], "John": [2, 5, 8]]

let mapped = scoresByName.map { $0.value }
// [[0, 5, 8], [2, 5, 8]] - An array of arrays
print(mapped)

let flatMapped = scoresByName.flatMap { $0.value }
// [0, 5, 8, 2, 5, 8] - flattened to only one array


let dict = ["one": "1", "two": "2"]
let result = dict["one"].flatMap{ Int($0) }// Optional(1)

filter函数

filter就是Sequence中默认提供的方法,允许调用者传入一个闭包来过滤集合中的元素:

//过滤数组中的奇数
let numbers = [3,2,4,5,6,8]
let oddNums = numbers.filter({ $0 % 2 != 0})
print(oddNums)//[3, 5]

for...Each

对于集合类型的元素,有时候不必要每次都通过for循环来去做遍历,Sequence同样提供了高阶函数来供我们使用:

let numberss = [3,2,4,6,8]
let oddNumss = numberss.forEach({print($0 + 1)})

大家猜一下,他的实现是啥?

那这个时候可能有人会问了,如果我想记录一下当前的元素index该如何去做,难道要重新设计一个变量,然后自己做累加吗?

No,我觉得Swift魅力也就在于此,函数式编程

Reduce 函数

合并集合中所有元素创建一个新值。

// MARK: reduce
let numbers2 = [1,2,3,4,5,6]
let result5 = numbers2.reduce(10, +)//10表示返回一个整数,初始值是10
print(result5)//打印31 10+1+2+3+4+5+6

源码:

  @inlinable
  public func reduce<Result>(//<Result>泛型
    _ initialResult: Result,//初始值,也就是第一个参数
    //第一个参数Result也决定了闭包表达式默认的参数
    _ nextPartialResult:
      (_ partialResult: Result, Element) throws -> Result//闭包表达式
  ) rethrows -> Result {
    var accumulator = initialResult//初始值给累加器
    for element in self {//遍历集合
        //对集合中的每一个元素执行闭包表达式操作
      accumulator = try nextPartialResult(accumulator, element)
    }
    return accumulator
  }

为了更好的理解 reduce 的工作原理,我们来试着实现一下 map , flatMap , filter 函数

func customMap(collection: [Int], transform: (Int) -> Int) -> [Int] {//方法返回Int数组
    return collection.reduce([Int]()){
        var arr: [Int] = $0
        arr.append(transform($1))
       return arr
    }
}

let result = customMap(collection: [1, 2, 3, 4, 5]) {
    $0 * 2
}

print(result)//[2, 4, 6, 8, 10]

当然这里因为参数是不可变的,所以我们总是需要转换一下,我们也可以借助下面这个函数来实现这样的方式

func customMap(collection: [Int], transform: (Int) -> Int) -> [Int] {
    return collection.reduce(into: [Int]()){
        $0.append(transform($1))
    }
}

let result = customMap(collection: [1, 2, 3, 4, 5]) {
    $0 * 2
}

print(result)

那么自然而然也可以通过这样的方式来实现 map 提供的功能

var arr = [1, 2, 3, 4, 5]

let result = arr.reduce([Int]()){
    var array = Array($0)
    array.append($1 * 2)
    return array
}

print(result)

所以下面的写法也是等价的

var arr = [1, 2, 3, 4, 5]

let resutl = arr.reduce(into: [Int]()) { (array, element) in
    array.append(element * 2)
}

print(resutl)

找数组中的最大值

let result = [1, 2, 3, 4, 5].reduce(0) {//初始值:0
   return  $0 < $1 ? $1 : $0
}

print("最大值:\(result)")

通过 reduce 函数逆序

let result = [1, 2, 3, 4, 5].reduce([Int]()){//初始值是空数组[Int](),返回[Int]() Int数组
    return [$1] + $0//$1:数组中的元素。 $0:第一个参数 是上一个数组。
}

print(result)

reduce第一个参数、闭包表达式的参数、返回值的参数是一致的。

求一个数组中偶数的平方和,使用高阶函数组合

let numbers2 = [1,2,3,4,5,6]
//方法一:使用高阶函数组合
let resSquare = numbers2.filter{
  $0 % 2 == 0//过滤
}.map{
  $0 * $0//偶数求平方
}.reduce(0){//尾随闭包
  $0 + $1
}
//方法二
let resSquare1: Int = numbers2.reduce(0){
  $1 % 2 == 0 ? $0 + $1 * $1 : $0
}
print(resSquare)
print(resSquare1)