FunDA(4)- 数据流内容控制:Stream data element control
上节我们探讨了通过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的更多相关文章
- 学习笔记20151211——AXI4 STREAM DATA FIFO
AXI4 STREAM DATA FIFO是输入输出接口均为AXIS接口的数据缓存器,和其他fifo一样是先进先出形式.可以在跨时钟域的应用中用于数据缓冲,避免亚稳态出现.支持数据的分割和数据拼接.在 ...
- 数据访问模式:数据并发控制(Data Concurrency Control)
1.数据并发控制(Data Concurrency Control)简介 数据并发控制(Data Concurrency Control)是用来处理在同一时刻对被持久化的业务对象进行多次修改的系统.当 ...
- 错误解决:SharePoint Designer 2010编辑后,出现数据源控件未能执行插入命令,data source control failed to execute the insert command
打了SharePoint 2010 最新的SP 2的补丁,但是使用SharePoint Designer 2010 定义任何一个列表的“插入视图”时,总是出现标题那样的错误: 数据源控件未能执行插入命 ...
- 创建数据表,自定义data element, field等。
参考:https://wenku.baidu.com/view/253ddbfaa5e9856a561260da.html 一:创建域. 使用T-CODE 11 搜索 数据操作系统. 选择domain ...
- 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 ...
- 关于$.data(element,key,value)与ele.data.(key,value)的区别
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- AXI4 STREAM DATA FIFO
参考:http://www.xilinx.com/support/documentation/ip_documentation/axis_infrastructure_ip_suite/v1_1/pg ...
- 泛函编程(12)-数据流-Stream
在前面的章节中我们介绍了List,也讨论了List的数据结构和操作函数.List这个东西从外表看上去挺美,但在现实中使用起来却可能很不实在.为什么?有两方面:其一,我们可以发现所有List的操作都是在 ...
- 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 ...
随机推荐
- Linux动态共享库
Linux操作系统上面的动态共享库大致分为三类: 一.操作系统级别的共享库和基础的系统工具库 libc.so, libz.so, libpthread.so等等,这些系统库会被放在/lib和/us ...
- Warning: Attempt to present A on B whose view is not in the window hierarchy!
昨天写豆瓣发广播Demo的时候,为了写Demo的简单,就使用了Storyboard,结果执行视图跳转时遇到了这个问题: Warning: Attempt to present <UINaviga ...
- 657. Judge Route Circle
static int wing=[]() { std::ios::sync_with_stdio(false); cin.tie(NULL); ; }(); class Solution { publ ...
- 如何通过cmd命令进入到某个硬盘的文件夹
1.使用快捷键win+R打开运行窗口,并输入cmd回车 2.进入到某个磁盘:在命令提示符中输入d:(代表的的是进入D盘的根目录)并回车 3.接着在cmd中输入dir(dir是directory目录的简 ...
- json&pickle
用于序列化的两个模块 json,用于字符串 和 python数据类型间进行转换pickle,用于python特有的类型 和 python的数据类型间进行转换Json模块提供了四个功能:dumps.du ...
- php PDO mysql
php PDO写法连接mysql: $db=new PDO("mysql:host=localhost;dbname=sql","root","roo ...
- (网络流 模板 Dinic) Drainage Ditches --POJ --1273
链接: http://poj.org/problem?id=1273 代码: //Dinic #include<stdio.h> #include<string.h> #inc ...
- spring读取数据库的配置信息(url、username、password)时的<bean>PropertyPlaceholderConfigurer的用法
用法1: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w ...
- [redis]Redis Transaction
https://github.com/phpredis/phpredis#transactions Transactions multi, exec, discard - Enter and exit ...
- jvm lock低性能分析
日志平台client面临着输出日志的问题.为了避免干扰业务系统,我们采用异步输出的方式.这实际上相当于一个多生产者-单消费者的多线程模型.传统的方式是使用同步加锁的方式,但是这种方式不够高效.之前 钟 ...