上节我们探讨了通过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]] } }

注意这个FDAROW类型:这是一种泛类型,因为在管道中流动的数据可能有多重类型,如数据行和QueryAction行。

流动控制方法:FDAValves.scala

 package com.bayakala.funda.fdapipes
import 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.fdapipes
import 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.fdapipes
import 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)
} }

下面我们就示范这个工具库的具体使用方法:examples/Example1.scala
设置示范环境:

 package com.bayakala.funda.fdapipes.examples
import 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(, "John", , 100.00)
val r2 = Employee(, "Peter", ,100.00)
val r3 = Employee(, "Kay", ,100.00)
val r4 = Employee(, "Cain", ,100.00)
val r5 = Employee(, "Catty", ,100.00)
val r6 = Employee(, "Little", ,80.00)

注意Employee是一种行类型,因为它extends FDAROW。

我们再写一个跟踪显示当前流动数据行的函数:examples/Helpers.scala

 package com.bayakala.funda.fdapipes.examples
import com.bayakala.funda.fdapipes._
import fs2.Task
object 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 >= ) && (a < )) => emp.copy(salary = emp.salary * 1.10)
case a if ((a >= )) => 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(,John,,100.0)
加薪后>> Employee(,John,,110.00)
加薪前>> Employee(,Peter,,100.0)
加薪后>> Employee(,Peter,,110.00)
加薪前>> Employee(,Kay,,100.0)
加薪后>> Employee(,Kay,,120.00)
加薪前>> Employee(,Cain,,100.0)
加薪后>> Employee(,Cain,,120.00)
加薪前>> Employee(,Catty,,100.0)
加薪后>> Employee(,Catty,,120.00)
加薪前>> Employee(,Little,,80.0)
加薪后>> Employee(,Little,,84.000)

2、在一组数据行内根据每条数据状态进行筛选:

  // 筛选40岁以上员工
def filter40: FDATask[FDAROW] = row => {
row match {
case emp: Employee => {
if (emp.age > )
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(,John,,100.0)
年龄>> Employee(,Peter,,100.0)
年龄>> Employee(,Kay,,100.0)
年龄>> Employee(,Cain,,100.0)
合格>> Employee(,Cain,,100.0)
年龄>> Employee(,Catty,,100.0)
年龄>> Employee(,Little,,80.0)
-

3、根据当前数据行状态终止作业:

   // 浏览至第一个30岁以上员工,跳出
def stopOn30: FDATask[Employee] = emp => {
if (emp.age > )
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(,John,,100.0)
选入名单>> Employee(,John,,100.0)
当前员工>> Employee(,Peter,,100.0)
选入名单>> Employee(,Peter,,100.0)
当前员工>> Employee(,Kay,,100.0)

在这个例子里用户指定了行类型统一为Employee。

我们还可以把多个功能串接起来。像下面这样把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(,John,,100.0)
加薪后>> Employee(,John,,110.00)
年龄>> Employee(,John,,110.00)
加薪前>> Employee(,Peter,,100.0)
加薪后>> Employee(,Peter,,110.00)
年龄>> Employee(,Peter,,110.00)
加薪前>> Employee(,Kay,,100.0)
加薪后>> Employee(,Kay,,120.00)
年龄>> Employee(,Kay,,120.00)
加薪前>> Employee(,Cain,,100.0)
加薪后>> Employee(,Cain,,120.00)
年龄>> Employee(,Cain,,120.00)
合格>> Employee(,Cain,,120.00)
加薪前>> Employee(,Catty,,100.0)
加薪后>> Employee(,Catty,,120.00)
年龄>> Employee(,Catty,,120.00)
加薪前>> Employee(,Little,,80.0)
加薪后>> Employee(,Little,,84.000)
年龄>> Employee(,Little,,84.000)

下面我把完整的示范代码提供给大家:

package com.bayakala.funda.fdapipes.examples
import 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(, "John", , 100.00)
val r2 = Employee(, "Peter", ,100.00)
val r3 = Employee(, "Kay", ,100.00)
val r4 = Employee(, "Cain", ,100.00)
val r5 = Employee(, "Catty", ,100.00)
val r6 = Employee(, "Little", ,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 >= ) && (a < )) => emp.copy(salary = emp.salary * 1.10)
case a if ((a >= )) => 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 > )
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 > )
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的更多相关文章

  1. 学习笔记20151211——AXI4 STREAM DATA FIFO

    AXI4 STREAM DATA FIFO是输入输出接口均为AXIS接口的数据缓存器,和其他fifo一样是先进先出形式.可以在跨时钟域的应用中用于数据缓冲,避免亚稳态出现.支持数据的分割和数据拼接.在 ...

  2. 数据访问模式:数据并发控制(Data Concurrency Control)

    1.数据并发控制(Data Concurrency Control)简介 数据并发控制(Data Concurrency Control)是用来处理在同一时刻对被持久化的业务对象进行多次修改的系统.当 ...

  3. 错误解决:SharePoint Designer 2010编辑后,出现数据源控件未能执行插入命令,data source control failed to execute the insert command

    打了SharePoint 2010 最新的SP 2的补丁,但是使用SharePoint Designer 2010 定义任何一个列表的“插入视图”时,总是出现标题那样的错误: 数据源控件未能执行插入命 ...

  4. 创建数据表,自定义data element, field等。

    参考:https://wenku.baidu.com/view/253ddbfaa5e9856a561260da.html 一:创建域. 使用T-CODE 11 搜索 数据操作系统. 选择domain ...

  5. Putting Apache Kafka To Use: A Practical Guide to Building a Stream Data Platform-part 1

    转自: http://www.confluent.io/blog/stream-data-platform-1/ These days you hear a lot about "strea ...

  6. 关于$.data(element,key,value)与ele.data.(key,value)的区别

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  7. AXI4 STREAM DATA FIFO

    参考:http://www.xilinx.com/support/documentation/ip_documentation/axis_infrastructure_ip_suite/v1_1/pg ...

  8. 泛函编程(12)-数据流-Stream

    在前面的章节中我们介绍了List,也讨论了List的数据结构和操作函数.List这个东西从外表看上去挺美,但在现实中使用起来却可能很不实在.为什么?有两方面:其一,我们可以发现所有List的操作都是在 ...

  9. Putting Apache Kafka To Use: A Practical Guide to Building a Stream Data Platform-part 2

    转自: http://confluent.io/blog/stream-data-platform-2          http://www.infoq.com/cn/news/2015/03/ap ...

随机推荐

  1. Sketch小妙招:在线分享设计

    Sketch是很多产品经理.UI.UX设计师常使用的一款的设计工具,但是我在使用Sketch的过程中发现了一个让我困扰的事儿,或者说它缺少了一个我非常需要的服务:在线分享设计.可能很多使用Sketch ...

  2. 客户端、服务器端中JSON字符串与对象的转换

    客户端: 字符串转为对象:$.parseJSON(json); 对象转为字符串:JSON.stringify(_pasteDataItem) 服务器端(c#): 对象: [DataContract(N ...

  3. 用个体软件过程(PSP)记录你的工作

    用个体软件过程(PSP)记录你的工作 首先,非常感谢大家对本门课程的学习所投入的时间和精力. 其次,已经进入数据时代,口说无凭,拿数据来.如果你认为你已经投入了大量精力在这门课程的学习和作业中,而且已 ...

  4. 2018.10.18 NOIP训练 01矩阵(组合数学)

    传送门 组合数学好题. 题目要求输出的结果成功把概率转化成了种类数. 本来可以枚举统计最小值为iii时的概率. 现在只需要统计最小值为iii时的方案数,每一行有不少于iii个1的方案数. 显然一行选i ...

  5. Django入门与实践-第19章:主题回复(完结)

    http://127.0.0.1:8000/boards/1/topics/1/reply/ http://127.0.0.1:8000/boards/1/topics/1/ #myproject/u ...

  6. hibernate createQuery和createSQLQuery 查询结果count计算

    createQuery 针对hql语句查询 Query query=getSession().createQuery(hql);int result =((Number) query.iterate( ...

  7. ESRI.ArcGIS.AnalysisTools.Erase 结果是空?

    用ESRI.ArcGIS.AnalysisTools.Erase擦除,结果总是空的. 这几天一直纠结一个问题,就是在AE下使用ESRI.ArcGIS.AnalysisTools中的工具(例如:泰森多边 ...

  8. Scala类型检查与转换

    Scala类型检查与转换 isInstanceOf:检查某个对象是否属于某个给定的类. asInstanceOf:将引用转换为子类的引用. classOf:如果想测试p指向的是一个Employee对象 ...

  9. hibernate中常用的Hql语句总结

    // HQL: Hibernate Query Language. // 特点: // >> 1,与SQL相似,SQL中的语法基本上都可以直接使用. // >> 2,SQL查询 ...

  10. eclipse/myeclipse清除workspace

    打开Eclipse后,选择功能菜单里的 Windows -> Preferences->, 弹出对话框后,选择 General -> Startup and Shutdownwor ...