第3章:Map、Filter和Reduce

231 阅读5分钟

接受其他函数作为参数的函数有时被称为高阶函数 本章介绍几个Swift标准库中作用于数组的高阶函数、以及泛型的介绍

github

1: 泛型介绍

1.1: 我们通过几个简单的函数来了解泛型

///  对给定数组中的整型数据加1,生成新数组
func incrementArray(xs: [Int]) -> [Int] {
    var result: [Int] = []
    for x in xs {
        result.append(x + 1)
    }
    return result
}

/// 对给定数组中的整型数据翻倍,生成新数组
func doubleArray1(xs: [Int]) -> [Int] {
    var result: [Int] = []
    for x in xs {
        result.append(x * 2)
    }
    return result
}

上面两个函数相同有着大量的相同代码,我们对参数进行改变,用函数做为参数来重写函数,以达到相同目的

func computeIntArray(xs: [Int], transform: (Int) -> Int) -> [Int] {
    var result: [Int] = []
    for x in xs {
        result.append(transform(x))
    }
    return result
}

第二个函数就可以修改成这样了

func doubleArray2(xs: [Int]) -> [Int] {
    return computeIntArray(xs: xs) { x in x * 2 }
}

1.2: 我们再通过一个对比函数来更深入了解泛型

/// 用于求数组中的数据是否为偶数
func computeBoolArray(xs: [Int], transform: (Int) -> Bool) -> [Bool] {
    var result: [Bool] = []
    for x in xs {
        result.append(transform(x))
    }
    return result
}

computeIntArraycomputeBoolArray两个函数来看 这两个函数之间存在的大量的相同代码,对此需要整合

1.computeIntArray、computeBoolArray定义是相同的,唯一的区别在于类型签名

2.事实上相同部分的代码可以用于任何类型

3.所以我们应用泛型来处理这种情况

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

同理上面的函数中局限了输入数据必须为Int类型,所以我们再次都函数就行优化

// 对于任何Element的数组和transform:Element -> T函数,它都会生成一个T的新数组
func map0<Element, T>(xs: [Element], transform: (Element) -> T) -> [T] {
    var result: [T] = []
    for x in xs {
        result.append(transform(x))
    }
    return result
}

func genericComputeArray2<T>(xs: [Int], transform: (Int) -> T) -> [T] {
     return map0(xs: xs, transform: transform)
 }

1.3 将map定义为Array的扩展

按照Swift的惯例将map定义为Array的扩展会更合适

extension Array {
    func map<T>(transform: (Element) -> T) -> [T] {
        var result: [T] = []
        for x in self {
            result.append(transform(x))
        }
        return result
    }
}

func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
    return xs.map(transform: transform)
}

2: Filter

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

我们要从exampleFiles数组中找出包含.swift的字符串并由新数组组成新的数组

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

我们接下来也可能会去使用同样的函数去筛选.swift.md字符串 所以为了进行一个这样的查找,我们可以定义一个名为filter的通用型函数

extension Array {
    func filter(includeElement: (Element) -> Bool) -> [Element] {
        var result: [Element] = []
        for x in self where includeElement(x) {
            result.append(x)
        }
        return result
    }
}

所以上面的函数可以这么写

func getSwiftFiles2(files: [String]) -> [String] {
    return files.filter { file in file.hasSuffix(".swift") 
    }
}

3: Reduce

在一些像OCaml和Haskell一样的函数式语言中,reduce函数被称为fold或fold_left

3.1 我们先写几个简单函数来引入reduce函数

 // 定义一个计算数组中所有整型值之和的函数
 func sum(xs: [Int]) -> Int {
     var result: Int = 0
     for x in xs {
         result += x
     }
     return result
 }
 
 // 定义一个计算数组中所有项的相乘之积的函数
 func product(xs: [Int]) -> Int {
     var result: Int = 1
     for x in xs {
         result = x * result
     }
     return result
 }
 
 // 定义一个连接数组中所有字符串的函数
 func concatenate(xs: [String]) -> String {
     var result: String = ""
     for x in xs {
         result += x
     }
     return result
 }
 
 // 定义一个连接数组中所有字符串并插入一个单独的首行,以及在每一项后面追加一个换行符的函数
func prettyPrintArray(xs: [String]) -> String {
    var result: String = "Entries in the array xs:\n"
    for x in xs {
        result = " " + result + x + "\n"
    }
    return result
}

// 假设有一个数组,它的每一项都是数组,而我们想将它展开为一个单一数组
 func flatten<T>(xss: [[T]]) -> [T] {
     var result: [T] = []
     for xs in xss {
         result += xs
     }
     return result
 }

这些函数都有什么共同点呢?

1.将变量result初始化为某个值 2.对输入数组xs的每一项进行遍历 3.以某种方式更新结果

所以为Array扩展一个reduce方法

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

3.2 之前的几个函数就可以改成下面这样

func sumUsingReduce(xs: [Int]) -> Int {
    return xs.reduce(0) { result, x in result + x }
}

func productUsingReduce(xs: [Int]) -> Int {
    return xs.reduce(initial: 1, combine: *)
}

func concatUsingReduce(xs: [String]) -> String {
    return xs.reduce(initial: "", combine: +)
}

func flattenUsingReduce<T>(xss: [[T]]) -> [T] {
    return xss.reduce([]) { result, xs in result + xs }
}

当然我们也可以用reduce函数来重写map函数和filter函数

extension Array {
    // 应用reduce重写map函数
    func mapUsingReduce<T>(transform: (Element) -> T) -> [T] {
        return reduce([]) { result, x in
            return result + [transform(x)]
        }
    }
    
    // 应用reduce重写filter函数
    func filterUsingReduce(includeElement: (Element) -> Bool) -> [Element] {
        return reduce([]) { result, x in
            return includeElement(x) ? result + [x] : result
        }
    }
}

4: 实际应用

应用map、filter、reduce函数来筛选出居民数量至少有一百万的城市

struct City {
    let name: String
    let population: Int
}

extension City {
    func cityByScalingPopulation() -> City {
        return City(name: name, population: population * 1000)
    }
}
    let paris = City(name: "Paris", population: 2241)
    let madrid = City(name: "Madrid", population: 3165)
    let amsterdam = City(name: "Amsterdam", population: 827)
    let berlin = City(name: "Berlin", population: 3562)

首先将居民数量少于一百万的城市过滤掉 其次将剩下的结果通过cityByScalingPopulation进行map操作 最后用reduce函数构建一个包含城市名字和人口数量列表的string

let cities = [paris, madrid, amsterdam, berlin]

print(cities.filter { $0.population > 1000 }
            .map { $0.cityByScalingPopulation() }
            .reduce("City: Population") { result, c in
                return result + "\n" + "\(c.name): \(c.population)"
        })

5: 泛型和Any类型

Any类型和泛型两者都能用于定义接受两个不同参数的类型 区别在于: 泛型可以用于定义灵活的函数,类型检查由编译器负责 Any类型则可以避开Swift的类型系统

// noOp和noOpAny两者都将接受任意参数
// 关键的区别在于我们所知道的返回值
// noOp返回值和输入值一样
// noOpAny返回值则是任意类型--甚至可以是和原来的输入值不同的类型
func noOp<T>(x: T) -> T {
    return x
}

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

// example: noOpAny的错误定义
// 其结果可能导致各种各样的运行时错误,所以应少用
func noOpAnyWrong(x: Any) -> Any {
    return 0
}