为了账号安全,请及时绑定邮箱和手机立即绑定

技术专栏 | 集合管道模式(下)

标签:
Premiere

前一篇文章中,我们了解了集合管道:集合管道是一种编程模式,将一些计算转化为一系列操作,通常情况下每个操作的输出结果是一个集合,同时该结果作为下一个操作的输入,常见的操作主要有filter、map和reduce。今天我们继续了解集合管道模式的定义等。

二、定义

我认为集合管道是一种指导我们如何模块化和构建软件的模式。和多数模式一样,它经常出现在各种场景中,虽然对此我们习以为常,但是这种模式却别具一格。模式可以解决特定的设计问题,帮助设计者将新的设计建立在以往工作的基础上,复用以往成功的设计方案。

集合管道展示了一系列彼此间传递集合的操作,这些操作的输入输出都是集合,但是其中不包括终端操作,因为终端操作只会输出单个结果。个别的操作可能非常简单,但是你可以使用各种简单操作构造复杂的行为,想象一下现实世界中纵横交错的管道。

集合管道是管道过滤器模式的一个特例,管道过滤器中的过滤相当于集合管道中的操作,之所以没有使用 "过滤" 这个词语,因为 "过滤" 是一种常用的管道操作名称。从另一个角度看,集合管道是一种组成高阶函数的特殊方式,其中涉及的所有函数均作用于某种形式的数据结构,该模式没有确切的名称,需要使用一个新的术语。

操作彼此间传递的信息在不同的环境中有着不同的形式:

  • 在 Unix 中集合是一个由多行文本组成的文件,各种值通过空格连接组成了其中的行,每个值具体表示的含义依赖于行中的排序。管道操作符可以将某个操作的输出重定向到下个操作的输入,集合由管道操作符组成,操作在 Unix 中表示进程。

  • 在面向对象程序中集合用集合类表示,例如 list、array 和 set 等。集合中的每个元素都是对象,对象可以是普通类或集合类的实例。操作是集合类本身(或基类)中定义的各种方法,可以由方法链组成。

  • 在函数式语言中集合与面向对象语言有些类似,集合元素可以定义复杂的层次结构,操作是函数,可以通过嵌套或者使用形成线性表示的运算符组成,例如 Clojure 的箭头运算符。

这种模式也会出现在其它地方。当关系模型首次定义时,其假定所有数据都表示为数学上的关系,就是说n个集合的笛卡儿积的一个子集,数据可以通过关系演算和关系代数的一种方式来操作,你可以将其视作一个集合管道,操作中产生的中间集合被约束为关系。SQL最初作为关系数据库的标准语言而提出,而在实际上总是违背它。所以SQL DBMS实际上不是真正的RDBMS,并且当前ISO SQL标准不提及关系模型或者使用关系术语或概念,SQL使用了一种类似于推导的方式(稍后我会讨论)。

这样一系列转换的概念是软件构建中常见的方法,这也是管道过滤器模式的设计意图。编译器工作原理相似,将源码转换为语法树,途经各种优化,最后输出目标代码。 关于集合管道的区别:各阶段公用的数据结构是集合,最后限定一组特定的公共管道操作。

三、探索更多管道和操作之 map 和 reduce

到目前为止,涉及的是一些常用的管道操作,接下来通过 Ruby 事例代码,让我们来探索更多的操作。诚然使用其它支持该模式的语言,也会构造相同形式的管道。

统计单词总数(map 和 reduce)

webp

Markdown

通过统计所有文章单词总数的例子,让我来介绍下两个最重要的管道操作。

第一个 map:使用给定的 lambda 表达式,作用于输入集合的每个元素,将 lambda 表达式结果以集合的方式返回。

[1, 2, 3].map{|i| i * i} # => [1, 4, 9]

通过使用 map 将文章列表转换为每篇文章单词总数列表。

第二个 reduce:输入集合经过累计运算,最终输出单个结果。具有类似功能的任何函数都可以称作 Reduction,Reduction 在集合管道中总是以终结者的身份最后登场。通常情况下,可以使用两个入参的 lambda 表达式来定义 Ruby 中的 reduce 函数,一个入参是集合元素,一个是累加器。 在 reduce 的过程中,使用 lambda 表达式作用于每个元素,累加器会累计每次 lambda 的返回结果。接下来你可以这样求和:

[1, 2, 3].reduce {|acc, each| acc + each} # => 6

之后使用 map 和 reduce 构造两步操作的管道来统计单词总数。

some_articles
  .map{|a| a.words}
  .reduce {|acc, w| acc + w}

第一步使用 map 将文章列表转换为每篇文章单词数列表,第二步使用 reduce 累计求和。
在这点上,值得一提的是管道上的操作你可以使用不同的方式定义,上面使用的是 lambda,其实仅使用函数名称也是可以的,例如在 Clojure 中:

(->> (articles) 
     (map :words) 
     (reduce +))

该场景中,你只需要关注函数名称,对于 Ruby 也可以使用同样的风格:

some_articles 
    .map(&:words) 
    .reduce(:+)

通常情况下使用函数名称看上去更精炼,但是你会受限于函数的声明和调用方式。lambdas 可以提供更大的灵活性,但是你需要了解更多的语法。关于使用何种语言构造管道,如果 Ruby,我倾向于使用 lambda,如果 Clojure,则是函数名称。具体使用何种方式,你可以自由选择。

四、探索更多管道和操作之 group-by

统计每种类型的文章数(group-by)

webp



作者:TalkingData
链接:https://www.jianshu.com/p/b401eed53ba2


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消