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 ...
随机推荐
- OpenSSL编程
简介 OpenSSL是一个功能丰富且自包含的开源安全工具箱.它提供的主要功能有:SSL协议实现(包括SSLv2.SSLv3和TLSv1).大量软算法(对称/非对称/摘要).大数运算.非对称算法密钥生成 ...
- code1213 解的个数 扩展欧几里得
很不错的题,加深了我对exgcd的理解 (以前我认为做题就是搜索.dp...原来数学也很重要) 理解了几个小时,终于明白了.但我什么都不打算写. 看代码吧: #include<iostream& ...
- Laravel Nginx 站点配置文件(Homestead)
server { listen 80; listen 443 ssl http2; server_name fmtmis.local; root "/home ...
- [SoapUI] Datasink怎么显示每次循环的结果
https://www.soapui.org/reporting/the-report-datasink.html
- Storm 系列(二)实时平台介绍
Storm 系列(二)实时平台介绍 本章中的实时平台是指针对大数据进行实时分析的一整套系统,包括数据的收集.处理.存储等.一般而言,大数据有 4 个特点: Volumn(大量). Velocity(高 ...
- 写点C++ 学习记录 充数
#include "stdafx.h" #include <cstdlib> #include <iostream> using namespace std ...
- Web Service测试工具小汇
1..NET WebService Studio 这款工具出自微软内部,最大的优点是可视化很好,不用去看那些XML文件,WebService的基础内容就有XML,但是测试中Case过多,每次测试结果都 ...
- python编码(一)
下面介绍一下python的编码机制,unicode, utf-8, utf-16, GBK, GB2312,ISO-8859-1等编码之间的转换. 1.自动识别字符串编码: #coding:utf8 ...
- 使用原生Java Web来实现大文件的上传
版权所有 2009-2018荆门泽优软件有限公司 保留所有权利 官方网站:http://www.ncmem.com/ 产品首页:http://www.ncmem.com/webapp/up6.2/in ...
- Java 容器 LinkedHashMap源码分析2
一.类签名 LinkedHashMap<K,V>继承自HashMap<K,V>,可知存入的节点key永远是唯一的.可以通过Android的LruCache了解LinkedHas ...