从泛型到Map函数

filter函数适用于过滤操作, 方法体是一个返回值为bool的闭包
map 函数适用于遍历的操作
reduce 函数适用于对原来的数据进行给定一个初始值, 进行修改, 并返回修改后的值

假如我们需要写一个函数,它接受一个给定的整型数组,通过计算得到并返回一个新数组,新 数组各项为原数组中对应的整型数据加一。这一切,仅仅只需要使用一个 for 循环就能非常容 易地实现:

  func incrementArray(xs:[Int]) -> [Int] {
        var result: [Int] = []
        for x in xs {
            result.append(x + 1)
        }
        return result
    }

现在假设我们还需要一个函数,用于生成一个每项都为参数数组对应项两倍的新数组。这同样 能很容易地使用一个 for 循环来实现:

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

这两个函数有大量相同的代码,我们能不能将没有区别的地方抽象出来,并单独写一个体现这
种模式且更通用的函数呢?像这样的函数需要追加一个新参数来接受一个函数,这个参数能根据各个数组项计算得到新的整型数值:

 func computeIntArray(xs: [Int], transform: (Int) -> Int ) -> [ Int ] {
        var result: [Int] = []
        for x in xs {
            result.append(transform(x))
        }
        return result
    }

现在,取决于我们想如何根据原数组得到一个新数组,我们可以向函数传递不同的参数。 doubleArray 函数和 incrementArray 函数都精简为了一行调用 computeIntArray 的语句:

  func doubleArray2(xs:[Int]) -> [Int] {
        return computeIntArray(xs:xs) {
            x in x * 2
        }
    }

代码仍然不像想象中的那么灵活。假设我们想要得到一个布尔型的新数组,用于表示原数组中
对应的数字是否是偶数。我们可能会尝试编写一些像下面这样的代码:

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

不幸的是,这段代码导致了一个类型错误。问题在于,我们的 computeIntArray 函数接受一个 Int -> Int 类型的参数,也就是说,该参数是一个返回整型值的函数。而在 isEvenArray 函数的 定义中,我们传递了一个 Int -> Bool 类型的参数,于是导致了类型错误。
我们应该如何解决这个问题呢?一种可行方案是定义新版本的 computeIntArray 函数,接受一 个 Int -> Bool 类型的函数作为参数。类似下面这样:

 func computeBoolArray(xs: [Int], transform: (Int) -> Bool) -> [Bool] {
        var result: [Bool] = []
        for x in xs {
            result.append(transform(x))
        }
        return result
    }

但是,这个方案的扩展性并不好。如果接下来需要计算 String 类型呢?我们是否还需要定义另 一个高阶函数来接受 Int -> String 类型的参数?

幸运的是,该问题有一个解决方案:我们可以使用泛型。computeBoolArray 和
computeIntArray 的定义是相同的;唯一的区别在于类型签名 (type signature)。假如我们定 义一个相似的函数 computeStringArray 来支持 String 类型,其函数体将会与先前两个函数完

全一致。事实上,相同部分的代码可以用于任何类型。我们真正想做的是写一个能够适用于每
种可能类型的泛型函数:

 func genericComputeArray1<T>(xs: [Int], transform: (Int) -> T) -> [T] {
        var result: [T] = []
        for x in xs {
            result.append(transform(x))
        }
        return result
    }

关于这段代码,最有意思的是它的类型签名。理解这个类型签名有助于你将 genericComputeArray<T> 理解为一个函数族。类型参数 T 的每个选择都会确定一个新函数。 该函数接受一个整型数组和一个 Int -> T 类型的函数作为参数,并返回一个 [T] 类型的数组。

我们仍能进一步将这个函数一般化。没有理由让它仅能对类型为 [Int] 的输入数组进行处理。 将数组类型进行抽象,能得到下面这样的类型签名:

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

这里我们写了一个 map 函数,它在两个维度都是通用的:对于任何 Element 的数组和 transform: Element -> T 函数,它都会生成一个 T 的新数组。这个 map 函数甚至比我们之前 看到的 genericComputeArray 函数更通用。事实上,我们可以通过 map 来定义 genericComputeArray:

 func genericComputeArray2<T>(xs: [Int], transform: (Int) -> T) -> [T] {
        return map(xs, transform: transform)
    }

同样的,上述函数的定义并没有什么太过特别之处:函数接受 xs 和 transform 两个参数之后, 将它们传递给 map 函数,然后返回结果。关于这个定义,最有意思非类型莫属。 genericComputeArray(_:transform:) 是 map 函数的一个实例,只是它有一个更具体的类型。
实际上,比起定义一个顶层 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 }
    }

我们在函数的 transform 参数中所使用的 Element 类型源自于 Swift 的 Array 中对 Element
所进行的泛型定义。
作为 map(xs, transform) 的替代,我们现在可以通过 xs.map(transform) 来调用 Array 的
map 函数:

   func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
        return xs.map(transform)
    }

想必你会很乐意听到其实并不需要自己像这样来定义 map 函数,因为它已经是 Swift 标准库的 一部分了

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

推荐阅读更多精彩内容

  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,656评论 1 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,275评论 18 399
  • 一、RN最先适配iOS,对iOS底层进行包装。所以她的性能和原生是一致的 二、环境安装步骤: http://mp....
    yuzhan550阅读 318评论 0 0
  • 中国人不过春节,感觉就是没有迈进新年。过完年开学的第一班会,我选择了让学生写给明年自己的一封信。 2016年比较短...
    矮穷但有才阅读 10,107评论 2 1
  • 今天我又去看班了,跟这么多孩子们在一起,又体会了一下当老师的滋味。 下午第三节课,班主任王老师拿着数学卷子进教室了...
    俩千金的妈阅读 128评论 0 0