美文网首页iOS点点滴滴
Swift函数式编程三(Map、Filter和Reduce)

Swift函数式编程三(Map、Filter和Reduce)

作者: 酒茶白开水 | 来源:发表于2019-03-01 11:27 被阅读5次

    代码地址

    泛型介绍

    需求为写一个这样的函数,此函数接收一个参数为整型数组,返回一个一个新数组,新数组各项为原数组对应的数据加一。

    func incrementArray(array: [Int]) -> [Int] {
        var result: Array<Int> = []
        for i in array {
            result.append(i + 1)
        }
        
        return result
    }
    

    新增需求,再写一个函数,此函数接收一个参数为整型数组,返回一个一个新数组,新数组各项为原数组对应的数据两倍。

    func doubleArray(array: [Int]) -> [Int] {
        var result: [Int] = []
        for i in array {
            result.append(i*2)
        }
        
        return result;
    }
    

    至此,发现这两个函数有大量的代码相同,能不能写一个更通用的函数?新增一个参数接收一个函数,这个参数根据各个数组项计算新的值。

    func doubleArray(array: [Int]) -> [Int] {
        var result: [Int] = []
        for i in array {
            result.append(i*2)
        }
        
        return result;
    }
    

    这样就可以简化一点incrementArray、doubleArray这两个函数:

    func incrementArray1(array: [Int]) -> [Int] {
        return computeIntArray(array: array, transform: { x in x + 1 })
    }
    func doubleArray1(array: [Int]) -> [Int] {
        return computeIntArray(array: array, transform: { x in x*2 })
    }
    

    代码任然不够灵活,如果需要得到一个布尔型数组,用于表示对应的数字是否为偶数。

    func isEvenArray(array: [Int] -> [Bool]) {
        return computeIntArray(array: array, transform: { x in x%2 == 0 })
    }
    

    不幸的是上面这段代码无法使用computeIntArray函数,因为类型错误。

    于是可以定义一个新的函数接受一个Int -> Bool类型的函数作为参数。

    但是这个方案并不好,如果还要计算String类型,还得定义一个高阶函数来接受一个Int -> String类型的函数作为参数。

    幸运的是泛型可以解决这个问题。相同的代码可以适用任何类型,写一个适用于每种可能类型的泛型函数:

    func genericComputeArray<T>(array: [Int], transform: (Int) -> T) -> [T] {
        var result: [T] = []
        for i in array {
            result.append(transform(i))
        }
        
        return result
    }
    

    可以进一步一般化这个函数,没有理由仅能对[Int]型的输入数组进行处理,可以将数组类型也进行抽象:

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

    对于这个map函数在两个维度是通用的,任何类型的数组和transform函数。

    按照Swift的惯例将map函数定义为Array的扩展会比定义为顶层函数更合适:

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

    Element源于Swift的Array中对Element所进行的泛型定义。

    map函数已经是Swift标准库中的一部分(基于SequenceType协议被定义)

    顶层函数和扩展

    在一开始将创建map函数时,为了简便起见,选择了顶层函数的版本。不过最终将map的泛型版本定义为Array的扩展,这和Swift标准库的实现十分相似。

    随着协议扩展(protocol extensions),开发者有了强有力的工具来定义扩展--不仅可以在Array这样的具体类型上上进行定义,还可以在SequenceType这样的协议上定义扩展。

    把处理确定类型的函数,定义为该类型的扩展。这样的优点是:

    • 自动补全更完善
    • 命名更少
    • 代码结构更清晰

    Filter

    filter函数像之前定义的map函数一样,接收一个函数作为参数,这个参数类型是(Elemeng) -> Bool--对于数组中的所有元素,此函数判定它是否被包含在结果中:

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

    Swift标准库中的数组类型已经定义好了filter函数。

    有没有更通用的函数,可以用来定义map,也可以定义filter?

    Reduce

    reduce函数将变量初始化为某个值,然后对数组的每一项进行遍历,以某种方式更新结果。

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

    reduce函数的泛型体现在两个方面:

    • 对于任意类型的数组,它会计算一个T类型的返回值。
    • 需要一个T类型的初始值,以及一个用于更新for循环中变量值的函数combine: (T, Elemeng) -> T。

    使用reduce定义函数。除了使用闭包,也可以使用操作符作为最后一个参数,使代码更简短。

    func sumUsingReduce(xs: [Int]) -> Int {
        return xs.reduce(initial: 0, combine: { result, i in result + i })
    }
    func productUsingReduce(xs: [Int]) -> Int {
        return xs.reduce(initial: 1, combine: *)
    }
    func concatUsingReduce(xs: [String]) -> String {
        return xs.reduce(initial: "", combine: +)
    }
    

    甚至可以使用reduce重新定义map、filter。

    extension Array {
        func mapUsingReduce<T>(transform: (Element) -> T) -> [T] {
            return self.reduce(initial: [T](), combine: { result, i in result + [transform(i)] })
        }
        func filterUsingReduce(includeElement: (Element) -> Bool) -> [Element] {
            return self.reduce(initial: [Element](), combine: { result, i in includeElement(i) ? result + [i] : result })
        }
    }
    

    能够用reduce表示这些函数,说明了reduce能够通过通用的方法来体现一种常见的编程模式:遍历数组并计算结果。

    注意:
    使用reduce来定义一切非常的简便,但是实践中这往往不是一个好主意。原因是,代码在最终的运行期间大量复制生成的数组,换句话说,它不得不反复的分配内存释放内存以及复制内存中的内容。像之前那样用一个可变数组定义map显然效率更高。理论上,编译器可以优化代码使其速度和可变数组一样快,但是Swift2.0并没有做优化。

    实际运用

    假设有一个City结构体,由城市名称和人口(万)组成。并定义了一些城市示例。

    struct City {
        let name: String
        let population: Int
    }
    let beijing = City(name: "北京", population: 4000)
    let shanghai = City(name: "上海", population: 3500)
    let guangzhou = City(name: "广州", population: 3000)
    let shenzhen = City(name: "深圳", population: 2500)
    
    let citys = [beijing, shanghai, guangzhou, shenzhen]
    

    现在赛选出居民数量至少为3000万的城市,并打印一份这些城市名称及人口数的列表。

    extension City {
        func cityByScalingPopulation() -> City { return City(name: self.name, population: self.population*10000) }
    }
    
    let table = citys.filter(includeElement: { city in city.population >= 3000 }).map(transform: { city in city.cityByScalingPopulation() }).reduce(initial: "\n城市:人口\n", combine: { result, city in result + "\n\(city.name):\(city.population)\n"})
    

    泛型和Any类型

    Any类型和泛型都能定义接收不同类型的参数的函数。然而两者之间的重要区别是:

    • 泛型可以用于定义灵活的函数,类型检查仍由编译器负责。
    • Any类型直接避开了Swift的类型系统(尽可能避免使用)。

    用泛型和Any类型分别构造一个函数,除了返回它的参数,其他什么也不做。

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

    noOp和noOpAny函数都接收任意参数,关键区别在于返回值,noOp的返回值类型必须跟参数一样,而noOpAny的返回值可以为任何类型,甚至可以和参数的类型不同。如下函数noOpWrong会导致类型错误:

    func noOpWrong<T>(x: T) -> T { return 0 }
    func noOpAnyWrong(x: Any) -> Any { return 0 }
    

    泛型函数的类型十分丰富,考虑把上一篇Swift函数式编程二(封装Core Image)中的函数组合运算符>>>定义为泛型版本:

    precedencegroup ComposeFunctionPrecedence {
        associativity: left
    }
    infix operator >>>: ComposeFunctionPrecedence
    func >>><A, B, C>(f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C {
        return { x in g(f(x)) }
    }
    

    最后用相同的方式定义一个泛型函数,这个函数的作用是将接受两个参数作为输入的函数进行柯里化处理,生成相应的柯里化版本:

    func curry<A, B, C>(f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
        return { x in return { y in f(x, y) } }
    }
    

    使用泛型,能够在不牺牲类型安全的情况下写出灵活的函数;而使用Any类型,则无法办到。

    相关文章

      网友评论

        本文标题:Swift函数式编程三(Map、Filter和Reduce)

        本文链接:https://www.haomeiwen.com/subject/elzruqtx.html