Swift 4.2 Reduce和CoreLocation:总行驶距离🚗

在最近的一个项目中,我遇到了一个非常确定的问题,我敢肯定其他许多开发人员也会遇到:计算列表中存储的位置之间的总距离。 例如,假设您在由CLLocation对象列表表示的地图上有一条自定义路线。 您如何仅从CLLocation对象计算路线的总长度?
其背后的数学很简单,绝对没有理由写一个故事: 总距离等于列表中所有连接的位置对之间的距离之和 。 对于位置[A,B,C,D]的列表,可以表示为: | AD | = | AB | + | BC | + | CD |。
但是,可以使用我们位置对象上提供的函数distance(from :)以不同的方式完成该实现。

样板解决方案:

使用传统的面向对象方法,可以通过几个变量和位置迭代来解决此问题:

样板解决方案

该实现完全按照我们的预期工作,并且对于任何初看它的开发人员来说都是可读性和可理解性。 但是(没有双行中断),对于需要参考下一个/上一个元素以及将在集合迭代期间进行调整的变量的任何给定问题,最有可能重写这9行代码因此命名为样板解决方案)。

递归解决方案:

递归专家将很快意识到这是一个递归问题:我们为递归列表的一部分积累一个临时结果,并使用积累的结果对列表的其余部分执行相同的操作。 这就是为什么递归函数可以帮助我们节省几行样板代码的原因:

递归解决方案

但是仅通过查看递归函数就可以肯定地说,我们获得的一行代码(与样板解决方案相比)不值得在递归调用中引入一致性和简单性的损失-请记住,并非所有开发人员对递归同样感到高兴。

简化的解决方案:

如果我告诉过您所有的样板代码和递归代码都可以限制为一行代码怎么办? 一个纯粹的面向对象的开发人员可能不会相信我,但是实际上,这是可以实现的。
如果考虑我们在做什么,我们将在递归数据结构上应用组合函数(distance(from :)),并以递归方式使用函数的每次应用结果。
这正是函数编程中高阶函数折叠所做的定义¹。 幸运的是在Swift中,我们有一个类似的功能可以折叠: reduce(_:_:)函数reduce(_:_:) 。 reduce函数将初始结果和闭包作为参数,然后在返回最终结果之前使用闭包组合Sequence的每个元素。
最简单的示例是使用reduce函数对整数列表求和:

  [2,5,3,10] .reduce(0,+)//返回20 

但是,在我们的情况下,我们需要更加具体,因为+函数实际上不适用于CLLocation对象。 相反,我们希望应用distance(from:)函数。 为此,我们还需要引用列表中的上一个(或下一个)元素。 reduce函数不会自动为您提供下一个元素的访问权限(与其他任何迭代一样),因此,为了解决此问题,我们可以使用元组作为初始累加结果,第一个元素是累加距离,第二个元素是累加距离先前的CLLocation对象: (CLLocationDistance, CLLocation?) 。 location元素是可选的,因为在我们提供给reduce函数的初始结果中没有先前的元素。
通过使用该元组,我们可以通过减少距闭合的距离来返回一个新的累积元组,方法是增加与第一个元素的距离,并用当前位置替换location元素。 如果要从reduce函数中提取闭包,则定义将如下所示:

用于reduce函数的闭包示例

可以看出,闭包采用了一个元组,其中包含累积的距离和可选的先前位置以及当前到达的位置。 闭包中的guard语句可确保存在一个先前的位置,但是可以通过使用nil合并运算符在闭包中仅包含一行来进一步限制该位置:

使用nil合并运算符制作单线

这样的闭包可以轻松地用于reduce函数中

  location.reduce((0.0,nil),闭包).0 

请注意最后的.0 ,因为reduce函数返回一个元组,并且我们对其中的第一个元素(距离)感兴趣。

为了便于解释,仅以类似于上面的类型显示闭包,但在使用闭包时,reduce函数将处理所有上下文类型,我们不需要编写除元组引用之外的任何内容,如$0和current CLLocation对象为$1 。 因此,最终的reduce调用可以简化为以下一行:

一个非常简化的解决方案

将此解决方案与初始样板解决方案进行比较,我们发现我们已将所有代码限制为一行。 有人会认为这行对于新开发人员几乎是不可读的-我部分同意,但是在了解了reduce函数和元组之后,我声称不需要花很多时间就可以了解发生了什么。

蛋糕上的糖衣

为了锦上添花,我们可以使用易于访问的,很好的计算变量来扩展CLLocation对象的列表:

为经常使用此功能的开发人员锦上添花

最终,这使我们可以.totalDistance CLLocation对象的Array调用.totalDistance

结论

本文的目的是阐明如何使用像reduce这样的高阶函数来解决计算CLLocation对象列表中位置之间的总距离的问题。 提出了三种不同的解决方案后,读者可以自由地从任何一种解决方案中寻求灵感-但是,我个人的观点是,单行reduce函数是避免尽可能多的样板代码的正确选择。 无论如何,可以肯定地说reduce函数确实确实减少了代码行😉
请随时询问您是否有任何疑问或疑问。

资料下载

如果您要下载完整的操场以及所有示例和解决方案,请按此处完成。

编码愉快!

1如果您不熟悉折叠和其他高阶函数,我强烈建议您阅读一些有关它的文章-它将完全改变您对编程的看法。 由于我们是Swift开发人员,因此我建议您从 Luna An Swift快速入门开始
2 在像F#这样的功能语言中,reduce和fold之间有很小的区别,但是基本思想是相同的。