函数式思想--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(<#T##initialResult: Result##Result#>, <#T##nextPartialResult: (Result, Int) throws -> 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<T>(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<T>(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<T>(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容