使用reduce实现map和filter(swift、泛型和闭包)

tags: swift, 函数式, 闭包, 泛型

其实要拍脑袋直接写好像也不难。
但是还是尝试一步步分解地写下来,这样的话问题就更加有普遍性。
因为并不是所有问题都能一步到位地解决,重要的可能不是问题本身,而是解决问题的步骤和方式,这才是需要精深练习的地方。

Map

1、先写测试用例

假设我自己实现的map函数名为ptxMap,假如ptxMap的结果和sdk实现的map方法返回结果是相同的,就可以认为成功啦。

testcase1:

let testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let resultA = testArray.map() { $0 * 2 }
let resultB = testArray.ptxMap() { $0 * 2 }
assert(resultA == resultB, "two results should equal")
//[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
//testcase1 fail

2、根据测试用例编写第一个版本的代码

第一个可能觉得生涩的地方,是闭包作为参数传入。
关于闭包的语法介绍

其实可以认为闭包是一个匿名函数(虽然实际上在swift中Functions are a special case of closures)
然后本身作为高阶函数,map就是接收一个处理函数。

所以在定义ptxMap时,会传入一个dealFunc的闭包。
因为题目是用reduce来实现map,所以在方法体里调用Array的reduce方法,而reduce的处理函数在这里只是简单做一个往原始数组里面添加元素的操作。

第二个生涩的地方应该是swift的具体语法了,如通过self来调用reduce,还有尾部闭包的语法,然后因为Array本身支持泛型,所以append操作的时候需要转类型。

这样,第一个版本的ptxMap就完成了,测试用例1也能满足。

version one:

extension Array {
    func ptxMap(_ dealFunc: (Int) -> Int) -> [Int] {
        return self.reduce([]) { (result, element) -> [Int] in
            var tempResult = result
            tempResult.append(dealFunc(element as! Int))
            return tempResult
        }
    }
}
//testcase1 succeed

3、添加测试用例,测试可能会失败

平时写代码的时候,虽然先写了一些测试场景,并且也通过了。
但是写着写着,可能就想到新的测试用例了。
testcase1,输出为[Int]类型,所以ptxMap的返回类型也被写死了。
所以这个时候会想到假如输出的类型是由处理闭包决定的呢?
比如下面的testcase2,闭包返回String,ptxMap的返回类型应该为[String],此时testcase2肯定不能通过测试,那就改代码呗。

testcase2:

let resultA = testArray.map() { "\($0)" }
let resultB = testArray.ptxMap() { "\($0)" }
assert(resultA == resultB, "two results should equal")
//["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
//testcase2 fail

4、改进程序

既然要满足不同类型的返回,那么有两个方法:
1、函数重载。函数名相同的情况下,参数类型不同。
2、泛型。
如果选择第一种方法,那么返回类型就是有限的,而选择第二种方法,则能支持 满足当前泛型的类型约束的 所有类型
所以第二个版本是支持泛型的版本。

在ptxMap函数名后声明一个即将要用到的泛型类型T,代码如下。

version two:

extension Array {
    public func ptxMap<T>(_ dealFunc: (Element) -> T) -> [T] {
        return self.reduce([]) { (result, element) -> [T] in
            var tempResult = result
            tempResult.append(dealFunc(element))
            return tempResult
        }
    }
}
//testcase1 succeed
//testcase2 succeed

这样,testcase2也满足了,其实这里的testcase并不完整,还应该添加一些边界的testcase,像空数组调用等。
好了,map的实现大概就这样了。

延伸,闭包的调用方式

在我们这个例子里,因为ptxMap的参数只有一个,并且为闭包,所以就使用下面这样方式调用,这也是比较建议的调用方式。
而后面这几种,则在这个例子中不太推荐。
其他情况参考raywenderlich swift-style-guide

Preferred:

testArray.ptxMap { $0 * 2 } 

Not Preferred:

testArray.ptxMap() { $0 * 2 } 
testArray.ptxMap({ (value) -> Int in
    return value * 2
})
testArray.ptxMap({ $0 * 2 })

Filter和完整代码

下面的代码实现了ptxFilter,因为filter并不涉及到输出类型的变化,所以并没有用到泛型。
下面是完整的代码,测试用例使用了do{}的方式,目的是隔离每个测试用例。

import Foundation

let testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

extension Array {
    public func ptxMap<T>(_ dealFunc: (Element) -> T) -> [T] {
        return self.reduce([]) { (result, element) -> [T] in
            var tempResult = result
            tempResult.append(dealFunc(element))
            return tempResult
        }
    }
    
    public func ptxFilter(_ dealFunc: (Element) -> Bool) -> [Element] {
        return self.reduce([]) { (result, element) -> [Element] in
            var tempResult = result
            if dealFunc(element) {
                tempResult.append(element)
            }
            return tempResult
        }
    }
}

do {
    let resultA = testArray.map { $0 * 2 }
    let resultB = testArray.ptxMap { $0 * 2 }
    assert(resultA == resultB, "two results should equal")
    print(resultB)
}

do {
    let resultA = testArray.map { "\($0)" }
    let resultB = testArray.ptxMap { "\($0)" }
    assert(resultA == resultB, "two results should equal")
    print(resultB)
}

do {
    let resultA = testArray.filter { $0 % 2 == 0 }
    let resultB = testArray.ptxFilter { $0 % 2 == 0 }
    assert(resultA == resultB, "two results should equal")
    print(resultB)
}

总结

额,为啥要蛋疼写总结呢
首先,在这里栽过跟头,然后,以后所有的学习结果都会用总结的方式来梳理一遍。

最近还在刷算法和数据结构,额,有一些主题大学的时候并没有涉及到,这次也算还回来了,后面再梳理下。
其实解题的时候方法也挺重要的,先明确,然后思考当前的所有方案,选择复杂度相对好的方案,实现,然后一步步优化。
这跟写业务的过程区别不大,所以,有时候需要注重解决问题的方法,因为这不仅仅适用于当前的问题。

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

推荐阅读更多精彩内容