跳转至

高阶函数

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

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

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

Map

Collection中的每个元素进行转换,生成一个新的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函数

作用

  1. 对每个元素进行转换(类似 map)。
  2. 自动解包可选值(Optional),并过滤 nil(Swift 4.1+)。
  3. 扁平化嵌套数组(将 [[T]] 变成 [T])。

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]

源码

  @inlinable
  public __consuming func filter(
    _ isIncluded: (Element) throws -> Bool  //闭包表达式作为参数
  ) rethrows -> [Element] {
    return try _filter(isIncluded)
  }

  @_transparent
  public func _filter(
    _ isIncluded: (Element) throws -> Bool
  ) rethrows -> [Element] {
        // 声明一个空的集合
    var result = ContiguousArray<Element>()
        // 生成一个迭代器
    var iterator = self.makeIterator()
        // 遍历元素
    while let element = iterator.next() {
      // 闭包表达式做条件判断,符合需求加入到集合中
      if try isIncluded(element) {
        result.append(element)
      }
    }

    return Array(result)
  }

for...Each

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

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

源码

  @_semantics("sequence.forEach")
  @inlinable
  public func forEach(
    _ body: (Element) throws -> Void
  ) rethrows {
    // 这里的本质也是使用for...in,执行闭包表达式
    for element in self {
      try body(element)
    }
  }

如果想记录一下当前的元素`index,函数式编程

let numbers = [3, 2, 4, 6, 8]
numbers.enumerated().forEach { index, element in
    print("index: \(index), element: \(element)")
}
// 或者
numbers.enumerated().forEach { print("index: \($0.offset), element: \($0.element)") }

打印结果:

index: 0, element: 3
index: 1, element: 2
index: 2, element: 4
index: 3, element: 6
index: 4, element: 8

Reduce(归纳、简化)

  • 参数1:结果的类型和初始值
  • 参数2:闭包
  • 闭包参数1:前面元素处理之后的结果
  • 闭包参数2:下一个元素

源码:

  @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(into: [Int]()){
        $0.append(transform($1))
    }
}

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

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

那么自然而然也可以通过这样的方式来实现 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)

合并集合

// 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

找最大值

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

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

数组逆序

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)

排列组合

多个数组排列组合成为一个二维数组。

    /// 计算多个数组的笛卡尔积(排列组合),这是一个泛型函数,可以处理任何类型 T 的数组
    /// - Parameter arrays: 是一个二维数组,包含多个一维数组
    /// - Returns: 一个二维数组,包含所有可能的组合
    func cartesianProduct<T>(_ arrays: [[T]]) -> [[T]] {
        // 处理空数组或只有一个数组的情况
        guard arrays.count > 1 else {
            // 如果 arrays 为空,返回空数组 []
            // 如果 arrays 只有一个数组,返回该数组中每个元素单独组成的数组(如输入 [["a","b"]] 返回 [["a"], ["b"]])
            return arrays.isEmpty ? [] : arrays[0].map { [$0] }
        }
        // 使用reduce逐步计算笛卡尔积
        /// reduce([[]]) 从包含一个空数组的数组开始 [[]]
        /// partialResult 是累积的中间结果
        /// array 是当前正在处理的数组
        return arrays.reduce([[]]) { partialResult, array in
            // 对于已经计算出的部分结果,与当前数组中的每个元素组合
            partialResult.flatMap { partial in
                array.map { element in
                    // 将部分结果与当前元素组合
                   return partial + [element]
                }
            }
        }      
    }


// 使用示例
let colors = ["红", "黄", "绿", "蓝"]
let sizes = ["大", "小"]
let materials = ["棉", "涤纶"]

// 计算颜色、尺码和材质的组合
let allCombinations = cartesianProduct([colors, sizes, materials])
print("\n颜色、尺码和材质组合:")
allCombinations.forEach { print($0) }

打印结果:

["红", "大", "棉"]
["红", "大", "涤纶"]
["红", "小", "棉"]
["红", "小", "涤纶"]
["黄", "大", "棉"]
["黄", "大", "涤纶"]
["黄", "小", "棉"]
["黄", "小", "涤纶"]
["绿", "大", "棉"]
["绿", "大", "涤纶"]
["绿", "小", "棉"]
["绿", "小", "涤纶"]
["蓝", "大", "棉"]
["蓝", "大", "涤纶"]
["蓝", "小", "棉"]
["蓝", "小", "涤纶"]

原始数据: var colors = ["红", "黄", "绿", "蓝"] var sizes = ["大", "小"] var materials = ["棉", "涤纶"]

初始组合的结果: ["红", "大", "棉"] ["红", "大", "涤纶"] ["红", "小", "棉"] ["红", "小", "涤纶"] ["黄", "大", "棉"] ["黄", "大", "涤纶"] ["黄", "小", "棉"] ["黄", "小", "涤纶"] ["绿", "大", "棉"] ["绿", "大", "涤纶"] ["绿", "小", "棉"] ["绿", "小", "涤纶"] ["蓝", "大", "棉"] ["蓝", "大", "涤纶"] ["蓝", "小", "棉"] ["蓝", "小", "涤纶"]

删除了某些数据: ["红", "大", "棉"] ["红", "大", "涤纶"] ["红", "小", "棉"] ["红", "小", "涤纶"] ["黄", "小", "涤纶"]

则原始的数组变为: colors = ["黄", "绿", "蓝"] sizes = ["大", "小"] materials = ["棉", "涤纶"]

即"红"色的移除,"黄"的因为没有完全删除,所以还要保留。

// 1. 泛型笛卡尔积函数 (保持不变)
func cartesianProduct<T>(_ arrays: [[T]]) -> [[T]] {
        //代码见上面
}

// 2. 泛型版本的过滤和更新函数
func filterCombinationsAndUpdateSources<T: Hashable>(arrays: inout [[T]], excluded: [[T]]) {
    // 生成所有可能的组合
    let allCombinations = cartesianProduct(arrays)

    // 过滤掉排除的组合
    let remainingCombinations = allCombinations.filter { !excluded.contains($0) }

    // 检查每个原始数组中的元素是否还有剩余的组合
    for i in 0..<arrays.count {
        let elementsInCombinations = Set(remainingCombinations.map { $0[i] })
        arrays[i] = arrays[i].filter { elementsInCombinations.contains($0) }
    }
}

// 使用示例
var colorArray = ["红", "黄", "绿", "蓝"]
var sizeArray = ["大", "小"]
var materialArray = ["棉", "涤纶"]

// 要排除的组合
let excludedCombinations = [
    ["红", "大", "棉"],
    ["红", "大", "涤纶"],
    ["红", "小", "棉"],
    ["红", "小", "涤纶"],
    ["黄", "小", "涤纶"]
]

// 将原始数组组合成二维数组
var sourceArrays = [colorArray, sizeArray, materialArray]

// 调用泛型函数
filterCombinationsAndUpdateSources(arrays: &sourceArrays, excluded: excludedCombinations)

// 更新后的原始数组
colorArray = sourceArrays[0]
sizeArray = sourceArrays[1]
materialArray = sourceArrays[2]

print("更新后的颜色数组: \(colorArray)")  // ["黄", "绿", "蓝"]
print("更新后的尺码数组: \(sizeArray)")   // ["大", "小"]
print("更新后的材质数组: \(materialArray)") // ["棉", "涤纶"]