FunDA(4)- 数据流内容控制:Stream data element control
来源:互联网 发布:nginx sticky 编辑:程序博客网 时间:2024/05/21 22:26
上节我们探讨了通过scalaz-stream-fs2来驱动一套数据处理流程,用fs2的Pipe类型来实现对数据流的逐行操作。本篇讨论准备在上节讨论的基础上对数据流的流动和元素操作进行优化完善。如数据流动中增加诸如next、skip、eof功能、内容控制中增加对行元素的append、insert、update、remove等操作方法。但是经过一番对fs2的再次解读,发现这些操作模式并不像我所想象那样的方式,实际上用fs2来实现数据行控制可能会更加简单和直接。这是因为与传统数据库行浏览方式不同的是fs2是一种拖式流(pull-model stream),它的数据行集合是一种泛函不可变集合。每一行一旦读取就等于直接消耗了断(consumed),所以只支持一种向前逐行读取模式。如果形象地描述的话,我们习惯的所谓数据集浏览可能是下面这样的场景:
读取一行数据 >>> (使用或更新行字段值)>>> 向下游发送新的一行数据。只有停止发送动作才代表终止运算。完成对上游的所有行数据读取并不代表终止操作,因为我们还可以不断向下游发送自定义产生的数据行。
我们用fs2模拟一套数据流管道FDAPipeLine,管道中间有不定数量的作业节点FDAWorkNode。作业方式包括从管道上游截取一个数据元素、对其进行处理、然后选择是否向下游的管道接口(FDAPipeJoint)发送。下面是这套模拟的类型:fdapipes/package.scala
package com.bayakala.funda { import fs2._ package object fdapipes { //数据行类型 trait FDAROW //数据处理管道 type FDAPipeLine[ROW] = Stream[Task, ROW] //数据作业节点 type FDAWorkNode[ROW] = Pipe[Task, ROW, ROW] //数据管道开关阀门,从此处获得管道内数据 type FDAValve[ROW] = Handle[Task, ROW] //管道连接器 type FDAPipeJoint[ROW] = Pull[Task, ROW, Unit] //作业类型 type FDATask[ROW] = ROW => Option[List[ROW]] }}
流动控制方法:FDAValves.scala
package com.bayakala.funda.fdapipesimport fs2._object FDAValves { //流动控制方法//跳过本行(不向下游发送) def fda_skip[ROW] = Some(List[ROW]())//将本行发送至下游连接管道 def fda_next[ROW](r: ROW) = Some(List[ROW](r))//终止流动 def fda_break = None}
数据发送方法:FDAPipes.scala
package com.bayakala.funda.fdapipesimport fs2._object FDAJoints { //数据发送方法//write rows down the pipeline def fda_pushRow[ROW](row: ROW) = Pull.output1(row) def fda_pushRows[ROW](rows: List[ROW]) = Pull.output(Chunk.seq(rows))}
作业节点工作方法:
package com.bayakala.funda.fdapipesimport FDAJoints._object FDANodes { //作业节点工作方法 def fda_execUserTask[ROW](task: FDATask[ROW]): FDAWorkNode[ROW] = { def go: FDAValve[ROW] => FDAPipeJoint[ROW] = h => { h.receive1Option { case Some((r, h)) => task(r) match { case Some(xr) => xr match { case Nil => go(h) case _ => fda_pushRows(xr) >> go(h) } case None => fda_halt } case None => fda_halt } } in => in.pull(go) }}
设置示范环境:
package com.bayakala.funda.fdapipes.examplesimport fs2._import com.bayakala.funda.fdapipes._import FDANodes._import FDAValves._import Helpers._object Example1 extends App { case class Employee(id: Int, name: String, age: Int, salary: BigDecimal) extends FDAROW// test data set val r1 = Employee(1, "John", 23, 100.00) val r2 = Employee(2, "Peter", 25,100.00) val r3 = Employee(3, "Kay", 35,100.00) val r4 = Employee(4, "Cain", 45,100.00) val r5 = Employee(5, "Catty", 35,100.00) val r6 = Employee(6, "Little", 19,80.00)
注意Employee是一种行类型,因为它extends FDAROW。
我们再写一个跟踪显示当前流动数据行的函数:examples/Helpers.scala
package com.bayakala.funda.fdapipes.examplesimport com.bayakala.funda.fdapipes._import fs2.Taskobject Helpers { def log[ROW](prompt: String): FDAWorkNode[ROW] = _.evalMap {row => Task.delay{ println(s"$prompt> $row"); row }}}
下面我们就用几个有不同要求的例子来示范流动控制和数据处理功能,这些例子就是给最终用户的标准编程示范版本,然后由用户照版编写:
1、根据每条数据状态逐行进行处理:
// 20 - 30岁加10%, 30岁> 加20%,其它加 5% def raisePay: FDATask[FDAROW] = row => { row match { case emp: Employee => { val cur = emp.age match { case a if ((a >= 20) && (a < 30)) => emp.copy(salary = emp.salary * 1.10) case a if ((a >= 30)) => emp.copy(salary = emp.salary * 1.20) case _ => emp.copy(salary = emp.salary * 1.05) } fda_next(cur) } case _ => fda_skip } }
用户提供的功能函数类型必须是FDATask[FDAROW]。类型参数FDAROW代表数据行通用类型。如果用户指定了FDATask[Employee]函数类型,那么必须保证管道中流动的数据行只有Employee一种类型。完成对当前行数据的处理后用fda_next(emp)把它发送到下一节连接管道。我们用下面的组合函数来进行运算:
Stream(r1,r2,r3,r4,r5,r6) .through(log("加薪前>")) .through(fda_execUserTask[FDAROW](raisePay)) .through(log("加薪后>")) .run.unsafeRun-----运算结果:加薪前>> Employee(1,John,23,100.0)加薪后>> Employee(1,John,23,110.00)加薪前>> Employee(2,Peter,25,100.0)加薪后>> Employee(2,Peter,25,110.00)加薪前>> Employee(3,Kay,35,100.0)加薪后>> Employee(3,Kay,35,120.00)加薪前>> Employee(4,Cain,45,100.0)加薪后>> Employee(4,Cain,45,120.00)加薪前>> Employee(5,Catty,35,100.0)加薪后>> Employee(5,Catty,35,120.00)加薪前>> Employee(6,Little,19,80.0)加薪后>> Employee(6,Little,19,84.000)
// 筛选40岁以上员工 def filter40: FDATask[FDAROW] = row => { row match { case emp: Employee => { if (emp.age > 40) Some(List(emp)) else fda_skip[Employee] } case _ => fda_break } } println("---------") Stream(r1,r2,r3,r4,r5,r6) .through(log("年龄>")) .through(fda_execUserTask[FDAROW](filter40)) .through(log("合格>")) .run.unsafeRun---运算结果:年龄>> Employee(1,John,23,100.0)年龄>> Employee(2,Peter,25,100.0)年龄>> Employee(3,Kay,35,100.0)年龄>> Employee(4,Cain,45,100.0)合格>> Employee(4,Cain,45,100.0)年龄>> Employee(5,Catty,35,100.0)年龄>> Employee(6,Little,19,80.0)-
3、根据当前数据行状态终止作业:
// 浏览至第一个30岁以上员工,跳出 def stopOn30: FDATask[Employee] = emp => { if (emp.age > 30) fda_break else Some(List(emp)) } println("---------") Stream(r1,r2,r3,r4,r5,r6) .through(log("当前员工>")) .through(fda_execUserTask[Employee](stopOn30)) .through(log("选入名单>")) .run.unsafeRun---运算结果:当前员工>> Employee(1,John,23,100.0)选入名单>> Employee(1,John,23,100.0)当前员工>> Employee(2,Peter,25,100.0)选入名单>> Employee(2,Peter,25,100.0)当前员工>> Employee(3,Kay,35,100.0)
我们还可以把多个功能串接起来。像下面这样把1和2两个功能连起来:
Stream(r1,r2,r3,r4,r5,r6) .through(log("加薪前>")) .through(fda_execUserTask[FDAROW](raisePay)) .through(log("加薪后>")) .through(log("年龄>")) .through(fda_execUserTask[FDAROW](filter40)) .through(log("合格>")) .run.unsafeRun---运算结果:加薪前>> Employee(1,John,23,100.0)加薪后>> Employee(1,John,23,110.00)年龄>> Employee(1,John,23,110.00)加薪前>> Employee(2,Peter,25,100.0)加薪后>> Employee(2,Peter,25,110.00)年龄>> Employee(2,Peter,25,110.00)加薪前>> Employee(3,Kay,35,100.0)加薪后>> Employee(3,Kay,35,120.00)年龄>> Employee(3,Kay,35,120.00)加薪前>> Employee(4,Cain,45,100.0)加薪后>> Employee(4,Cain,45,120.00)年龄>> Employee(4,Cain,45,120.00)合格>> Employee(4,Cain,45,120.00)加薪前>> Employee(5,Catty,35,100.0)加薪后>> Employee(5,Catty,35,120.00)年龄>> Employee(5,Catty,35,120.00)加薪前>> Employee(6,Little,19,80.0)加薪后>> Employee(6,Little,19,84.000)年龄>> Employee(6,Little,19,84.000)
下面我把完整的示范代码提供给大家:
package com.bayakala.funda.fdapipes.examplesimport fs2._import com.bayakala.funda.fdapipes._import FDANodes._import FDAValves._import Helpers._object Example1 extends App { case class Employee(id: Int, name: String, age: Int, salary: BigDecimal) extends FDAROW// test data set val r1 = Employee(1, "John", 23, 100.00) val r2 = Employee(2, "Peter", 25,100.00) val r3 = Employee(3, "Kay", 35,100.00) val r4 = Employee(4, "Cain", 45,100.00) val r5 = Employee(5, "Catty", 35,100.00) val r6 = Employee(6, "Little", 19,80.00)// 20 - 30岁加10%, 30岁> 加20%,其它加 5% def raisePay: FDATask[FDAROW] = row => { row match { case emp: Employee => { val cur = emp.age match { case a if ((a >= 20) && (a < 30)) => emp.copy(salary = emp.salary * 1.10) case a if ((a >= 30)) => emp.copy(salary = emp.salary * 1.20) case _ => emp.copy(salary = emp.salary * 1.05) } fda_next(cur) } case _ => fda_skip } } Stream(r1,r2,r3,r4,r5,r6) .through(log("加薪前>")) .through(fda_execUserTask[FDAROW](raisePay)) .through(log("加薪后>")) .run.unsafeRun // 筛选40岁以上员工 def filter40: FDATask[FDAROW] = row => { row match { case emp: Employee => { if (emp.age > 40) Some(List(emp)) else fda_skip[Employee] } case _ => fda_break } } println("---------") Stream(r1,r2,r3,r4,r5,r6) .through(log("年龄>")) .through(fda_execUserTask[FDAROW](filter40)) .through(log("合格>")) .run.unsafeRun // 浏览至第一个30岁以上员工,跳出 def stopOn30: FDATask[Employee] = emp => { if (emp.age > 30) fda_break else Some(List(emp)) } println("---------") Stream(r1,r2,r3,r4,r5,r6) .through(log("当前员工>")) .through(fda_execUserTask[Employee](stopOn30)) .through(log("选入名单>")) .run.unsafeRun println("---------") Stream(r1,r2,r3,r4,r5,r6) .through(log("加薪前>")) .through(fda_execUserTask[FDAROW](raisePay)) .through(log("加薪后>")) .through(log("年龄>")) .through(fda_execUserTask[FDAROW](filter40)) .through(log("合格>")) .run.unsafeRun}
- FunDA(4)- 数据流内容控制:Stream data element control
- FunDA(9)- Stream Source:reactive data streams
- Akka(24): Stream:从外部系统控制数据流-control live stream from external system
- FunDA(0)- Functional Data Access accessible to all
- LeetCode 295. Find Median from Data Stream(数据流中位数)
- LeetCode 352. Data Stream as Disjoint Intervals(数据流区间)
- FunDA(3)- 流动数据行操作:FDAPipeLine operations using scalaz-stream-fs2
- FunDA(2)- Streaming Data Operation:流式数据操作
- FunDA(11)- 数据库操作的并行运算:Parallel data processing
- Nodejs数据流(Stream)手册
- LeetCode 346. Moving Average from Data Stream(数据流移动平均值)
- 泛函编程(12)-数据流-Stream
- Stream数据流
- Stream数据流
- 295. Find Median from Data Stream&数据流中的中位数
- 【8】mysql数据控制语言DCL(Data Control Language)
- Mysql数据控制语言DCL(Data Control Language)
- 【分享】推特数据流(tweet stream)
- iOS10系统之后页面disappear 调用scrollviewdidscroll方法
- oracle运用(八) oracle中的日期查询二
- 使用poi重复读取excel时报错:stream closed
- zoj2388
- @Cacheable、@CachePut和@CacheEvict介绍
- FunDA(4)- 数据流内容控制:Stream data element control
- JavaWeb学习总结十九、JSP标签
- Java反转一个List或ArrayList
- Git教程学习(三)—创建版本库
- poi操作execl如何在cell里做一个超链接访问当前路径文件夹或文件
- Android N数据业务总结
- 队列
- android studio中的show history按钮
- ReactNative总结(1)