[03]-Map, Filter 和 Reduce

泛型(完整代码见文末)


当两个函数的主体逻辑即定义相同,只是参数/返回值的类型不同时,即类型签名(type signature)不同,可以使用泛型以使函数可以适用于每种可能的类型

逻辑部分: 有一个 Int 类型的数组 intArray,数组中的每个元素经过变形后,被添加到 result 数组中, 其中每个元素变形后的类型不确定,可能是 Int 或者 String 等,这里使用泛型 T 代表变形后的元素类型,以及 result 数组的元素类型。
关于这段代码,最有意思的是它的类型签名。理解这个类型签名有助于你将 genericComputeArray<T> 理解为一个函数族。类型参数 T 的每个选择都会确定一个新函数。 该函数接受一个整型数组和一个 (Int) -> T 类型的函数作为参数,并返回一个 [T] 类型的数组。

func genericComputeArray1<T>(intArray: [Int], transform: (Int) -> T) -> [T] {
    var result: [T] = []
    for x in intArray {
        result.append(transform(x))
    }
    return result
}

Map

再对数组进行抽象,数组的元素可以为任何类型,不一定是 Int

func map<Element, T>(anyArray: [Element], transform: (Element) -> T) -> [T] {
    var result: [T] = []
    for element in anyArray {
        result.append(transform(element))
    }
    return result
}

这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 类型的数组和 transform: (Element) -> T 函数,它都会生成一个 T 类型的新数组。这个 map 函数比我们之前看到的 genericComputeArray 函数更通用。
我们在函数的 transform 参数中所使用的 Element 类型源自于 Swift 的 Array 中对 Element 所进行的泛型定义。

// 实际上,比起定义一个顶层 map 函数,按照 Swift 的惯例将 map 定义为 Array 的扩展会更合适:
extension Array{
    func mapCustom<T>(transform: (Element) -> T) -> [T] {
        var result: [T] = []
        for element in self {
            result.append(transform(element))
        }
        return result
    }
    
}
[1,3].mapCustom { (element) -> Int in
    element + 1
}

我们建议遵循此规则,并把处理确定类型的函数定义为该类型的扩展。这样做的优点是自动补全更完善,暧昧的命名更少,以及 (通常) 代码结构更清晰。

Filter

假设我们有一个由字符串组成的数组,代表文件夹的内容。现在如果我们想要一个包含所有 .swift 文件的数组,可以很容易通过简单的循环得到:

let exampleFiles = ["README.md", "HelloWorld.swift", "FlappyBird.swift"]

func getSwiftFiles(files: [String]) -> [String] {
    var result: [String] = []
    for file in files {
        if file.hasSuffix(".swift") {
            result.append(file)
        }
    }
    return result
}

// 使用这个函数来取得 exampleFiles 数组中的 Swift 文件:
getSwiftFiles(files: exampleFiles)   // ["HelloWorld.swift", "FlappyBird.swift"]

我们可以定义一个名为 Filter 的通用型函数。就像之前看到的 map 那样, Filter 函数接受一个函数作为参数。 Filter 函数的类型是 (Element) -> Bool —— 对于数 组中的所有元素,此函数都会判定它是否应该被包含在结果中:

extension Array {
    func filterCustom(isIncludeElement: (Element) -> Bool) -> [Element] {
        var result: [Element] = []
        for element in self {
            if isIncludeElement(element) {
                result.append(element)
            }
        }
        return result
    }
}
let exampleResult = ["README.md", "HelloWorld.swift", "FlappyBird.swift"].filterCustom { (element) -> Bool in
      element.hasSuffix(".swift")
}

Reduce

// 定义一个计算 Int 类型数组中所有元素之和的函数:
func sumCustom(intArray: [Int]) -> Int {
    var result: Int = 0
    for number in intArray {
        result += number
    }
    return result
}

sumCustom(intArray: [1, 3, 4])  // 8

// 定义一个计算 Int 类型数组中所有元素之积的函数:
func produceCustom(intArray: [Int]) -> Int {
    var result: Int = 1
    for number in intArray {
        result *= number
    }
    return result
}
produceCustom(intArray: [1, 3, 4])  // 12

这些函数有什么共同点呢?它们都将变量 result 初始化为某个值。随后对输入数组 intArray 的每一项进行遍历,最后以某种方式更新 result 。为了定义一个可以体现所需类型的泛型函数,我们需要对两份信息进行抽象:赋给 result 变量的初始值,和用于在每一次循环中更新 result 的函数。

extension Array {
    func reduceCustom<T>(initial: T, combine:(T, Element) -> T) -> T {
        var result = initial
        for element in self {
            result = combine(result, element)
        }
        return result
    }
}

[1,3,4].reduceCustom(initial: 0) { (initial, element) -> Int in  initial + element }  // 8
[1,3,4].reduceCustom(initial: 1) { (initial, element) -> Int in  initial * element }  // 12
[1,3,4].reduceCustom(initial: 0, combine: +)  // 8
[1,3,4].reduceCustom(initial: 1, combine: *)  // 12

泛型和 Any 类型

除了泛型,Swift 还支持 Any 类型,它能代表任何类型的值。从表面上看,这好像和泛型极其相似。Any 类型和泛型两者都能用于定义接受两个不同类型参数的函数。然而,理解两者之间 的区别至关重要:泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而 Any 类型则 可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。

func noOpt<T>(x: T) -> T {
    return x
}

func noOptAny(x: Any) -> Any {
    return x
}

func noOpAnyWrong(x: Any) -> Any {
    return 0  // Error: 会报错
}

完整代码

import Foundation

/* 泛型 */
//当两个函数的主体逻辑即定义相同,只是参数/返回值的类型不同时,即类型签名(type signature)不同,可以使用泛型以使函数可以适用于每种可能的类型
//逻辑部分: 有一个 Int 类型的数组 intArray,数组中的每个元素经过变形后,被添加到 result 数组中, 其中每个元素变形后的类型不确定,可能是 Int 或者 String 等,这里使用泛型 T 代表变形后的元素类型,以及 result 数组的元素类型。
// 关于这段代码,最有意思的是它的类型签名。理解这个类型签名有助于你将 genericComputeArray<T> 理解为一个函数族。类型参数 T 的每个选择都会确定一个新函数。 该函数接受一个整型数组和一个 (Int) -> T 类型的函数作为参数,并返回一个 [T] 类型的数组。

func genericComputeArray1<T>(intArray: [Int], transform: (Int) -> T) -> [T] {
    var result: [T] = []
    for x in intArray {
        result.append(transform(x))
    }
    return result
}


/* Map */

//再对数组进行抽象,数组的元素可以为任何类型,不一定是 Int
func map<Element, T>(anyArray: [Element], transform: (Element) -> T) -> [T] {
    var result: [T] = []
    for element in anyArray {
        result.append(transform(element))
    }
    return result
}

//这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 类型的数组和 transform: (Element) -> T 函数,它都会生成一个 T 类型的新数组。这个 map 函数比我们之前看到的 genericComputeArray 函数更通用。
//我们在函数的 transform 参数中所使用的 Element 类型源自于 Swift 的 Array 中对 Element 所进行的泛型定义。
// 实际上,比起定义一个顶层 map 函数,按照 Swift 的惯例将 map 定义为 Array 的扩展会更合适:
extension Array{
    func mapCustom<T>(transform: (Element) -> T) -> [T] {
        var result: [T] = []
        for element in self {
            result.append(transform(element))
        }
        return result
    }
    
}
[1,3].mapCustom { (element) -> Int in
    element + 1
}

// 我们建议遵循此规则,并把处理确定类型的函数定义为该类型的扩展。这样做的优点是自动补全更完善,暧昧的命名更少,以及 (通常) 代码结构更清晰。

/* Filter */
// 假设我们有一个由字符串组成的数组,代表文件夹的内容。现在如果我们想要一个包含所有 .swift 文件的数组,可以很容易通过简单的循环得到:
let exampleFiles = ["README.md", "HelloWorld.swift", "FlappyBird.swift"]

func getSwiftFiles(files: [String]) -> [String] {
    var result: [String] = []
    for file in files {
        if file.hasSuffix(".swift") {
            result.append(file)
        }
    }
    return result
}

// 使用这个函数来取得 exampleFiles 数组中的 Swift 文件:
getSwiftFiles(files: exampleFiles)   // ["HelloWorld.swift", "FlappyBird.swift"]
// 我们可以定义一个名为 Filter 的通用型函数。就像之前看到的 map 那样, Filter 函数接受一个函数作为参数。 Filter 函数的类型是 (Element) -> Bool —— 对于数 组中的所有元素,此函数都会判定它是否应该被包含在结果中:
extension Array {
    func filterCustom(isIncludeElement: (Element) -> Bool) -> [Element] {
        var result: [Element] = []
        for element in self {
            if isIncludeElement(element) {
                result.append(element)
            }
        }
        return result
    }
}
let exampleResult = ["README.md", "HelloWorld.swift", "FlappyBird.swift"].filterCustom { (element) -> Bool in
      element.hasSuffix(".swift")
}

/*  Reduce */
// 定义一个计算 Int 类型数组中所有元素之和的函数:
func sumCustom(intArray: [Int]) -> Int {
    var result: Int = 0
    for number in intArray {
        result += number
    }
    return result
}

sumCustom(intArray: [1, 3, 4])  // 8

// 定义一个计算 Int 类型数组中所有元素之积的函数:
func produceCustom(intArray: [Int]) -> Int {
    var result: Int = 1
    for number in intArray {
        result *= number
    }
    return result
}
produceCustom(intArray: [1, 3, 4])  // 12

// 这些函数有什么共同点呢?它们都将变量 result 初始化为某个值。随后对输入数组 intArray 的每一项进行遍历,最后以某种方式更新 result 。为了定义一个可以体现所需类型的泛型函数,我们需要对两份信息进行抽象:赋给 result 变量的初始值,和用于在每一次循环中更新 result 的函数。
extension Array {
    func reduceCustom<T>(initial: T, combine:(T, Element) -> T) -> T {
        var result = initial
        for element in self {
            result = combine(result, element)
        }
        return result
    }
}

[1,3,4].reduceCustom(initial: 0) { (initial, element) -> Int in  initial + element }  // 8
[1,3,4].reduceCustom(initial: 1) { (initial, element) -> Int in  initial * element }  // 12
[1,3,4].reduceCustom(initial: 0, combine: +)  // 8
[1,3,4].reduceCustom(initial: 1, combine: *)  // 12


/* 泛型和 Any 类型*/
//除了泛型,Swift 还支持 Any 类型,它能代表任何类型的值。从表面上看,这好像和泛型极其相似。Any 类型和泛型两者都能用于定义接受两个不同类型参数的函数。然而,理解两者之间 的区别至关重要:泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而 Any 类型则 可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。
func noOpt<T>(x: T) -> T {
    return x
}

func noOptAny(x: Any) -> Any {
    return x
}

func noOpAnyWrong(x: Any) -> Any {
    return 0  // Error: 会报错
}



//          ___
//        //|||\\
//       //|-_-|\\
//          |||
//           |
//           |
//          / \


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容