函数式Swift3 - Map,Reduce和Filter

本文是一个系列,是函数式Swift的读书笔记(其实是为了备忘)

接受其它函数作为参数的函数有时被称为高阶函数。在 Swift 标准库中就有几个作用于数组的高阶函数

1. 泛型介绍
//有如下需求:让数组中元素加一
 func increment(array:[Int]) -> [Int] {
        var result:[Int] = []
        for x in array {
            result.append(x+1)
        }
        return result
    }

    // 又有如下需求
    func double(array:[Int]) -> [Int] {
        var result:[Int] = []
        for x in array {
            result.append(x*2)
        }
        return result
    }

  //上边的写法有大量的重复代码,可以用swift的写法抽象出来:
    func compute(array:[Int],transform:(Int)->Int) -> [Int] {
        var result:[Int] = []
        for x in array{
            result.append(transform(x))
        }
        return result 
    }

  //那么,现在,上边的double方法的实现方式如下
  func double2(array:[Int]) -> [Int] {
        return compute(array: array, transform: {
            $0*2
        })
    }
现在我们想要得到一个bool型的新数组,代码如下
 func isEven(array:[Int]) -> [Bool] {
        return compute(array: array, transform: {
            $0 % 2 == 0 
        })
    }
    //报类型错误, 因为compose函数接受的是(Int)->Int 类型,但是我们给了(Int)->Bool类型。

// 一种方案是 定义一个新的函数
  func computeBool(array:[Int],transform:(Int)->Bool) -> [Bool] {
        var result:[Bool] = []
        for x in array{
            result.append(transform(x))
        }
        return result
    }
 //但是如果我们需要一个[String]类型的数组,又要重新定义一个函数。

// 这种问题,可以用泛型解决
  func genericCompute<T>(array: [Int], transform: (Int) -> T) -> [T] {
        var result: [T] = []
        for x in array {
            result.append(transform(x))
        }
        return result
    }

//没有理由array数组都是[Int]类型的,所以可以继续优化
  func genericCompute<Element,T>(array: [Element], transform: (Element) -> T) -> [T] {
        var result: [T] = []
        for x in array {
            result.append(transform(x))
        }
        return result
    }
//“这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 的数组和 transform: (Element) -> T 函数,它都会生成一个 T 的新数组
实际上,比起定义一个顶层的map函数,按照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
        }
    }
//不需要自己像这样来定义 map 函数,因为它已经是 Swift 标准库的一部分了 (实际上,它基于 Sequence 协议被定义,会在之后关于迭代器和序列的章节中提到)。

//本章的重点并不是说你应该自己定义 map;我们只是想要告诉你 map 的定义中并没有什么复杂难懂的魔法 
2. 顶层函数与拓展

//本章开始,我们用顶层函数来作为例子展示,为了简单起见。
// 最终我们将map定义为Array的扩展,这与Swift的标准库的实现方式相似。

//随着swift的发展,协议扩展的支持,移除了使用顶层函数的方式, 我们在array这样的具体类型上定义扩展,还可以在Sequence这样的 协议上定义扩展。

// 建议遵循此规则,并把处理确定类型的函数定义为该类型的扩展。这样做的优点是自动补全更完善,有歧义的命名更少,代码结构更清晰。

3. Filter

// filter 函数接受一个函数作为参数。filter 函数的类型是 (Element) -> Bool —— 对于数组中的所有元素,此函数都会判定它是否应该被包含在结果中

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

let exampleFiles = ["readMe.md","helloworld.swift","flappybird.swift"]

func getSwiftFile(in file:[String])->[String]{
  return files.filter{
      file in
          file.hasSuffix(".swift")
  }
}
//Swift标准库中有filter,除非联系,没必要重写它。
4.Reduce
例子
    func sum(integers:[Int]) -> Int {
        var result: Int = 0
        for x in integers {
            result += x
        }
        return result
    }
    //抽向: 给一个初始值,然后便利数组更新初始值。

extension Array{
    func reduce<T>(_ initial: T, combine:(T,Element)->T) -> T {
        var result = initial
        for x in self {
            result = combine(retult,x)
        }
        return result
    }
}
//对于任意 [Element] 类型的输入数组来说,它会计算一个类型为 T 的返回值。这么做的前提是,首先需要一个 T 类型的初始值 (赋给 result 变量),以及一个用于更新 for 循环中变量值的函数 combine: (T, Element) -> T

reduce 通过常见通用的方法体现一个常见的编程模式:便利数组并计算结果。

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

// 我们也可以使用reduce来重新定义map 和filter
extension Array{
        //map
    func mapUsingReduce<T>(_ transform:(Element)->T) -> [T] {
        return reduce([], combine: {
             result , x in
                result + [transform(x)]
        })
    }
  
 // filter
   func filterUsingReduce(_ includeElement:(Element)->Bool) -> [Element] {
        return reduce([], combine: {
            result, x  in
            includeElement(x) ? result+[x] : result
        })
    }
}

5. 实际应用
struct City {
    let name:String
    let population:Int
}

let paras = City(name: "Paras", population: 2214)
let madrid = City(name: "Madirid", population: 3165)
let beijing = City(name: "Beijing", population: 5000)
let nanjing = City(name: "Nanjing", population: 850)

let citys = [paras,madrid,beijing,nanjing]

extension City{
    func scalingPopulation() -> City {
        return City(name: name, population: population*1000)
    }
}

citys.filter{$0.population>1000}
    .map{$0.scalingPopulation()}
    .reduce("City:Population"){result,city in return result + "\n" + "\(city.name):\(city.population)"}
// 先filter, 挑选, 再map,所有的都执行scaling方法,然后在reduce,打印出来。
6. 泛型和Any类型

Any 类型和泛型两者都能用于定义接受两个不同类型参数的函数。

然而,理解两者之间的区别至关重要:泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而 Any 类型则可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。

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

func noOpAny(_ x:Any)->Any{
      return x
}
//在 noOp 的定义中,我们可以清楚地看到返回值和输入值完全一样。而 noOpAny 的例子则不太一样,返回值是任意类型 — 甚至可以是和原来的输入值不同的类型。

使用泛型允许你无需牺牲类型安全就能够在编译器的帮助下写出灵活的函数

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

推荐阅读更多精彩内容