转载

函数式思想--Swift中Map、Filter、Reduce函数实现原理及仿写

原文

对于绝大多数的程序员来说接触更多的是面向对象和面向过程的方式进行思考。实际上还存在一种更高效的思考方式函数式。也许有很多人听过,但不一定使用过。Swift的语言特性能把函数式思想发挥到淋淋尽职,但是对于习惯了用OC开发在转向Swift语言的程序眼来说,很多人并没有意思到这一点。如果对函数式思想赶兴趣的话,推荐函数式Swift这本书。很难给出函数式一个准确的定义,只能说在函数式的世界中函数式中的函数不是一个函数,你可以把函数看成一个值,就像一般的常量或变量一样。如果习惯了这种思维方式,就会认识到什么是函数式。先从一个1+1的计算来介绍函数式思想,之后再说标准库函数Map Filter Reduce的实现原理,并仿写一个标准系统库的Map Filter Reduce函数。

一、从 “1+1” 表达式认识函数式

假设我们想通过一个方法计算两个数之和,通常会这样写:

func add(x: Int , y: Int) -> Int {
        return x + y
    }

这种方式是很容易想到的,也符合我们正常的思维逻辑,实际上除了这种写法,还有另一种函数式的写法,先看代码,然后我来说明这种写法是什么意思。

func add(x: Int) -> ((Int) -> (Int)) {
        return { y in return x + y }
    }

代码很简单,但是对于没接触函数式的人而言,还是需要仔细看才能看的懂。接下来请仔细看分析。对于a+b=c(1+1=2)计算两个数之和的这种算法,我们可以这样想假设a是一个已知数,b是一个未知数,这时只需要一个确定的函数,以及传入一个b,我们就能得到c。所以在上面的方法中会出现(Int) -> (Int)这个闭包,这个闭包我们就可以把它看做是一个函数,在a已知的情况下,传入一个数,我们就可以知道a加上这个未知数的结果。而上面一直提到的a就是add方法中的x。

第一个add方法的调用形式是add(1,2),对于第二个add方法的调用,通常是这种形式add(1)(2)。这两种调用形式的意义完全不同,第二种首先是传入1,返回一个闭包,然后将2作为参数传入到闭包中,最后得出结果。第二种调用形式的第二步是把:已知数+未知数=确定数 看做是一个函数进行处理的,这便是函数式视为方式。这种1+1=2的计算太简单,到目前为止也许你还是没有看到函数式思维的强大性,别急,请继续往看

二、温习标准库函数Map、Filter、Reduce的使用

这里就简单温习一下使用,不做过多解释,这不是重点,重点是要一步步仿写出系统库的Map、Filter、Reduce函数。直接看下面的温习代码

    func standardTest() {
        /*
         Map函数返回数组的元素类型不一定要与原数组相同
         Map还能返回判断数组中的元素是否满足某种条件的Bool值数组
         flatMap 与 map 不同之处是
         flatMap返回后的数组中不存在 nil 同时它会把Optional解包;
         flatMap 还能把数组中存有数组的数组 一同打开变成一个新的数组 ;
         flatMap也能把两个不同的数组合并成一个数组 这个合并的数组元素个数是前面两个数组元素个数的乘
        */
        let mapArray1:[Int] = [1,2,3,4]
        let mapArray2 = mapArray1.map { (number: Int) -> Int in
            return number + 1
        }
        //简单的写法  $0代表mapArray1中的每一个元素
        let mapArray3 = mapArray1.map{ $0 + 1 }
        print("标准库函数测试结果>>>>> mapArray2:/(mapArray2)")
        print("标准库函数测试结果>>>>> mapArray3:/(mapArray3)")


        //filter函数
        let filterArray1 = [1,2,3,4]
        let filterArray2 = filterArray1.filter { (number:Int) -> Bool in
            if number > 2 {
                return true
            }else {
                return false
            }
        }
        print("标准库函数测试结果>>>>> filterArray2:/(filterArray2)")


        //reduce函数  10是初始值 10 + 1 + 2 + 3 + 4
        let reduceArray1:[Int] = [1,2,3,4]
        //let sum = reduceArray1.reduce(,  Result##(Result, Int) throws -> Result#>)
        let reduceSum = reduceArray1.reduce(10) { (total, num) -> Int in
            return total + num
        }
        print("标准库函数测试结果>>>>> reduceSum:/(reduceSum)")
    }

三、标准库函数Map的实现原理

接下来我会一步步实现Map函数,先从简单的实现说起,然后在此基础上一步步的优化。我要分三步来实现并优化,先看第一步实现。

1、第一步简单实现

func customMap1(arr: [Int],transform: ((Int) -> (Int))) -> [Int] {
        var rs: [Int] = []
        for x in arr {
            rs.append(transform(x))
        }
        return rs
    }
//代码调用形式
let customMapResultArray1 = self.customMap1(arr: customMapArray1) { (num: Int) -> (Int) in
            return num + 1
        }

代码很少,实现起来的调用形式并非是系统库函数那种形式。不要着急,先从简单的说起,然后一步步的优化。注意customMap1方法中有一个transform这样一个闭包,这个闭包的功能实际上就是传入一个Int类型的数可以映射成另一个数,也就是函数式思维中的函数,这里是把这个函数当做参数处理的(这是函数式思维的本质)。

在customMap1方法中,首先是定义了一个[Int]类型数组,在遍历外部传入的arr数组的时候,执行了这样一句代码rs.append(transform(x)),transform(x)是一个Int类型的数值,因为transform是一个参数为Int类型,返回值为Int类型的闭包,这里我们只把闭包的返回值保存到rs数组中,至于tansform内部具体是怎么实现的,这个方法里面并不用管,只要把它当做参数来看即可。至于transform具体是怎样映射的,完全交由外部来决定,在调用的时候涉及映射规则,但是目前的规则就是Int类型映射成Int类型。上面的代码中映射规则是将原本的Int类型加1。

2、第二步优化

这一步实现,主要是借助Swift中的泛型语法,可以将Int类型的数组,映射成为任意类型的数组。实现代码如下:

func customMap2(arr: [Int],transform: ((Int) -> (T))) -> [T] {
        var rs: [T] = []
        for x in arr {
            rs.append(transform(x))
        }
        return rs
    }

和第一步相比,仅仅只要把第一步的实现中的返回值改为T即可。至于泛型语法,这里不做过多解释。虽然是可以将Int类型数组映射成为其他类型的数组,但是如果想把String类型的数组映射成为其他类型的数组,此时就并不能满足。如何实现,请看下一步优化。

3、最终优化成型

这一步优化,主要是借助extension来实现。实现代码如下:

extension Array{
    //Map的第三部优化
    func customMap(transform:((Element) -> T)) -> [T]{
        var rs:[T] = []
        for element in self {
            rs.append(transform(element))
        }
        return rs
    }
}

借助于extension我们可以实现任意类型的数组调用此方法,不仅仅局限于[Int]类型数组。extension是个好东西,是要好好利用。调用形式如下:

let customMapResultArray1 = customMapArray1.customMap { (num:Int) -> String in
            return "/(num)"
        }
        print("自定义Map函数测试结果>>>>> customMapResultArray1:/(customMapResultArray1)")

截止目前,我们就实现了标准系统库函数map的仿写。代码还是很简单的,主要是这种思维方式的转化,接下来看看Filter以及Reduce函数的实现。

四、标准库函数Filter的实现原理

有了上面对Map函数的认识,想实现Filter函数,只要简单的套用就行了。对于Filter函数,无非就是将:一个数值是否满摸个条件作为函数来处理,然后将这个函数看做一个参数。同样是在extension Array中实现代码如下:

//Filter的实现
    func customFilter(includeElement:(Element) -> Bool) -> [Element] {
        var rs:[Element] = []
        for x in self {
            if includeElement(x) == true {
                rs.append(x)
            }
        }
        return rs
    }
//调用形式
let customFilterResultArray1 = customMapArray1.customFilter { (num:Int) -> Bool in
            return num > 2
        }
        print("自定义Filter函数测试结果>>>>> customFilterResultArray1:/(customFilterResultArray1)")

五、标准库函数Reduce的实现原理

对于Reduce还是直接上代码吧。

//Reduce函数的实现
    func customReduce(initial:T, combine:(T,Element)->T) -> T {
        var rs = initial
        for x in self {
            rs = combine(rs, x)
        }
        return rs
    }

外部调用形式。

let sum = customMapArray1.customReduce(initial: 10) { (a:Int, b:Int) -> Int in
            return a + b
        }
        print("自定义Reduce函数测试结果>>>>> sum:/(sum)")

总结

函数式思考方式感觉还是很高效的,虽然不可能在实际开发中全部使用函数式来处理问题,但是在处理默写问题上,函数式思维方式绝对会简单很多。后期还会继续研究函数式,多多关注。上诉实现由代码,代码仅供简单参考。代码下载链接: https://github.com/ZhengYaWei1992/FunctionThinking1

作者:ZhengYaWei

链接:http://www.jianshu.com/p/6f45a6f99411

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

正文到此结束
Loading...