如何:在Swift中映射,缩小和过滤
由Reinder de Vries撰写于2017年5月3日在App Development中
Swift的编程功能Map,Reduce和Filter可能难以挑战。 特别是,如果您总是编写for循环来解决迭代问题!
Map,Reduce和Filter函数来自函数编程领域。 在Swift中,您可以使用Map,Reduce和Filter遍历诸如Array
和Dictionary
集合类型,而无需使用for循环。
在构建应用程序时,通常使用过程式或面向对象的编程方式。 函数式编程是不同的:它仅处理函数。 没有变量,没有“状态”,没有for循环-只是函数。
Swift编程语言非常适合于函数式编程。 但是,您无需严格地编写功能代码,只需采用功能编程中的概念(例如Map,Reduce和Filter)即可帮助您学习如何更好地编写代码。
Map,Reduce和Filter通常被称为高阶函数 ,因为这些函数将一个函数(一个闭包)作为输入,而将返回函数作为输出。 严格来说,Swift将在使用高阶函数时返回操作(即映射数组)的结果,而“纯”函数语言将返回函数的集合。
如果您在想:“伙计,我不需要功能编程或数据处理,因为我的应用程序不需要这样做!” —不要在这里停下来。
在最近的应用程序项目中,我多次使用过Map,Reduce和Filter:
- 过滤成本/收入值,使其达到一定阈值,然后再将其包含在折线图中
- 将成千上万的电影评分降低为一个平均评分
- 在此Photo App模板中的标签上映射一些操作(小写,删除“#”)
您当然可以通过for循环解决所有这些问题,但是您会发现使用map,reduce和filter函数可以使代码更简洁,可读性和性能更高。
在本指南中,您将学习如何使用地图,缩小和过滤功能。 您将对集合类型 (如Array
执行这些功能。
以下是简要概述:
-
map
函数遍历集合中的每个项目,并对集合中的每个元素执行操作 -
reduce
函数循环遍历集合中的每个项目,并将它们组合为一个值 -
filter
函数遍历集合中的每个项目,并返回仅包含满足包含条件的项目的集合
换句话说:
-
map
功能将功能应用于集合中的每个项目。 考虑将一组值“映射”到另一组值。 -
reduce
函数将一个集合变成一个值。 可以认为它实际上是将多个值减少为一个值。 -
filter
函数仅返回通过if
-statement条件且仅当该条件导致true
的值的数组。
有趣的事实: MapReduce是基本的大数据处理概念,其中对集合并行执行密集操作。 一个示例是将一本书的页面概括为一个单词(映射),然后将这些单词存储在字母框中(减少)。
跳至相关章节:
- 使用地图功能
- 使用减少功能
- 使用过滤功能
- 链接图,缩小和过滤
- 结论
准备? 我们走吧!
成为专业的iOS开发人员
开始使用iOS 12和Swift 4
注册我们的iOS开发课程“ 零到App Store”,以学习Swift 4的iOS开发,并开始您的专业iOS生涯。
了解更多 免费开始
map
函数遍历集合中的每个项目,并对集合中的每个元素进行操作。 它返回操作所应用到的结果项的数组。
让我们看一个例子。 假设您要转换为华氏温度,并在摄氏有一系列温度。
您可以使用for循环:
设celcius = [-5.0、10.0、21.0、33.0、50.0]
var fahrenheit:[Double] = []表示摄氏{
华氏度+ = [值*(9/5)+ 32]
}打印(华氏度)
//输出:[23.0、50.0、69.8、91.4、122.0]
尽管代码可以正常工作,但它不是最有效的。 您需要一个可变的“帮助程序”变量fahrenheit
来存储计算的转换,同时还要进行转换,而转换本身需要3行代码。
使用map
函数签出代码示例:
设celcius = [-5.0、10.0、21.0、33.0、50.0]
let华氏= celcius.map {$ 0 *(9/5)+ 32}
打印(华氏度)
//输出:[23.0、50.0、69.8、91.4、122.0]
这里会发生什么?
- 首先,定义一个常量
celcius
,其类型为Array
ofDouble
,并使用一些随机的Celcius值进行初始化。 - 其次,在常数
celcius
上调用函数map
。 该函数有一个参数,即Closure ,它从Celcius转换为Fahrenheit。 - 最后,结果被打印出来。
让我们仔细看一下闭包 。 如果您以前使用过闭包,那么您会认识到简写的闭包语法 。 这是编码闭包的一种较短方法,并且省去了闭包的大部分语法。
基本上,您只能使用输入,如$0
和方括号{ }
。 $0
是闭包的第一个输入参数, {
和}
表示闭包的开始和结束。 如果闭包具有多个参数,则第二个参数为$1
,第三个参数$1
$2
,以此类推。
这是“扩展”代码示例的样子:
let celcius = [-5.0,10.0,21.0,33.0,50.0] let fahrenheit = celcius.map({(value:Double)-> Double in
返回值*(9/5)+ 32
})print(华氏度)
实际的map
函数调用及其关闭是这样的:
... = celcius.map({(value:Double)-> Double in
返回值*(9/5)+ 32
})
如您所见, map
函数在数组celcius
上调用。 这个map
函数接受一个参数:闭包。
闭包的第一部分以{
开头,表示此闭包具有一个Double
类型的参数value
,并且应返回Double
类型的值。 以return
开头的闭包主体只是将Celcius的结果返回华氏转换。
如果将简写闭合语法与上面的扩展代码进行比较,则会看到:
- 省略函数括号
(
和)
,因为您可以在函数调用的最后一个参数为闭包时将其省略。 - 可以省略
() -> in
部分,因为Swift可以推断您使用一个Double
参数作为输入,并且期望返回Double
。 现在您已经省略了value
变量,可以使用缩写$0
。 - 还可以省略
return
语句,因为无论如何该闭合都将返回表达式的结果。
即使上面的代码示例使用Double
类型,您也不限于这些类型。 map
函数的结果类型可以与您输入的map
函数类型不同,并且还可以在Dictionary
上使用map
。
这是来自Photo App模板的更复杂的示例:
...
regex.matches(in:self,options:[],range:NSRange(location:0,length:self.characters.count))。map {
string.substring(with:$ 0.range).replacingOccurrences(of:“#”,with:“”).lowercased()
}
...
在此示例中,将映射正则表达式的结果,删除#
字符,然后将字符串转换为小写。
这就是您的map
功能!
reduce
函数循环遍历集合中的每个项目,并将它们组合为一个值。 可以认为它实际上是将多个值减少为一个值。
Reduce功能可能是最难理解的Map,Reduce,Filter。 您如何从一组价值转变为一个价值?
一些例子:
- 创建多个值的总和,即
3 + 4 + 5 = 12
- 连接字符串集合,即
["Zaphod", "Trillian", "Ford"] = "Zaphod, Trillian, Ford"
- 平均一组值,即
(7 + 3 + 10) / 3 = 7/3 + 3/3 + 10/3 = 6.667
在数据处理中,您可以想象出很多情况,例如像这样的简单操作会派上用场。 像以前一样,您可以使用for循环解决任何这些问题,但是reduce
会更短,更快。
这是您使用reduce
:
让值= [3,4,5]
让sum = values.reduce(0,+)
打印(总和)
//输出:12
函数reduce
带有两个参数,一个初始值和一个闭包。 如您在上面的示例中看到的,您可以使用诸如+
, -
或*
的简单表达式替换此闭包。
同样,您也可以提供自己的闭包,以及简化的闭包语法,如下所示:
让值= [7.0、3.0、10.0]
让avg:Double = values.reduce(0.0){$ 0 +($ 1 / Double(values.count))}
打印(平均)
//输出:6.667
在上面的示例中,您正在计算三个值7、3、10的平均值。 因为所有类型都需要匹配,所以values
数组, avg
常量和values.count
的类型均为Double
。
重要的是在此处注意,用于reduce
函数的闭包有两个参数。 第一个是先前减少的结果,即函数的输出,用$0
表示。 第二个是要减少的当前项,即当前迭代的输入,用$1
表示。
如果您“扩展”前面的示例,闭包的输入将变得更加清晰,例如:
让值= [7.0、3.0、10.0]
让avg = values.reduce(0.0,{(结果:Double,item:Double)-> Double in
返回结果+(item / Double(values.count))
})
打印(平均)
//输出:6.667
在上面的示例中,您可以看到两个闭包参数result
和item
,均为Double
类型。 这也清楚了为什么reduce
函数需要一个初始值-这是循环的第一次迭代的result
!
减少可能很难把握。 您正在对两个操作数(例如A + B
执行一个运算,但是怎么可能只有一个结果呢? 在集合中的所有项目中,哪些项目用作操作数?
看一下这个:
3 + 4 + 5 + 6
((3 + 4)+ 5)+ 6
3 + 4 = 7
7 + 5 = 12
12 + 6 = 18
在上面的示例中,您使用+
运算符添加数字列表。 在第二行上,您可以看到执行顺序,即首先执行3 + 4
。 在接下来的几行中,您可以看到3 + 4
的结果被加到第三项5
,结果为12
,用作12 + 6
的左侧操作数,结果为18
。
在Swift代码中,是这样的:
让值= [3、4、5、6]
让sum = values.reduce(0,+)
打印(总和)
//输出:18
在上面的示例中,第一次迭代需要初始值为0
: 0 + 3 = 3
。
filter
函数循环遍历集合中的每个项目,并返回仅包含满足包含条件的项目的集合。 这就像将if-语句应用于集合,而只获取那些将条件传递回来的语句。
看看这个例子:
let值= [11、13、14、17、21、33、22]
让偶数= values.filter {$ 0%2 == 0}
打印(偶数)
//输出:[14,22]
在上面的示例中,您要从偶数中选择数字。
表达式$0 % 2 == 0
使用余数运算符%
来确定$0
是否可被2
整除。 如果操作的余数为0
,则数字必须为偶数。
5%2 == 0
//输出:false16%2 == 0
//输出:true
5/2将1作为余数,而16/2将0作为余数,因此16必须是偶数。
您还可以看到上面的表达式返回Bool
,因此是true
或false
。 您可以假设filter
的闭包需要返回Bool
,然后仅返回通过条件的值数组,即返回true
。
这是前面的示例“扩展”,仅包括奇数 :
let值= [11、13、14、17、21、33、22]
让偶数= values.filter({(value:Int)-> Bool in
返回值%2!= 0
})打印(偶数)
//输出:[11、13、17、21、33]
在示例中,闭包应该返回一个布尔值,用-> Bool
表示。 它提供了一个参数(集合中的项目),并返回value % 2 != 0
。
您可以结合使用Map,Reduce和Filter功能吗? 你当然可以!
假设我们有一班学生,每个学生都知道他们的出生年份。 您想对2000年或之后出生的所有学生的年龄求和。
这是您的操作方式:
让年= [1989、1992、2003、1970、2014、2001、2015、1990、2000、1999]
let sum = years.filter({$ 0> = 2000})。map({2017-$ 0})。reduce(0,+)
打印(总和)
//输出:52
上面的代码示例使用chaining ,您可以在其中链接函数,并将一个函数的结果用作另一个函数的输入。
换句话说,在filter
函数的结果数组上调用map
函数,在map
函数的结果数组上调用reduce
函数。
代码本身非常简单:
- 首先,您将任意出生年份的数组声明为整数。
- 然后,使用
filter
功能过滤该数组。 如果该值大于或等于2000
,则它将包含在结果数组中。 在此必须注意,结果的大小小于年的大小,因为不包括1989
这样的值! - 然后,使用
map
功能对每个项目执行操作,计算学生在2017年的年龄。 - 最后,使用
+
操作将集合减小为一个值,创建所有年龄的总和。
成为专业的iOS开发人员
开始使用iOS 12和Swift 4
注册我们的iOS开发课程“ 零到App Store”,以学习Swift 4的iOS开发,并开始您的专业iOS生涯。
了解更多 免费开始
现在,如果要使用for循环编写所有代码,该怎么办? 您至少需要3个for循环,1个if语句和3套辅助集合变量。 使用Map,Reduce和Filter,您只需两行代码即可完成操作,而不会损失可读性和性能。 太棒了!
Map,Reduce和Filter函数在许多编程语言(包括Java,JavaScript,Ruby和PHP)中具有同样强大的实现。 学习如何使用函数式编程概念来处理数据可能是一个挑战,但它无疑会节省您的时间,并使您的代码更简洁,可读性和性能更高。
学到更多? 以下是有关Map,Reduce和Filter的Apple官方文档:
- 地图(_:)
- 降低(_:_:)
- 过滤(_:)